##// END OF EJS Templates
feat(vcs): dropped validate SSL for vcs-operations....
super-admin -
r5531:8639dbc5 default
parent child Browse files
Show More
@@ -1,708 +1,707 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 import rhodecode
27 import rhodecode
28
28
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
35 from rhodecode.apps.svn_support import config_keys
35 from rhodecode.apps.svn_support import config_keys
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
41 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
41 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
44
44
45 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 return c
70 return c
71
71
72 @classmethod
72 @classmethod
73 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
74 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
75
75
76 if not ret:
76 if not ret:
77 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
78 settings = {}
78 settings = {}
79 for each in ret:
79 for each in ret:
80 k = each.ui_key
80 k = each.ui_key
81 v = each.ui_value
81 v = each.ui_value
82 if k == '/':
82 if k == '/':
83 k = 'root_path'
83 k = 'root_path'
84
84
85 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['publish', 'enabled']:
86 v = str2bool(v)
86 v = str2bool(v)
87
87
88 if k.find('.') != -1:
88 if k.find('.') != -1:
89 k = k.replace('.', '_')
89 k = k.replace('.', '_')
90
90
91 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
92 v = each.ui_active
92 v = each.ui_active
93
93
94 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
95 return settings
95 return settings
96
96
97 @classmethod
97 @classmethod
98 def _form_defaults(cls):
98 def _form_defaults(cls):
99 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
100 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
101
101
102 defaults.update({
102 defaults.update({
103 'new_svn_branch': '',
103 'new_svn_branch': '',
104 'new_svn_tag': '',
104 'new_svn_tag': '',
105 })
105 })
106 return defaults
106 return defaults
107
107
108 @LoginRequired()
108 @LoginRequired()
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def settings_vcs(self):
110 def settings_vcs(self):
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'vcs'
112 c.active = 'vcs'
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
116 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
117 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
117 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
118 defaults = self._form_defaults()
118 defaults = self._form_defaults()
119
119
120 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
120 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
121
121
122 data = render('rhodecode:templates/admin/settings/settings.mako',
122 data = render('rhodecode:templates/admin/settings/settings.mako',
123 self._get_template_context(c), self.request)
123 self._get_template_context(c), self.request)
124 html = formencode.htmlfill.render(
124 html = formencode.htmlfill.render(
125 data,
125 data,
126 defaults=defaults,
126 defaults=defaults,
127 encoding="UTF-8",
127 encoding="UTF-8",
128 force_defaults=False
128 force_defaults=False
129 )
129 )
130 return Response(html)
130 return Response(html)
131
131
132 @LoginRequired()
132 @LoginRequired()
133 @HasPermissionAllDecorator('hg.admin')
133 @HasPermissionAllDecorator('hg.admin')
134 @CSRFRequired()
134 @CSRFRequired()
135 def settings_vcs_update(self):
135 def settings_vcs_update(self):
136 _ = self.request.translate
136 _ = self.request.translate
137 c = self.load_default_context()
137 c = self.load_default_context()
138 c.active = 'vcs'
138 c.active = 'vcs'
139
139
140 model = VcsSettingsModel()
140 model = VcsSettingsModel()
141 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
141 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
142 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
142 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
143
143
144 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
144 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
145 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
145 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
146 application_form = ApplicationUiSettingsForm(self.request.translate)()
146 application_form = ApplicationUiSettingsForm(self.request.translate)()
147
147
148 try:
148 try:
149 form_result = application_form.to_python(dict(self.request.POST))
149 form_result = application_form.to_python(dict(self.request.POST))
150 except formencode.Invalid as errors:
150 except formencode.Invalid as errors:
151 h.flash(
151 h.flash(
152 _("Some form inputs contain invalid data."),
152 _("Some form inputs contain invalid data."),
153 category='error')
153 category='error')
154 data = render('rhodecode:templates/admin/settings/settings.mako',
154 data = render('rhodecode:templates/admin/settings/settings.mako',
155 self._get_template_context(c), self.request)
155 self._get_template_context(c), self.request)
156 html = formencode.htmlfill.render(
156 html = formencode.htmlfill.render(
157 data,
157 data,
158 defaults=errors.value,
158 defaults=errors.value,
159 errors=errors.unpack_errors() or {},
159 errors=errors.unpack_errors() or {},
160 prefix_error=False,
160 prefix_error=False,
161 encoding="UTF-8",
161 encoding="UTF-8",
162 force_defaults=False
162 force_defaults=False
163 )
163 )
164 return Response(html)
164 return Response(html)
165
165
166 try:
166 try:
167 model.update_global_ssl_setting(form_result['web_push_ssl'])
168 model.update_global_hook_settings(form_result)
167 model.update_global_hook_settings(form_result)
169
168
170 model.create_or_update_global_svn_settings(form_result)
169 model.create_or_update_global_svn_settings(form_result)
171 model.create_or_update_global_hg_settings(form_result)
170 model.create_or_update_global_hg_settings(form_result)
172 model.create_or_update_global_git_settings(form_result)
171 model.create_or_update_global_git_settings(form_result)
173 model.create_or_update_global_pr_settings(form_result)
172 model.create_or_update_global_pr_settings(form_result)
174 except Exception:
173 except Exception:
175 log.exception("Exception while updating settings")
174 log.exception("Exception while updating settings")
176 h.flash(_('Error occurred during updating '
175 h.flash(_('Error occurred during updating '
177 'application settings'), category='error')
176 'application settings'), category='error')
178 else:
177 else:
179 Session().commit()
178 Session().commit()
180 h.flash(_('Updated VCS settings'), category='success')
179 h.flash(_('Updated VCS settings'), category='success')
181 raise HTTPFound(h.route_path('admin_settings_vcs'))
180 raise HTTPFound(h.route_path('admin_settings_vcs'))
182
181
183 data = render('rhodecode:templates/admin/settings/settings.mako',
182 data = render('rhodecode:templates/admin/settings/settings.mako',
184 self._get_template_context(c), self.request)
183 self._get_template_context(c), self.request)
185 html = formencode.htmlfill.render(
184 html = formencode.htmlfill.render(
186 data,
185 data,
187 defaults=self._form_defaults(),
186 defaults=self._form_defaults(),
188 encoding="UTF-8",
187 encoding="UTF-8",
189 force_defaults=False
188 force_defaults=False
190 )
189 )
191 return Response(html)
190 return Response(html)
192
191
193 @LoginRequired()
192 @LoginRequired()
194 @HasPermissionAllDecorator('hg.admin')
193 @HasPermissionAllDecorator('hg.admin')
195 @CSRFRequired()
194 @CSRFRequired()
196 def settings_vcs_delete_svn_pattern(self):
195 def settings_vcs_delete_svn_pattern(self):
197 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
196 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
198 model = VcsSettingsModel()
197 model = VcsSettingsModel()
199 try:
198 try:
200 model.delete_global_svn_pattern(delete_pattern_id)
199 model.delete_global_svn_pattern(delete_pattern_id)
201 except SettingNotFound:
200 except SettingNotFound:
202 log.exception(
201 log.exception(
203 'Failed to delete svn_pattern with id %s', delete_pattern_id)
202 'Failed to delete svn_pattern with id %s', delete_pattern_id)
204 raise HTTPNotFound()
203 raise HTTPNotFound()
205
204
206 Session().commit()
205 Session().commit()
207 return True
206 return True
208
207
209 @LoginRequired()
208 @LoginRequired()
210 @HasPermissionAllDecorator('hg.admin')
209 @HasPermissionAllDecorator('hg.admin')
211 def settings_mapping(self):
210 def settings_mapping(self):
212 c = self.load_default_context()
211 c = self.load_default_context()
213 c.active = 'mapping'
212 c.active = 'mapping'
214 c.storage_path = get_rhodecode_repo_store_path()
213 c.storage_path = get_rhodecode_repo_store_path()
215 data = render('rhodecode:templates/admin/settings/settings.mako',
214 data = render('rhodecode:templates/admin/settings/settings.mako',
216 self._get_template_context(c), self.request)
215 self._get_template_context(c), self.request)
217 html = formencode.htmlfill.render(
216 html = formencode.htmlfill.render(
218 data,
217 data,
219 defaults=self._form_defaults(),
218 defaults=self._form_defaults(),
220 encoding="UTF-8",
219 encoding="UTF-8",
221 force_defaults=False
220 force_defaults=False
222 )
221 )
223 return Response(html)
222 return Response(html)
224
223
225 @LoginRequired()
224 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
225 @HasPermissionAllDecorator('hg.admin')
227 @CSRFRequired()
226 @CSRFRequired()
228 def settings_mapping_update(self):
227 def settings_mapping_update(self):
229 _ = self.request.translate
228 _ = self.request.translate
230 c = self.load_default_context()
229 c = self.load_default_context()
231 c.active = 'mapping'
230 c.active = 'mapping'
232 rm_obsolete = self.request.POST.get('destroy', False)
231 rm_obsolete = self.request.POST.get('destroy', False)
233 invalidate_cache = self.request.POST.get('invalidate', False)
232 invalidate_cache = self.request.POST.get('invalidate', False)
234 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
233 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
235
234
236 if invalidate_cache:
235 if invalidate_cache:
237 log.debug('invalidating all repositories cache')
236 log.debug('invalidating all repositories cache')
238 for repo in Repository.get_all():
237 for repo in Repository.get_all():
239 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
238 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
240
239
241 filesystem_repos = ScmModel().repo_scan()
240 filesystem_repos = ScmModel().repo_scan()
242 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
241 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
243 PermissionModel().trigger_permission_flush()
242 PermissionModel().trigger_permission_flush()
244
243
245 def _repr(rm_repo):
244 def _repr(rm_repo):
246 return ', '.join(map(safe_str, rm_repo)) or '-'
245 return ', '.join(map(safe_str, rm_repo)) or '-'
247
246
248 h.flash(_('Repositories successfully '
247 h.flash(_('Repositories successfully '
249 'rescanned added: %s ; removed: %s') %
248 'rescanned added: %s ; removed: %s') %
250 (_repr(added), _repr(removed)),
249 (_repr(added), _repr(removed)),
251 category='success')
250 category='success')
252 raise HTTPFound(h.route_path('admin_settings_mapping'))
251 raise HTTPFound(h.route_path('admin_settings_mapping'))
253
252
254 @LoginRequired()
253 @LoginRequired()
255 @HasPermissionAllDecorator('hg.admin')
254 @HasPermissionAllDecorator('hg.admin')
256 def settings_global(self):
255 def settings_global(self):
257 c = self.load_default_context()
256 c = self.load_default_context()
258 c.active = 'global'
257 c.active = 'global'
259 c.personal_repo_group_default_pattern = RepoGroupModel()\
258 c.personal_repo_group_default_pattern = RepoGroupModel()\
260 .get_personal_group_name_pattern()
259 .get_personal_group_name_pattern()
261
260
262 data = render('rhodecode:templates/admin/settings/settings.mako',
261 data = render('rhodecode:templates/admin/settings/settings.mako',
263 self._get_template_context(c), self.request)
262 self._get_template_context(c), self.request)
264 html = formencode.htmlfill.render(
263 html = formencode.htmlfill.render(
265 data,
264 data,
266 defaults=self._form_defaults(),
265 defaults=self._form_defaults(),
267 encoding="UTF-8",
266 encoding="UTF-8",
268 force_defaults=False
267 force_defaults=False
269 )
268 )
270 return Response(html)
269 return Response(html)
271
270
272 @LoginRequired()
271 @LoginRequired()
273 @HasPermissionAllDecorator('hg.admin')
272 @HasPermissionAllDecorator('hg.admin')
274 @CSRFRequired()
273 @CSRFRequired()
275 def settings_global_update(self):
274 def settings_global_update(self):
276 _ = self.request.translate
275 _ = self.request.translate
277 c = self.load_default_context()
276 c = self.load_default_context()
278 c.active = 'global'
277 c.active = 'global'
279 c.personal_repo_group_default_pattern = RepoGroupModel()\
278 c.personal_repo_group_default_pattern = RepoGroupModel()\
280 .get_personal_group_name_pattern()
279 .get_personal_group_name_pattern()
281 application_form = ApplicationSettingsForm(self.request.translate)()
280 application_form = ApplicationSettingsForm(self.request.translate)()
282 try:
281 try:
283 form_result = application_form.to_python(dict(self.request.POST))
282 form_result = application_form.to_python(dict(self.request.POST))
284 except formencode.Invalid as errors:
283 except formencode.Invalid as errors:
285 h.flash(
284 h.flash(
286 _("Some form inputs contain invalid data."),
285 _("Some form inputs contain invalid data."),
287 category='error')
286 category='error')
288 data = render('rhodecode:templates/admin/settings/settings.mako',
287 data = render('rhodecode:templates/admin/settings/settings.mako',
289 self._get_template_context(c), self.request)
288 self._get_template_context(c), self.request)
290 html = formencode.htmlfill.render(
289 html = formencode.htmlfill.render(
291 data,
290 data,
292 defaults=errors.value,
291 defaults=errors.value,
293 errors=errors.unpack_errors() or {},
292 errors=errors.unpack_errors() or {},
294 prefix_error=False,
293 prefix_error=False,
295 encoding="UTF-8",
294 encoding="UTF-8",
296 force_defaults=False
295 force_defaults=False
297 )
296 )
298 return Response(html)
297 return Response(html)
299
298
300 settings = [
299 settings = [
301 ('title', 'rhodecode_title', 'unicode'),
300 ('title', 'rhodecode_title', 'unicode'),
302 ('realm', 'rhodecode_realm', 'unicode'),
301 ('realm', 'rhodecode_realm', 'unicode'),
303 ('pre_code', 'rhodecode_pre_code', 'unicode'),
302 ('pre_code', 'rhodecode_pre_code', 'unicode'),
304 ('post_code', 'rhodecode_post_code', 'unicode'),
303 ('post_code', 'rhodecode_post_code', 'unicode'),
305 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
304 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
306 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
305 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
307 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
306 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
308 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
307 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
309 ]
308 ]
310
309
311 try:
310 try:
312 for setting, form_key, type_ in settings:
311 for setting, form_key, type_ in settings:
313 sett = SettingsModel().create_or_update_setting(
312 sett = SettingsModel().create_or_update_setting(
314 setting, form_result[form_key], type_)
313 setting, form_result[form_key], type_)
315 Session().add(sett)
314 Session().add(sett)
316
315
317 Session().commit()
316 Session().commit()
318 SettingsModel().invalidate_settings_cache()
317 SettingsModel().invalidate_settings_cache()
319 h.flash(_('Updated application settings'), category='success')
318 h.flash(_('Updated application settings'), category='success')
320 except Exception:
319 except Exception:
321 log.exception("Exception while updating application settings")
320 log.exception("Exception while updating application settings")
322 h.flash(
321 h.flash(
323 _('Error occurred during updating application settings'),
322 _('Error occurred during updating application settings'),
324 category='error')
323 category='error')
325
324
326 raise HTTPFound(h.route_path('admin_settings_global'))
325 raise HTTPFound(h.route_path('admin_settings_global'))
327
326
328 @LoginRequired()
327 @LoginRequired()
329 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
330 def settings_visual(self):
329 def settings_visual(self):
331 c = self.load_default_context()
330 c = self.load_default_context()
332 c.active = 'visual'
331 c.active = 'visual'
333
332
334 data = render('rhodecode:templates/admin/settings/settings.mako',
333 data = render('rhodecode:templates/admin/settings/settings.mako',
335 self._get_template_context(c), self.request)
334 self._get_template_context(c), self.request)
336 html = formencode.htmlfill.render(
335 html = formencode.htmlfill.render(
337 data,
336 data,
338 defaults=self._form_defaults(),
337 defaults=self._form_defaults(),
339 encoding="UTF-8",
338 encoding="UTF-8",
340 force_defaults=False
339 force_defaults=False
341 )
340 )
342 return Response(html)
341 return Response(html)
343
342
344 @LoginRequired()
343 @LoginRequired()
345 @HasPermissionAllDecorator('hg.admin')
344 @HasPermissionAllDecorator('hg.admin')
346 @CSRFRequired()
345 @CSRFRequired()
347 def settings_visual_update(self):
346 def settings_visual_update(self):
348 _ = self.request.translate
347 _ = self.request.translate
349 c = self.load_default_context()
348 c = self.load_default_context()
350 c.active = 'visual'
349 c.active = 'visual'
351 application_form = ApplicationVisualisationForm(self.request.translate)()
350 application_form = ApplicationVisualisationForm(self.request.translate)()
352 try:
351 try:
353 form_result = application_form.to_python(dict(self.request.POST))
352 form_result = application_form.to_python(dict(self.request.POST))
354 except formencode.Invalid as errors:
353 except formencode.Invalid as errors:
355 h.flash(
354 h.flash(
356 _("Some form inputs contain invalid data."),
355 _("Some form inputs contain invalid data."),
357 category='error')
356 category='error')
358 data = render('rhodecode:templates/admin/settings/settings.mako',
357 data = render('rhodecode:templates/admin/settings/settings.mako',
359 self._get_template_context(c), self.request)
358 self._get_template_context(c), self.request)
360 html = formencode.htmlfill.render(
359 html = formencode.htmlfill.render(
361 data,
360 data,
362 defaults=errors.value,
361 defaults=errors.value,
363 errors=errors.unpack_errors() or {},
362 errors=errors.unpack_errors() or {},
364 prefix_error=False,
363 prefix_error=False,
365 encoding="UTF-8",
364 encoding="UTF-8",
366 force_defaults=False
365 force_defaults=False
367 )
366 )
368 return Response(html)
367 return Response(html)
369
368
370 try:
369 try:
371 settings = [
370 settings = [
372 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
371 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
373 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
372 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
374 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
373 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
375 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
374 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
376 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
375 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
377 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
376 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
378 ('show_version', 'rhodecode_show_version', 'bool'),
377 ('show_version', 'rhodecode_show_version', 'bool'),
379 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
378 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
380 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
379 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
381 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
380 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
382 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
381 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
383 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
382 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
384 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
383 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
385 ('support_url', 'rhodecode_support_url', 'unicode'),
384 ('support_url', 'rhodecode_support_url', 'unicode'),
386 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
385 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
387 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
386 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
388 ]
387 ]
389 for setting, form_key, type_ in settings:
388 for setting, form_key, type_ in settings:
390 sett = SettingsModel().create_or_update_setting(
389 sett = SettingsModel().create_or_update_setting(
391 setting, form_result[form_key], type_)
390 setting, form_result[form_key], type_)
392 Session().add(sett)
391 Session().add(sett)
393
392
394 Session().commit()
393 Session().commit()
395 SettingsModel().invalidate_settings_cache()
394 SettingsModel().invalidate_settings_cache()
396 h.flash(_('Updated visualisation settings'), category='success')
395 h.flash(_('Updated visualisation settings'), category='success')
397 except Exception:
396 except Exception:
398 log.exception("Exception updating visualization settings")
397 log.exception("Exception updating visualization settings")
399 h.flash(_('Error occurred during updating '
398 h.flash(_('Error occurred during updating '
400 'visualisation settings'),
399 'visualisation settings'),
401 category='error')
400 category='error')
402
401
403 raise HTTPFound(h.route_path('admin_settings_visual'))
402 raise HTTPFound(h.route_path('admin_settings_visual'))
404
403
405 @LoginRequired()
404 @LoginRequired()
406 @HasPermissionAllDecorator('hg.admin')
405 @HasPermissionAllDecorator('hg.admin')
407 def settings_issuetracker(self):
406 def settings_issuetracker(self):
408 c = self.load_default_context()
407 c = self.load_default_context()
409 c.active = 'issuetracker'
408 c.active = 'issuetracker'
410 defaults = c.rc_config
409 defaults = c.rc_config
411
410
412 entry_key = 'rhodecode_issuetracker_pat_'
411 entry_key = 'rhodecode_issuetracker_pat_'
413
412
414 c.issuetracker_entries = {}
413 c.issuetracker_entries = {}
415 for k, v in defaults.items():
414 for k, v in defaults.items():
416 if k.startswith(entry_key):
415 if k.startswith(entry_key):
417 uid = k[len(entry_key):]
416 uid = k[len(entry_key):]
418 c.issuetracker_entries[uid] = None
417 c.issuetracker_entries[uid] = None
419
418
420 for uid in c.issuetracker_entries:
419 for uid in c.issuetracker_entries:
421 c.issuetracker_entries[uid] = AttributeDict({
420 c.issuetracker_entries[uid] = AttributeDict({
422 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
421 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
423 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
422 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
424 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
423 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
425 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
424 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
426 })
425 })
427
426
428 return self._get_template_context(c)
427 return self._get_template_context(c)
429
428
430 @LoginRequired()
429 @LoginRequired()
431 @HasPermissionAllDecorator('hg.admin')
430 @HasPermissionAllDecorator('hg.admin')
432 @CSRFRequired()
431 @CSRFRequired()
433 def settings_issuetracker_test(self):
432 def settings_issuetracker_test(self):
434 error_container = []
433 error_container = []
435
434
436 urlified_commit = h.urlify_commit_message(
435 urlified_commit = h.urlify_commit_message(
437 self.request.POST.get('test_text', ''),
436 self.request.POST.get('test_text', ''),
438 'repo_group/test_repo1', error_container=error_container)
437 'repo_group/test_repo1', error_container=error_container)
439 if error_container:
438 if error_container:
440 def converter(inp):
439 def converter(inp):
441 return h.html_escape(inp)
440 return h.html_escape(inp)
442
441
443 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
442 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
444
443
445 return urlified_commit
444 return urlified_commit
446
445
447 @LoginRequired()
446 @LoginRequired()
448 @HasPermissionAllDecorator('hg.admin')
447 @HasPermissionAllDecorator('hg.admin')
449 @CSRFRequired()
448 @CSRFRequired()
450 def settings_issuetracker_update(self):
449 def settings_issuetracker_update(self):
451 _ = self.request.translate
450 _ = self.request.translate
452 self.load_default_context()
451 self.load_default_context()
453 settings_model = IssueTrackerSettingsModel()
452 settings_model = IssueTrackerSettingsModel()
454
453
455 try:
454 try:
456 form = IssueTrackerPatternsForm(self.request.translate)()
455 form = IssueTrackerPatternsForm(self.request.translate)()
457 data = form.to_python(self.request.POST)
456 data = form.to_python(self.request.POST)
458 except formencode.Invalid as errors:
457 except formencode.Invalid as errors:
459 log.exception('Failed to add new pattern')
458 log.exception('Failed to add new pattern')
460 error = errors
459 error = errors
461 h.flash(_(f'Invalid issue tracker pattern: {error}'),
460 h.flash(_(f'Invalid issue tracker pattern: {error}'),
462 category='error')
461 category='error')
463 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
462 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
464
463
465 if data:
464 if data:
466 for uid in data.get('delete_patterns', []):
465 for uid in data.get('delete_patterns', []):
467 settings_model.delete_entries(uid)
466 settings_model.delete_entries(uid)
468
467
469 for pattern in data.get('patterns', []):
468 for pattern in data.get('patterns', []):
470 for setting, value, type_ in pattern:
469 for setting, value, type_ in pattern:
471 sett = settings_model.create_or_update_setting(
470 sett = settings_model.create_or_update_setting(
472 setting, value, type_)
471 setting, value, type_)
473 Session().add(sett)
472 Session().add(sett)
474
473
475 Session().commit()
474 Session().commit()
476
475
477 SettingsModel().invalidate_settings_cache()
476 SettingsModel().invalidate_settings_cache()
478 h.flash(_('Updated issue tracker entries'), category='success')
477 h.flash(_('Updated issue tracker entries'), category='success')
479 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
478 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
480
479
481 @LoginRequired()
480 @LoginRequired()
482 @HasPermissionAllDecorator('hg.admin')
481 @HasPermissionAllDecorator('hg.admin')
483 @CSRFRequired()
482 @CSRFRequired()
484 def settings_issuetracker_delete(self):
483 def settings_issuetracker_delete(self):
485 _ = self.request.translate
484 _ = self.request.translate
486 self.load_default_context()
485 self.load_default_context()
487 uid = self.request.POST.get('uid')
486 uid = self.request.POST.get('uid')
488 try:
487 try:
489 IssueTrackerSettingsModel().delete_entries(uid)
488 IssueTrackerSettingsModel().delete_entries(uid)
490 except Exception:
489 except Exception:
491 log.exception('Failed to delete issue tracker setting %s', uid)
490 log.exception('Failed to delete issue tracker setting %s', uid)
492 raise HTTPNotFound()
491 raise HTTPNotFound()
493
492
494 SettingsModel().invalidate_settings_cache()
493 SettingsModel().invalidate_settings_cache()
495 h.flash(_('Removed issue tracker entry.'), category='success')
494 h.flash(_('Removed issue tracker entry.'), category='success')
496
495
497 return {'deleted': uid}
496 return {'deleted': uid}
498
497
499 @LoginRequired()
498 @LoginRequired()
500 @HasPermissionAllDecorator('hg.admin')
499 @HasPermissionAllDecorator('hg.admin')
501 def settings_email(self):
500 def settings_email(self):
502 c = self.load_default_context()
501 c = self.load_default_context()
503 c.active = 'email'
502 c.active = 'email'
504 c.rhodecode_ini = rhodecode.CONFIG
503 c.rhodecode_ini = rhodecode.CONFIG
505
504
506 data = render('rhodecode:templates/admin/settings/settings.mako',
505 data = render('rhodecode:templates/admin/settings/settings.mako',
507 self._get_template_context(c), self.request)
506 self._get_template_context(c), self.request)
508 html = formencode.htmlfill.render(
507 html = formencode.htmlfill.render(
509 data,
508 data,
510 defaults=self._form_defaults(),
509 defaults=self._form_defaults(),
511 encoding="UTF-8",
510 encoding="UTF-8",
512 force_defaults=False
511 force_defaults=False
513 )
512 )
514 return Response(html)
513 return Response(html)
515
514
516 @LoginRequired()
515 @LoginRequired()
517 @HasPermissionAllDecorator('hg.admin')
516 @HasPermissionAllDecorator('hg.admin')
518 @CSRFRequired()
517 @CSRFRequired()
519 def settings_email_update(self):
518 def settings_email_update(self):
520 _ = self.request.translate
519 _ = self.request.translate
521 c = self.load_default_context()
520 c = self.load_default_context()
522 c.active = 'email'
521 c.active = 'email'
523
522
524 test_email = self.request.POST.get('test_email')
523 test_email = self.request.POST.get('test_email')
525
524
526 if not test_email:
525 if not test_email:
527 h.flash(_('Please enter email address'), category='error')
526 h.flash(_('Please enter email address'), category='error')
528 raise HTTPFound(h.route_path('admin_settings_email'))
527 raise HTTPFound(h.route_path('admin_settings_email'))
529
528
530 email_kwargs = {
529 email_kwargs = {
531 'date': datetime.datetime.now(),
530 'date': datetime.datetime.now(),
532 'user': self._rhodecode_db_user
531 'user': self._rhodecode_db_user
533 }
532 }
534
533
535 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
534 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
536 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
535 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
537
536
538 recipients = [test_email] if test_email else None
537 recipients = [test_email] if test_email else None
539
538
540 run_task(tasks.send_email, recipients, subject,
539 run_task(tasks.send_email, recipients, subject,
541 email_body_plaintext, email_body)
540 email_body_plaintext, email_body)
542
541
543 h.flash(_('Send email task created'), category='success')
542 h.flash(_('Send email task created'), category='success')
544 raise HTTPFound(h.route_path('admin_settings_email'))
543 raise HTTPFound(h.route_path('admin_settings_email'))
545
544
546 @LoginRequired()
545 @LoginRequired()
547 @HasPermissionAllDecorator('hg.admin')
546 @HasPermissionAllDecorator('hg.admin')
548 def settings_hooks(self):
547 def settings_hooks(self):
549 c = self.load_default_context()
548 c = self.load_default_context()
550 c.active = 'hooks'
549 c.active = 'hooks'
551
550
552 model = SettingsModel()
551 model = SettingsModel()
553 c.hooks = model.get_builtin_hooks()
552 c.hooks = model.get_builtin_hooks()
554 c.custom_hooks = model.get_custom_hooks()
553 c.custom_hooks = model.get_custom_hooks()
555
554
556 data = render('rhodecode:templates/admin/settings/settings.mako',
555 data = render('rhodecode:templates/admin/settings/settings.mako',
557 self._get_template_context(c), self.request)
556 self._get_template_context(c), self.request)
558 html = formencode.htmlfill.render(
557 html = formencode.htmlfill.render(
559 data,
558 data,
560 defaults=self._form_defaults(),
559 defaults=self._form_defaults(),
561 encoding="UTF-8",
560 encoding="UTF-8",
562 force_defaults=False
561 force_defaults=False
563 )
562 )
564 return Response(html)
563 return Response(html)
565
564
566 @LoginRequired()
565 @LoginRequired()
567 @HasPermissionAllDecorator('hg.admin')
566 @HasPermissionAllDecorator('hg.admin')
568 @CSRFRequired()
567 @CSRFRequired()
569 def settings_hooks_update(self):
568 def settings_hooks_update(self):
570 _ = self.request.translate
569 _ = self.request.translate
571 c = self.load_default_context()
570 c = self.load_default_context()
572 c.active = 'hooks'
571 c.active = 'hooks'
573 if c.visual.allow_custom_hooks_settings:
572 if c.visual.allow_custom_hooks_settings:
574 ui_key = self.request.POST.get('new_hook_ui_key')
573 ui_key = self.request.POST.get('new_hook_ui_key')
575 ui_value = self.request.POST.get('new_hook_ui_value')
574 ui_value = self.request.POST.get('new_hook_ui_value')
576
575
577 hook_id = self.request.POST.get('hook_id')
576 hook_id = self.request.POST.get('hook_id')
578 new_hook = False
577 new_hook = False
579
578
580 model = SettingsModel()
579 model = SettingsModel()
581 try:
580 try:
582 if ui_value and ui_key:
581 if ui_value and ui_key:
583 model.create_or_update_hook(ui_key, ui_value)
582 model.create_or_update_hook(ui_key, ui_value)
584 h.flash(_('Added new hook'), category='success')
583 h.flash(_('Added new hook'), category='success')
585 new_hook = True
584 new_hook = True
586 elif hook_id:
585 elif hook_id:
587 RhodeCodeUi.delete(hook_id)
586 RhodeCodeUi.delete(hook_id)
588 Session().commit()
587 Session().commit()
589
588
590 # check for edits
589 # check for edits
591 update = False
590 update = False
592 _d = self.request.POST.dict_of_lists()
591 _d = self.request.POST.dict_of_lists()
593 for k, v in zip(_d.get('hook_ui_key', []),
592 for k, v in zip(_d.get('hook_ui_key', []),
594 _d.get('hook_ui_value_new', [])):
593 _d.get('hook_ui_value_new', [])):
595 model.create_or_update_hook(k, v)
594 model.create_or_update_hook(k, v)
596 update = True
595 update = True
597
596
598 if update and not new_hook:
597 if update and not new_hook:
599 h.flash(_('Updated hooks'), category='success')
598 h.flash(_('Updated hooks'), category='success')
600 Session().commit()
599 Session().commit()
601 except Exception:
600 except Exception:
602 log.exception("Exception during hook creation")
601 log.exception("Exception during hook creation")
603 h.flash(_('Error occurred during hook creation'),
602 h.flash(_('Error occurred during hook creation'),
604 category='error')
603 category='error')
605
604
606 raise HTTPFound(h.route_path('admin_settings_hooks'))
605 raise HTTPFound(h.route_path('admin_settings_hooks'))
607
606
608 @LoginRequired()
607 @LoginRequired()
609 @HasPermissionAllDecorator('hg.admin')
608 @HasPermissionAllDecorator('hg.admin')
610 def settings_search(self):
609 def settings_search(self):
611 c = self.load_default_context()
610 c = self.load_default_context()
612 c.active = 'search'
611 c.active = 'search'
613
612
614 c.searcher = searcher_from_config(self.request.registry.settings)
613 c.searcher = searcher_from_config(self.request.registry.settings)
615 c.statistics = c.searcher.statistics(self.request.translate)
614 c.statistics = c.searcher.statistics(self.request.translate)
616
615
617 return self._get_template_context(c)
616 return self._get_template_context(c)
618
617
619 @LoginRequired()
618 @LoginRequired()
620 @HasPermissionAllDecorator('hg.admin')
619 @HasPermissionAllDecorator('hg.admin')
621 def settings_labs(self):
620 def settings_labs(self):
622 c = self.load_default_context()
621 c = self.load_default_context()
623 if not c.labs_active:
622 if not c.labs_active:
624 raise HTTPFound(h.route_path('admin_settings'))
623 raise HTTPFound(h.route_path('admin_settings'))
625
624
626 c.active = 'labs'
625 c.active = 'labs'
627 c.lab_settings = _LAB_SETTINGS
626 c.lab_settings = _LAB_SETTINGS
628
627
629 data = render('rhodecode:templates/admin/settings/settings.mako',
628 data = render('rhodecode:templates/admin/settings/settings.mako',
630 self._get_template_context(c), self.request)
629 self._get_template_context(c), self.request)
631 html = formencode.htmlfill.render(
630 html = formencode.htmlfill.render(
632 data,
631 data,
633 defaults=self._form_defaults(),
632 defaults=self._form_defaults(),
634 encoding="UTF-8",
633 encoding="UTF-8",
635 force_defaults=False
634 force_defaults=False
636 )
635 )
637 return Response(html)
636 return Response(html)
638
637
639 @LoginRequired()
638 @LoginRequired()
640 @HasPermissionAllDecorator('hg.admin')
639 @HasPermissionAllDecorator('hg.admin')
641 @CSRFRequired()
640 @CSRFRequired()
642 def settings_labs_update(self):
641 def settings_labs_update(self):
643 _ = self.request.translate
642 _ = self.request.translate
644 c = self.load_default_context()
643 c = self.load_default_context()
645 c.active = 'labs'
644 c.active = 'labs'
646
645
647 application_form = LabsSettingsForm(self.request.translate)()
646 application_form = LabsSettingsForm(self.request.translate)()
648 try:
647 try:
649 form_result = application_form.to_python(dict(self.request.POST))
648 form_result = application_form.to_python(dict(self.request.POST))
650 except formencode.Invalid as errors:
649 except formencode.Invalid as errors:
651 h.flash(
650 h.flash(
652 _("Some form inputs contain invalid data."),
651 _("Some form inputs contain invalid data."),
653 category='error')
652 category='error')
654 data = render('rhodecode:templates/admin/settings/settings.mako',
653 data = render('rhodecode:templates/admin/settings/settings.mako',
655 self._get_template_context(c), self.request)
654 self._get_template_context(c), self.request)
656 html = formencode.htmlfill.render(
655 html = formencode.htmlfill.render(
657 data,
656 data,
658 defaults=errors.value,
657 defaults=errors.value,
659 errors=errors.unpack_errors() or {},
658 errors=errors.unpack_errors() or {},
660 prefix_error=False,
659 prefix_error=False,
661 encoding="UTF-8",
660 encoding="UTF-8",
662 force_defaults=False
661 force_defaults=False
663 )
662 )
664 return Response(html)
663 return Response(html)
665
664
666 try:
665 try:
667 session = Session()
666 session = Session()
668 for setting in _LAB_SETTINGS:
667 for setting in _LAB_SETTINGS:
669 setting_name = setting.key[len('rhodecode_'):]
668 setting_name = setting.key[len('rhodecode_'):]
670 sett = SettingsModel().create_or_update_setting(
669 sett = SettingsModel().create_or_update_setting(
671 setting_name, form_result[setting.key], setting.type)
670 setting_name, form_result[setting.key], setting.type)
672 session.add(sett)
671 session.add(sett)
673
672
674 except Exception:
673 except Exception:
675 log.exception('Exception while updating lab settings')
674 log.exception('Exception while updating lab settings')
676 h.flash(_('Error occurred during updating labs settings'),
675 h.flash(_('Error occurred during updating labs settings'),
677 category='error')
676 category='error')
678 else:
677 else:
679 Session().commit()
678 Session().commit()
680 SettingsModel().invalidate_settings_cache()
679 SettingsModel().invalidate_settings_cache()
681 h.flash(_('Updated Labs settings'), category='success')
680 h.flash(_('Updated Labs settings'), category='success')
682 raise HTTPFound(h.route_path('admin_settings_labs'))
681 raise HTTPFound(h.route_path('admin_settings_labs'))
683
682
684 data = render('rhodecode:templates/admin/settings/settings.mako',
683 data = render('rhodecode:templates/admin/settings/settings.mako',
685 self._get_template_context(c), self.request)
684 self._get_template_context(c), self.request)
686 html = formencode.htmlfill.render(
685 html = formencode.htmlfill.render(
687 data,
686 data,
688 defaults=self._form_defaults(),
687 defaults=self._form_defaults(),
689 encoding="UTF-8",
688 encoding="UTF-8",
690 force_defaults=False
689 force_defaults=False
691 )
690 )
692 return Response(html)
691 return Response(html)
693
692
694
693
695 # :param key: name of the setting including the 'rhodecode_' prefix
694 # :param key: name of the setting including the 'rhodecode_' prefix
696 # :param type: the RhodeCodeSetting type to use.
695 # :param type: the RhodeCodeSetting type to use.
697 # :param group: the i18ned group in which we should dispaly this setting
696 # :param group: the i18ned group in which we should dispaly this setting
698 # :param label: the i18ned label we should display for this setting
697 # :param label: the i18ned label we should display for this setting
699 # :param help: the i18ned help we should dispaly for this setting
698 # :param help: the i18ned help we should dispaly for this setting
700 LabSetting = collections.namedtuple(
699 LabSetting = collections.namedtuple(
701 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
700 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
702
701
703
702
704 # This list has to be kept in sync with the form
703 # This list has to be kept in sync with the form
705 # rhodecode.model.forms.LabsSettingsForm.
704 # rhodecode.model.forms.LabsSettingsForm.
706 _LAB_SETTINGS = [
705 _LAB_SETTINGS = [
707
706
708 ]
707 ]
@@ -1,470 +1,471 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import sys
20 import sys
21 import collections
21 import collections
22
22
23 import time
23 import time
24 import logging.config
24 import logging.config
25
25
26 from paste.gzipper import make_gzip_middleware
26 from paste.gzipper import make_gzip_middleware
27 import pyramid.events
27 import pyramid.events
28 from pyramid.wsgi import wsgiapp
28 from pyramid.wsgi import wsgiapp
29 from pyramid.config import Configurator
29 from pyramid.config import Configurator
30 from pyramid.settings import asbool, aslist
30 from pyramid.settings import asbool, aslist
31 from pyramid.httpexceptions import (
31 from pyramid.httpexceptions import (
32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
32 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
33 from pyramid.renderers import render_to_response
33 from pyramid.renderers import render_to_response
34
34
35 from rhodecode.model import meta
35 from rhodecode.model import meta
36 from rhodecode.config import patches
36 from rhodecode.config import patches
37
37
38 from rhodecode.config.environment import load_pyramid_environment, propagate_rhodecode_config
38 from rhodecode.config.environment import load_pyramid_environment, propagate_rhodecode_config
39
39
40 import rhodecode.events
40 import rhodecode.events
41 from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults
41 from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults
42 from rhodecode.lib.middleware.vcs import VCSMiddleware
42 from rhodecode.lib.middleware.vcs import VCSMiddleware
43 from rhodecode.lib.request import Request
43 from rhodecode.lib.request import Request
44 from rhodecode.lib.vcs import VCSCommunicationError
44 from rhodecode.lib.vcs import VCSCommunicationError
45 from rhodecode.lib.exceptions import VCSServerUnavailable
45 from rhodecode.lib.exceptions import VCSServerUnavailable
46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
46 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
47 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
48 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
49 from rhodecode.lib.utils2 import AttributeDict
49 from rhodecode.lib.utils2 import AttributeDict
50 from rhodecode.lib.exc_tracking import store_exception, format_exc
50 from rhodecode.lib.exc_tracking import store_exception, format_exc
51 from rhodecode.subscribers import (
51 from rhodecode.subscribers import (
52 scan_repositories_if_enabled, write_js_routes_if_enabled,
52 scan_repositories_if_enabled, write_js_routes_if_enabled,
53 write_metadata_if_needed, write_usage_data)
53 write_metadata_if_needed, write_usage_data, import_license_if_present)
54 from rhodecode.lib.statsd_client import StatsdClient
54 from rhodecode.lib.statsd_client import StatsdClient
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def is_http_error(response):
59 def is_http_error(response):
60 # error which should have traceback
60 # error which should have traceback
61 return response.status_code > 499
61 return response.status_code > 499
62
62
63
63
64 def should_load_all():
64 def should_load_all():
65 """
65 """
66 Returns if all application components should be loaded. In some cases it's
66 Returns if all application components should be loaded. In some cases it's
67 desired to skip apps loading for faster shell script execution
67 desired to skip apps loading for faster shell script execution
68 """
68 """
69 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
69 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
70 if ssh_cmd:
70 if ssh_cmd:
71 return False
71 return False
72
72
73 return True
73 return True
74
74
75
75
76 def make_pyramid_app(global_config, **settings):
76 def make_pyramid_app(global_config, **settings):
77 """
77 """
78 Constructs the WSGI application based on Pyramid.
78 Constructs the WSGI application based on Pyramid.
79
79
80 Specials:
80 Specials:
81
81
82 * The application can also be integrated like a plugin via the call to
82 * The application can also be integrated like a plugin via the call to
83 `includeme`. This is accompanied with the other utility functions which
83 `includeme`. This is accompanied with the other utility functions which
84 are called. Changing this should be done with great care to not break
84 are called. Changing this should be done with great care to not break
85 cases when these fragments are assembled from another place.
85 cases when these fragments are assembled from another place.
86
86
87 """
87 """
88 start_time = time.time()
88 start_time = time.time()
89 log.info('Pyramid app config starting')
89 log.info('Pyramid app config starting')
90
90
91 sanitize_settings_and_apply_defaults(global_config, settings)
91 sanitize_settings_and_apply_defaults(global_config, settings)
92
92
93 # init and bootstrap StatsdClient
93 # init and bootstrap StatsdClient
94 StatsdClient.setup(settings)
94 StatsdClient.setup(settings)
95
95
96 config = Configurator(settings=settings)
96 config = Configurator(settings=settings)
97 # Init our statsd at very start
97 # Init our statsd at very start
98 config.registry.statsd = StatsdClient.statsd
98 config.registry.statsd = StatsdClient.statsd
99
99
100 # Apply compatibility patches
100 # Apply compatibility patches
101 patches.inspect_getargspec()
101 patches.inspect_getargspec()
102 patches.repoze_sendmail_lf_fix()
102 patches.repoze_sendmail_lf_fix()
103
103
104 load_pyramid_environment(global_config, settings)
104 load_pyramid_environment(global_config, settings)
105
105
106 # Static file view comes first
106 # Static file view comes first
107 includeme_first(config)
107 includeme_first(config)
108
108
109 includeme(config)
109 includeme(config)
110
110
111 pyramid_app = config.make_wsgi_app()
111 pyramid_app = config.make_wsgi_app()
112 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
112 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
113 pyramid_app.config = config
113 pyramid_app.config = config
114
114
115 celery_settings = get_celery_config(settings)
115 celery_settings = get_celery_config(settings)
116 config.configure_celery(celery_settings)
116 config.configure_celery(celery_settings)
117
117
118 # final config set...
118 # final config set...
119 propagate_rhodecode_config(global_config, settings, config.registry.settings)
119 propagate_rhodecode_config(global_config, settings, config.registry.settings)
120
120
121 # creating the app uses a connection - return it after we are done
121 # creating the app uses a connection - return it after we are done
122 meta.Session.remove()
122 meta.Session.remove()
123
123
124 total_time = time.time() - start_time
124 total_time = time.time() - start_time
125 log.info('Pyramid app created and configured in %.2fs', total_time)
125 log.info('Pyramid app created and configured in %.2fs', total_time)
126 return pyramid_app
126 return pyramid_app
127
127
128
128
129 def get_celery_config(settings):
129 def get_celery_config(settings):
130 """
130 """
131 Converts basic ini configuration into celery 4.X options
131 Converts basic ini configuration into celery 4.X options
132 """
132 """
133
133
134 def key_converter(key_name):
134 def key_converter(key_name):
135 pref = 'celery.'
135 pref = 'celery.'
136 if key_name.startswith(pref):
136 if key_name.startswith(pref):
137 return key_name[len(pref):].replace('.', '_').lower()
137 return key_name[len(pref):].replace('.', '_').lower()
138
138
139 def type_converter(parsed_key, value):
139 def type_converter(parsed_key, value):
140 # cast to int
140 # cast to int
141 if value.isdigit():
141 if value.isdigit():
142 return int(value)
142 return int(value)
143
143
144 # cast to bool
144 # cast to bool
145 if value.lower() in ['true', 'false', 'True', 'False']:
145 if value.lower() in ['true', 'false', 'True', 'False']:
146 return value.lower() == 'true'
146 return value.lower() == 'true'
147 return value
147 return value
148
148
149 celery_config = {}
149 celery_config = {}
150 for k, v in settings.items():
150 for k, v in settings.items():
151 pref = 'celery.'
151 pref = 'celery.'
152 if k.startswith(pref):
152 if k.startswith(pref):
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
154
154
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
156 # beat_config = {}
156 # beat_config = {}
157 # for section in parser.sections():
157 # for section in parser.sections():
158 # if section.startswith('celerybeat:'):
158 # if section.startswith('celerybeat:'):
159 # name = section.split(':', 1)[1]
159 # name = section.split(':', 1)[1]
160 # beat_config[name] = get_beat_config(parser, section)
160 # beat_config[name] = get_beat_config(parser, section)
161
161
162 # final compose of settings
162 # final compose of settings
163 celery_settings = {}
163 celery_settings = {}
164
164
165 if celery_config:
165 if celery_config:
166 celery_settings.update(celery_config)
166 celery_settings.update(celery_config)
167 # if beat_config:
167 # if beat_config:
168 # celery_settings.update({'beat_schedule': beat_config})
168 # celery_settings.update({'beat_schedule': beat_config})
169
169
170 return celery_settings
170 return celery_settings
171
171
172
172
173 def not_found_view(request):
173 def not_found_view(request):
174 """
174 """
175 This creates the view which should be registered as not-found-view to
175 This creates the view which should be registered as not-found-view to
176 pyramid.
176 pyramid.
177 """
177 """
178
178
179 if not getattr(request, 'vcs_call', None):
179 if not getattr(request, 'vcs_call', None):
180 # handle like regular case with our error_handler
180 # handle like regular case with our error_handler
181 return error_handler(HTTPNotFound(), request)
181 return error_handler(HTTPNotFound(), request)
182
182
183 # handle not found view as a vcs call
183 # handle not found view as a vcs call
184 settings = request.registry.settings
184 settings = request.registry.settings
185 ae_client = getattr(request, 'ae_client', None)
185 ae_client = getattr(request, 'ae_client', None)
186 vcs_app = VCSMiddleware(
186 vcs_app = VCSMiddleware(
187 HTTPNotFound(), request.registry, settings,
187 HTTPNotFound(), request.registry, settings,
188 appenlight_client=ae_client)
188 appenlight_client=ae_client)
189
189
190 return wsgiapp(vcs_app)(None, request)
190 return wsgiapp(vcs_app)(None, request)
191
191
192
192
193 def error_handler(exception, request):
193 def error_handler(exception, request):
194 import rhodecode
194 import rhodecode
195 from rhodecode.lib import helpers
195 from rhodecode.lib import helpers
196
196
197 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
197 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
198
198
199 base_response = HTTPInternalServerError()
199 base_response = HTTPInternalServerError()
200 # prefer original exception for the response since it may have headers set
200 # prefer original exception for the response since it may have headers set
201 if isinstance(exception, HTTPException):
201 if isinstance(exception, HTTPException):
202 base_response = exception
202 base_response = exception
203 elif isinstance(exception, VCSCommunicationError):
203 elif isinstance(exception, VCSCommunicationError):
204 base_response = VCSServerUnavailable()
204 base_response = VCSServerUnavailable()
205
205
206 if is_http_error(base_response):
206 if is_http_error(base_response):
207 traceback_info = format_exc(request.exc_info)
207 traceback_info = format_exc(request.exc_info)
208 log.error(
208 log.error(
209 'error occurred handling this request for path: %s, \n%s',
209 'error occurred handling this request for path: %s, \n%s',
210 request.path, traceback_info)
210 request.path, traceback_info)
211
211
212 error_explanation = base_response.explanation or str(base_response)
212 error_explanation = base_response.explanation or str(base_response)
213 if base_response.status_code == 404:
213 if base_response.status_code == 404:
214 error_explanation += " Optionally you don't have permission to access this page."
214 error_explanation += " Optionally you don't have permission to access this page."
215 c = AttributeDict()
215 c = AttributeDict()
216 c.error_message = base_response.status
216 c.error_message = base_response.status
217 c.error_explanation = error_explanation
217 c.error_explanation = error_explanation
218 c.visual = AttributeDict()
218 c.visual = AttributeDict()
219
219
220 c.visual.rhodecode_support_url = (
220 c.visual.rhodecode_support_url = (
221 request.registry.settings.get('rhodecode_support_url') or
221 request.registry.settings.get('rhodecode_support_url') or
222 request.route_url('rhodecode_support')
222 request.route_url('rhodecode_support')
223 )
223 )
224 c.redirect_time = 0
224 c.redirect_time = 0
225 c.rhodecode_name = rhodecode_title
225 c.rhodecode_name = rhodecode_title
226 if not c.rhodecode_name:
226 if not c.rhodecode_name:
227 c.rhodecode_name = 'Rhodecode'
227 c.rhodecode_name = 'Rhodecode'
228
228
229 c.causes = []
229 c.causes = []
230 if is_http_error(base_response):
230 if is_http_error(base_response):
231 c.causes.append('Server is overloaded.')
231 c.causes.append('Server is overloaded.')
232 c.causes.append('Server database connection is lost.')
232 c.causes.append('Server database connection is lost.')
233 c.causes.append('Server expected unhandled error.')
233 c.causes.append('Server expected unhandled error.')
234
234
235 if hasattr(base_response, 'causes'):
235 if hasattr(base_response, 'causes'):
236 c.causes = base_response.causes
236 c.causes = base_response.causes
237
237
238 c.messages = helpers.flash.pop_messages(request=request)
238 c.messages = helpers.flash.pop_messages(request=request)
239 exc_info = sys.exc_info()
239 exc_info = sys.exc_info()
240 c.exception_id = id(exc_info)
240 c.exception_id = id(exc_info)
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
242 or base_response.status_code > 499
242 or base_response.status_code > 499
243 c.exception_id_url = request.route_url(
243 c.exception_id_url = request.route_url(
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
245
245
246 debug_mode = rhodecode.ConfigGet().get_bool('debug')
246 debug_mode = rhodecode.ConfigGet().get_bool('debug')
247 if c.show_exception_id:
247 if c.show_exception_id:
248 store_exception(c.exception_id, exc_info)
248 store_exception(c.exception_id, exc_info)
249 c.exception_debug = debug_mode
249 c.exception_debug = debug_mode
250 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
250 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
251
251
252 if debug_mode:
252 if debug_mode:
253 try:
253 try:
254 from rich.traceback import install
254 from rich.traceback import install
255 install(show_locals=True)
255 install(show_locals=True)
256 log.debug('Installing rich tracebacks...')
256 log.debug('Installing rich tracebacks...')
257 except ImportError:
257 except ImportError:
258 pass
258 pass
259
259
260 response = render_to_response(
260 response = render_to_response(
261 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
261 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
262 response=base_response)
262 response=base_response)
263
263
264 response.headers["X-RC-Exception-Id"] = str(c.exception_id)
264 response.headers["X-RC-Exception-Id"] = str(c.exception_id)
265
265
266 statsd = request.registry.statsd
266 statsd = request.registry.statsd
267 if statsd and base_response.status_code > 499:
267 if statsd and base_response.status_code > 499:
268 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
268 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
269 statsd.incr('rhodecode_exception_total',
269 statsd.incr('rhodecode_exception_total',
270 tags=["exc_source:web",
270 tags=["exc_source:web",
271 f"http_code:{base_response.status_code}",
271 f"http_code:{base_response.status_code}",
272 f"type:{exc_type}"])
272 f"type:{exc_type}"])
273
273
274 return response
274 return response
275
275
276
276
277 def includeme_first(config):
277 def includeme_first(config):
278 # redirect automatic browser favicon.ico requests to correct place
278 # redirect automatic browser favicon.ico requests to correct place
279 def favicon_redirect(context, request):
279 def favicon_redirect(context, request):
280 return HTTPFound(
280 return HTTPFound(
281 request.static_path('rhodecode:public/images/favicon.ico'))
281 request.static_path('rhodecode:public/images/favicon.ico'))
282
282
283 config.add_view(favicon_redirect, route_name='favicon')
283 config.add_view(favicon_redirect, route_name='favicon')
284 config.add_route('favicon', '/favicon.ico')
284 config.add_route('favicon', '/favicon.ico')
285
285
286 def robots_redirect(context, request):
286 def robots_redirect(context, request):
287 return HTTPFound(
287 return HTTPFound(
288 request.static_path('rhodecode:public/robots.txt'))
288 request.static_path('rhodecode:public/robots.txt'))
289
289
290 config.add_view(robots_redirect, route_name='robots')
290 config.add_view(robots_redirect, route_name='robots')
291 config.add_route('robots', '/robots.txt')
291 config.add_route('robots', '/robots.txt')
292
292
293 config.add_static_view(
293 config.add_static_view(
294 '_static/deform', 'deform:static')
294 '_static/deform', 'deform:static')
295 config.add_static_view(
295 config.add_static_view(
296 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
296 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
297
297
298
298
299 ce_auth_resources = [
299 ce_auth_resources = [
300 'rhodecode.authentication.plugins.auth_crowd',
300 'rhodecode.authentication.plugins.auth_crowd',
301 'rhodecode.authentication.plugins.auth_headers',
301 'rhodecode.authentication.plugins.auth_headers',
302 'rhodecode.authentication.plugins.auth_jasig_cas',
302 'rhodecode.authentication.plugins.auth_jasig_cas',
303 'rhodecode.authentication.plugins.auth_ldap',
303 'rhodecode.authentication.plugins.auth_ldap',
304 'rhodecode.authentication.plugins.auth_pam',
304 'rhodecode.authentication.plugins.auth_pam',
305 'rhodecode.authentication.plugins.auth_rhodecode',
305 'rhodecode.authentication.plugins.auth_rhodecode',
306 'rhodecode.authentication.plugins.auth_token',
306 'rhodecode.authentication.plugins.auth_token',
307 ]
307 ]
308
308
309
309
310 def includeme(config, auth_resources=None):
310 def includeme(config, auth_resources=None):
311 from rhodecode.lib.celerylib.loader import configure_celery
311 from rhodecode.lib.celerylib.loader import configure_celery
312 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
312 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
313 settings = config.registry.settings
313 settings = config.registry.settings
314 config.set_request_factory(Request)
314 config.set_request_factory(Request)
315
315
316 # plugin information
316 # plugin information
317 config.registry.rhodecode_plugins = collections.OrderedDict()
317 config.registry.rhodecode_plugins = collections.OrderedDict()
318
318
319 config.add_directive(
319 config.add_directive(
320 'register_rhodecode_plugin', register_rhodecode_plugin)
320 'register_rhodecode_plugin', register_rhodecode_plugin)
321
321
322 config.add_directive('configure_celery', configure_celery)
322 config.add_directive('configure_celery', configure_celery)
323
323
324 if settings.get('appenlight', False):
324 if settings.get('appenlight', False):
325 config.include('appenlight_client.ext.pyramid_tween')
325 config.include('appenlight_client.ext.pyramid_tween')
326
326
327 load_all = should_load_all()
327 load_all = should_load_all()
328
328
329 # Includes which are required. The application would fail without them.
329 # Includes which are required. The application would fail without them.
330 config.include('pyramid_mako')
330 config.include('pyramid_mako')
331 config.include('rhodecode.lib.rc_beaker')
331 config.include('rhodecode.lib.rc_beaker')
332 config.include('rhodecode.lib.rc_cache')
332 config.include('rhodecode.lib.rc_cache')
333 config.include('rhodecode.lib.archive_cache')
333 config.include('rhodecode.lib.archive_cache')
334
334
335 config.include('rhodecode.apps._base.navigation')
335 config.include('rhodecode.apps._base.navigation')
336 config.include('rhodecode.apps._base.subscribers')
336 config.include('rhodecode.apps._base.subscribers')
337 config.include('rhodecode.tweens')
337 config.include('rhodecode.tweens')
338 config.include('rhodecode.authentication')
338 config.include('rhodecode.authentication')
339
339
340 if load_all:
340 if load_all:
341
341
342 # load CE authentication plugins
342 # load CE authentication plugins
343
343
344 if auth_resources:
344 if auth_resources:
345 ce_auth_resources.extend(auth_resources)
345 ce_auth_resources.extend(auth_resources)
346
346
347 for resource in ce_auth_resources:
347 for resource in ce_auth_resources:
348 config.include(resource)
348 config.include(resource)
349
349
350 # Auto discover authentication plugins and include their configuration.
350 # Auto discover authentication plugins and include their configuration.
351 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
351 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
352 from rhodecode.authentication import discover_legacy_plugins
352 from rhodecode.authentication import discover_legacy_plugins
353 discover_legacy_plugins(config)
353 discover_legacy_plugins(config)
354
354
355 # apps
355 # apps
356 if load_all:
356 if load_all:
357 log.debug('Starting config.include() calls')
357 log.debug('Starting config.include() calls')
358 config.include('rhodecode.api.includeme')
358 config.include('rhodecode.api.includeme')
359 config.include('rhodecode.apps._base.includeme')
359 config.include('rhodecode.apps._base.includeme')
360 config.include('rhodecode.apps._base.navigation.includeme')
360 config.include('rhodecode.apps._base.navigation.includeme')
361 config.include('rhodecode.apps._base.subscribers.includeme')
361 config.include('rhodecode.apps._base.subscribers.includeme')
362 config.include('rhodecode.apps.hovercards.includeme')
362 config.include('rhodecode.apps.hovercards.includeme')
363 config.include('rhodecode.apps.ops.includeme')
363 config.include('rhodecode.apps.ops.includeme')
364 config.include('rhodecode.apps.channelstream.includeme')
364 config.include('rhodecode.apps.channelstream.includeme')
365 config.include('rhodecode.apps.file_store.includeme')
365 config.include('rhodecode.apps.file_store.includeme')
366 config.include('rhodecode.apps.admin.includeme')
366 config.include('rhodecode.apps.admin.includeme')
367 config.include('rhodecode.apps.login.includeme')
367 config.include('rhodecode.apps.login.includeme')
368 config.include('rhodecode.apps.home.includeme')
368 config.include('rhodecode.apps.home.includeme')
369 config.include('rhodecode.apps.journal.includeme')
369 config.include('rhodecode.apps.journal.includeme')
370
370
371 config.include('rhodecode.apps.repository.includeme')
371 config.include('rhodecode.apps.repository.includeme')
372 config.include('rhodecode.apps.repo_group.includeme')
372 config.include('rhodecode.apps.repo_group.includeme')
373 config.include('rhodecode.apps.user_group.includeme')
373 config.include('rhodecode.apps.user_group.includeme')
374 config.include('rhodecode.apps.search.includeme')
374 config.include('rhodecode.apps.search.includeme')
375 config.include('rhodecode.apps.user_profile.includeme')
375 config.include('rhodecode.apps.user_profile.includeme')
376 config.include('rhodecode.apps.user_group_profile.includeme')
376 config.include('rhodecode.apps.user_group_profile.includeme')
377 config.include('rhodecode.apps.my_account.includeme')
377 config.include('rhodecode.apps.my_account.includeme')
378 config.include('rhodecode.apps.gist.includeme')
378 config.include('rhodecode.apps.gist.includeme')
379
379
380 config.include('rhodecode.apps.svn_support.includeme')
380 config.include('rhodecode.apps.svn_support.includeme')
381 config.include('rhodecode.apps.ssh_support.includeme')
381 config.include('rhodecode.apps.ssh_support.includeme')
382 config.include('rhodecode.apps.debug_style')
382 config.include('rhodecode.apps.debug_style')
383
383
384 if load_all:
384 if load_all:
385 config.include('rhodecode.integrations.includeme')
385 config.include('rhodecode.integrations.includeme')
386 config.include('rhodecode.integrations.routes.includeme')
386 config.include('rhodecode.integrations.routes.includeme')
387
387
388 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
388 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
389 settings['default_locale_name'] = settings.get('lang', 'en')
389 settings['default_locale_name'] = settings.get('lang', 'en')
390 config.add_translation_dirs('rhodecode:i18n/')
390 config.add_translation_dirs('rhodecode:i18n/')
391
391
392 # Add subscribers.
392 # Add subscribers.
393 if load_all:
393 if load_all:
394 log.debug('Adding subscribers...')
394 log.debug('Adding subscribers...')
395 config.add_subscriber(scan_repositories_if_enabled,
395 config.add_subscriber(scan_repositories_if_enabled,
396 pyramid.events.ApplicationCreated)
396 pyramid.events.ApplicationCreated)
397 config.add_subscriber(write_metadata_if_needed,
397 config.add_subscriber(write_metadata_if_needed,
398 pyramid.events.ApplicationCreated)
398 pyramid.events.ApplicationCreated)
399 config.add_subscriber(write_usage_data,
399 config.add_subscriber(write_usage_data,
400 pyramid.events.ApplicationCreated)
400 pyramid.events.ApplicationCreated)
401 config.add_subscriber(write_js_routes_if_enabled,
401 config.add_subscriber(write_js_routes_if_enabled,
402 pyramid.events.ApplicationCreated)
402 pyramid.events.ApplicationCreated)
403
403 config.add_subscriber(import_license_if_present,
404 pyramid.events.ApplicationCreated)
404
405
405 # Set the default renderer for HTML templates to mako.
406 # Set the default renderer for HTML templates to mako.
406 config.add_mako_renderer('.html')
407 config.add_mako_renderer('.html')
407
408
408 config.add_renderer(
409 config.add_renderer(
409 name='json_ext',
410 name='json_ext',
410 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
411 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
411
412
412 config.add_renderer(
413 config.add_renderer(
413 name='string_html',
414 name='string_html',
414 factory='rhodecode.lib.string_renderer.html')
415 factory='rhodecode.lib.string_renderer.html')
415
416
416 # include RhodeCode plugins
417 # include RhodeCode plugins
417 includes = aslist(settings.get('rhodecode.includes', []))
418 includes = aslist(settings.get('rhodecode.includes', []))
418 log.debug('processing rhodecode.includes data...')
419 log.debug('processing rhodecode.includes data...')
419 for inc in includes:
420 for inc in includes:
420 config.include(inc)
421 config.include(inc)
421
422
422 # custom not found view, if our pyramid app doesn't know how to handle
423 # custom not found view, if our pyramid app doesn't know how to handle
423 # the request pass it to potential VCS handling ap
424 # the request pass it to potential VCS handling ap
424 config.add_notfound_view(not_found_view)
425 config.add_notfound_view(not_found_view)
425 if not settings.get('debugtoolbar.enabled', False):
426 if not settings.get('debugtoolbar.enabled', False):
426 # disabled debugtoolbar handle all exceptions via the error_handlers
427 # disabled debugtoolbar handle all exceptions via the error_handlers
427 config.add_view(error_handler, context=Exception)
428 config.add_view(error_handler, context=Exception)
428
429
429 # all errors including 403/404/50X
430 # all errors including 403/404/50X
430 config.add_view(error_handler, context=HTTPError)
431 config.add_view(error_handler, context=HTTPError)
431
432
432
433
433 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
434 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
434 """
435 """
435 Apply outer WSGI middlewares around the application.
436 Apply outer WSGI middlewares around the application.
436 """
437 """
437 registry = config.registry
438 registry = config.registry
438 settings = registry.settings
439 settings = registry.settings
439
440
440 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
441 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
441 pyramid_app = HttpsFixup(pyramid_app, settings)
442 pyramid_app = HttpsFixup(pyramid_app, settings)
442
443
443 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
444 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
444 pyramid_app, settings)
445 pyramid_app, settings)
445 registry.ae_client = _ae_client
446 registry.ae_client = _ae_client
446
447
447 if settings['gzip_responses']:
448 if settings['gzip_responses']:
448 pyramid_app = make_gzip_middleware(
449 pyramid_app = make_gzip_middleware(
449 pyramid_app, settings, compress_level=1)
450 pyramid_app, settings, compress_level=1)
450
451
451 # this should be the outer most middleware in the wsgi stack since
452 # this should be the outer most middleware in the wsgi stack since
452 # middleware like Routes make database calls
453 # middleware like Routes make database calls
453 def pyramid_app_with_cleanup(environ, start_response):
454 def pyramid_app_with_cleanup(environ, start_response):
454 start = time.time()
455 start = time.time()
455 try:
456 try:
456 return pyramid_app(environ, start_response)
457 return pyramid_app(environ, start_response)
457 finally:
458 finally:
458 # Dispose current database session and rollback uncommitted
459 # Dispose current database session and rollback uncommitted
459 # transactions.
460 # transactions.
460 meta.Session.remove()
461 meta.Session.remove()
461
462
462 # In a single threaded mode server, on non sqlite db we should have
463 # In a single threaded mode server, on non sqlite db we should have
463 # '0 Current Checked out connections' at the end of a request,
464 # '0 Current Checked out connections' at the end of a request,
464 # if not, then something, somewhere is leaving a connection open
465 # if not, then something, somewhere is leaving a connection open
465 pool = meta.get_engine().pool
466 pool = meta.get_engine().pool
466 log.debug('sa pool status: %s', pool.status())
467 log.debug('sa pool status: %s', pool.status())
467 total = time.time() - start
468 total = time.time() - start
468 log.debug('Request processing finalized: %.4fs', total)
469 log.debug('Request processing finalized: %.4fs', total)
469
470
470 return pyramid_app_with_cleanup
471 return pyramid_app_with_cleanup
@@ -1,679 +1,678 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Database creation, and setup module for RhodeCode Enterprise. Used for creation
20 Database creation, and setup module for RhodeCode Enterprise. Used for creation
21 of database as well as for migration operations
21 of database as well as for migration operations
22 """
22 """
23
23
24 import os
24 import os
25 import sys
25 import sys
26 import time
26 import time
27 import uuid
27 import uuid
28 import logging
28 import logging
29 import getpass
29 import getpass
30 from os.path import dirname as dn, join as jn
30 from os.path import dirname as dn, join as jn
31
31
32 from sqlalchemy.engine import create_engine
32 from sqlalchemy.engine import create_engine
33
33
34 from rhodecode import __dbversion__
34 from rhodecode import __dbversion__
35 from rhodecode.model import init_model
35 from rhodecode.model import init_model
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
38 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
39 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
39 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
40 from rhodecode.model.meta import Session, Base
40 from rhodecode.model.meta import Session, Base
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.repo_group import RepoGroupModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def notify(msg):
50 def notify(msg):
51 """
51 """
52 Notification for migrations messages
52 Notification for migrations messages
53 """
53 """
54 ml = len(msg) + (4 * 2)
54 ml = len(msg) + (4 * 2)
55 print((('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()))
55 print((('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()))
56
56
57
57
58 class DbManage(object):
58 class DbManage(object):
59
59
60 def __init__(self, log_sql, dbconf, root, tests=False,
60 def __init__(self, log_sql, dbconf, root, tests=False,
61 SESSION=None, cli_args=None, enc_key=b''):
61 SESSION=None, cli_args=None, enc_key=b''):
62
62
63 self.dbname = dbconf.split('/')[-1]
63 self.dbname = dbconf.split('/')[-1]
64 self.tests = tests
64 self.tests = tests
65 self.root = root
65 self.root = root
66 self.dburi = dbconf
66 self.dburi = dbconf
67 self.log_sql = log_sql
67 self.log_sql = log_sql
68 self.cli_args = cli_args or {}
68 self.cli_args = cli_args or {}
69 self.sa = None
69 self.sa = None
70 self.engine = None
70 self.engine = None
71 self.enc_key = enc_key
71 self.enc_key = enc_key
72 # sets .sa .engine
72 # sets .sa .engine
73 self.init_db(SESSION=SESSION)
73 self.init_db(SESSION=SESSION)
74
74
75 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
75 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
76
76
77 def db_exists(self):
77 def db_exists(self):
78 if not self.sa:
78 if not self.sa:
79 self.init_db()
79 self.init_db()
80 try:
80 try:
81 self.sa.query(RhodeCodeUi)\
81 self.sa.query(RhodeCodeUi)\
82 .filter(RhodeCodeUi.ui_key == '/')\
82 .filter(RhodeCodeUi.ui_key == '/')\
83 .scalar()
83 .scalar()
84 return True
84 return True
85 except Exception:
85 except Exception:
86 return False
86 return False
87 finally:
87 finally:
88 self.sa.rollback()
88 self.sa.rollback()
89
89
90 def get_ask_ok_func(self, param):
90 def get_ask_ok_func(self, param):
91 if param not in [None]:
91 if param not in [None]:
92 # return a function lambda that has a default set to param
92 # return a function lambda that has a default set to param
93 return lambda *args, **kwargs: param
93 return lambda *args, **kwargs: param
94 else:
94 else:
95 from rhodecode.lib.utils import ask_ok
95 from rhodecode.lib.utils import ask_ok
96 return ask_ok
96 return ask_ok
97
97
98 def init_db(self, SESSION=None):
98 def init_db(self, SESSION=None):
99
99
100 if SESSION:
100 if SESSION:
101 self.sa = SESSION
101 self.sa = SESSION
102 self.engine = SESSION.bind
102 self.engine = SESSION.bind
103 else:
103 else:
104 # init new sessions
104 # init new sessions
105 engine = create_engine(self.dburi, echo=self.log_sql)
105 engine = create_engine(self.dburi, echo=self.log_sql)
106 init_model(engine, encryption_key=self.enc_key)
106 init_model(engine, encryption_key=self.enc_key)
107 self.sa = Session()
107 self.sa = Session()
108 self.engine = engine
108 self.engine = engine
109
109
110 def create_tables(self, override=False):
110 def create_tables(self, override=False):
111 """
111 """
112 Create a auth database
112 Create a auth database
113 """
113 """
114
114
115 log.info("Existing database with the same name is going to be destroyed.")
115 log.info("Existing database with the same name is going to be destroyed.")
116 log.info("Setup command will run DROP ALL command on that database.")
116 log.info("Setup command will run DROP ALL command on that database.")
117 engine = self.engine
117 engine = self.engine
118
118
119 if self.tests:
119 if self.tests:
120 destroy = True
120 destroy = True
121 else:
121 else:
122 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
122 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
123 if not destroy:
123 if not destroy:
124 log.info('db tables bootstrap: Nothing done.')
124 log.info('db tables bootstrap: Nothing done.')
125 sys.exit(0)
125 sys.exit(0)
126 if destroy:
126 if destroy:
127 Base.metadata.drop_all(bind=engine)
127 Base.metadata.drop_all(bind=engine)
128
128
129 checkfirst = not override
129 checkfirst = not override
130 Base.metadata.create_all(bind=engine, checkfirst=checkfirst)
130 Base.metadata.create_all(bind=engine, checkfirst=checkfirst)
131 log.info('Created tables for %s', self.dbname)
131 log.info('Created tables for %s', self.dbname)
132
132
133 def set_db_version(self):
133 def set_db_version(self):
134 ver = DbMigrateVersion()
134 ver = DbMigrateVersion()
135 ver.version = __dbversion__
135 ver.version = __dbversion__
136 ver.repository_id = 'rhodecode_db_migrations'
136 ver.repository_id = 'rhodecode_db_migrations'
137 ver.repository_path = 'versions'
137 ver.repository_path = 'versions'
138 self.sa.add(ver)
138 self.sa.add(ver)
139 log.info('db version set to: %s', __dbversion__)
139 log.info('db version set to: %s', __dbversion__)
140
140
141 def run_post_migration_tasks(self):
141 def run_post_migration_tasks(self):
142 """
142 """
143 Run various tasks before actually doing migrations
143 Run various tasks before actually doing migrations
144 """
144 """
145 # delete cache keys on each upgrade
145 # delete cache keys on each upgrade
146 total = CacheKey.query().count()
146 total = CacheKey.query().count()
147 log.info("Deleting (%s) cache keys now...", total)
147 log.info("Deleting (%s) cache keys now...", total)
148 CacheKey.delete_all_cache()
148 CacheKey.delete_all_cache()
149
149
150 def upgrade(self, version=None):
150 def upgrade(self, version=None):
151 """
151 """
152 Upgrades given database schema to given revision following
152 Upgrades given database schema to given revision following
153 all needed steps, to perform the upgrade
153 all needed steps, to perform the upgrade
154
154
155 """
155 """
156
156
157 from rhodecode.lib.dbmigrate.migrate.versioning import api
157 from rhodecode.lib.dbmigrate.migrate.versioning import api
158 from rhodecode.lib.dbmigrate.migrate.exceptions import DatabaseNotControlledError
158 from rhodecode.lib.dbmigrate.migrate.exceptions import DatabaseNotControlledError
159
159
160 if 'sqlite' in self.dburi:
160 if 'sqlite' in self.dburi:
161 print(
161 print(
162 '********************** WARNING **********************\n'
162 '********************** WARNING **********************\n'
163 'Make sure your version of sqlite is at least 3.7.X. \n'
163 'Make sure your version of sqlite is at least 3.7.X. \n'
164 'Earlier versions are known to fail on some migrations\n'
164 'Earlier versions are known to fail on some migrations\n'
165 '*****************************************************\n')
165 '*****************************************************\n')
166
166
167 upgrade = self.ask_ok(
167 upgrade = self.ask_ok(
168 'You are about to perform a database upgrade. Make '
168 'You are about to perform a database upgrade. Make '
169 'sure you have backed up your database. '
169 'sure you have backed up your database. '
170 'Continue ? [y/n]')
170 'Continue ? [y/n]')
171 if not upgrade:
171 if not upgrade:
172 log.info('No upgrade performed')
172 log.info('No upgrade performed')
173 sys.exit(0)
173 sys.exit(0)
174
174
175 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
175 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
176 'rhodecode/lib/dbmigrate')
176 'rhodecode/lib/dbmigrate')
177 db_uri = self.dburi
177 db_uri = self.dburi
178
178
179 if version:
179 if version:
180 DbMigrateVersion.set_version(version)
180 DbMigrateVersion.set_version(version)
181
181
182 try:
182 try:
183 curr_version = api.db_version(db_uri, repository_path)
183 curr_version = api.db_version(db_uri, repository_path)
184 msg = (f'Found current database db_uri under version '
184 msg = (f'Found current database db_uri under version '
185 f'control with version {curr_version}')
185 f'control with version {curr_version}')
186
186
187 except (RuntimeError, DatabaseNotControlledError):
187 except (RuntimeError, DatabaseNotControlledError):
188 curr_version = 1
188 curr_version = 1
189 msg = f'Current database is not under version control. ' \
189 msg = f'Current database is not under version control. ' \
190 f'Setting as version {curr_version}'
190 f'Setting as version {curr_version}'
191 api.version_control(db_uri, repository_path, curr_version)
191 api.version_control(db_uri, repository_path, curr_version)
192
192
193 notify(msg)
193 notify(msg)
194
194
195 if curr_version == __dbversion__:
195 if curr_version == __dbversion__:
196 log.info('This database is already at the newest version')
196 log.info('This database is already at the newest version')
197 sys.exit(0)
197 sys.exit(0)
198
198
199 upgrade_steps = list(range(curr_version + 1, __dbversion__ + 1))
199 upgrade_steps = list(range(curr_version + 1, __dbversion__ + 1))
200 notify(f'attempting to upgrade database from '
200 notify(f'attempting to upgrade database from '
201 f'version {curr_version} to version {__dbversion__}')
201 f'version {curr_version} to version {__dbversion__}')
202
202
203 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
203 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
204 final_step = 'latest'
204 final_step = 'latest'
205 for step in upgrade_steps:
205 for step in upgrade_steps:
206 notify(f'performing upgrade step {step}')
206 notify(f'performing upgrade step {step}')
207 time.sleep(0.5)
207 time.sleep(0.5)
208
208
209 api.upgrade(db_uri, repository_path, step)
209 api.upgrade(db_uri, repository_path, step)
210 self.sa.rollback()
210 self.sa.rollback()
211 notify(f'schema upgrade for step {step} completed')
211 notify(f'schema upgrade for step {step} completed')
212
212
213 final_step = step
213 final_step = step
214
214
215 self.run_post_migration_tasks()
215 self.run_post_migration_tasks()
216 notify(f'upgrade to version {final_step} successful')
216 notify(f'upgrade to version {final_step} successful')
217
217
218 def fix_repo_paths(self):
218 def fix_repo_paths(self):
219 """
219 """
220 Fixes an old RhodeCode version path into new one without a '*'
220 Fixes an old RhodeCode version path into new one without a '*'
221 """
221 """
222
222
223 paths = self.sa.query(RhodeCodeUi)\
223 paths = self.sa.query(RhodeCodeUi)\
224 .filter(RhodeCodeUi.ui_key == '/')\
224 .filter(RhodeCodeUi.ui_key == '/')\
225 .scalar()
225 .scalar()
226
226
227 paths.ui_value = paths.ui_value.replace('*', '')
227 paths.ui_value = paths.ui_value.replace('*', '')
228
228
229 try:
229 try:
230 self.sa.add(paths)
230 self.sa.add(paths)
231 self.sa.commit()
231 self.sa.commit()
232 except Exception:
232 except Exception:
233 self.sa.rollback()
233 self.sa.rollback()
234 raise
234 raise
235
235
236 def fix_default_user(self):
236 def fix_default_user(self):
237 """
237 """
238 Fixes an old default user with some 'nicer' default values,
238 Fixes an old default user with some 'nicer' default values,
239 used mostly for anonymous access
239 used mostly for anonymous access
240 """
240 """
241 def_user = self.sa.query(User)\
241 def_user = self.sa.query(User)\
242 .filter(User.username == User.DEFAULT_USER)\
242 .filter(User.username == User.DEFAULT_USER)\
243 .one()
243 .one()
244
244
245 def_user.name = 'Anonymous'
245 def_user.name = 'Anonymous'
246 def_user.lastname = 'User'
246 def_user.lastname = 'User'
247 def_user.email = User.DEFAULT_USER_EMAIL
247 def_user.email = User.DEFAULT_USER_EMAIL
248
248
249 try:
249 try:
250 self.sa.add(def_user)
250 self.sa.add(def_user)
251 self.sa.commit()
251 self.sa.commit()
252 except Exception:
252 except Exception:
253 self.sa.rollback()
253 self.sa.rollback()
254 raise
254 raise
255
255
256 def fix_settings(self):
256 def fix_settings(self):
257 """
257 """
258 Fixes rhodecode settings and adds ga_code key for google analytics
258 Fixes rhodecode settings and adds ga_code key for google analytics
259 """
259 """
260
260
261 hgsettings3 = RhodeCodeSetting('ga_code', '')
261 hgsettings3 = RhodeCodeSetting('ga_code', '')
262
262
263 try:
263 try:
264 self.sa.add(hgsettings3)
264 self.sa.add(hgsettings3)
265 self.sa.commit()
265 self.sa.commit()
266 except Exception:
266 except Exception:
267 self.sa.rollback()
267 self.sa.rollback()
268 raise
268 raise
269
269
270 def create_admin_and_prompt(self):
270 def create_admin_and_prompt(self):
271
271
272 # defaults
272 # defaults
273 defaults = self.cli_args
273 defaults = self.cli_args
274 username = defaults.get('username')
274 username = defaults.get('username')
275 password = defaults.get('password')
275 password = defaults.get('password')
276 email = defaults.get('email')
276 email = defaults.get('email')
277
277
278 if username is None:
278 if username is None:
279 username = input('Specify admin username:')
279 username = input('Specify admin username:')
280 if password is None:
280 if password is None:
281 password = self._get_admin_password()
281 password = self._get_admin_password()
282 if not password:
282 if not password:
283 # second try
283 # second try
284 password = self._get_admin_password()
284 password = self._get_admin_password()
285 if not password:
285 if not password:
286 sys.exit()
286 sys.exit()
287 if email is None:
287 if email is None:
288 email = input('Specify admin email:')
288 email = input('Specify admin email:')
289 api_key = self.cli_args.get('api_key')
289 api_key = self.cli_args.get('api_key')
290 self.create_user(username, password, email, True,
290 self.create_user(username, password, email, True,
291 strict_creation_check=False,
291 strict_creation_check=False,
292 api_key=api_key)
292 api_key=api_key)
293
293
294 def _get_admin_password(self):
294 def _get_admin_password(self):
295 password = getpass.getpass('Specify admin password '
295 password = getpass.getpass('Specify admin password '
296 '(min 6 chars):')
296 '(min 6 chars):')
297 confirm = getpass.getpass('Confirm password:')
297 confirm = getpass.getpass('Confirm password:')
298
298
299 if password != confirm:
299 if password != confirm:
300 log.error('passwords mismatch')
300 log.error('passwords mismatch')
301 return False
301 return False
302 if len(password) < 6:
302 if len(password) < 6:
303 log.error('password is too short - use at least 6 characters')
303 log.error('password is too short - use at least 6 characters')
304 return False
304 return False
305
305
306 return password
306 return password
307
307
308 def create_test_admin_and_users(self):
308 def create_test_admin_and_users(self):
309 log.info('creating admin and regular test users')
309 log.info('creating admin and regular test users')
310 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
310 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
311 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
311 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
312 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
312 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
313 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
313 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
314 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
314 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
315
315
316 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
316 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
317 TEST_USER_ADMIN_EMAIL, True, api_key=True)
317 TEST_USER_ADMIN_EMAIL, True, api_key=True)
318
318
319 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
319 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
320 TEST_USER_REGULAR_EMAIL, False, api_key=True)
320 TEST_USER_REGULAR_EMAIL, False, api_key=True)
321
321
322 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
322 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
323 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
323 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
324
324
325 def create_ui_settings(self, repo_store_path):
325 def create_ui_settings(self, repo_store_path):
326 """
326 """
327 Creates ui settings, fills out hooks
327 Creates ui settings, fills out hooks
328 and disables dotencode
328 and disables dotencode
329 """
329 """
330 settings_model = SettingsModel(sa=self.sa)
330 settings_model = SettingsModel(sa=self.sa)
331 from rhodecode.lib.vcs.backends.hg import largefiles_store
331 from rhodecode.lib.vcs.backends.hg import largefiles_store
332 from rhodecode.lib.vcs.backends.git import lfs_store
332 from rhodecode.lib.vcs.backends.git import lfs_store
333
333
334 # Build HOOKS
334 # Build HOOKS
335 hooks = [
335 hooks = [
336 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
336 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
337
337
338 # HG
338 # HG
339 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
339 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
340 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
340 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
341 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
341 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
342 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
342 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
343 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
343 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
344 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
344 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
345
345
346 ]
346 ]
347
347
348 for key, value in hooks:
348 for key, value in hooks:
349 hook_obj = settings_model.get_ui_by_key(key)
349 hook_obj = settings_model.get_ui_by_key(key)
350 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
350 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
351 hooks2.ui_section = 'hooks'
351 hooks2.ui_section = 'hooks'
352 hooks2.ui_key = key
352 hooks2.ui_key = key
353 hooks2.ui_value = value
353 hooks2.ui_value = value
354 self.sa.add(hooks2)
354 self.sa.add(hooks2)
355
355
356 # enable largefiles
356 # enable largefiles
357 largefiles = RhodeCodeUi()
357 largefiles = RhodeCodeUi()
358 largefiles.ui_section = 'extensions'
358 largefiles.ui_section = 'extensions'
359 largefiles.ui_key = 'largefiles'
359 largefiles.ui_key = 'largefiles'
360 largefiles.ui_value = ''
360 largefiles.ui_value = ''
361 self.sa.add(largefiles)
361 self.sa.add(largefiles)
362
362
363 # set default largefiles cache dir, defaults to
363 # set default largefiles cache dir, defaults to
364 # /repo_store_location/.cache/largefiles
364 # /repo_store_location/.cache/largefiles
365 largefiles = RhodeCodeUi()
365 largefiles = RhodeCodeUi()
366 largefiles.ui_section = 'largefiles'
366 largefiles.ui_section = 'largefiles'
367 largefiles.ui_key = 'usercache'
367 largefiles.ui_key = 'usercache'
368 largefiles.ui_value = largefiles_store(repo_store_path)
368 largefiles.ui_value = largefiles_store(repo_store_path)
369
369
370 self.sa.add(largefiles)
370 self.sa.add(largefiles)
371
371
372 # set default lfs cache dir, defaults to
372 # set default lfs cache dir, defaults to
373 # /repo_store_location/.cache/lfs_store
373 # /repo_store_location/.cache/lfs_store
374 lfsstore = RhodeCodeUi()
374 lfsstore = RhodeCodeUi()
375 lfsstore.ui_section = 'vcs_git_lfs'
375 lfsstore.ui_section = 'vcs_git_lfs'
376 lfsstore.ui_key = 'store_location'
376 lfsstore.ui_key = 'store_location'
377 lfsstore.ui_value = lfs_store(repo_store_path)
377 lfsstore.ui_value = lfs_store(repo_store_path)
378
378
379 self.sa.add(lfsstore)
379 self.sa.add(lfsstore)
380
380
381 # enable hgevolve disabled by default
381 # enable hgevolve disabled by default
382 hgevolve = RhodeCodeUi()
382 hgevolve = RhodeCodeUi()
383 hgevolve.ui_section = 'extensions'
383 hgevolve.ui_section = 'extensions'
384 hgevolve.ui_key = 'evolve'
384 hgevolve.ui_key = 'evolve'
385 hgevolve.ui_value = ''
385 hgevolve.ui_value = ''
386 hgevolve.ui_active = False
386 hgevolve.ui_active = False
387 self.sa.add(hgevolve)
387 self.sa.add(hgevolve)
388
388
389 hgevolve = RhodeCodeUi()
389 hgevolve = RhodeCodeUi()
390 hgevolve.ui_section = 'experimental'
390 hgevolve.ui_section = 'experimental'
391 hgevolve.ui_key = 'evolution'
391 hgevolve.ui_key = 'evolution'
392 hgevolve.ui_value = ''
392 hgevolve.ui_value = ''
393 hgevolve.ui_active = False
393 hgevolve.ui_active = False
394 self.sa.add(hgevolve)
394 self.sa.add(hgevolve)
395
395
396 hgevolve = RhodeCodeUi()
396 hgevolve = RhodeCodeUi()
397 hgevolve.ui_section = 'experimental'
397 hgevolve.ui_section = 'experimental'
398 hgevolve.ui_key = 'evolution.exchange'
398 hgevolve.ui_key = 'evolution.exchange'
399 hgevolve.ui_value = ''
399 hgevolve.ui_value = ''
400 hgevolve.ui_active = False
400 hgevolve.ui_active = False
401 self.sa.add(hgevolve)
401 self.sa.add(hgevolve)
402
402
403 hgevolve = RhodeCodeUi()
403 hgevolve = RhodeCodeUi()
404 hgevolve.ui_section = 'extensions'
404 hgevolve.ui_section = 'extensions'
405 hgevolve.ui_key = 'topic'
405 hgevolve.ui_key = 'topic'
406 hgevolve.ui_value = ''
406 hgevolve.ui_value = ''
407 hgevolve.ui_active = False
407 hgevolve.ui_active = False
408 self.sa.add(hgevolve)
408 self.sa.add(hgevolve)
409
409
410 # enable hggit disabled by default
410 # enable hggit disabled by default
411 hggit = RhodeCodeUi()
411 hggit = RhodeCodeUi()
412 hggit.ui_section = 'extensions'
412 hggit.ui_section = 'extensions'
413 hggit.ui_key = 'hggit'
413 hggit.ui_key = 'hggit'
414 hggit.ui_value = ''
414 hggit.ui_value = ''
415 hggit.ui_active = False
415 hggit.ui_active = False
416 self.sa.add(hggit)
416 self.sa.add(hggit)
417
417
418 # set svn branch defaults
418 # set svn branch defaults
419 branches = ["/branches/*", "/trunk"]
419 branches = ["/branches/*", "/trunk"]
420 tags = ["/tags/*"]
420 tags = ["/tags/*"]
421
421
422 for branch in branches:
422 for branch in branches:
423 settings_model.create_ui_section_value(
423 settings_model.create_ui_section_value(
424 RhodeCodeUi.SVN_BRANCH_ID, branch)
424 RhodeCodeUi.SVN_BRANCH_ID, branch)
425
425
426 for tag in tags:
426 for tag in tags:
427 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
427 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
428
428
429 def create_auth_plugin_options(self, skip_existing=False):
429 def create_auth_plugin_options(self, skip_existing=False):
430 """
430 """
431 Create default auth plugin settings, and make it active
431 Create default auth plugin settings, and make it active
432
432
433 :param skip_existing:
433 :param skip_existing:
434 """
434 """
435 defaults = [
435 defaults = [
436 ('auth_plugins',
436 ('auth_plugins',
437 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
437 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
438 'list'),
438 'list'),
439
439
440 ('auth_authtoken_enabled',
440 ('auth_authtoken_enabled',
441 'True',
441 'True',
442 'bool'),
442 'bool'),
443
443
444 ('auth_rhodecode_enabled',
444 ('auth_rhodecode_enabled',
445 'True',
445 'True',
446 'bool'),
446 'bool'),
447 ]
447 ]
448 for k, v, t in defaults:
448 for k, v, t in defaults:
449 if (skip_existing and
449 if (skip_existing and
450 SettingsModel().get_setting_by_name(k) is not None):
450 SettingsModel().get_setting_by_name(k) is not None):
451 log.debug('Skipping option %s', k)
451 log.debug('Skipping option %s', k)
452 continue
452 continue
453 setting = RhodeCodeSetting(k, v, t)
453 setting = RhodeCodeSetting(k, v, t)
454 self.sa.add(setting)
454 self.sa.add(setting)
455
455
456 def create_default_options(self, skip_existing=False):
456 def create_default_options(self, skip_existing=False):
457 """Creates default settings"""
457 """Creates default settings"""
458
458
459 for k, v, t in [
459 for k, v, t in [
460 ('default_repo_enable_locking', False, 'bool'),
460 ('default_repo_enable_locking', False, 'bool'),
461 ('default_repo_enable_downloads', False, 'bool'),
461 ('default_repo_enable_downloads', False, 'bool'),
462 ('default_repo_enable_statistics', False, 'bool'),
462 ('default_repo_enable_statistics', False, 'bool'),
463 ('default_repo_private', False, 'bool'),
463 ('default_repo_private', False, 'bool'),
464 ('default_repo_type', 'hg', 'unicode')]:
464 ('default_repo_type', 'hg', 'unicode')]:
465
465
466 if (skip_existing and
466 if (skip_existing and
467 SettingsModel().get_setting_by_name(k) is not None):
467 SettingsModel().get_setting_by_name(k) is not None):
468 log.debug('Skipping option %s', k)
468 log.debug('Skipping option %s', k)
469 continue
469 continue
470 setting = RhodeCodeSetting(k, v, t)
470 setting = RhodeCodeSetting(k, v, t)
471 self.sa.add(setting)
471 self.sa.add(setting)
472
472
473 def fixup_groups(self):
473 def fixup_groups(self):
474 def_usr = User.get_default_user()
474 def_usr = User.get_default_user()
475 for g in RepoGroup.query().all():
475 for g in RepoGroup.query().all():
476 g.group_name = g.get_new_name(g.name)
476 g.group_name = g.get_new_name(g.name)
477 self.sa.add(g)
477 self.sa.add(g)
478 # get default perm
478 # get default perm
479 default = UserRepoGroupToPerm.query()\
479 default = UserRepoGroupToPerm.query()\
480 .filter(UserRepoGroupToPerm.group == g)\
480 .filter(UserRepoGroupToPerm.group == g)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
482 .scalar()
482 .scalar()
483
483
484 if default is None:
484 if default is None:
485 log.debug('missing default permission for group %s adding', g)
485 log.debug('missing default permission for group %s adding', g)
486 perm_obj = RepoGroupModel()._create_default_perms(g)
486 perm_obj = RepoGroupModel()._create_default_perms(g)
487 self.sa.add(perm_obj)
487 self.sa.add(perm_obj)
488
488
489 def reset_permissions(self, username):
489 def reset_permissions(self, username):
490 """
490 """
491 Resets permissions to default state, useful when old systems had
491 Resets permissions to default state, useful when old systems had
492 bad permissions, we must clean them up
492 bad permissions, we must clean them up
493
493
494 :param username:
494 :param username:
495 """
495 """
496 default_user = User.get_by_username(username)
496 default_user = User.get_by_username(username)
497 if not default_user:
497 if not default_user:
498 return
498 return
499
499
500 u2p = UserToPerm.query()\
500 u2p = UserToPerm.query()\
501 .filter(UserToPerm.user == default_user).all()
501 .filter(UserToPerm.user == default_user).all()
502 fixed = False
502 fixed = False
503 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
503 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
504 for p in u2p:
504 for p in u2p:
505 Session().delete(p)
505 Session().delete(p)
506 fixed = True
506 fixed = True
507 self.populate_default_permissions()
507 self.populate_default_permissions()
508 return fixed
508 return fixed
509
509
510 def config_prompt(self, test_repo_path='', retries=3):
510 def config_prompt(self, test_repo_path='', retries=3):
511 defaults = self.cli_args
511 defaults = self.cli_args
512 _path = defaults.get('repos_location')
512 _path = defaults.get('repos_location')
513 if retries == 3:
513 if retries == 3:
514 log.info('Setting up repositories config')
514 log.info('Setting up repositories config')
515
515
516 if _path is not None:
516 if _path is not None:
517 path = _path
517 path = _path
518 elif not self.tests and not test_repo_path:
518 elif not self.tests and not test_repo_path:
519 path = input(
519 path = input(
520 'Enter a valid absolute path to store repositories. '
520 'Enter a valid absolute path to store repositories. '
521 'All repositories in that path will be added automatically:'
521 'All repositories in that path will be added automatically:'
522 )
522 )
523 else:
523 else:
524 path = test_repo_path
524 path = test_repo_path
525 path_ok = True
525 path_ok = True
526
526
527 # check proper dir
527 # check proper dir
528 if not os.path.isdir(path):
528 if not os.path.isdir(path):
529 path_ok = False
529 path_ok = False
530 log.error('Given path %s is not a valid directory', path)
530 log.error('Given path %s is not a valid directory', path)
531
531
532 elif not os.path.isabs(path):
532 elif not os.path.isabs(path):
533 path_ok = False
533 path_ok = False
534 log.error('Given path %s is not an absolute path', path)
534 log.error('Given path %s is not an absolute path', path)
535
535
536 # check if path is at least readable.
536 # check if path is at least readable.
537 if not os.access(path, os.R_OK):
537 if not os.access(path, os.R_OK):
538 path_ok = False
538 path_ok = False
539 log.error('Given path %s is not readable', path)
539 log.error('Given path %s is not readable', path)
540
540
541 # check write access, warn user about non writeable paths
541 # check write access, warn user about non writeable paths
542 elif not os.access(path, os.W_OK) and path_ok:
542 elif not os.access(path, os.W_OK) and path_ok:
543 log.warning('No write permission to given path %s', path)
543 log.warning('No write permission to given path %s', path)
544
544
545 q = (f'Given path {path} is not writeable, do you want to '
545 q = (f'Given path {path} is not writeable, do you want to '
546 f'continue with read only mode ? [y/n]')
546 f'continue with read only mode ? [y/n]')
547 if not self.ask_ok(q):
547 if not self.ask_ok(q):
548 log.error('Canceled by user')
548 log.error('Canceled by user')
549 sys.exit(-1)
549 sys.exit(-1)
550
550
551 if retries == 0:
551 if retries == 0:
552 sys.exit('max retries reached')
552 sys.exit('max retries reached')
553 if not path_ok:
553 if not path_ok:
554 retries -= 1
554 retries -= 1
555 return self.config_prompt(test_repo_path, retries)
555 return self.config_prompt(test_repo_path, retries)
556
556
557 real_path = os.path.normpath(os.path.realpath(path))
557 real_path = os.path.normpath(os.path.realpath(path))
558
558
559 if real_path != os.path.normpath(path):
559 if real_path != os.path.normpath(path):
560 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
560 q = (f'Path looks like a symlink, RhodeCode Enterprise will store '
561 f'given path as {real_path} ? [y/n]')
561 f'given path as {real_path} ? [y/n]')
562 if not self.ask_ok(q):
562 if not self.ask_ok(q):
563 log.error('Canceled by user')
563 log.error('Canceled by user')
564 sys.exit(-1)
564 sys.exit(-1)
565
565
566 return real_path
566 return real_path
567
567
568 def create_settings(self, path):
568 def create_settings(self, path):
569
569
570 self.create_ui_settings(path)
570 self.create_ui_settings(path)
571
571
572 ui_config = [
572 ui_config = [
573 ('web', 'push_ssl', 'False'),
574 ('web', 'allow_archive', 'gz zip bz2'),
573 ('web', 'allow_archive', 'gz zip bz2'),
575 ('web', 'allow_push', '*'),
574 ('web', 'allow_push', '*'),
576 ('web', 'baseurl', '/'),
575 ('web', 'baseurl', '/'),
577 ('paths', '/', path),
576 ('paths', '/', path),
578 ('phases', 'publish', 'True')
577 ('phases', 'publish', 'True')
579 ]
578 ]
580 for section, key, value in ui_config:
579 for section, key, value in ui_config:
581 ui_conf = RhodeCodeUi()
580 ui_conf = RhodeCodeUi()
582 setattr(ui_conf, 'ui_section', section)
581 setattr(ui_conf, 'ui_section', section)
583 setattr(ui_conf, 'ui_key', key)
582 setattr(ui_conf, 'ui_key', key)
584 setattr(ui_conf, 'ui_value', value)
583 setattr(ui_conf, 'ui_value', value)
585 self.sa.add(ui_conf)
584 self.sa.add(ui_conf)
586
585
587 # rhodecode app settings
586 # rhodecode app settings
588 settings = [
587 settings = [
589 ('realm', 'RhodeCode', 'unicode'),
588 ('realm', 'RhodeCode', 'unicode'),
590 ('title', '', 'unicode'),
589 ('title', '', 'unicode'),
591 ('pre_code', '', 'unicode'),
590 ('pre_code', '', 'unicode'),
592 ('post_code', '', 'unicode'),
591 ('post_code', '', 'unicode'),
593
592
594 # Visual
593 # Visual
595 ('show_public_icon', True, 'bool'),
594 ('show_public_icon', True, 'bool'),
596 ('show_private_icon', True, 'bool'),
595 ('show_private_icon', True, 'bool'),
597 ('stylify_metatags', True, 'bool'),
596 ('stylify_metatags', True, 'bool'),
598 ('dashboard_items', 100, 'int'),
597 ('dashboard_items', 100, 'int'),
599 ('admin_grid_items', 25, 'int'),
598 ('admin_grid_items', 25, 'int'),
600
599
601 ('markup_renderer', 'markdown', 'unicode'),
600 ('markup_renderer', 'markdown', 'unicode'),
602
601
603 ('repository_fields', True, 'bool'),
602 ('repository_fields', True, 'bool'),
604 ('show_version', True, 'bool'),
603 ('show_version', True, 'bool'),
605 ('show_revision_number', True, 'bool'),
604 ('show_revision_number', True, 'bool'),
606 ('show_sha_length', 12, 'int'),
605 ('show_sha_length', 12, 'int'),
607
606
608 ('use_gravatar', False, 'bool'),
607 ('use_gravatar', False, 'bool'),
609 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
608 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
610
609
611 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
610 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
612 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
611 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
613 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
612 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
614 ('support_url', '', 'unicode'),
613 ('support_url', '', 'unicode'),
615 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
614 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
616
615
617 # VCS Settings
616 # VCS Settings
618 ('pr_merge_enabled', True, 'bool'),
617 ('pr_merge_enabled', True, 'bool'),
619 ('use_outdated_comments', True, 'bool'),
618 ('use_outdated_comments', True, 'bool'),
620 ('diff_cache', True, 'bool'),
619 ('diff_cache', True, 'bool'),
621 ]
620 ]
622
621
623 for key, val, type_ in settings:
622 for key, val, type_ in settings:
624 sett = RhodeCodeSetting(key, val, type_)
623 sett = RhodeCodeSetting(key, val, type_)
625 self.sa.add(sett)
624 self.sa.add(sett)
626
625
627 self.create_auth_plugin_options()
626 self.create_auth_plugin_options()
628 self.create_default_options()
627 self.create_default_options()
629
628
630 log.info('created ui config')
629 log.info('created ui config')
631
630
632 def create_user(self, username, password, email='', admin=False,
631 def create_user(self, username, password, email='', admin=False,
633 strict_creation_check=True, api_key=None):
632 strict_creation_check=True, api_key=None):
634 log.info('creating user `%s`', username)
633 log.info('creating user `%s`', username)
635 user = UserModel().create_or_update(
634 user = UserModel().create_or_update(
636 username, password, email, firstname='RhodeCode', lastname='Admin',
635 username, password, email, firstname='RhodeCode', lastname='Admin',
637 active=True, admin=admin, extern_type="rhodecode",
636 active=True, admin=admin, extern_type="rhodecode",
638 strict_creation_check=strict_creation_check)
637 strict_creation_check=strict_creation_check)
639
638
640 if api_key:
639 if api_key:
641 log.info('setting a new default auth token for user `%s`', username)
640 log.info('setting a new default auth token for user `%s`', username)
642 UserModel().add_auth_token(
641 UserModel().add_auth_token(
643 user=user, lifetime_minutes=-1,
642 user=user, lifetime_minutes=-1,
644 role=UserModel.auth_token_role.ROLE_ALL,
643 role=UserModel.auth_token_role.ROLE_ALL,
645 description='BUILTIN TOKEN')
644 description='BUILTIN TOKEN')
646
645
647 def create_default_user(self):
646 def create_default_user(self):
648 log.info('creating default user')
647 log.info('creating default user')
649 # create default user for handling default permissions.
648 # create default user for handling default permissions.
650 user = UserModel().create_or_update(username=User.DEFAULT_USER,
649 user = UserModel().create_or_update(username=User.DEFAULT_USER,
651 password=str(uuid.uuid1())[:20],
650 password=str(uuid.uuid1())[:20],
652 email=User.DEFAULT_USER_EMAIL,
651 email=User.DEFAULT_USER_EMAIL,
653 firstname='Anonymous',
652 firstname='Anonymous',
654 lastname='User',
653 lastname='User',
655 strict_creation_check=False)
654 strict_creation_check=False)
656 # based on configuration options activate/de-activate this user which
655 # based on configuration options activate/de-activate this user which
657 # controls anonymous access
656 # controls anonymous access
658 if self.cli_args.get('public_access') is False:
657 if self.cli_args.get('public_access') is False:
659 log.info('Public access disabled')
658 log.info('Public access disabled')
660 user.active = False
659 user.active = False
661 Session().add(user)
660 Session().add(user)
662 Session().commit()
661 Session().commit()
663
662
664 def create_permissions(self):
663 def create_permissions(self):
665 """
664 """
666 Creates all permissions defined in the system
665 Creates all permissions defined in the system
667 """
666 """
668 # module.(access|create|change|delete)_[name]
667 # module.(access|create|change|delete)_[name]
669 # module.(none|read|write|admin)
668 # module.(none|read|write|admin)
670 log.info('creating permissions')
669 log.info('creating permissions')
671 PermissionModel(self.sa).create_permissions()
670 PermissionModel(self.sa).create_permissions()
672
671
673 def populate_default_permissions(self):
672 def populate_default_permissions(self):
674 """
673 """
675 Populate default permissions. It will create only the default
674 Populate default permissions. It will create only the default
676 permissions that are missing, and not alter already defined ones
675 permissions that are missing, and not alter already defined ones
677 """
676 """
678 log.info('creating default user permissions')
677 log.info('creating default user permissions')
679 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
678 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,683 +1,662 b''
1
1
2
2
3 # Copyright (C) 2014-2023 RhodeCode GmbH
3 # Copyright (C) 2014-2023 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 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import importlib
29 import importlib
30 from functools import wraps
30 from functools import wraps
31
31
32 import time
32 import time
33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34
34
35 from pyramid.httpexceptions import (
35 from pyramid.httpexceptions import (
36 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
36 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
37 from zope.cachedescriptors.property import Lazy as LazyProperty
37 from zope.cachedescriptors.property import Lazy as LazyProperty
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
40 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
41 from rhodecode.lib import rc_cache
41 from rhodecode.lib import rc_cache
42 from rhodecode.lib.svn_txn_utils import store_txn_id_data
42 from rhodecode.lib.svn_txn_utils import store_txn_id_data
43 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
43 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.base import (
44 from rhodecode.lib.base import (
45 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
45 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
46 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
47 from rhodecode.lib.hook_daemon.base import prepare_callback_daemon
48 from rhodecode.lib.middleware import appenlight
48 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware.utils import scm_app_http
49 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.str_utils import safe_bytes, safe_int
50 from rhodecode.lib.str_utils import safe_bytes, safe_int
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
54 from rhodecode.lib.vcs.backends import base
54 from rhodecode.lib.vcs.backends import base
55
55
56 from rhodecode.model import meta
56 from rhodecode.model import meta
57 from rhodecode.model.db import User, Repository, PullRequest
57 from rhodecode.model.db import User, Repository, PullRequest
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def initialize_generator(factory):
65 def initialize_generator(factory):
66 """
66 """
67 Initializes the returned generator by draining its first element.
67 Initializes the returned generator by draining its first element.
68
68
69 This can be used to give a generator an initializer, which is the code
69 This can be used to give a generator an initializer, which is the code
70 up to the first yield statement. This decorator enforces that the first
70 up to the first yield statement. This decorator enforces that the first
71 produced element has the value ``"__init__"`` to make its special
71 produced element has the value ``"__init__"`` to make its special
72 purpose very explicit in the using code.
72 purpose very explicit in the using code.
73 """
73 """
74
74
75 @wraps(factory)
75 @wraps(factory)
76 def wrapper(*args, **kwargs):
76 def wrapper(*args, **kwargs):
77 gen = factory(*args, **kwargs)
77 gen = factory(*args, **kwargs)
78 try:
78 try:
79 init = next(gen)
79 init = next(gen)
80 except StopIteration:
80 except StopIteration:
81 raise ValueError('Generator must yield at least one element.')
81 raise ValueError('Generator must yield at least one element.')
82 if init != "__init__":
82 if init != "__init__":
83 raise ValueError('First yielded element must be "__init__".')
83 raise ValueError('First yielded element must be "__init__".')
84 return gen
84 return gen
85 return wrapper
85 return wrapper
86
86
87
87
88 class SimpleVCS(object):
88 class SimpleVCS(object):
89 """Common functionality for SCM HTTP handlers."""
89 """Common functionality for SCM HTTP handlers."""
90
90
91 SCM = 'unknown'
91 SCM = 'unknown'
92
92
93 acl_repo_name = None
93 acl_repo_name = None
94 url_repo_name = None
94 url_repo_name = None
95 vcs_repo_name = None
95 vcs_repo_name = None
96 rc_extras = {}
96 rc_extras = {}
97
97
98 # We have to handle requests to shadow repositories different than requests
98 # We have to handle requests to shadow repositories different than requests
99 # to normal repositories. Therefore we have to distinguish them. To do this
99 # to normal repositories. Therefore we have to distinguish them. To do this
100 # we use this regex which will match only on URLs pointing to shadow
100 # we use this regex which will match only on URLs pointing to shadow
101 # repositories.
101 # repositories.
102 shadow_repo_re = re.compile(
102 shadow_repo_re = re.compile(
103 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
103 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
104 '(?P<target>{slug_pat})/' # target repo
104 '(?P<target>{slug_pat})/' # target repo
105 'pull-request/(?P<pr_id>\\d+)/' # pull request
105 'pull-request/(?P<pr_id>\\d+)/' # pull request
106 'repository$' # shadow repo
106 'repository$' # shadow repo
107 .format(slug_pat=SLUG_RE.pattern))
107 .format(slug_pat=SLUG_RE.pattern))
108
108
109 def __init__(self, config, registry):
109 def __init__(self, config, registry):
110 self.registry = registry
110 self.registry = registry
111 self.config = config
111 self.config = config
112 # re-populated by specialized middleware
112 # re-populated by specialized middleware
113 self.repo_vcs_config = base.Config()
113 self.repo_vcs_config = base.Config()
114
114
115 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
115 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
116 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
116 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
117
117
118 # authenticate this VCS request using authfunc
118 # authenticate this VCS request using authfunc
119 auth_ret_code_detection = \
119 auth_ret_code_detection = \
120 str2bool(self.config.get('auth_ret_code_detection', False))
120 str2bool(self.config.get('auth_ret_code_detection', False))
121 self.authenticate = BasicAuth(
121 self.authenticate = BasicAuth(
122 '', authenticate, registry, config.get('auth_ret_code'),
122 '', authenticate, registry, config.get('auth_ret_code'),
123 auth_ret_code_detection, rc_realm=realm)
123 auth_ret_code_detection, rc_realm=realm)
124 self.ip_addr = '0.0.0.0'
124 self.ip_addr = '0.0.0.0'
125
125
126 @LazyProperty
126 @LazyProperty
127 def global_vcs_config(self):
127 def global_vcs_config(self):
128 try:
128 try:
129 return VcsSettingsModel().get_ui_settings_as_config_obj()
129 return VcsSettingsModel().get_ui_settings_as_config_obj()
130 except Exception:
130 except Exception:
131 return base.Config()
131 return base.Config()
132
132
133 @property
133 @property
134 def base_path(self):
134 def base_path(self):
135 settings_path = self.config.get('repo_store.path')
135 settings_path = self.config.get('repo_store.path')
136
136
137 if not settings_path:
137 if not settings_path:
138 raise ValueError('FATAL: repo_store.path is empty')
138 raise ValueError('FATAL: repo_store.path is empty')
139 return settings_path
139 return settings_path
140
140
141 def set_repo_names(self, environ):
141 def set_repo_names(self, environ):
142 """
142 """
143 This will populate the attributes acl_repo_name, url_repo_name,
143 This will populate the attributes acl_repo_name, url_repo_name,
144 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
144 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
145 shadow) repositories all names are equal. In case of requests to a
145 shadow) repositories all names are equal. In case of requests to a
146 shadow repository the acl-name points to the target repo of the pull
146 shadow repository the acl-name points to the target repo of the pull
147 request and the vcs-name points to the shadow repo file system path.
147 request and the vcs-name points to the shadow repo file system path.
148 The url-name is always the URL used by the vcs client program.
148 The url-name is always the URL used by the vcs client program.
149
149
150 Example in case of a shadow repo:
150 Example in case of a shadow repo:
151 acl_repo_name = RepoGroup/MyRepo
151 acl_repo_name = RepoGroup/MyRepo
152 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
152 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
153 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
153 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
154 """
154 """
155 # First we set the repo name from URL for all attributes. This is the
155 # First we set the repo name from URL for all attributes. This is the
156 # default if handling normal (non shadow) repo requests.
156 # default if handling normal (non shadow) repo requests.
157 self.url_repo_name = self._get_repository_name(environ)
157 self.url_repo_name = self._get_repository_name(environ)
158 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
158 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
159 self.is_shadow_repo = False
159 self.is_shadow_repo = False
160
160
161 # Check if this is a request to a shadow repository.
161 # Check if this is a request to a shadow repository.
162 match = self.shadow_repo_re.match(self.url_repo_name)
162 match = self.shadow_repo_re.match(self.url_repo_name)
163 if match:
163 if match:
164 match_dict = match.groupdict()
164 match_dict = match.groupdict()
165
165
166 # Build acl repo name from regex match.
166 # Build acl repo name from regex match.
167 acl_repo_name = safe_str('{groups}{target}'.format(
167 acl_repo_name = safe_str('{groups}{target}'.format(
168 groups=match_dict['groups'] or '',
168 groups=match_dict['groups'] or '',
169 target=match_dict['target']))
169 target=match_dict['target']))
170
170
171 # Retrieve pull request instance by ID from regex match.
171 # Retrieve pull request instance by ID from regex match.
172 pull_request = PullRequest.get(match_dict['pr_id'])
172 pull_request = PullRequest.get(match_dict['pr_id'])
173
173
174 # Only proceed if we got a pull request and if acl repo name from
174 # Only proceed if we got a pull request and if acl repo name from
175 # URL equals the target repo name of the pull request.
175 # URL equals the target repo name of the pull request.
176 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
176 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
177
177
178 # Get file system path to shadow repository.
178 # Get file system path to shadow repository.
179 workspace_id = PullRequestModel()._workspace_id(pull_request)
179 workspace_id = PullRequestModel()._workspace_id(pull_request)
180 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
180 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
181
181
182 # Store names for later usage.
182 # Store names for later usage.
183 self.vcs_repo_name = vcs_repo_name
183 self.vcs_repo_name = vcs_repo_name
184 self.acl_repo_name = acl_repo_name
184 self.acl_repo_name = acl_repo_name
185 self.is_shadow_repo = True
185 self.is_shadow_repo = True
186
186
187 log.debug('Setting all VCS repository names: %s', {
187 log.debug('Setting all VCS repository names: %s', {
188 'acl_repo_name': self.acl_repo_name,
188 'acl_repo_name': self.acl_repo_name,
189 'url_repo_name': self.url_repo_name,
189 'url_repo_name': self.url_repo_name,
190 'vcs_repo_name': self.vcs_repo_name,
190 'vcs_repo_name': self.vcs_repo_name,
191 })
191 })
192
192
193 @property
193 @property
194 def scm_app(self):
194 def scm_app(self):
195 custom_implementation = self.config['vcs.scm_app_implementation']
195 custom_implementation = self.config['vcs.scm_app_implementation']
196 if custom_implementation == 'http':
196 if custom_implementation == 'http':
197 log.debug('Using HTTP implementation of scm app.')
197 log.debug('Using HTTP implementation of scm app.')
198 scm_app_impl = scm_app_http
198 scm_app_impl = scm_app_http
199 else:
199 else:
200 log.debug('Using custom implementation of scm_app: "{}"'.format(
200 log.debug('Using custom implementation of scm_app: "{}"'.format(
201 custom_implementation))
201 custom_implementation))
202 scm_app_impl = importlib.import_module(custom_implementation)
202 scm_app_impl = importlib.import_module(custom_implementation)
203 return scm_app_impl
203 return scm_app_impl
204
204
205 def _get_by_id(self, repo_name):
205 def _get_by_id(self, repo_name):
206 """
206 """
207 Gets a special pattern _<ID> from clone url and tries to replace it
207 Gets a special pattern _<ID> from clone url and tries to replace it
208 with a repository_name for support of _<ID> non changeable urls
208 with a repository_name for support of _<ID> non changeable urls
209 """
209 """
210
210
211 data = repo_name.split('/')
211 data = repo_name.split('/')
212 if len(data) >= 2:
212 if len(data) >= 2:
213 from rhodecode.model.repo import RepoModel
213 from rhodecode.model.repo import RepoModel
214 by_id_match = RepoModel().get_repo_by_id(repo_name)
214 by_id_match = RepoModel().get_repo_by_id(repo_name)
215 if by_id_match:
215 if by_id_match:
216 data[1] = by_id_match.repo_name
216 data[1] = by_id_match.repo_name
217
217
218 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
218 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
219 # and we use this data
219 # and we use this data
220 maybe_new_path = '/'.join(data)
220 maybe_new_path = '/'.join(data)
221 return safe_bytes(maybe_new_path).decode('latin1')
221 return safe_bytes(maybe_new_path).decode('latin1')
222
222
223 def _invalidate_cache(self, repo_name):
223 def _invalidate_cache(self, repo_name):
224 """
224 """
225 Set's cache for this repository for invalidation on next access
225 Set's cache for this repository for invalidation on next access
226
226
227 :param repo_name: full repo name, also a cache key
227 :param repo_name: full repo name, also a cache key
228 """
228 """
229 ScmModel().mark_for_invalidation(repo_name)
229 ScmModel().mark_for_invalidation(repo_name)
230
230
231 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
231 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
232 db_repo = Repository.get_by_repo_name(repo_name)
232 db_repo = Repository.get_by_repo_name(repo_name)
233 if not db_repo:
233 if not db_repo:
234 log.debug('Repository `%s` not found inside the database.',
234 log.debug('Repository `%s` not found inside the database.',
235 repo_name)
235 repo_name)
236 return False
236 return False
237
237
238 if db_repo.repo_type != scm_type:
238 if db_repo.repo_type != scm_type:
239 log.warning(
239 log.warning(
240 'Repository `%s` have incorrect scm_type, expected %s got %s',
240 'Repository `%s` have incorrect scm_type, expected %s got %s',
241 repo_name, db_repo.repo_type, scm_type)
241 repo_name, db_repo.repo_type, scm_type)
242 return False
242 return False
243
243
244 config = db_repo._config
244 config = db_repo._config
245 config.set('extensions', 'largefiles', '')
245 config.set('extensions', 'largefiles', '')
246 return is_valid_repo(
246 return is_valid_repo(
247 repo_name, base_path,
247 repo_name, base_path,
248 explicit_scm=scm_type, expect_scm=scm_type, config=config)
248 explicit_scm=scm_type, expect_scm=scm_type, config=config)
249
249
250 def valid_and_active_user(self, user):
250 def valid_and_active_user(self, user):
251 """
251 """
252 Checks if that user is not empty, and if it's actually object it checks
252 Checks if that user is not empty, and if it's actually object it checks
253 if he's active.
253 if he's active.
254
254
255 :param user: user object or None
255 :param user: user object or None
256 :return: boolean
256 :return: boolean
257 """
257 """
258 if user is None:
258 if user is None:
259 return False
259 return False
260
260
261 elif user.active:
261 elif user.active:
262 return True
262 return True
263
263
264 return False
264 return False
265
265
266 @property
266 @property
267 def is_shadow_repo_dir(self):
267 def is_shadow_repo_dir(self):
268 return os.path.isdir(self.vcs_repo_name)
268 return os.path.isdir(self.vcs_repo_name)
269
269
270 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
270 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
271 plugin_id='', plugin_cache_active=False, cache_ttl=0):
271 plugin_id='', plugin_cache_active=False, cache_ttl=0):
272 """
272 """
273 Checks permissions using action (push/pull) user and repository
273 Checks permissions using action (push/pull) user and repository
274 name. If plugin_cache and ttl is set it will use the plugin which
274 name. If plugin_cache and ttl is set it will use the plugin which
275 authenticated the user to store the cached permissions result for N
275 authenticated the user to store the cached permissions result for N
276 amount of seconds as in cache_ttl
276 amount of seconds as in cache_ttl
277
277
278 :param action: push or pull action
278 :param action: push or pull action
279 :param user: user instance
279 :param user: user instance
280 :param repo_name: repository name
280 :param repo_name: repository name
281 """
281 """
282
282
283 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
283 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
284 plugin_id, plugin_cache_active, cache_ttl)
284 plugin_id, plugin_cache_active, cache_ttl)
285
285
286 user_id = user.user_id
286 user_id = user.user_id
287 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
287 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
288 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
288 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
289
289
290 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
290 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
291 expiration_time=cache_ttl,
291 expiration_time=cache_ttl,
292 condition=plugin_cache_active)
292 condition=plugin_cache_active)
293 def compute_perm_vcs(
293 def compute_perm_vcs(
294 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
294 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
295
295
296 log.debug('auth: calculating permission access now...')
296 log.debug('auth: calculating permission access now...')
297 # check IP
297 # check IP
298 inherit = user.inherit_default_permissions
298 inherit = user.inherit_default_permissions
299 ip_allowed = AuthUser.check_ip_allowed(
299 ip_allowed = AuthUser.check_ip_allowed(
300 user_id, ip_addr, inherit_from_default=inherit)
300 user_id, ip_addr, inherit_from_default=inherit)
301 if ip_allowed:
301 if ip_allowed:
302 log.info('Access for IP:%s allowed', ip_addr)
302 log.info('Access for IP:%s allowed', ip_addr)
303 else:
303 else:
304 return False
304 return False
305
305
306 if action == 'push':
306 if action == 'push':
307 perms = ('repository.write', 'repository.admin')
307 perms = ('repository.write', 'repository.admin')
308 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
308 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
309 return False
309 return False
310
310
311 else:
311 else:
312 # any other action need at least read permission
312 # any other action need at least read permission
313 perms = (
313 perms = (
314 'repository.read', 'repository.write', 'repository.admin')
314 'repository.read', 'repository.write', 'repository.admin')
315 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
315 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
316 return False
316 return False
317
317
318 return True
318 return True
319
319
320 start = time.time()
320 start = time.time()
321 log.debug('Running plugin `%s` permissions check', plugin_id)
321 log.debug('Running plugin `%s` permissions check', plugin_id)
322
322
323 # for environ based auth, password can be empty, but then the validation is
323 # for environ based auth, password can be empty, but then the validation is
324 # on the server that fills in the env data needed for authentication
324 # on the server that fills in the env data needed for authentication
325 perm_result = compute_perm_vcs(
325 perm_result = compute_perm_vcs(
326 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
326 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
327
327
328 auth_time = time.time() - start
328 auth_time = time.time() - start
329 log.debug('Permissions for plugin `%s` completed in %.4fs, '
329 log.debug('Permissions for plugin `%s` completed in %.4fs, '
330 'expiration time of fetched cache %.1fs.',
330 'expiration time of fetched cache %.1fs.',
331 plugin_id, auth_time, cache_ttl)
331 plugin_id, auth_time, cache_ttl)
332
332
333 return perm_result
333 return perm_result
334
334
335 def _get_http_scheme(self, environ):
335 def _get_http_scheme(self, environ):
336 try:
336 try:
337 return environ['wsgi.url_scheme']
337 return environ['wsgi.url_scheme']
338 except Exception:
338 except Exception:
339 log.exception('Failed to read http scheme')
339 log.exception('Failed to read http scheme')
340 return 'http'
340 return 'http'
341
341
342 def _check_ssl(self, environ, start_response):
343 """
344 Checks the SSL check flag and returns False if SSL is not present
345 and required True otherwise
346 """
347 org_proto = environ['wsgi._org_proto']
348 # check if we have SSL required ! if not it's a bad request !
349 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
350 if require_ssl and org_proto == 'http':
351 log.debug(
352 'Bad request: detected protocol is `%s` and '
353 'SSL/HTTPS is required.', org_proto)
354 return False
355 return True
356
357 def _get_default_cache_ttl(self):
342 def _get_default_cache_ttl(self):
358 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
343 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
359 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
344 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
360 plugin_settings = plugin.get_settings()
345 plugin_settings = plugin.get_settings()
361 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
346 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
362 plugin_settings) or (False, 0)
347 plugin_settings) or (False, 0)
363 return plugin_cache_active, cache_ttl
348 return plugin_cache_active, cache_ttl
364
349
365 def __call__(self, environ, start_response):
350 def __call__(self, environ, start_response):
366 try:
351 try:
367 return self._handle_request(environ, start_response)
352 return self._handle_request(environ, start_response)
368 except Exception:
353 except Exception:
369 log.exception("Exception while handling request")
354 log.exception("Exception while handling request")
370 appenlight.track_exception(environ)
355 appenlight.track_exception(environ)
371 return HTTPInternalServerError()(environ, start_response)
356 return HTTPInternalServerError()(environ, start_response)
372 finally:
357 finally:
373 meta.Session.remove()
358 meta.Session.remove()
374
359
375 def _handle_request(self, environ, start_response):
360 def _handle_request(self, environ, start_response):
376 if not self._check_ssl(environ, start_response):
377 reason = ('SSL required, while RhodeCode was unable '
378 'to detect this as SSL request')
379 log.debug('User not allowed to proceed, %s', reason)
380 return HTTPNotAcceptable(reason)(environ, start_response)
381
382 if not self.url_repo_name:
361 if not self.url_repo_name:
383 log.warning('Repository name is empty: %s', self.url_repo_name)
362 log.warning('Repository name is empty: %s', self.url_repo_name)
384 # failed to get repo name, we fail now
363 # failed to get repo name, we fail now
385 return HTTPNotFound()(environ, start_response)
364 return HTTPNotFound()(environ, start_response)
386 log.debug('Extracted repo name is %s', self.url_repo_name)
365 log.debug('Extracted repo name is %s', self.url_repo_name)
387
366
388 ip_addr = get_ip_addr(environ)
367 ip_addr = get_ip_addr(environ)
389 user_agent = get_user_agent(environ)
368 user_agent = get_user_agent(environ)
390 username = None
369 username = None
391
370
392 # skip passing error to error controller
371 # skip passing error to error controller
393 environ['pylons.status_code_redirect'] = True
372 environ['pylons.status_code_redirect'] = True
394
373
395 # ======================================================================
374 # ======================================================================
396 # GET ACTION PULL or PUSH
375 # GET ACTION PULL or PUSH
397 # ======================================================================
376 # ======================================================================
398 action = self._get_action(environ)
377 action = self._get_action(environ)
399
378
400 # ======================================================================
379 # ======================================================================
401 # Check if this is a request to a shadow repository of a pull request.
380 # Check if this is a request to a shadow repository of a pull request.
402 # In this case only pull action is allowed.
381 # In this case only pull action is allowed.
403 # ======================================================================
382 # ======================================================================
404 if self.is_shadow_repo and action != 'pull':
383 if self.is_shadow_repo and action != 'pull':
405 reason = 'Only pull action is allowed for shadow repositories.'
384 reason = 'Only pull action is allowed for shadow repositories.'
406 log.debug('User not allowed to proceed, %s', reason)
385 log.debug('User not allowed to proceed, %s', reason)
407 return HTTPNotAcceptable(reason)(environ, start_response)
386 return HTTPNotAcceptable(reason)(environ, start_response)
408
387
409 # Check if the shadow repo actually exists, in case someone refers
388 # Check if the shadow repo actually exists, in case someone refers
410 # to it, and it has been deleted because of successful merge.
389 # to it, and it has been deleted because of successful merge.
411 if self.is_shadow_repo and not self.is_shadow_repo_dir:
390 if self.is_shadow_repo and not self.is_shadow_repo_dir:
412 log.debug(
391 log.debug(
413 'Shadow repo detected, and shadow repo dir `%s` is missing',
392 'Shadow repo detected, and shadow repo dir `%s` is missing',
414 self.is_shadow_repo_dir)
393 self.is_shadow_repo_dir)
415 return HTTPNotFound()(environ, start_response)
394 return HTTPNotFound()(environ, start_response)
416
395
417 # ======================================================================
396 # ======================================================================
418 # CHECK ANONYMOUS PERMISSION
397 # CHECK ANONYMOUS PERMISSION
419 # ======================================================================
398 # ======================================================================
420 detect_force_push = False
399 detect_force_push = False
421 check_branch_perms = False
400 check_branch_perms = False
422 if action in ['pull', 'push']:
401 if action in ['pull', 'push']:
423 user_obj = anonymous_user = User.get_default_user()
402 user_obj = anonymous_user = User.get_default_user()
424 auth_user = user_obj.AuthUser()
403 auth_user = user_obj.AuthUser()
425 username = anonymous_user.username
404 username = anonymous_user.username
426 if anonymous_user.active:
405 if anonymous_user.active:
427 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
406 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
428 # ONLY check permissions if the user is activated
407 # ONLY check permissions if the user is activated
429 anonymous_perm = self._check_permission(
408 anonymous_perm = self._check_permission(
430 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
409 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
431 plugin_id='anonymous_access',
410 plugin_id='anonymous_access',
432 plugin_cache_active=plugin_cache_active,
411 plugin_cache_active=plugin_cache_active,
433 cache_ttl=cache_ttl,
412 cache_ttl=cache_ttl,
434 )
413 )
435 else:
414 else:
436 anonymous_perm = False
415 anonymous_perm = False
437
416
438 if not anonymous_user.active or not anonymous_perm:
417 if not anonymous_user.active or not anonymous_perm:
439 if not anonymous_user.active:
418 if not anonymous_user.active:
440 log.debug('Anonymous access is disabled, running '
419 log.debug('Anonymous access is disabled, running '
441 'authentication')
420 'authentication')
442
421
443 if not anonymous_perm:
422 if not anonymous_perm:
444 log.debug('Not enough credentials to access repo: `%s` '
423 log.debug('Not enough credentials to access repo: `%s` '
445 'repository as anonymous user', self.acl_repo_name)
424 'repository as anonymous user', self.acl_repo_name)
446
425
447 username = None
426 username = None
448 # ==============================================================
427 # ==============================================================
449 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
428 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
450 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
429 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
451 # ==============================================================
430 # ==============================================================
452
431
453 # try to auth based on environ, container auth methods
432 # try to auth based on environ, container auth methods
454 log.debug('Running PRE-AUTH for container|headers based authentication')
433 log.debug('Running PRE-AUTH for container|headers based authentication')
455
434
456 # headers auth, by just reading special headers and bypass the auth with user/passwd
435 # headers auth, by just reading special headers and bypass the auth with user/passwd
457 pre_auth = authenticate(
436 pre_auth = authenticate(
458 '', '', environ, VCS_TYPE, registry=self.registry,
437 '', '', environ, VCS_TYPE, registry=self.registry,
459 acl_repo_name=self.acl_repo_name)
438 acl_repo_name=self.acl_repo_name)
460
439
461 if pre_auth and pre_auth.get('username'):
440 if pre_auth and pre_auth.get('username'):
462 username = pre_auth['username']
441 username = pre_auth['username']
463 log.debug('PRE-AUTH got `%s` as username', username)
442 log.debug('PRE-AUTH got `%s` as username', username)
464 if pre_auth:
443 if pre_auth:
465 log.debug('PRE-AUTH successful from %s',
444 log.debug('PRE-AUTH successful from %s',
466 pre_auth.get('auth_data', {}).get('_plugin'))
445 pre_auth.get('auth_data', {}).get('_plugin'))
467
446
468 # If not authenticated by the container, running basic auth
447 # If not authenticated by the container, running basic auth
469 # before inject the calling repo_name for special scope checks
448 # before inject the calling repo_name for special scope checks
470 self.authenticate.acl_repo_name = self.acl_repo_name
449 self.authenticate.acl_repo_name = self.acl_repo_name
471
450
472 plugin_cache_active, cache_ttl = False, 0
451 plugin_cache_active, cache_ttl = False, 0
473 plugin = None
452 plugin = None
474
453
475 # regular auth chain
454 # regular auth chain
476 if not username:
455 if not username:
477 self.authenticate.realm = self.authenticate.get_rc_realm()
456 self.authenticate.realm = self.authenticate.get_rc_realm()
478
457
479 try:
458 try:
480 auth_result = self.authenticate(environ)
459 auth_result = self.authenticate(environ)
481 except (UserCreationError, NotAllowedToCreateUserError) as e:
460 except (UserCreationError, NotAllowedToCreateUserError) as e:
482 log.error(e)
461 log.error(e)
483 reason = safe_str(e)
462 reason = safe_str(e)
484 return HTTPNotAcceptable(reason)(environ, start_response)
463 return HTTPNotAcceptable(reason)(environ, start_response)
485
464
486 if isinstance(auth_result, dict):
465 if isinstance(auth_result, dict):
487 AUTH_TYPE.update(environ, 'basic')
466 AUTH_TYPE.update(environ, 'basic')
488 REMOTE_USER.update(environ, auth_result['username'])
467 REMOTE_USER.update(environ, auth_result['username'])
489 username = auth_result['username']
468 username = auth_result['username']
490 plugin = auth_result.get('auth_data', {}).get('_plugin')
469 plugin = auth_result.get('auth_data', {}).get('_plugin')
491 log.info(
470 log.info(
492 'MAIN-AUTH successful for user `%s` from %s plugin',
471 'MAIN-AUTH successful for user `%s` from %s plugin',
493 username, plugin)
472 username, plugin)
494
473
495 plugin_cache_active, cache_ttl = auth_result.get(
474 plugin_cache_active, cache_ttl = auth_result.get(
496 'auth_data', {}).get('_ttl_cache') or (False, 0)
475 'auth_data', {}).get('_ttl_cache') or (False, 0)
497 else:
476 else:
498 return auth_result.wsgi_application(environ, start_response)
477 return auth_result.wsgi_application(environ, start_response)
499
478
500 # ==============================================================
479 # ==============================================================
501 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
480 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
502 # ==============================================================
481 # ==============================================================
503 user = User.get_by_username(username)
482 user = User.get_by_username(username)
504 if not self.valid_and_active_user(user):
483 if not self.valid_and_active_user(user):
505 return HTTPForbidden()(environ, start_response)
484 return HTTPForbidden()(environ, start_response)
506 username = user.username
485 username = user.username
507 user_id = user.user_id
486 user_id = user.user_id
508
487
509 # check user attributes for password change flag
488 # check user attributes for password change flag
510 user_obj = user
489 user_obj = user
511 auth_user = user_obj.AuthUser()
490 auth_user = user_obj.AuthUser()
512 if user_obj and user_obj.username != User.DEFAULT_USER and \
491 if user_obj and user_obj.username != User.DEFAULT_USER and \
513 user_obj.user_data.get('force_password_change'):
492 user_obj.user_data.get('force_password_change'):
514 reason = 'password change required'
493 reason = 'password change required'
515 log.debug('User not allowed to authenticate, %s', reason)
494 log.debug('User not allowed to authenticate, %s', reason)
516 return HTTPNotAcceptable(reason)(environ, start_response)
495 return HTTPNotAcceptable(reason)(environ, start_response)
517
496
518 # check permissions for this repository
497 # check permissions for this repository
519 perm = self._check_permission(
498 perm = self._check_permission(
520 action, user, auth_user, self.acl_repo_name, ip_addr,
499 action, user, auth_user, self.acl_repo_name, ip_addr,
521 plugin, plugin_cache_active, cache_ttl)
500 plugin, plugin_cache_active, cache_ttl)
522 if not perm:
501 if not perm:
523 return HTTPForbidden()(environ, start_response)
502 return HTTPForbidden()(environ, start_response)
524 environ['rc_auth_user_id'] = str(user_id)
503 environ['rc_auth_user_id'] = str(user_id)
525
504
526 if action == 'push':
505 if action == 'push':
527 perms = auth_user.get_branch_permissions(self.acl_repo_name)
506 perms = auth_user.get_branch_permissions(self.acl_repo_name)
528 if perms:
507 if perms:
529 check_branch_perms = True
508 check_branch_perms = True
530 detect_force_push = True
509 detect_force_push = True
531
510
532 # extras are injected into UI object and later available
511 # extras are injected into UI object and later available
533 # in hooks executed by RhodeCode
512 # in hooks executed by RhodeCode
534 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
513 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
535
514
536 extras = vcs_operation_context(
515 extras = vcs_operation_context(
537 environ, repo_name=self.acl_repo_name, username=username,
516 environ, repo_name=self.acl_repo_name, username=username,
538 action=action, scm=self.SCM, check_locking=check_locking,
517 action=action, scm=self.SCM, check_locking=check_locking,
539 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
518 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
540 detect_force_push=detect_force_push
519 detect_force_push=detect_force_push
541 )
520 )
542
521
543 # ======================================================================
522 # ======================================================================
544 # REQUEST HANDLING
523 # REQUEST HANDLING
545 # ======================================================================
524 # ======================================================================
546 repo_path = os.path.join(
525 repo_path = os.path.join(
547 safe_str(self.base_path), safe_str(self.vcs_repo_name))
526 safe_str(self.base_path), safe_str(self.vcs_repo_name))
548 log.debug('Repository path is %s', repo_path)
527 log.debug('Repository path is %s', repo_path)
549
528
550 fix_PATH()
529 fix_PATH()
551
530
552 log.info(
531 log.info(
553 '%s action on %s repo "%s" by "%s" from %s %s',
532 '%s action on %s repo "%s" by "%s" from %s %s',
554 action, self.SCM, safe_str(self.url_repo_name),
533 action, self.SCM, safe_str(self.url_repo_name),
555 safe_str(username), ip_addr, user_agent)
534 safe_str(username), ip_addr, user_agent)
556
535
557 return self._generate_vcs_response(
536 return self._generate_vcs_response(
558 environ, start_response, repo_path, extras, action)
537 environ, start_response, repo_path, extras, action)
559
538
560 def _get_txn_id(self, environ):
539 def _get_txn_id(self, environ):
561
540
562 for k in ['RAW_URI', 'HTTP_DESTINATION']:
541 for k in ['RAW_URI', 'HTTP_DESTINATION']:
563 url = environ.get(k)
542 url = environ.get(k)
564 if not url:
543 if not url:
565 continue
544 continue
566
545
567 # regex to search for svn-txn-id
546 # regex to search for svn-txn-id
568 pattern = r'/!svn/txr/([^/]+)/'
547 pattern = r'/!svn/txr/([^/]+)/'
569
548
570 # Search for the pattern in the URL
549 # Search for the pattern in the URL
571 match = re.search(pattern, url)
550 match = re.search(pattern, url)
572
551
573 # Check if a match is found and extract the captured group
552 # Check if a match is found and extract the captured group
574 if match:
553 if match:
575 txn_id = match.group(1)
554 txn_id = match.group(1)
576 return txn_id
555 return txn_id
577
556
578 @initialize_generator
557 @initialize_generator
579 def _generate_vcs_response(
558 def _generate_vcs_response(
580 self, environ, start_response, repo_path, extras, action):
559 self, environ, start_response, repo_path, extras, action):
581 """
560 """
582 Returns a generator for the response content.
561 Returns a generator for the response content.
583
562
584 This method is implemented as a generator, so that it can trigger
563 This method is implemented as a generator, so that it can trigger
585 the cache validation after all content sent back to the client. It
564 the cache validation after all content sent back to the client. It
586 also handles the locking exceptions which will be triggered when
565 also handles the locking exceptions which will be triggered when
587 the first chunk is produced by the underlying WSGI application.
566 the first chunk is produced by the underlying WSGI application.
588 """
567 """
589 svn_txn_id = ''
568 svn_txn_id = ''
590 if action == 'push':
569 if action == 'push':
591 svn_txn_id = self._get_txn_id(environ)
570 svn_txn_id = self._get_txn_id(environ)
592
571
593 callback_daemon, extras = self._prepare_callback_daemon(
572 callback_daemon, extras = self._prepare_callback_daemon(
594 extras, environ, action, txn_id=svn_txn_id)
573 extras, environ, action, txn_id=svn_txn_id)
595
574
596 if svn_txn_id:
575 if svn_txn_id:
597
576
598 port = safe_int(extras['hooks_uri'].split(':')[-1])
577 port = safe_int(extras['hooks_uri'].split(':')[-1])
599 txn_id_data = extras.copy()
578 txn_id_data = extras.copy()
600 txn_id_data.update({'port': port})
579 txn_id_data.update({'port': port})
601 txn_id_data.update({'req_method': environ['REQUEST_METHOD']})
580 txn_id_data.update({'req_method': environ['REQUEST_METHOD']})
602
581
603 full_repo_path = repo_path
582 full_repo_path = repo_path
604 store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data)
583 store_txn_id_data(full_repo_path, svn_txn_id, txn_id_data)
605
584
606 log.debug('HOOKS extras is %s', extras)
585 log.debug('HOOKS extras is %s', extras)
607
586
608 http_scheme = self._get_http_scheme(environ)
587 http_scheme = self._get_http_scheme(environ)
609
588
610 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
589 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
611 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
590 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
612 with callback_daemon:
591 with callback_daemon:
613 app.rc_extras = extras
592 app.rc_extras = extras
614
593
615 try:
594 try:
616 response = app(environ, start_response)
595 response = app(environ, start_response)
617 finally:
596 finally:
618 # This statement works together with the decorator
597 # This statement works together with the decorator
619 # "initialize_generator" above. The decorator ensures that
598 # "initialize_generator" above. The decorator ensures that
620 # we hit the first yield statement before the generator is
599 # we hit the first yield statement before the generator is
621 # returned back to the WSGI server. This is needed to
600 # returned back to the WSGI server. This is needed to
622 # ensure that the call to "app" above triggers the
601 # ensure that the call to "app" above triggers the
623 # needed callback to "start_response" before the
602 # needed callback to "start_response" before the
624 # generator is actually used.
603 # generator is actually used.
625 yield "__init__"
604 yield "__init__"
626
605
627 # iter content
606 # iter content
628 for chunk in response:
607 for chunk in response:
629 yield chunk
608 yield chunk
630
609
631 try:
610 try:
632 # invalidate cache on push
611 # invalidate cache on push
633 if action == 'push':
612 if action == 'push':
634 self._invalidate_cache(self.url_repo_name)
613 self._invalidate_cache(self.url_repo_name)
635 finally:
614 finally:
636 meta.Session.remove()
615 meta.Session.remove()
637
616
638 def _get_repository_name(self, environ):
617 def _get_repository_name(self, environ):
639 """Get repository name out of the environmnent
618 """Get repository name out of the environmnent
640
619
641 :param environ: WSGI environment
620 :param environ: WSGI environment
642 """
621 """
643 raise NotImplementedError()
622 raise NotImplementedError()
644
623
645 def _get_action(self, environ):
624 def _get_action(self, environ):
646 """Map request commands into a pull or push command.
625 """Map request commands into a pull or push command.
647
626
648 :param environ: WSGI environment
627 :param environ: WSGI environment
649 """
628 """
650 raise NotImplementedError()
629 raise NotImplementedError()
651
630
652 def _create_wsgi_app(self, repo_path, repo_name, config):
631 def _create_wsgi_app(self, repo_path, repo_name, config):
653 """Return the WSGI app that will finally handle the request."""
632 """Return the WSGI app that will finally handle the request."""
654 raise NotImplementedError()
633 raise NotImplementedError()
655
634
656 def _create_config(self, extras, repo_name, scheme='http'):
635 def _create_config(self, extras, repo_name, scheme='http'):
657 """Create a safe config representation."""
636 """Create a safe config representation."""
658 raise NotImplementedError()
637 raise NotImplementedError()
659
638
660 def _should_use_callback_daemon(self, extras, environ, action):
639 def _should_use_callback_daemon(self, extras, environ, action):
661 if extras.get('is_shadow_repo'):
640 if extras.get('is_shadow_repo'):
662 # we don't want to execute hooks, and callback daemon for shadow repos
641 # we don't want to execute hooks, and callback daemon for shadow repos
663 return False
642 return False
664 return True
643 return True
665
644
666 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
645 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
667 protocol = vcs_settings.HOOKS_PROTOCOL
646 protocol = vcs_settings.HOOKS_PROTOCOL
668
647
669 if not self._should_use_callback_daemon(extras, environ, action):
648 if not self._should_use_callback_daemon(extras, environ, action):
670 # disable callback daemon for actions that don't require it
649 # disable callback daemon for actions that don't require it
671 protocol = 'local'
650 protocol = 'local'
672
651
673 return prepare_callback_daemon(
652 return prepare_callback_daemon(
674 extras, protocol=protocol,
653 extras, protocol=protocol,
675 host=vcs_settings.HOOKS_HOST, txn_id=txn_id)
654 host=vcs_settings.HOOKS_HOST, txn_id=txn_id)
676
655
677
656
678 def _should_check_locking(query_string):
657 def _should_check_locking(query_string):
679 # this is kind of hacky, but due to how mercurial handles client-server
658 # this is kind of hacky, but due to how mercurial handles client-server
680 # server see all operation on commit; bookmarks, phases and
659 # server see all operation on commit; bookmarks, phases and
681 # obsolescence marker in different transaction, we don't want to check
660 # obsolescence marker in different transaction, we don't want to check
682 # locking on those
661 # locking on those
683 return query_string not in ['cmd=listkeys']
662 return query_string not in ['cmd=listkeys']
@@ -1,827 +1,826 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 Utilities library for RhodeCode
20 Utilities library for RhodeCode
21 """
21 """
22
22
23 import datetime
23 import datetime
24
24
25 import decorator
25 import decorator
26 import logging
26 import logging
27 import os
27 import os
28 import re
28 import re
29 import sys
29 import sys
30 import shutil
30 import shutil
31 import socket
31 import socket
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35
35
36 from functools import wraps
36 from functools import wraps
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
41 from webhelpers2.text import collapse, strip_tags, convert_accented_entities, convert_misc_entities
42
42
43 from mako import exceptions
43 from mako import exceptions
44
44
45 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
45 from rhodecode.lib.hash_utils import sha256_safe, md5, sha1
46 from rhodecode.lib.type_utils import AttributeDict
46 from rhodecode.lib.type_utils import AttributeDict
47 from rhodecode.lib.str_utils import safe_bytes, safe_str
47 from rhodecode.lib.str_utils import safe_bytes, safe_str
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.ext_json import sjson as json
51 from rhodecode.lib.ext_json import sjson as json
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import (
53 from rhodecode.model.db import (
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
54 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
61
62 # String which contains characters that are not allowed in slug names for
62 # String which contains characters that are not allowed in slug names for
63 # repositories or repository groups. It is properly escaped to use it in
63 # repositories or repository groups. It is properly escaped to use it in
64 # regular expressions.
64 # regular expressions.
65 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
65 SLUG_BAD_CHARS = re.escape(r'`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66
66
67 # Regex that matches forbidden characters in repo/group slugs.
67 # Regex that matches forbidden characters in repo/group slugs.
68 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
68 SLUG_BAD_CHAR_RE = re.compile(r'[{}\x00-\x08\x0b-\x0c\x0e-\x1f]'.format(SLUG_BAD_CHARS))
69
69
70 # Regex that matches allowed characters in repo/group slugs.
70 # Regex that matches allowed characters in repo/group slugs.
71 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
71 SLUG_GOOD_CHAR_RE = re.compile(r'[^{}]'.format(SLUG_BAD_CHARS))
72
72
73 # Regex that matches whole repo/group slugs.
73 # Regex that matches whole repo/group slugs.
74 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
74 SLUG_RE = re.compile(r'[^{}]+'.format(SLUG_BAD_CHARS))
75
75
76 _license_cache = None
76 _license_cache = None
77
77
78
78
79 def adopt_for_celery(func):
79 def adopt_for_celery(func):
80 """
80 """
81 Decorator designed to adopt hooks (from rhodecode.lib.hooks_base)
81 Decorator designed to adopt hooks (from rhodecode.lib.hooks_base)
82 for further usage as a celery tasks.
82 for further usage as a celery tasks.
83 """
83 """
84 @wraps(func)
84 @wraps(func)
85 def wrapper(extras):
85 def wrapper(extras):
86 extras = AttributeDict(extras)
86 extras = AttributeDict(extras)
87 try:
87 try:
88 # HooksResponse implements to_json method which must be used there.
88 # HooksResponse implements to_json method which must be used there.
89 return func(extras).to_json()
89 return func(extras).to_json()
90 except Exception as e:
90 except Exception as e:
91 return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args}
91 return {'status': 128, 'exception': type(e).__name__, 'exception_args': e.args}
92 return wrapper
92 return wrapper
93
93
94
94
95 def repo_name_slug(value):
95 def repo_name_slug(value):
96 """
96 """
97 Return slug of name of repository
97 Return slug of name of repository
98 This function is called on each creation/modification
98 This function is called on each creation/modification
99 of repository to prevent bad names in repo
99 of repository to prevent bad names in repo
100 """
100 """
101
101
102 replacement_char = '-'
102 replacement_char = '-'
103
103
104 slug = strip_tags(value)
104 slug = strip_tags(value)
105 slug = convert_accented_entities(slug)
105 slug = convert_accented_entities(slug)
106 slug = convert_misc_entities(slug)
106 slug = convert_misc_entities(slug)
107
107
108 slug = SLUG_BAD_CHAR_RE.sub('', slug)
108 slug = SLUG_BAD_CHAR_RE.sub('', slug)
109 slug = re.sub(r'[\s]+', '-', slug)
109 slug = re.sub(r'[\s]+', '-', slug)
110 slug = collapse(slug, replacement_char)
110 slug = collapse(slug, replacement_char)
111
111
112 return slug
112 return slug
113
113
114
114
115 #==============================================================================
115 #==============================================================================
116 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
116 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
117 #==============================================================================
117 #==============================================================================
118 def get_repo_slug(request):
118 def get_repo_slug(request):
119 _repo = ''
119 _repo = ''
120
120
121 if hasattr(request, 'db_repo_name'):
121 if hasattr(request, 'db_repo_name'):
122 # if our requests has set db reference use it for name, this
122 # if our requests has set db reference use it for name, this
123 # translates the example.com/_<id> into proper repo names
123 # translates the example.com/_<id> into proper repo names
124 _repo = request.db_repo_name
124 _repo = request.db_repo_name
125 elif getattr(request, 'matchdict', None):
125 elif getattr(request, 'matchdict', None):
126 # pyramid
126 # pyramid
127 _repo = request.matchdict.get('repo_name')
127 _repo = request.matchdict.get('repo_name')
128
128
129 if _repo:
129 if _repo:
130 _repo = _repo.rstrip('/')
130 _repo = _repo.rstrip('/')
131 return _repo
131 return _repo
132
132
133
133
134 def get_repo_group_slug(request):
134 def get_repo_group_slug(request):
135 _group = ''
135 _group = ''
136 if hasattr(request, 'db_repo_group'):
136 if hasattr(request, 'db_repo_group'):
137 # if our requests has set db reference use it for name, this
137 # if our requests has set db reference use it for name, this
138 # translates the example.com/_<id> into proper repo group names
138 # translates the example.com/_<id> into proper repo group names
139 _group = request.db_repo_group.group_name
139 _group = request.db_repo_group.group_name
140 elif getattr(request, 'matchdict', None):
140 elif getattr(request, 'matchdict', None):
141 # pyramid
141 # pyramid
142 _group = request.matchdict.get('repo_group_name')
142 _group = request.matchdict.get('repo_group_name')
143
143
144 if _group:
144 if _group:
145 _group = _group.rstrip('/')
145 _group = _group.rstrip('/')
146 return _group
146 return _group
147
147
148
148
149 def get_user_group_slug(request):
149 def get_user_group_slug(request):
150 _user_group = ''
150 _user_group = ''
151
151
152 if hasattr(request, 'db_user_group'):
152 if hasattr(request, 'db_user_group'):
153 _user_group = request.db_user_group.users_group_name
153 _user_group = request.db_user_group.users_group_name
154 elif getattr(request, 'matchdict', None):
154 elif getattr(request, 'matchdict', None):
155 # pyramid
155 # pyramid
156 _user_group = request.matchdict.get('user_group_id')
156 _user_group = request.matchdict.get('user_group_id')
157 _user_group_name = request.matchdict.get('user_group_name')
157 _user_group_name = request.matchdict.get('user_group_name')
158 try:
158 try:
159 if _user_group:
159 if _user_group:
160 _user_group = UserGroup.get(_user_group)
160 _user_group = UserGroup.get(_user_group)
161 elif _user_group_name:
161 elif _user_group_name:
162 _user_group = UserGroup.get_by_group_name(_user_group_name)
162 _user_group = UserGroup.get_by_group_name(_user_group_name)
163
163
164 if _user_group:
164 if _user_group:
165 _user_group = _user_group.users_group_name
165 _user_group = _user_group.users_group_name
166 except Exception:
166 except Exception:
167 log.exception('Failed to get user group by id and name')
167 log.exception('Failed to get user group by id and name')
168 # catch all failures here
168 # catch all failures here
169 return None
169 return None
170
170
171 return _user_group
171 return _user_group
172
172
173
173
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184 log.debug('now scanning in %s location recursive:%s...', path, recursive)
184 log.debug('now scanning in %s location recursive:%s...', path, recursive)
185
185
186 def _get_repos(p):
186 def _get_repos(p):
187 dirpaths = get_dirpaths(p)
187 dirpaths = get_dirpaths(p)
188 if not _is_dir_writable(p):
188 if not _is_dir_writable(p):
189 log.warning('repo path without write access: %s', p)
189 log.warning('repo path without write access: %s', p)
190
190
191 for dirpath in dirpaths:
191 for dirpath in dirpaths:
192 if os.path.isfile(os.path.join(p, dirpath)):
192 if os.path.isfile(os.path.join(p, dirpath)):
193 continue
193 continue
194 cur_path = os.path.join(p, dirpath)
194 cur_path = os.path.join(p, dirpath)
195
195
196 # skip removed repos
196 # skip removed repos
197 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
198 continue
198 continue
199
199
200 #skip .<somethin> dirs
200 #skip .<somethin> dirs
201 if dirpath.startswith('.'):
201 if dirpath.startswith('.'):
202 continue
202 continue
203
203
204 try:
204 try:
205 scm_info = get_scm(cur_path)
205 scm_info = get_scm(cur_path)
206 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
207 except VCSError:
207 except VCSError:
208 if not recursive:
208 if not recursive:
209 continue
209 continue
210 #check if this dir containts other repos for recursive scan
210 #check if this dir containts other repos for recursive scan
211 rec_path = os.path.join(p, dirpath)
211 rec_path = os.path.join(p, dirpath)
212 if os.path.isdir(rec_path):
212 if os.path.isdir(rec_path):
213 yield from _get_repos(rec_path)
213 yield from _get_repos(rec_path)
214
214
215 return _get_repos(path)
215 return _get_repos(path)
216
216
217
217
218 def get_dirpaths(p: str) -> list:
218 def get_dirpaths(p: str) -> list:
219 try:
219 try:
220 # OS-independable way of checking if we have at least read-only
220 # OS-independable way of checking if we have at least read-only
221 # access or not.
221 # access or not.
222 dirpaths = os.listdir(p)
222 dirpaths = os.listdir(p)
223 except OSError:
223 except OSError:
224 log.warning('ignoring repo path without read access: %s', p)
224 log.warning('ignoring repo path without read access: %s', p)
225 return []
225 return []
226
226
227 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
227 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
228 # decode paths and suddenly returns unicode objects itself. The items it
228 # decode paths and suddenly returns unicode objects itself. The items it
229 # cannot decode are returned as strings and cause issues.
229 # cannot decode are returned as strings and cause issues.
230 #
230 #
231 # Those paths are ignored here until a solid solution for path handling has
231 # Those paths are ignored here until a solid solution for path handling has
232 # been built.
232 # been built.
233 expected_type = type(p)
233 expected_type = type(p)
234
234
235 def _has_correct_type(item):
235 def _has_correct_type(item):
236 if type(item) is not expected_type:
236 if type(item) is not expected_type:
237 log.error(
237 log.error(
238 "Ignoring path %s since it cannot be decoded into str.",
238 "Ignoring path %s since it cannot be decoded into str.",
239 # Using "repr" to make sure that we see the byte value in case
239 # Using "repr" to make sure that we see the byte value in case
240 # of support.
240 # of support.
241 repr(item))
241 repr(item))
242 return False
242 return False
243 return True
243 return True
244
244
245 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
245 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
246
246
247 return dirpaths
247 return dirpaths
248
248
249
249
250 def _is_dir_writable(path):
250 def _is_dir_writable(path):
251 """
251 """
252 Probe if `path` is writable.
252 Probe if `path` is writable.
253
253
254 Due to trouble on Cygwin / Windows, this is actually probing if it is
254 Due to trouble on Cygwin / Windows, this is actually probing if it is
255 possible to create a file inside of `path`, stat does not produce reliable
255 possible to create a file inside of `path`, stat does not produce reliable
256 results in this case.
256 results in this case.
257 """
257 """
258 try:
258 try:
259 with tempfile.TemporaryFile(dir=path):
259 with tempfile.TemporaryFile(dir=path):
260 pass
260 pass
261 except OSError:
261 except OSError:
262 return False
262 return False
263 return True
263 return True
264
264
265
265
266 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
266 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None, config=None):
267 """
267 """
268 Returns True if given path is a valid repository False otherwise.
268 Returns True if given path is a valid repository False otherwise.
269 If expect_scm param is given also, compare if given scm is the same
269 If expect_scm param is given also, compare if given scm is the same
270 as expected from scm parameter. If explicit_scm is given don't try to
270 as expected from scm parameter. If explicit_scm is given don't try to
271 detect the scm, just use the given one to check if repo is valid
271 detect the scm, just use the given one to check if repo is valid
272
272
273 :param repo_name:
273 :param repo_name:
274 :param base_path:
274 :param base_path:
275 :param expect_scm:
275 :param expect_scm:
276 :param explicit_scm:
276 :param explicit_scm:
277 :param config:
277 :param config:
278
278
279 :return True: if given path is a valid repository
279 :return True: if given path is a valid repository
280 """
280 """
281 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
281 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
282 log.debug('Checking if `%s` is a valid path for repository. '
282 log.debug('Checking if `%s` is a valid path for repository. '
283 'Explicit type: %s', repo_name, explicit_scm)
283 'Explicit type: %s', repo_name, explicit_scm)
284
284
285 try:
285 try:
286 if explicit_scm:
286 if explicit_scm:
287 detected_scms = [get_scm_backend(explicit_scm)(
287 detected_scms = [get_scm_backend(explicit_scm)(
288 full_path, config=config).alias]
288 full_path, config=config).alias]
289 else:
289 else:
290 detected_scms = get_scm(full_path)
290 detected_scms = get_scm(full_path)
291
291
292 if expect_scm:
292 if expect_scm:
293 return detected_scms[0] == expect_scm
293 return detected_scms[0] == expect_scm
294 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
294 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
295 return True
295 return True
296 except VCSError:
296 except VCSError:
297 log.debug('path: %s is not a valid repo !', full_path)
297 log.debug('path: %s is not a valid repo !', full_path)
298 return False
298 return False
299
299
300
300
301 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
301 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
302 """
302 """
303 Returns True if a given path is a repository group, False otherwise
303 Returns True if a given path is a repository group, False otherwise
304
304
305 :param repo_group_name:
305 :param repo_group_name:
306 :param base_path:
306 :param base_path:
307 """
307 """
308 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
308 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
309 log.debug('Checking if `%s` is a valid path for repository group',
309 log.debug('Checking if `%s` is a valid path for repository group',
310 repo_group_name)
310 repo_group_name)
311
311
312 # check if it's not a repo
312 # check if it's not a repo
313 if is_valid_repo(repo_group_name, base_path):
313 if is_valid_repo(repo_group_name, base_path):
314 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
314 log.debug('Repo called %s exist, it is not a valid repo group', repo_group_name)
315 return False
315 return False
316
316
317 try:
317 try:
318 # we need to check bare git repos at higher level
318 # we need to check bare git repos at higher level
319 # since we might match branches/hooks/info/objects or possible
319 # since we might match branches/hooks/info/objects or possible
320 # other things inside bare git repo
320 # other things inside bare git repo
321 maybe_repo = os.path.dirname(full_path)
321 maybe_repo = os.path.dirname(full_path)
322 if maybe_repo == base_path:
322 if maybe_repo == base_path:
323 # skip root level repo check; we know root location CANNOT BE a repo group
323 # skip root level repo check; we know root location CANNOT BE a repo group
324 return False
324 return False
325
325
326 scm_ = get_scm(maybe_repo)
326 scm_ = get_scm(maybe_repo)
327 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
327 log.debug('path: %s is a vcs object:%s, not valid repo group', full_path, scm_)
328 return False
328 return False
329 except VCSError:
329 except VCSError:
330 pass
330 pass
331
331
332 # check if it's a valid path
332 # check if it's a valid path
333 if skip_path_check or os.path.isdir(full_path):
333 if skip_path_check or os.path.isdir(full_path):
334 log.debug('path: %s is a valid repo group !', full_path)
334 log.debug('path: %s is a valid repo group !', full_path)
335 return True
335 return True
336
336
337 log.debug('path: %s is not a valid repo group !', full_path)
337 log.debug('path: %s is not a valid repo group !', full_path)
338 return False
338 return False
339
339
340
340
341 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
341 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
342 while True:
342 while True:
343 ok = input(prompt)
343 ok = input(prompt)
344 if ok.lower() in ('y', 'ye', 'yes'):
344 if ok.lower() in ('y', 'ye', 'yes'):
345 return True
345 return True
346 if ok.lower() in ('n', 'no', 'nop', 'nope'):
346 if ok.lower() in ('n', 'no', 'nop', 'nope'):
347 return False
347 return False
348 retries = retries - 1
348 retries = retries - 1
349 if retries < 0:
349 if retries < 0:
350 raise OSError
350 raise OSError
351 print(complaint)
351 print(complaint)
352
352
353 # propagated from mercurial documentation
353 # propagated from mercurial documentation
354 ui_sections = [
354 ui_sections = [
355 'alias', 'auth',
355 'alias', 'auth',
356 'decode/encode', 'defaults',
356 'decode/encode', 'defaults',
357 'diff', 'email',
357 'diff', 'email',
358 'extensions', 'format',
358 'extensions', 'format',
359 'merge-patterns', 'merge-tools',
359 'merge-patterns', 'merge-tools',
360 'hooks', 'http_proxy',
360 'hooks', 'http_proxy',
361 'smtp', 'patch',
361 'smtp', 'patch',
362 'paths', 'profiling',
362 'paths', 'profiling',
363 'server', 'trusted',
363 'server', 'trusted',
364 'ui', 'web', ]
364 'ui', 'web', ]
365
365
366
366
367 def config_data_from_db(clear_session=True, repo=None):
367 def config_data_from_db(clear_session=True, repo=None):
368 """
368 """
369 Read the configuration data from the database and return configuration
369 Read the configuration data from the database and return configuration
370 tuples.
370 tuples.
371 """
371 """
372 from rhodecode.model.settings import VcsSettingsModel
372 from rhodecode.model.settings import VcsSettingsModel
373
373
374 config = []
374 config = []
375
375
376 sa = meta.Session()
376 sa = meta.Session()
377 settings_model = VcsSettingsModel(repo=repo, sa=sa)
377 settings_model = VcsSettingsModel(repo=repo, sa=sa)
378
378
379 ui_settings = settings_model.get_ui_settings()
379 ui_settings = settings_model.get_ui_settings()
380
380
381 ui_data = []
381 ui_data = []
382 for setting in ui_settings:
382 for setting in ui_settings:
383 if setting.active:
383 if setting.active:
384 ui_data.append((setting.section, setting.key, setting.value))
384 ui_data.append((setting.section, setting.key, setting.value))
385 config.append((
385 config.append((
386 safe_str(setting.section), safe_str(setting.key),
386 safe_str(setting.section), safe_str(setting.key),
387 safe_str(setting.value)))
387 safe_str(setting.value)))
388 if setting.key == 'push_ssl':
388 if setting.key == 'push_ssl':
389 # force set push_ssl requirement to False, rhodecode
389 # force set push_ssl requirement to False this is deprecated, and we must force it to False
390 # handles that
391 config.append((
390 config.append((
392 safe_str(setting.section), safe_str(setting.key), False))
391 safe_str(setting.section), safe_str(setting.key), False))
393 log.debug(
392 log.debug(
394 'settings ui from db@repo[%s]: %s',
393 'settings ui from db@repo[%s]: %s',
395 repo,
394 repo,
396 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
395 ','.join(['[{}] {}={}'.format(*s) for s in ui_data]))
397 if clear_session:
396 if clear_session:
398 meta.Session.remove()
397 meta.Session.remove()
399
398
400 # TODO: mikhail: probably it makes no sense to re-read hooks information.
399 # TODO: mikhail: probably it makes no sense to re-read hooks information.
401 # It's already there and activated/deactivated
400 # It's already there and activated/deactivated
402 skip_entries = []
401 skip_entries = []
403 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
402 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
404 if 'pull' not in enabled_hook_classes:
403 if 'pull' not in enabled_hook_classes:
405 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
404 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
406 if 'push' not in enabled_hook_classes:
405 if 'push' not in enabled_hook_classes:
407 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
406 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
408 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
407 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
409 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
408 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
410
409
411 config = [entry for entry in config if entry[:2] not in skip_entries]
410 config = [entry for entry in config if entry[:2] not in skip_entries]
412
411
413 return config
412 return config
414
413
415
414
416 def make_db_config(clear_session=True, repo=None):
415 def make_db_config(clear_session=True, repo=None):
417 """
416 """
418 Create a :class:`Config` instance based on the values in the database.
417 Create a :class:`Config` instance based on the values in the database.
419 """
418 """
420 config = Config()
419 config = Config()
421 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
420 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
422 for section, option, value in config_data:
421 for section, option, value in config_data:
423 config.set(section, option, value)
422 config.set(section, option, value)
424 return config
423 return config
425
424
426
425
427 def get_enabled_hook_classes(ui_settings):
426 def get_enabled_hook_classes(ui_settings):
428 """
427 """
429 Return the enabled hook classes.
428 Return the enabled hook classes.
430
429
431 :param ui_settings: List of ui_settings as returned
430 :param ui_settings: List of ui_settings as returned
432 by :meth:`VcsSettingsModel.get_ui_settings`
431 by :meth:`VcsSettingsModel.get_ui_settings`
433
432
434 :return: a list with the enabled hook classes. The order is not guaranteed.
433 :return: a list with the enabled hook classes. The order is not guaranteed.
435 :rtype: list
434 :rtype: list
436 """
435 """
437 enabled_hooks = []
436 enabled_hooks = []
438 active_hook_keys = [
437 active_hook_keys = [
439 key for section, key, value, active in ui_settings
438 key for section, key, value, active in ui_settings
440 if section == 'hooks' and active]
439 if section == 'hooks' and active]
441
440
442 hook_names = {
441 hook_names = {
443 RhodeCodeUi.HOOK_PUSH: 'push',
442 RhodeCodeUi.HOOK_PUSH: 'push',
444 RhodeCodeUi.HOOK_PULL: 'pull',
443 RhodeCodeUi.HOOK_PULL: 'pull',
445 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
444 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
446 }
445 }
447
446
448 for key in active_hook_keys:
447 for key in active_hook_keys:
449 hook = hook_names.get(key)
448 hook = hook_names.get(key)
450 if hook:
449 if hook:
451 enabled_hooks.append(hook)
450 enabled_hooks.append(hook)
452
451
453 return enabled_hooks
452 return enabled_hooks
454
453
455
454
456 def set_rhodecode_config(config):
455 def set_rhodecode_config(config):
457 """
456 """
458 Updates pyramid config with new settings from database
457 Updates pyramid config with new settings from database
459
458
460 :param config:
459 :param config:
461 """
460 """
462 from rhodecode.model.settings import SettingsModel
461 from rhodecode.model.settings import SettingsModel
463 app_settings = SettingsModel().get_all_settings()
462 app_settings = SettingsModel().get_all_settings()
464
463
465 for k, v in list(app_settings.items()):
464 for k, v in list(app_settings.items()):
466 config[k] = v
465 config[k] = v
467
466
468
467
469 def get_rhodecode_realm():
468 def get_rhodecode_realm():
470 """
469 """
471 Return the rhodecode realm from database.
470 Return the rhodecode realm from database.
472 """
471 """
473 from rhodecode.model.settings import SettingsModel
472 from rhodecode.model.settings import SettingsModel
474 realm = SettingsModel().get_setting_by_name('realm')
473 realm = SettingsModel().get_setting_by_name('realm')
475 return safe_str(realm.app_settings_value)
474 return safe_str(realm.app_settings_value)
476
475
477
476
478 def get_rhodecode_repo_store_path():
477 def get_rhodecode_repo_store_path():
479 """
478 """
480 Returns the base path. The base path is the filesystem path which points
479 Returns the base path. The base path is the filesystem path which points
481 to the repository store.
480 to the repository store.
482 """
481 """
483
482
484 import rhodecode
483 import rhodecode
485 return rhodecode.CONFIG['repo_store.path']
484 return rhodecode.CONFIG['repo_store.path']
486
485
487
486
488 def map_groups(path):
487 def map_groups(path):
489 """
488 """
490 Given a full path to a repository, create all nested groups that this
489 Given a full path to a repository, create all nested groups that this
491 repo is inside. This function creates parent-child relationships between
490 repo is inside. This function creates parent-child relationships between
492 groups and creates default perms for all new groups.
491 groups and creates default perms for all new groups.
493
492
494 :param paths: full path to repository
493 :param paths: full path to repository
495 """
494 """
496 from rhodecode.model.repo_group import RepoGroupModel
495 from rhodecode.model.repo_group import RepoGroupModel
497 sa = meta.Session()
496 sa = meta.Session()
498 groups = path.split(Repository.NAME_SEP)
497 groups = path.split(Repository.NAME_SEP)
499 parent = None
498 parent = None
500 group = None
499 group = None
501
500
502 # last element is repo in nested groups structure
501 # last element is repo in nested groups structure
503 groups = groups[:-1]
502 groups = groups[:-1]
504 rgm = RepoGroupModel(sa)
503 rgm = RepoGroupModel(sa)
505 owner = User.get_first_super_admin()
504 owner = User.get_first_super_admin()
506 for lvl, group_name in enumerate(groups):
505 for lvl, group_name in enumerate(groups):
507 group_name = '/'.join(groups[:lvl] + [group_name])
506 group_name = '/'.join(groups[:lvl] + [group_name])
508 group = RepoGroup.get_by_group_name(group_name)
507 group = RepoGroup.get_by_group_name(group_name)
509 desc = '%s group' % group_name
508 desc = '%s group' % group_name
510
509
511 # skip folders that are now removed repos
510 # skip folders that are now removed repos
512 if REMOVED_REPO_PAT.match(group_name):
511 if REMOVED_REPO_PAT.match(group_name):
513 break
512 break
514
513
515 if group is None:
514 if group is None:
516 log.debug('creating group level: %s group_name: %s',
515 log.debug('creating group level: %s group_name: %s',
517 lvl, group_name)
516 lvl, group_name)
518 group = RepoGroup(group_name, parent)
517 group = RepoGroup(group_name, parent)
519 group.group_description = desc
518 group.group_description = desc
520 group.user = owner
519 group.user = owner
521 sa.add(group)
520 sa.add(group)
522 perm_obj = rgm._create_default_perms(group)
521 perm_obj = rgm._create_default_perms(group)
523 sa.add(perm_obj)
522 sa.add(perm_obj)
524 sa.flush()
523 sa.flush()
525
524
526 parent = group
525 parent = group
527 return group
526 return group
528
527
529
528
530 def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
529 def repo2db_mapper(initial_repo_list, remove_obsolete=False, force_hooks_rebuild=False):
531 """
530 """
532 maps all repos given in initial_repo_list, non existing repositories
531 maps all repos given in initial_repo_list, non existing repositories
533 are created, if remove_obsolete is True it also checks for db entries
532 are created, if remove_obsolete is True it also checks for db entries
534 that are not in initial_repo_list and removes them.
533 that are not in initial_repo_list and removes them.
535
534
536 :param initial_repo_list: list of repositories found by scanning methods
535 :param initial_repo_list: list of repositories found by scanning methods
537 :param remove_obsolete: check for obsolete entries in database
536 :param remove_obsolete: check for obsolete entries in database
538 """
537 """
539 from rhodecode.model.repo import RepoModel
538 from rhodecode.model.repo import RepoModel
540 from rhodecode.model.repo_group import RepoGroupModel
539 from rhodecode.model.repo_group import RepoGroupModel
541 from rhodecode.model.settings import SettingsModel
540 from rhodecode.model.settings import SettingsModel
542
541
543 sa = meta.Session()
542 sa = meta.Session()
544 repo_model = RepoModel()
543 repo_model = RepoModel()
545 user = User.get_first_super_admin()
544 user = User.get_first_super_admin()
546 added = []
545 added = []
547
546
548 # creation defaults
547 # creation defaults
549 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
548 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
550 enable_statistics = defs.get('repo_enable_statistics')
549 enable_statistics = defs.get('repo_enable_statistics')
551 enable_locking = defs.get('repo_enable_locking')
550 enable_locking = defs.get('repo_enable_locking')
552 enable_downloads = defs.get('repo_enable_downloads')
551 enable_downloads = defs.get('repo_enable_downloads')
553 private = defs.get('repo_private')
552 private = defs.get('repo_private')
554
553
555 for name, repo in list(initial_repo_list.items()):
554 for name, repo in list(initial_repo_list.items()):
556 group = map_groups(name)
555 group = map_groups(name)
557 str_name = safe_str(name)
556 str_name = safe_str(name)
558 db_repo = repo_model.get_by_repo_name(str_name)
557 db_repo = repo_model.get_by_repo_name(str_name)
559
558
560 # found repo that is on filesystem not in RhodeCode database
559 # found repo that is on filesystem not in RhodeCode database
561 if not db_repo:
560 if not db_repo:
562 log.info('repository `%s` not found in the database, creating now', name)
561 log.info('repository `%s` not found in the database, creating now', name)
563 added.append(name)
562 added.append(name)
564 desc = (repo.description
563 desc = (repo.description
565 if repo.description != 'unknown'
564 if repo.description != 'unknown'
566 else '%s repository' % name)
565 else '%s repository' % name)
567
566
568 db_repo = repo_model._create_repo(
567 db_repo = repo_model._create_repo(
569 repo_name=name,
568 repo_name=name,
570 repo_type=repo.alias,
569 repo_type=repo.alias,
571 description=desc,
570 description=desc,
572 repo_group=getattr(group, 'group_id', None),
571 repo_group=getattr(group, 'group_id', None),
573 owner=user,
572 owner=user,
574 enable_locking=enable_locking,
573 enable_locking=enable_locking,
575 enable_downloads=enable_downloads,
574 enable_downloads=enable_downloads,
576 enable_statistics=enable_statistics,
575 enable_statistics=enable_statistics,
577 private=private,
576 private=private,
578 state=Repository.STATE_CREATED
577 state=Repository.STATE_CREATED
579 )
578 )
580 sa.commit()
579 sa.commit()
581 # we added that repo just now, and make sure we updated server info
580 # we added that repo just now, and make sure we updated server info
582 if db_repo.repo_type == 'git':
581 if db_repo.repo_type == 'git':
583 git_repo = db_repo.scm_instance()
582 git_repo = db_repo.scm_instance()
584 # update repository server-info
583 # update repository server-info
585 log.debug('Running update server info')
584 log.debug('Running update server info')
586 git_repo._update_server_info(force=True)
585 git_repo._update_server_info(force=True)
587
586
588 db_repo.update_commit_cache(recursive=False)
587 db_repo.update_commit_cache(recursive=False)
589
588
590 config = db_repo._config
589 config = db_repo._config
591 config.set('extensions', 'largefiles', '')
590 config.set('extensions', 'largefiles', '')
592 repo = db_repo.scm_instance(config=config)
591 repo = db_repo.scm_instance(config=config)
593 repo.install_hooks(force=force_hooks_rebuild)
592 repo.install_hooks(force=force_hooks_rebuild)
594
593
595 removed = []
594 removed = []
596 if remove_obsolete:
595 if remove_obsolete:
597 # remove from database those repositories that are not in the filesystem
596 # remove from database those repositories that are not in the filesystem
598 for repo in sa.query(Repository).all():
597 for repo in sa.query(Repository).all():
599 if repo.repo_name not in list(initial_repo_list.keys()):
598 if repo.repo_name not in list(initial_repo_list.keys()):
600 log.debug("Removing non-existing repository found in db `%s`",
599 log.debug("Removing non-existing repository found in db `%s`",
601 repo.repo_name)
600 repo.repo_name)
602 try:
601 try:
603 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
602 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
604 sa.commit()
603 sa.commit()
605 removed.append(repo.repo_name)
604 removed.append(repo.repo_name)
606 except Exception:
605 except Exception:
607 # don't hold further removals on error
606 # don't hold further removals on error
608 log.error(traceback.format_exc())
607 log.error(traceback.format_exc())
609 sa.rollback()
608 sa.rollback()
610
609
611 def splitter(full_repo_name):
610 def splitter(full_repo_name):
612 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
611 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
613 gr_name = None
612 gr_name = None
614 if len(_parts) == 2:
613 if len(_parts) == 2:
615 gr_name = _parts[0]
614 gr_name = _parts[0]
616 return gr_name
615 return gr_name
617
616
618 initial_repo_group_list = [splitter(x) for x in
617 initial_repo_group_list = [splitter(x) for x in
619 list(initial_repo_list.keys()) if splitter(x)]
618 list(initial_repo_list.keys()) if splitter(x)]
620
619
621 # remove from database those repository groups that are not in the
620 # remove from database those repository groups that are not in the
622 # filesystem due to parent child relationships we need to delete them
621 # filesystem due to parent child relationships we need to delete them
623 # in a specific order of most nested first
622 # in a specific order of most nested first
624 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
623 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
625 def nested_sort(gr):
624 def nested_sort(gr):
626 return len(gr.split('/'))
625 return len(gr.split('/'))
627 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
626 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
628 if group_name not in initial_repo_group_list:
627 if group_name not in initial_repo_group_list:
629 repo_group = RepoGroup.get_by_group_name(group_name)
628 repo_group = RepoGroup.get_by_group_name(group_name)
630 if (repo_group.children.all() or
629 if (repo_group.children.all() or
631 not RepoGroupModel().check_exist_filesystem(
630 not RepoGroupModel().check_exist_filesystem(
632 group_name=group_name, exc_on_failure=False)):
631 group_name=group_name, exc_on_failure=False)):
633 continue
632 continue
634
633
635 log.info(
634 log.info(
636 'Removing non-existing repository group found in db `%s`',
635 'Removing non-existing repository group found in db `%s`',
637 group_name)
636 group_name)
638 try:
637 try:
639 RepoGroupModel(sa).delete(group_name, fs_remove=False)
638 RepoGroupModel(sa).delete(group_name, fs_remove=False)
640 sa.commit()
639 sa.commit()
641 removed.append(group_name)
640 removed.append(group_name)
642 except Exception:
641 except Exception:
643 # don't hold further removals on error
642 # don't hold further removals on error
644 log.exception(
643 log.exception(
645 'Unable to remove repository group `%s`',
644 'Unable to remove repository group `%s`',
646 group_name)
645 group_name)
647 sa.rollback()
646 sa.rollback()
648 raise
647 raise
649
648
650 return added, removed
649 return added, removed
651
650
652
651
653 def load_rcextensions(root_path):
652 def load_rcextensions(root_path):
654 import rhodecode
653 import rhodecode
655 from rhodecode.config import conf
654 from rhodecode.config import conf
656
655
657 path = os.path.join(root_path)
656 path = os.path.join(root_path)
658 sys.path.append(path)
657 sys.path.append(path)
659
658
660 try:
659 try:
661 rcextensions = __import__('rcextensions')
660 rcextensions = __import__('rcextensions')
662 except ImportError:
661 except ImportError:
663 if os.path.isdir(os.path.join(path, 'rcextensions')):
662 if os.path.isdir(os.path.join(path, 'rcextensions')):
664 log.warning('Unable to load rcextensions from %s', path)
663 log.warning('Unable to load rcextensions from %s', path)
665 rcextensions = None
664 rcextensions = None
666
665
667 if rcextensions:
666 if rcextensions:
668 log.info('Loaded rcextensions from %s...', rcextensions)
667 log.info('Loaded rcextensions from %s...', rcextensions)
669 rhodecode.EXTENSIONS = rcextensions
668 rhodecode.EXTENSIONS = rcextensions
670
669
671 # Additional mappings that are not present in the pygments lexers
670 # Additional mappings that are not present in the pygments lexers
672 conf.LANGUAGES_EXTENSIONS_MAP.update(
671 conf.LANGUAGES_EXTENSIONS_MAP.update(
673 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
672 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
674
673
675
674
676 def get_custom_lexer(extension):
675 def get_custom_lexer(extension):
677 """
676 """
678 returns a custom lexer if it is defined in rcextensions module, or None
677 returns a custom lexer if it is defined in rcextensions module, or None
679 if there's no custom lexer defined
678 if there's no custom lexer defined
680 """
679 """
681 import rhodecode
680 import rhodecode
682 from pygments import lexers
681 from pygments import lexers
683
682
684 # custom override made by RhodeCode
683 # custom override made by RhodeCode
685 if extension in ['mako']:
684 if extension in ['mako']:
686 return lexers.get_lexer_by_name('html+mako')
685 return lexers.get_lexer_by_name('html+mako')
687
686
688 # check if we didn't define this extension as other lexer
687 # check if we didn't define this extension as other lexer
689 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
688 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
690 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
689 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
691 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
690 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
692 return lexers.get_lexer_by_name(_lexer_name)
691 return lexers.get_lexer_by_name(_lexer_name)
693
692
694
693
695 #==============================================================================
694 #==============================================================================
696 # TEST FUNCTIONS AND CREATORS
695 # TEST FUNCTIONS AND CREATORS
697 #==============================================================================
696 #==============================================================================
698 def create_test_index(repo_location, config):
697 def create_test_index(repo_location, config):
699 """
698 """
700 Makes default test index.
699 Makes default test index.
701 """
700 """
702 try:
701 try:
703 import rc_testdata
702 import rc_testdata
704 except ImportError:
703 except ImportError:
705 raise ImportError('Failed to import rc_testdata, '
704 raise ImportError('Failed to import rc_testdata, '
706 'please make sure this package is installed from requirements_test.txt')
705 'please make sure this package is installed from requirements_test.txt')
707 rc_testdata.extract_search_index(
706 rc_testdata.extract_search_index(
708 'vcs_search_index', os.path.dirname(config['search.location']))
707 'vcs_search_index', os.path.dirname(config['search.location']))
709
708
710
709
711 def create_test_directory(test_path):
710 def create_test_directory(test_path):
712 """
711 """
713 Create test directory if it doesn't exist.
712 Create test directory if it doesn't exist.
714 """
713 """
715 if not os.path.isdir(test_path):
714 if not os.path.isdir(test_path):
716 log.debug('Creating testdir %s', test_path)
715 log.debug('Creating testdir %s', test_path)
717 os.makedirs(test_path)
716 os.makedirs(test_path)
718
717
719
718
720 def create_test_database(test_path, config):
719 def create_test_database(test_path, config):
721 """
720 """
722 Makes a fresh database.
721 Makes a fresh database.
723 """
722 """
724 from rhodecode.lib.db_manage import DbManage
723 from rhodecode.lib.db_manage import DbManage
725 from rhodecode.lib.utils2 import get_encryption_key
724 from rhodecode.lib.utils2 import get_encryption_key
726
725
727 # PART ONE create db
726 # PART ONE create db
728 dbconf = config['sqlalchemy.db1.url']
727 dbconf = config['sqlalchemy.db1.url']
729 enc_key = get_encryption_key(config)
728 enc_key = get_encryption_key(config)
730
729
731 log.debug('making test db %s', dbconf)
730 log.debug('making test db %s', dbconf)
732
731
733 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
732 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
734 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
733 tests=True, cli_args={'force_ask': True}, enc_key=enc_key)
735 dbmanage.create_tables(override=True)
734 dbmanage.create_tables(override=True)
736 dbmanage.set_db_version()
735 dbmanage.set_db_version()
737 # for tests dynamically set new root paths based on generated content
736 # for tests dynamically set new root paths based on generated content
738 dbmanage.create_settings(dbmanage.config_prompt(test_path))
737 dbmanage.create_settings(dbmanage.config_prompt(test_path))
739 dbmanage.create_default_user()
738 dbmanage.create_default_user()
740 dbmanage.create_test_admin_and_users()
739 dbmanage.create_test_admin_and_users()
741 dbmanage.create_permissions()
740 dbmanage.create_permissions()
742 dbmanage.populate_default_permissions()
741 dbmanage.populate_default_permissions()
743 Session().commit()
742 Session().commit()
744
743
745
744
746 def create_test_repositories(test_path, config):
745 def create_test_repositories(test_path, config):
747 """
746 """
748 Creates test repositories in the temporary directory. Repositories are
747 Creates test repositories in the temporary directory. Repositories are
749 extracted from archives within the rc_testdata package.
748 extracted from archives within the rc_testdata package.
750 """
749 """
751 import rc_testdata
750 import rc_testdata
752 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
751 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
753
752
754 log.debug('making test vcs repositories')
753 log.debug('making test vcs repositories')
755
754
756 idx_path = config['search.location']
755 idx_path = config['search.location']
757 data_path = config['cache_dir']
756 data_path = config['cache_dir']
758
757
759 # clean index and data
758 # clean index and data
760 if idx_path and os.path.exists(idx_path):
759 if idx_path and os.path.exists(idx_path):
761 log.debug('remove %s', idx_path)
760 log.debug('remove %s', idx_path)
762 shutil.rmtree(idx_path)
761 shutil.rmtree(idx_path)
763
762
764 if data_path and os.path.exists(data_path):
763 if data_path and os.path.exists(data_path):
765 log.debug('remove %s', data_path)
764 log.debug('remove %s', data_path)
766 shutil.rmtree(data_path)
765 shutil.rmtree(data_path)
767
766
768 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
767 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
769 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
768 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
770
769
771 # Note: Subversion is in the process of being integrated with the system,
770 # Note: Subversion is in the process of being integrated with the system,
772 # until we have a properly packed version of the test svn repository, this
771 # until we have a properly packed version of the test svn repository, this
773 # tries to copy over the repo from a package "rc_testdata"
772 # tries to copy over the repo from a package "rc_testdata"
774 svn_repo_path = rc_testdata.get_svn_repo_archive()
773 svn_repo_path = rc_testdata.get_svn_repo_archive()
775 with tarfile.open(svn_repo_path) as tar:
774 with tarfile.open(svn_repo_path) as tar:
776 tar.extractall(jn(test_path, SVN_REPO))
775 tar.extractall(jn(test_path, SVN_REPO))
777
776
778
777
779 def password_changed(auth_user, session):
778 def password_changed(auth_user, session):
780 # Never report password change in case of default user or anonymous user.
779 # Never report password change in case of default user or anonymous user.
781 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
780 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
782 return False
781 return False
783
782
784 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
783 password_hash = md5(safe_bytes(auth_user.password)) if auth_user.password else None
785 rhodecode_user = session.get('rhodecode_user', {})
784 rhodecode_user = session.get('rhodecode_user', {})
786 session_password_hash = rhodecode_user.get('password', '')
785 session_password_hash = rhodecode_user.get('password', '')
787 return password_hash != session_password_hash
786 return password_hash != session_password_hash
788
787
789
788
790 def read_opensource_licenses():
789 def read_opensource_licenses():
791 global _license_cache
790 global _license_cache
792
791
793 if not _license_cache:
792 if not _license_cache:
794 licenses = pkg_resources.resource_string(
793 licenses = pkg_resources.resource_string(
795 'rhodecode', 'config/licenses.json')
794 'rhodecode', 'config/licenses.json')
796 _license_cache = json.loads(licenses)
795 _license_cache = json.loads(licenses)
797
796
798 return _license_cache
797 return _license_cache
799
798
800
799
801 def generate_platform_uuid():
800 def generate_platform_uuid():
802 """
801 """
803 Generates platform UUID based on it's name
802 Generates platform UUID based on it's name
804 """
803 """
805 import platform
804 import platform
806
805
807 try:
806 try:
808 uuid_list = [platform.platform()]
807 uuid_list = [platform.platform()]
809 return sha256_safe(':'.join(uuid_list))
808 return sha256_safe(':'.join(uuid_list))
810 except Exception as e:
809 except Exception as e:
811 log.error('Failed to generate host uuid: %s', e)
810 log.error('Failed to generate host uuid: %s', e)
812 return 'UNDEFINED'
811 return 'UNDEFINED'
813
812
814
813
815 def send_test_email(recipients, email_body='TEST EMAIL'):
814 def send_test_email(recipients, email_body='TEST EMAIL'):
816 """
815 """
817 Simple code for generating test emails.
816 Simple code for generating test emails.
818 Usage::
817 Usage::
819
818
820 from rhodecode.lib import utils
819 from rhodecode.lib import utils
821 utils.send_test_email()
820 utils.send_test_email()
822 """
821 """
823 from rhodecode.lib.celerylib import tasks, run_task
822 from rhodecode.lib.celerylib import tasks, run_task
824
823
825 email_body = email_body_plaintext = email_body
824 email_body = email_body_plaintext = email_body
826 subject = f'SUBJECT FROM: {socket.gethostname()}'
825 subject = f'SUBJECT FROM: {socket.gethostname()}'
827 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
826 tasks.send_email(recipients, subject, email_body_plaintext, email_body)
@@ -1,669 +1,668 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 this is forms validation classes
20 this is forms validation classes
21 http://formencode.org/module-formencode.validators.html
21 http://formencode.org/module-formencode.validators.html
22 for list off all availible validators
22 for list off all availible validators
23
23
24 we can create our own validators
24 we can create our own validators
25
25
26 The table below outlines the options which can be used in a schema in addition to the validators themselves
26 The table below outlines the options which can be used in a schema in addition to the validators themselves
27 pre_validators [] These validators will be applied before the schema
27 pre_validators [] These validators will be applied before the schema
28 chained_validators [] These validators will be applied after the schema
28 chained_validators [] These validators will be applied after the schema
29 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
29 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
30 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
30 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
31 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
31 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
32 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
32 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
33
33
34
34
35 <name> = formencode.validators.<name of validator>
35 <name> = formencode.validators.<name of validator>
36 <name> must equal form name
36 <name> must equal form name
37 list=[1,2,3,4,5]
37 list=[1,2,3,4,5]
38 for SELECT use formencode.All(OneOf(list), Int())
38 for SELECT use formencode.All(OneOf(list), Int())
39
39
40 """
40 """
41
41
42 import deform
42 import deform
43 import logging
43 import logging
44 import formencode
44 import formencode
45
45
46 from pkg_resources import resource_filename
46 from pkg_resources import resource_filename
47 from formencode import All, Pipe
47 from formencode import All, Pipe
48
48
49 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
50
50
51 from rhodecode import BACKENDS
51 from rhodecode import BACKENDS
52 from rhodecode.lib import helpers
52 from rhodecode.lib import helpers
53 from rhodecode.model import validators as v
53 from rhodecode.model import validators as v
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 deform_templates = resource_filename('deform', 'templates')
58 deform_templates = resource_filename('deform', 'templates')
59 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
59 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
60 search_path = (rhodecode_templates, deform_templates)
60 search_path = (rhodecode_templates, deform_templates)
61
61
62
62
63 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
63 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
64 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
64 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
65 def __call__(self, template_name, **kw):
65 def __call__(self, template_name, **kw):
66 kw['h'] = helpers
66 kw['h'] = helpers
67 kw['request'] = get_current_request()
67 kw['request'] = get_current_request()
68 return self.load(template_name)(**kw)
68 return self.load(template_name)(**kw)
69
69
70
70
71 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
71 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 deform.Form.set_default_renderer(form_renderer)
72 deform.Form.set_default_renderer(form_renderer)
73
73
74
74
75 def LoginForm(localizer):
75 def LoginForm(localizer):
76 _ = localizer
76 _ = localizer
77
77
78 class _LoginForm(formencode.Schema):
78 class _LoginForm(formencode.Schema):
79 allow_extra_fields = True
79 allow_extra_fields = True
80 filter_extra_fields = True
80 filter_extra_fields = True
81 username = v.UnicodeString(
81 username = v.UnicodeString(
82 strip=True,
82 strip=True,
83 min=1,
83 min=1,
84 not_empty=True,
84 not_empty=True,
85 messages={
85 messages={
86 'empty': _('Please enter a login'),
86 'empty': _('Please enter a login'),
87 'tooShort': _('Enter a value %(min)i characters long or more')
87 'tooShort': _('Enter a value %(min)i characters long or more')
88 }
88 }
89 )
89 )
90
90
91 password = v.UnicodeString(
91 password = v.UnicodeString(
92 strip=False,
92 strip=False,
93 min=3,
93 min=3,
94 max=72,
94 max=72,
95 not_empty=True,
95 not_empty=True,
96 messages={
96 messages={
97 'empty': _('Please enter a password'),
97 'empty': _('Please enter a password'),
98 'tooShort': _('Enter %(min)i characters or more')}
98 'tooShort': _('Enter %(min)i characters or more')}
99 )
99 )
100
100
101 remember = v.StringBoolean(if_missing=False)
101 remember = v.StringBoolean(if_missing=False)
102
102
103 chained_validators = [v.ValidAuth(localizer)]
103 chained_validators = [v.ValidAuth(localizer)]
104 return _LoginForm
104 return _LoginForm
105
105
106
106
107 def TOTPForm(localizer, user, allow_recovery_code_use=False):
107 def TOTPForm(localizer, user, allow_recovery_code_use=False):
108 _ = localizer
108 _ = localizer
109
109
110 class _TOTPForm(formencode.Schema):
110 class _TOTPForm(formencode.Schema):
111 allow_extra_fields = True
111 allow_extra_fields = True
112 filter_extra_fields = False
112 filter_extra_fields = False
113 totp = v.Regex(r'^(?:\d{6}|[A-Z0-9]{32})$')
113 totp = v.Regex(r'^(?:\d{6}|[A-Z0-9]{32})$')
114 secret_totp = v.String()
114 secret_totp = v.String()
115
115
116 def to_python(self, value, state=None):
116 def to_python(self, value, state=None):
117 validation_checks = [user.is_totp_valid]
117 validation_checks = [user.is_totp_valid]
118 if allow_recovery_code_use:
118 if allow_recovery_code_use:
119 validation_checks.append(user.is_2fa_recovery_code_valid)
119 validation_checks.append(user.is_2fa_recovery_code_valid)
120 form_data = super().to_python(value, state)
120 form_data = super().to_python(value, state)
121 received_code = form_data['totp']
121 received_code = form_data['totp']
122 secret = form_data.get('secret_totp')
122 secret = form_data.get('secret_totp')
123
123
124 if not any(map(lambda func: func(received_code, secret), validation_checks)):
124 if not any(map(lambda func: func(received_code, secret), validation_checks)):
125 error_msg = _('Code is invalid. Try again!')
125 error_msg = _('Code is invalid. Try again!')
126 raise formencode.Invalid(error_msg, v, state, error_dict={'totp': error_msg})
126 raise formencode.Invalid(error_msg, v, state, error_dict={'totp': error_msg})
127 return form_data
127 return form_data
128
128
129 return _TOTPForm
129 return _TOTPForm
130
130
131
131
132 def WhitelistedVcsClientsForm(localizer):
132 def WhitelistedVcsClientsForm(localizer):
133 _ = localizer
133 _ = localizer
134
134
135 class _WhitelistedVcsClientsForm(formencode.Schema):
135 class _WhitelistedVcsClientsForm(formencode.Schema):
136 regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$'
136 regexp = r'^(?:\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\*)\s*(?:,\s*[<>=~^!]*\s*\d{1,2}\.\d{1,2}(?:\.\d{1,2})?\s*|\s*\*\s*)*$'
137 allow_extra_fields = True
137 allow_extra_fields = True
138 filter_extra_fields = True
138 filter_extra_fields = True
139 git = v.Regex(regexp)
139 git = v.Regex(regexp)
140 hg = v.Regex(regexp)
140 hg = v.Regex(regexp)
141 svn = v.Regex(regexp)
141 svn = v.Regex(regexp)
142
142
143 return _WhitelistedVcsClientsForm
143 return _WhitelistedVcsClientsForm
144
144
145
145
146 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
146 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
147 old_data = old_data or {}
147 old_data = old_data or {}
148 available_languages = available_languages or []
148 available_languages = available_languages or []
149 _ = localizer
149 _ = localizer
150
150
151 class _UserForm(formencode.Schema):
151 class _UserForm(formencode.Schema):
152 allow_extra_fields = True
152 allow_extra_fields = True
153 filter_extra_fields = True
153 filter_extra_fields = True
154 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
154 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
155 v.ValidUsername(localizer, edit, old_data))
155 v.ValidUsername(localizer, edit, old_data))
156 if edit:
156 if edit:
157 new_password = All(
157 new_password = All(
158 v.ValidPassword(localizer),
158 v.ValidPassword(localizer),
159 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
159 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
160 )
160 )
161 password_confirmation = All(
161 password_confirmation = All(
162 v.ValidPassword(localizer),
162 v.ValidPassword(localizer),
163 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
163 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
164 )
164 )
165 admin = v.StringBoolean(if_missing=False)
165 admin = v.StringBoolean(if_missing=False)
166 else:
166 else:
167 password = All(
167 password = All(
168 v.ValidPassword(localizer),
168 v.ValidPassword(localizer),
169 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
169 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
170 )
170 )
171 password_confirmation = All(
171 password_confirmation = All(
172 v.ValidPassword(localizer),
172 v.ValidPassword(localizer),
173 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
173 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
174 )
174 )
175
175
176 password_change = v.StringBoolean(if_missing=False)
176 password_change = v.StringBoolean(if_missing=False)
177 create_repo_group = v.StringBoolean(if_missing=False)
177 create_repo_group = v.StringBoolean(if_missing=False)
178
178
179 active = v.StringBoolean(if_missing=False)
179 active = v.StringBoolean(if_missing=False)
180 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
180 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
181 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
181 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
182 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
182 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
183 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
183 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
184 if_missing='')
184 if_missing='')
185 extern_name = v.UnicodeString(strip=True)
185 extern_name = v.UnicodeString(strip=True)
186 extern_type = v.UnicodeString(strip=True)
186 extern_type = v.UnicodeString(strip=True)
187 language = v.OneOf(available_languages, hideList=False,
187 language = v.OneOf(available_languages, hideList=False,
188 testValueList=True, if_missing=None)
188 testValueList=True, if_missing=None)
189 chained_validators = [v.ValidPasswordsMatch(localizer)]
189 chained_validators = [v.ValidPasswordsMatch(localizer)]
190 return _UserForm
190 return _UserForm
191
191
192
192
193 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
193 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
194 old_data = old_data or {}
194 old_data = old_data or {}
195 _ = localizer
195 _ = localizer
196
196
197 class _UserGroupForm(formencode.Schema):
197 class _UserGroupForm(formencode.Schema):
198 allow_extra_fields = True
198 allow_extra_fields = True
199 filter_extra_fields = True
199 filter_extra_fields = True
200
200
201 users_group_name = All(
201 users_group_name = All(
202 v.UnicodeString(strip=True, min=1, not_empty=True),
202 v.UnicodeString(strip=True, min=1, not_empty=True),
203 v.ValidUserGroup(localizer, edit, old_data)
203 v.ValidUserGroup(localizer, edit, old_data)
204 )
204 )
205 user_group_description = v.UnicodeString(strip=True, min=1,
205 user_group_description = v.UnicodeString(strip=True, min=1,
206 not_empty=False)
206 not_empty=False)
207
207
208 users_group_active = v.StringBoolean(if_missing=False)
208 users_group_active = v.StringBoolean(if_missing=False)
209
209
210 if edit:
210 if edit:
211 # this is user group owner
211 # this is user group owner
212 user = All(
212 user = All(
213 v.UnicodeString(not_empty=True),
213 v.UnicodeString(not_empty=True),
214 v.ValidRepoUser(localizer, allow_disabled))
214 v.ValidRepoUser(localizer, allow_disabled))
215 return _UserGroupForm
215 return _UserGroupForm
216
216
217
217
218 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
218 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
219 can_create_in_root=False, allow_disabled=False):
219 can_create_in_root=False, allow_disabled=False):
220 _ = localizer
220 _ = localizer
221 old_data = old_data or {}
221 old_data = old_data or {}
222 available_groups = available_groups or []
222 available_groups = available_groups or []
223
223
224 class _RepoGroupForm(formencode.Schema):
224 class _RepoGroupForm(formencode.Schema):
225 allow_extra_fields = True
225 allow_extra_fields = True
226 filter_extra_fields = False
226 filter_extra_fields = False
227
227
228 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
228 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
229 v.SlugifyName(localizer),)
229 v.SlugifyName(localizer),)
230 group_description = v.UnicodeString(strip=True, min=1,
230 group_description = v.UnicodeString(strip=True, min=1,
231 not_empty=False)
231 not_empty=False)
232 group_copy_permissions = v.StringBoolean(if_missing=False)
232 group_copy_permissions = v.StringBoolean(if_missing=False)
233
233
234 group_parent_id = v.OneOf(available_groups, hideList=False,
234 group_parent_id = v.OneOf(available_groups, hideList=False,
235 testValueList=True, not_empty=True)
235 testValueList=True, not_empty=True)
236 enable_locking = v.StringBoolean(if_missing=False)
236 enable_locking = v.StringBoolean(if_missing=False)
237 chained_validators = [
237 chained_validators = [
238 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
238 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
239
239
240 if edit:
240 if edit:
241 # this is repo group owner
241 # this is repo group owner
242 user = All(
242 user = All(
243 v.UnicodeString(not_empty=True),
243 v.UnicodeString(not_empty=True),
244 v.ValidRepoUser(localizer, allow_disabled))
244 v.ValidRepoUser(localizer, allow_disabled))
245 return _RepoGroupForm
245 return _RepoGroupForm
246
246
247
247
248 def RegisterForm(localizer, edit=False, old_data=None):
248 def RegisterForm(localizer, edit=False, old_data=None):
249 _ = localizer
249 _ = localizer
250 old_data = old_data or {}
250 old_data = old_data or {}
251
251
252 class _RegisterForm(formencode.Schema):
252 class _RegisterForm(formencode.Schema):
253 allow_extra_fields = True
253 allow_extra_fields = True
254 filter_extra_fields = True
254 filter_extra_fields = True
255 username = All(
255 username = All(
256 v.ValidUsername(localizer, edit, old_data),
256 v.ValidUsername(localizer, edit, old_data),
257 v.UnicodeString(strip=True, min=1, not_empty=True)
257 v.UnicodeString(strip=True, min=1, not_empty=True)
258 )
258 )
259 password = All(
259 password = All(
260 v.ValidPassword(localizer),
260 v.ValidPassword(localizer),
261 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
261 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
262 )
262 )
263 password_confirmation = All(
263 password_confirmation = All(
264 v.ValidPassword(localizer),
264 v.ValidPassword(localizer),
265 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
265 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
266 )
266 )
267 active = v.StringBoolean(if_missing=False)
267 active = v.StringBoolean(if_missing=False)
268 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
268 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
269 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
269 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
270 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
270 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
271
271
272 chained_validators = [v.ValidPasswordsMatch(localizer)]
272 chained_validators = [v.ValidPasswordsMatch(localizer)]
273 return _RegisterForm
273 return _RegisterForm
274
274
275
275
276 def PasswordResetForm(localizer):
276 def PasswordResetForm(localizer):
277 _ = localizer
277 _ = localizer
278
278
279 class _PasswordResetForm(formencode.Schema):
279 class _PasswordResetForm(formencode.Schema):
280 allow_extra_fields = True
280 allow_extra_fields = True
281 filter_extra_fields = True
281 filter_extra_fields = True
282 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
282 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
283 return _PasswordResetForm
283 return _PasswordResetForm
284
284
285
285
286 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
286 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
287 _ = localizer
287 _ = localizer
288 old_data = old_data or {}
288 old_data = old_data or {}
289 repo_groups = repo_groups or []
289 repo_groups = repo_groups or []
290 supported_backends = BACKENDS.keys()
290 supported_backends = BACKENDS.keys()
291
291
292 class _RepoForm(formencode.Schema):
292 class _RepoForm(formencode.Schema):
293 allow_extra_fields = True
293 allow_extra_fields = True
294 filter_extra_fields = False
294 filter_extra_fields = False
295 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
295 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
296 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
296 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
297 repo_group = All(v.CanWriteGroup(localizer, old_data),
297 repo_group = All(v.CanWriteGroup(localizer, old_data),
298 v.OneOf(repo_groups, hideList=True))
298 v.OneOf(repo_groups, hideList=True))
299 repo_type = v.OneOf(supported_backends, required=False,
299 repo_type = v.OneOf(supported_backends, required=False,
300 if_missing=old_data.get('repo_type'))
300 if_missing=old_data.get('repo_type'))
301 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
301 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
302 repo_private = v.StringBoolean(if_missing=False)
302 repo_private = v.StringBoolean(if_missing=False)
303 repo_copy_permissions = v.StringBoolean(if_missing=False)
303 repo_copy_permissions = v.StringBoolean(if_missing=False)
304 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
304 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
305
305
306 repo_enable_statistics = v.StringBoolean(if_missing=False)
306 repo_enable_statistics = v.StringBoolean(if_missing=False)
307 repo_enable_downloads = v.StringBoolean(if_missing=False)
307 repo_enable_downloads = v.StringBoolean(if_missing=False)
308 repo_enable_locking = v.StringBoolean(if_missing=False)
308 repo_enable_locking = v.StringBoolean(if_missing=False)
309
309
310 if edit:
310 if edit:
311 # this is repo owner
311 # this is repo owner
312 user = All(
312 user = All(
313 v.UnicodeString(not_empty=True),
313 v.UnicodeString(not_empty=True),
314 v.ValidRepoUser(localizer, allow_disabled))
314 v.ValidRepoUser(localizer, allow_disabled))
315 clone_uri_change = v.UnicodeString(
315 clone_uri_change = v.UnicodeString(
316 not_empty=False, if_missing=v.Missing)
316 not_empty=False, if_missing=v.Missing)
317
317
318 chained_validators = [v.ValidCloneUri(localizer),
318 chained_validators = [v.ValidCloneUri(localizer),
319 v.ValidRepoName(localizer, edit, old_data)]
319 v.ValidRepoName(localizer, edit, old_data)]
320 return _RepoForm
320 return _RepoForm
321
321
322
322
323 def RepoPermsForm(localizer):
323 def RepoPermsForm(localizer):
324 _ = localizer
324 _ = localizer
325
325
326 class _RepoPermsForm(formencode.Schema):
326 class _RepoPermsForm(formencode.Schema):
327 allow_extra_fields = True
327 allow_extra_fields = True
328 filter_extra_fields = False
328 filter_extra_fields = False
329 chained_validators = [v.ValidPerms(localizer, type_='repo')]
329 chained_validators = [v.ValidPerms(localizer, type_='repo')]
330 return _RepoPermsForm
330 return _RepoPermsForm
331
331
332
332
333 def RepoGroupPermsForm(localizer, valid_recursive_choices):
333 def RepoGroupPermsForm(localizer, valid_recursive_choices):
334 _ = localizer
334 _ = localizer
335
335
336 class _RepoGroupPermsForm(formencode.Schema):
336 class _RepoGroupPermsForm(formencode.Schema):
337 allow_extra_fields = True
337 allow_extra_fields = True
338 filter_extra_fields = False
338 filter_extra_fields = False
339 recursive = v.OneOf(valid_recursive_choices)
339 recursive = v.OneOf(valid_recursive_choices)
340 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
340 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
341 return _RepoGroupPermsForm
341 return _RepoGroupPermsForm
342
342
343
343
344 def UserGroupPermsForm(localizer):
344 def UserGroupPermsForm(localizer):
345 _ = localizer
345 _ = localizer
346
346
347 class _UserPermsForm(formencode.Schema):
347 class _UserPermsForm(formencode.Schema):
348 allow_extra_fields = True
348 allow_extra_fields = True
349 filter_extra_fields = False
349 filter_extra_fields = False
350 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
350 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
351 return _UserPermsForm
351 return _UserPermsForm
352
352
353
353
354 def RepoFieldForm(localizer):
354 def RepoFieldForm(localizer):
355 _ = localizer
355 _ = localizer
356
356
357 class _RepoFieldForm(formencode.Schema):
357 class _RepoFieldForm(formencode.Schema):
358 filter_extra_fields = True
358 filter_extra_fields = True
359 allow_extra_fields = True
359 allow_extra_fields = True
360
360
361 new_field_key = All(v.FieldKey(localizer),
361 new_field_key = All(v.FieldKey(localizer),
362 v.UnicodeString(strip=True, min=3, not_empty=True))
362 v.UnicodeString(strip=True, min=3, not_empty=True))
363 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
363 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
364 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
364 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
365 if_missing='str')
365 if_missing='str')
366 new_field_label = v.UnicodeString(not_empty=False)
366 new_field_label = v.UnicodeString(not_empty=False)
367 new_field_desc = v.UnicodeString(not_empty=False)
367 new_field_desc = v.UnicodeString(not_empty=False)
368 return _RepoFieldForm
368 return _RepoFieldForm
369
369
370
370
371 def RepoForkForm(localizer, edit=False, old_data=None,
371 def RepoForkForm(localizer, edit=False, old_data=None,
372 supported_backends=BACKENDS.keys(), repo_groups=None):
372 supported_backends=BACKENDS.keys(), repo_groups=None):
373 _ = localizer
373 _ = localizer
374 old_data = old_data or {}
374 old_data = old_data or {}
375 repo_groups = repo_groups or []
375 repo_groups = repo_groups or []
376
376
377 class _RepoForkForm(formencode.Schema):
377 class _RepoForkForm(formencode.Schema):
378 allow_extra_fields = True
378 allow_extra_fields = True
379 filter_extra_fields = False
379 filter_extra_fields = False
380 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
380 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
381 v.SlugifyName(localizer))
381 v.SlugifyName(localizer))
382 repo_group = All(v.CanWriteGroup(localizer, ),
382 repo_group = All(v.CanWriteGroup(localizer, ),
383 v.OneOf(repo_groups, hideList=True))
383 v.OneOf(repo_groups, hideList=True))
384 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
384 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
385 description = v.UnicodeString(strip=True, min=1, not_empty=True)
385 description = v.UnicodeString(strip=True, min=1, not_empty=True)
386 private = v.StringBoolean(if_missing=False)
386 private = v.StringBoolean(if_missing=False)
387 copy_permissions = v.StringBoolean(if_missing=False)
387 copy_permissions = v.StringBoolean(if_missing=False)
388 fork_parent_id = v.UnicodeString()
388 fork_parent_id = v.UnicodeString()
389 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
389 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
390 return _RepoForkForm
390 return _RepoForkForm
391
391
392
392
393 def ApplicationSettingsForm(localizer):
393 def ApplicationSettingsForm(localizer):
394 _ = localizer
394 _ = localizer
395
395
396 class _ApplicationSettingsForm(formencode.Schema):
396 class _ApplicationSettingsForm(formencode.Schema):
397 allow_extra_fields = True
397 allow_extra_fields = True
398 filter_extra_fields = False
398 filter_extra_fields = False
399 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
399 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
400 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
400 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
401 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
401 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
402 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
402 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
403 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
403 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
404 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
404 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
405 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
405 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
406 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
406 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
407 return _ApplicationSettingsForm
407 return _ApplicationSettingsForm
408
408
409
409
410 def ApplicationVisualisationForm(localizer):
410 def ApplicationVisualisationForm(localizer):
411 from rhodecode.model.db import Repository
411 from rhodecode.model.db import Repository
412 _ = localizer
412 _ = localizer
413
413
414 class _ApplicationVisualisationForm(formencode.Schema):
414 class _ApplicationVisualisationForm(formencode.Schema):
415 allow_extra_fields = True
415 allow_extra_fields = True
416 filter_extra_fields = False
416 filter_extra_fields = False
417 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
417 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
418 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
418 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
419 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
419 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
420
420
421 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
421 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
422 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
422 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
423 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
423 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
424 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
424 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
425 rhodecode_show_version = v.StringBoolean(if_missing=False)
425 rhodecode_show_version = v.StringBoolean(if_missing=False)
426 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
426 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
427 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
427 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
428 rhodecode_gravatar_url = v.UnicodeString(min=3)
428 rhodecode_gravatar_url = v.UnicodeString(min=3)
429 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
429 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
430 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
430 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
431 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
431 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
432 rhodecode_support_url = v.UnicodeString()
432 rhodecode_support_url = v.UnicodeString()
433 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
433 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
434 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
434 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
435 return _ApplicationVisualisationForm
435 return _ApplicationVisualisationForm
436
436
437
437
438 class _BaseVcsSettingsForm(formencode.Schema):
438 class _BaseVcsSettingsForm(formencode.Schema):
439
439
440 allow_extra_fields = True
440 allow_extra_fields = True
441 filter_extra_fields = False
441 filter_extra_fields = False
442 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
442 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
443 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
443 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
444 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
444 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
445
445
446 # PR/Code-review
446 # PR/Code-review
447 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
447 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
448 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
448 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
449
449
450 # hg
450 # hg
451 extensions_largefiles = v.StringBoolean(if_missing=False)
451 extensions_largefiles = v.StringBoolean(if_missing=False)
452 extensions_evolve = v.StringBoolean(if_missing=False)
452 extensions_evolve = v.StringBoolean(if_missing=False)
453 phases_publish = v.StringBoolean(if_missing=False)
453 phases_publish = v.StringBoolean(if_missing=False)
454
454
455 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
455 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
456 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
456 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
457
457
458 # git
458 # git
459 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
459 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
460 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
460 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
461 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
461 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
462
462
463 # cache
463 # cache
464 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
464 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
465
465
466
466
467 def ApplicationUiSettingsForm(localizer):
467 def ApplicationUiSettingsForm(localizer):
468 _ = localizer
468 _ = localizer
469
469
470 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
470 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
471 web_push_ssl = v.StringBoolean(if_missing=False)
472 largefiles_usercache = All(
471 largefiles_usercache = All(
473 v.ValidPath(localizer),
472 v.ValidPath(localizer),
474 v.UnicodeString(strip=True, min=2, not_empty=True))
473 v.UnicodeString(strip=True, min=2, not_empty=True))
475 vcs_git_lfs_store_location = All(
474 vcs_git_lfs_store_location = All(
476 v.ValidPath(localizer),
475 v.ValidPath(localizer),
477 v.UnicodeString(strip=True, min=2, not_empty=True))
476 v.UnicodeString(strip=True, min=2, not_empty=True))
478 extensions_hggit = v.StringBoolean(if_missing=False)
477 extensions_hggit = v.StringBoolean(if_missing=False)
479 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
478 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
480 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
479 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
481 return _ApplicationUiSettingsForm
480 return _ApplicationUiSettingsForm
482
481
483
482
484 def RepoVcsSettingsForm(localizer, repo_name):
483 def RepoVcsSettingsForm(localizer, repo_name):
485 _ = localizer
484 _ = localizer
486
485
487 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
486 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
488 inherit_global_settings = v.StringBoolean(if_missing=False)
487 inherit_global_settings = v.StringBoolean(if_missing=False)
489 new_svn_branch = v.ValidSvnPattern(localizer,
488 new_svn_branch = v.ValidSvnPattern(localizer,
490 section='vcs_svn_branch', repo_name=repo_name)
489 section='vcs_svn_branch', repo_name=repo_name)
491 new_svn_tag = v.ValidSvnPattern(localizer,
490 new_svn_tag = v.ValidSvnPattern(localizer,
492 section='vcs_svn_tag', repo_name=repo_name)
491 section='vcs_svn_tag', repo_name=repo_name)
493 return _RepoVcsSettingsForm
492 return _RepoVcsSettingsForm
494
493
495
494
496 def LabsSettingsForm(localizer):
495 def LabsSettingsForm(localizer):
497 _ = localizer
496 _ = localizer
498
497
499 class _LabSettingsForm(formencode.Schema):
498 class _LabSettingsForm(formencode.Schema):
500 allow_extra_fields = True
499 allow_extra_fields = True
501 filter_extra_fields = False
500 filter_extra_fields = False
502 return _LabSettingsForm
501 return _LabSettingsForm
503
502
504
503
505 def ApplicationPermissionsForm(
504 def ApplicationPermissionsForm(
506 localizer, register_choices, password_reset_choices,
505 localizer, register_choices, password_reset_choices,
507 extern_activate_choices):
506 extern_activate_choices):
508 _ = localizer
507 _ = localizer
509
508
510 class _DefaultPermissionsForm(formencode.Schema):
509 class _DefaultPermissionsForm(formencode.Schema):
511 allow_extra_fields = True
510 allow_extra_fields = True
512 filter_extra_fields = True
511 filter_extra_fields = True
513
512
514 anonymous = v.StringBoolean(if_missing=False)
513 anonymous = v.StringBoolean(if_missing=False)
515 default_register = v.OneOf(register_choices)
514 default_register = v.OneOf(register_choices)
516 default_register_message = v.UnicodeString()
515 default_register_message = v.UnicodeString()
517 default_password_reset = v.OneOf(password_reset_choices)
516 default_password_reset = v.OneOf(password_reset_choices)
518 default_extern_activate = v.OneOf(extern_activate_choices)
517 default_extern_activate = v.OneOf(extern_activate_choices)
519 return _DefaultPermissionsForm
518 return _DefaultPermissionsForm
520
519
521
520
522 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
521 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
523 user_group_perms_choices):
522 user_group_perms_choices):
524 _ = localizer
523 _ = localizer
525
524
526 class _ObjectPermissionsForm(formencode.Schema):
525 class _ObjectPermissionsForm(formencode.Schema):
527 allow_extra_fields = True
526 allow_extra_fields = True
528 filter_extra_fields = True
527 filter_extra_fields = True
529 overwrite_default_repo = v.StringBoolean(if_missing=False)
528 overwrite_default_repo = v.StringBoolean(if_missing=False)
530 overwrite_default_group = v.StringBoolean(if_missing=False)
529 overwrite_default_group = v.StringBoolean(if_missing=False)
531 overwrite_default_user_group = v.StringBoolean(if_missing=False)
530 overwrite_default_user_group = v.StringBoolean(if_missing=False)
532
531
533 default_repo_perm = v.OneOf(repo_perms_choices)
532 default_repo_perm = v.OneOf(repo_perms_choices)
534 default_group_perm = v.OneOf(group_perms_choices)
533 default_group_perm = v.OneOf(group_perms_choices)
535 default_user_group_perm = v.OneOf(user_group_perms_choices)
534 default_user_group_perm = v.OneOf(user_group_perms_choices)
536
535
537 return _ObjectPermissionsForm
536 return _ObjectPermissionsForm
538
537
539
538
540 def BranchPermissionsForm(localizer, branch_perms_choices):
539 def BranchPermissionsForm(localizer, branch_perms_choices):
541 _ = localizer
540 _ = localizer
542
541
543 class _BranchPermissionsForm(formencode.Schema):
542 class _BranchPermissionsForm(formencode.Schema):
544 allow_extra_fields = True
543 allow_extra_fields = True
545 filter_extra_fields = True
544 filter_extra_fields = True
546 overwrite_default_branch = v.StringBoolean(if_missing=False)
545 overwrite_default_branch = v.StringBoolean(if_missing=False)
547 default_branch_perm = v.OneOf(branch_perms_choices)
546 default_branch_perm = v.OneOf(branch_perms_choices)
548
547
549 return _BranchPermissionsForm
548 return _BranchPermissionsForm
550
549
551
550
552 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
551 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
553 repo_group_create_choices, user_group_create_choices,
552 repo_group_create_choices, user_group_create_choices,
554 fork_choices, inherit_default_permissions_choices):
553 fork_choices, inherit_default_permissions_choices):
555 _ = localizer
554 _ = localizer
556
555
557 class _DefaultPermissionsForm(formencode.Schema):
556 class _DefaultPermissionsForm(formencode.Schema):
558 allow_extra_fields = True
557 allow_extra_fields = True
559 filter_extra_fields = True
558 filter_extra_fields = True
560
559
561 anonymous = v.StringBoolean(if_missing=False)
560 anonymous = v.StringBoolean(if_missing=False)
562
561
563 default_repo_create = v.OneOf(create_choices)
562 default_repo_create = v.OneOf(create_choices)
564 default_repo_create_on_write = v.OneOf(create_on_write_choices)
563 default_repo_create_on_write = v.OneOf(create_on_write_choices)
565 default_user_group_create = v.OneOf(user_group_create_choices)
564 default_user_group_create = v.OneOf(user_group_create_choices)
566 default_repo_group_create = v.OneOf(repo_group_create_choices)
565 default_repo_group_create = v.OneOf(repo_group_create_choices)
567 default_fork_create = v.OneOf(fork_choices)
566 default_fork_create = v.OneOf(fork_choices)
568 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
567 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
569 return _DefaultPermissionsForm
568 return _DefaultPermissionsForm
570
569
571
570
572 def UserIndividualPermissionsForm(localizer):
571 def UserIndividualPermissionsForm(localizer):
573 _ = localizer
572 _ = localizer
574
573
575 class _DefaultPermissionsForm(formencode.Schema):
574 class _DefaultPermissionsForm(formencode.Schema):
576 allow_extra_fields = True
575 allow_extra_fields = True
577 filter_extra_fields = True
576 filter_extra_fields = True
578
577
579 inherit_default_permissions = v.StringBoolean(if_missing=False)
578 inherit_default_permissions = v.StringBoolean(if_missing=False)
580 return _DefaultPermissionsForm
579 return _DefaultPermissionsForm
581
580
582
581
583 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
582 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
584 _ = localizer
583 _ = localizer
585 old_data = old_data or {}
584 old_data = old_data or {}
586
585
587 class _DefaultsForm(formencode.Schema):
586 class _DefaultsForm(formencode.Schema):
588 allow_extra_fields = True
587 allow_extra_fields = True
589 filter_extra_fields = True
588 filter_extra_fields = True
590 default_repo_type = v.OneOf(supported_backends)
589 default_repo_type = v.OneOf(supported_backends)
591 default_repo_private = v.StringBoolean(if_missing=False)
590 default_repo_private = v.StringBoolean(if_missing=False)
592 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
591 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
593 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
592 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
594 default_repo_enable_locking = v.StringBoolean(if_missing=False)
593 default_repo_enable_locking = v.StringBoolean(if_missing=False)
595 return _DefaultsForm
594 return _DefaultsForm
596
595
597
596
598 def AuthSettingsForm(localizer):
597 def AuthSettingsForm(localizer):
599 _ = localizer
598 _ = localizer
600
599
601 class _AuthSettingsForm(formencode.Schema):
600 class _AuthSettingsForm(formencode.Schema):
602 allow_extra_fields = True
601 allow_extra_fields = True
603 filter_extra_fields = True
602 filter_extra_fields = True
604 auth_plugins = All(v.ValidAuthPlugins(localizer),
603 auth_plugins = All(v.ValidAuthPlugins(localizer),
605 v.UniqueListFromString(localizer)(not_empty=True))
604 v.UniqueListFromString(localizer)(not_empty=True))
606 return _AuthSettingsForm
605 return _AuthSettingsForm
607
606
608
607
609 def UserExtraEmailForm(localizer):
608 def UserExtraEmailForm(localizer):
610 _ = localizer
609 _ = localizer
611
610
612 class _UserExtraEmailForm(formencode.Schema):
611 class _UserExtraEmailForm(formencode.Schema):
613 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
612 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
614 return _UserExtraEmailForm
613 return _UserExtraEmailForm
615
614
616
615
617 def UserExtraIpForm(localizer):
616 def UserExtraIpForm(localizer):
618 _ = localizer
617 _ = localizer
619
618
620 class _UserExtraIpForm(formencode.Schema):
619 class _UserExtraIpForm(formencode.Schema):
621 ip = v.ValidIp(localizer)(not_empty=True)
620 ip = v.ValidIp(localizer)(not_empty=True)
622 return _UserExtraIpForm
621 return _UserExtraIpForm
623
622
624
623
625 def PullRequestForm(localizer, repo_id):
624 def PullRequestForm(localizer, repo_id):
626 _ = localizer
625 _ = localizer
627
626
628 class ReviewerForm(formencode.Schema):
627 class ReviewerForm(formencode.Schema):
629 user_id = v.Int(not_empty=True)
628 user_id = v.Int(not_empty=True)
630 reasons = All()
629 reasons = All()
631 rules = All(v.UniqueList(localizer, convert=int)())
630 rules = All(v.UniqueList(localizer, convert=int)())
632 mandatory = v.StringBoolean()
631 mandatory = v.StringBoolean()
633 role = v.String(if_missing='reviewer')
632 role = v.String(if_missing='reviewer')
634
633
635 class ObserverForm(formencode.Schema):
634 class ObserverForm(formencode.Schema):
636 user_id = v.Int(not_empty=True)
635 user_id = v.Int(not_empty=True)
637 reasons = All()
636 reasons = All()
638 rules = All(v.UniqueList(localizer, convert=int)())
637 rules = All(v.UniqueList(localizer, convert=int)())
639 mandatory = v.StringBoolean()
638 mandatory = v.StringBoolean()
640 role = v.String(if_missing='observer')
639 role = v.String(if_missing='observer')
641
640
642 class _PullRequestForm(formencode.Schema):
641 class _PullRequestForm(formencode.Schema):
643 allow_extra_fields = True
642 allow_extra_fields = True
644 filter_extra_fields = True
643 filter_extra_fields = True
645
644
646 common_ancestor = v.UnicodeString(strip=True, required=True)
645 common_ancestor = v.UnicodeString(strip=True, required=True)
647 source_repo = v.UnicodeString(strip=True, required=True)
646 source_repo = v.UnicodeString(strip=True, required=True)
648 source_ref = v.UnicodeString(strip=True, required=True)
647 source_ref = v.UnicodeString(strip=True, required=True)
649 target_repo = v.UnicodeString(strip=True, required=True)
648 target_repo = v.UnicodeString(strip=True, required=True)
650 target_ref = v.UnicodeString(strip=True, required=True)
649 target_ref = v.UnicodeString(strip=True, required=True)
651 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
650 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
652 v.UniqueList(localizer)(not_empty=True))
651 v.UniqueList(localizer)(not_empty=True))
653 review_members = formencode.ForEach(ReviewerForm())
652 review_members = formencode.ForEach(ReviewerForm())
654 observer_members = formencode.ForEach(ObserverForm())
653 observer_members = formencode.ForEach(ObserverForm())
655 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
654 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
656 pullrequest_desc = v.UnicodeString(strip=True, required=False)
655 pullrequest_desc = v.UnicodeString(strip=True, required=False)
657 description_renderer = v.UnicodeString(strip=True, required=False)
656 description_renderer = v.UnicodeString(strip=True, required=False)
658
657
659 return _PullRequestForm
658 return _PullRequestForm
660
659
661
660
662 def IssueTrackerPatternsForm(localizer):
661 def IssueTrackerPatternsForm(localizer):
663 _ = localizer
662 _ = localizer
664
663
665 class _IssueTrackerPatternsForm(formencode.Schema):
664 class _IssueTrackerPatternsForm(formencode.Schema):
666 allow_extra_fields = True
665 allow_extra_fields = True
667 filter_extra_fields = False
666 filter_extra_fields = False
668 chained_validators = [v.ValidPattern(localizer)]
667 chained_validators = [v.ValidPattern(localizer)]
669 return _IssueTrackerPatternsForm
668 return _IssueTrackerPatternsForm
@@ -1,902 +1,897 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import re
20 import re
21 import logging
21 import logging
22 import time
22 import time
23 import functools
23 import functools
24 from collections import namedtuple
24 from collections import namedtuple
25
25
26 from pyramid.threadlocal import get_current_request
26 from pyramid.threadlocal import get_current_request
27
27
28 from rhodecode.lib import rc_cache
28 from rhodecode.lib import rc_cache
29 from rhodecode.lib.hash_utils import sha1_safe
29 from rhodecode.lib.hash_utils import sha1_safe
30 from rhodecode.lib.html_filters import sanitize_html
30 from rhodecode.lib.html_filters import sanitize_html
31 from rhodecode.lib.utils2 import (
31 from rhodecode.lib.utils2 import (
32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
32 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
33 from rhodecode.lib.vcs.backends import base
33 from rhodecode.lib.vcs.backends import base
34 from rhodecode.lib.statsd_client import StatsdClient
34 from rhodecode.lib.statsd_client import StatsdClient
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import (
36 from rhodecode.model.db import (
37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
37 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 UiSetting = namedtuple(
44 UiSetting = namedtuple(
45 'UiSetting', ['section', 'key', 'value', 'active'])
45 'UiSetting', ['section', 'key', 'value', 'active'])
46
46
47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
47 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
48
48
49
49
50 class SettingNotFound(Exception):
50 class SettingNotFound(Exception):
51 def __init__(self, setting_id):
51 def __init__(self, setting_id):
52 msg = f'Setting `{setting_id}` is not found'
52 msg = f'Setting `{setting_id}` is not found'
53 super().__init__(msg)
53 super().__init__(msg)
54
54
55
55
56 class SettingsModel(BaseModel):
56 class SettingsModel(BaseModel):
57 BUILTIN_HOOKS = (
57 BUILTIN_HOOKS = (
58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
58 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
59 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
60 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
61 RhodeCodeUi.HOOK_PUSH_KEY,)
61 RhodeCodeUi.HOOK_PUSH_KEY,)
62 HOOKS_SECTION = 'hooks'
62 HOOKS_SECTION = 'hooks'
63
63
64 def __init__(self, sa=None, repo=None):
64 def __init__(self, sa=None, repo=None):
65 self.repo = repo
65 self.repo = repo
66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
66 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
67 self.SettingsDbModel = (
67 self.SettingsDbModel = (
68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
68 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
69 super().__init__(sa)
69 super().__init__(sa)
70
70
71 def get_keyname(self, key_name, prefix='rhodecode_'):
71 def get_keyname(self, key_name, prefix='rhodecode_'):
72 return f'{prefix}{key_name}'
72 return f'{prefix}{key_name}'
73
73
74 def get_ui_by_key(self, key):
74 def get_ui_by_key(self, key):
75 q = self.UiDbModel.query()
75 q = self.UiDbModel.query()
76 q = q.filter(self.UiDbModel.ui_key == key)
76 q = q.filter(self.UiDbModel.ui_key == key)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 return q.scalar()
78 return q.scalar()
79
79
80 def get_ui_by_section(self, section):
80 def get_ui_by_section(self, section):
81 q = self.UiDbModel.query()
81 q = self.UiDbModel.query()
82 q = q.filter(self.UiDbModel.ui_section == section)
82 q = q.filter(self.UiDbModel.ui_section == section)
83 q = self._filter_by_repo(RepoRhodeCodeUi, q)
83 q = self._filter_by_repo(RepoRhodeCodeUi, q)
84 return q.all()
84 return q.all()
85
85
86 def get_ui_by_section_and_key(self, section, key):
86 def get_ui_by_section_and_key(self, section, key):
87 q = self.UiDbModel.query()
87 q = self.UiDbModel.query()
88 q = q.filter(self.UiDbModel.ui_section == section)
88 q = q.filter(self.UiDbModel.ui_section == section)
89 q = q.filter(self.UiDbModel.ui_key == key)
89 q = q.filter(self.UiDbModel.ui_key == key)
90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90 q = self._filter_by_repo(RepoRhodeCodeUi, q)
91 return q.scalar()
91 return q.scalar()
92
92
93 def get_ui(self, section=None, key=None):
93 def get_ui(self, section=None, key=None):
94 q = self.UiDbModel.query()
94 q = self.UiDbModel.query()
95 q = self._filter_by_repo(RepoRhodeCodeUi, q)
95 q = self._filter_by_repo(RepoRhodeCodeUi, q)
96
96
97 if section:
97 if section:
98 q = q.filter(self.UiDbModel.ui_section == section)
98 q = q.filter(self.UiDbModel.ui_section == section)
99 if key:
99 if key:
100 q = q.filter(self.UiDbModel.ui_key == key)
100 q = q.filter(self.UiDbModel.ui_key == key)
101
101
102 # TODO: mikhail: add caching
102 # TODO: mikhail: add caching
103 result = [
103 result = [
104 UiSetting(
104 UiSetting(
105 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
105 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
106 value=safe_str(r.ui_value), active=r.ui_active
106 value=safe_str(r.ui_value), active=r.ui_active
107 )
107 )
108 for r in q.all()
108 for r in q.all()
109 ]
109 ]
110 return result
110 return result
111
111
112 def get_builtin_hooks(self):
112 def get_builtin_hooks(self):
113 q = self.UiDbModel.query()
113 q = self.UiDbModel.query()
114 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
115 return self._get_hooks(q)
115 return self._get_hooks(q)
116
116
117 def get_custom_hooks(self):
117 def get_custom_hooks(self):
118 q = self.UiDbModel.query()
118 q = self.UiDbModel.query()
119 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
119 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
120 return self._get_hooks(q)
120 return self._get_hooks(q)
121
121
122 def create_ui_section_value(self, section, val, key=None, active=True):
122 def create_ui_section_value(self, section, val, key=None, active=True):
123 new_ui = self.UiDbModel()
123 new_ui = self.UiDbModel()
124 new_ui.ui_section = section
124 new_ui.ui_section = section
125 new_ui.ui_value = val
125 new_ui.ui_value = val
126 new_ui.ui_active = active
126 new_ui.ui_active = active
127
127
128 repository_id = ''
128 repository_id = ''
129 if self.repo:
129 if self.repo:
130 repo = self._get_repo(self.repo)
130 repo = self._get_repo(self.repo)
131 repository_id = repo.repo_id
131 repository_id = repo.repo_id
132 new_ui.repository_id = repository_id
132 new_ui.repository_id = repository_id
133
133
134 if not key:
134 if not key:
135 # keys are unique so they need appended info
135 # keys are unique so they need appended info
136 if self.repo:
136 if self.repo:
137 key = sha1_safe(f'{section}{val}{repository_id}')
137 key = sha1_safe(f'{section}{val}{repository_id}')
138 else:
138 else:
139 key = sha1_safe(f'{section}{val}')
139 key = sha1_safe(f'{section}{val}')
140
140
141 new_ui.ui_key = key
141 new_ui.ui_key = key
142
142
143 Session().add(new_ui)
143 Session().add(new_ui)
144 return new_ui
144 return new_ui
145
145
146 def create_or_update_hook(self, key, value):
146 def create_or_update_hook(self, key, value):
147 ui = (
147 ui = (
148 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
148 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
149 self.UiDbModel())
149 self.UiDbModel())
150 ui.ui_section = self.HOOKS_SECTION
150 ui.ui_section = self.HOOKS_SECTION
151 ui.ui_active = True
151 ui.ui_active = True
152 ui.ui_key = key
152 ui.ui_key = key
153 ui.ui_value = value
153 ui.ui_value = value
154
154
155 if self.repo:
155 if self.repo:
156 repo = self._get_repo(self.repo)
156 repo = self._get_repo(self.repo)
157 repository_id = repo.repo_id
157 repository_id = repo.repo_id
158 ui.repository_id = repository_id
158 ui.repository_id = repository_id
159
159
160 Session().add(ui)
160 Session().add(ui)
161 return ui
161 return ui
162
162
163 def delete_ui(self, id_):
163 def delete_ui(self, id_):
164 ui = self.UiDbModel.get(id_)
164 ui = self.UiDbModel.get(id_)
165 if not ui:
165 if not ui:
166 raise SettingNotFound(id_)
166 raise SettingNotFound(id_)
167 Session().delete(ui)
167 Session().delete(ui)
168
168
169 def get_setting_by_name(self, name):
169 def get_setting_by_name(self, name):
170 q = self._get_settings_query()
170 q = self._get_settings_query()
171 q = q.filter(self.SettingsDbModel.app_settings_name == name)
171 q = q.filter(self.SettingsDbModel.app_settings_name == name)
172 return q.scalar()
172 return q.scalar()
173
173
174 def create_or_update_setting(
174 def create_or_update_setting(
175 self, name, val: Optional | str = Optional(''), type_: Optional | str = Optional('unicode')):
175 self, name, val: Optional | str = Optional(''), type_: Optional | str = Optional('unicode')):
176 """
176 """
177 Creates or updates RhodeCode setting. If updates are triggered, it will
177 Creates or updates RhodeCode setting. If updates are triggered, it will
178 only update parameters that are explicitly set Optional instance will
178 only update parameters that are explicitly set Optional instance will
179 be skipped
179 be skipped
180
180
181 :param name:
181 :param name:
182 :param val:
182 :param val:
183 :param type_:
183 :param type_:
184 :return:
184 :return:
185 """
185 """
186
186
187 res = self.get_setting_by_name(name)
187 res = self.get_setting_by_name(name)
188 repo = self._get_repo(self.repo) if self.repo else None
188 repo = self._get_repo(self.repo) if self.repo else None
189
189
190 if not res:
190 if not res:
191 val = Optional.extract(val)
191 val = Optional.extract(val)
192 type_ = Optional.extract(type_)
192 type_ = Optional.extract(type_)
193
193
194 args = (
194 args = (
195 (repo.repo_id, name, val, type_)
195 (repo.repo_id, name, val, type_)
196 if repo else (name, val, type_))
196 if repo else (name, val, type_))
197 res = self.SettingsDbModel(*args)
197 res = self.SettingsDbModel(*args)
198
198
199 else:
199 else:
200 if self.repo:
200 if self.repo:
201 res.repository_id = repo.repo_id
201 res.repository_id = repo.repo_id
202
202
203 res.app_settings_name = name
203 res.app_settings_name = name
204 if not isinstance(type_, Optional):
204 if not isinstance(type_, Optional):
205 # update if set
205 # update if set
206 res.app_settings_type = type_
206 res.app_settings_type = type_
207 if not isinstance(val, Optional):
207 if not isinstance(val, Optional):
208 # update if set
208 # update if set
209 res.app_settings_value = val
209 res.app_settings_value = val
210
210
211 Session().add(res)
211 Session().add(res)
212 return res
212 return res
213
213
214 def get_cache_region(self):
214 def get_cache_region(self):
215 repo = self._get_repo(self.repo) if self.repo else None
215 repo = self._get_repo(self.repo) if self.repo else None
216 cache_key = f"repo.v1.{repo.repo_id}" if repo else "repo.v1.ALL"
216 cache_key = f"repo.v1.{repo.repo_id}" if repo else "repo.v1.ALL"
217 cache_namespace_uid = f'cache_settings.{cache_key}'
217 cache_namespace_uid = f'cache_settings.{cache_key}'
218 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
218 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
219 return region, cache_namespace_uid
219 return region, cache_namespace_uid
220
220
221 def invalidate_settings_cache(self, hard=False):
221 def invalidate_settings_cache(self, hard=False):
222 region, namespace_key = self.get_cache_region()
222 region, namespace_key = self.get_cache_region()
223 log.debug('Invalidation cache [%s] region %s for cache_key: %s',
223 log.debug('Invalidation cache [%s] region %s for cache_key: %s',
224 'invalidate_settings_cache', region, namespace_key)
224 'invalidate_settings_cache', region, namespace_key)
225
225
226 # we use hard cleanup if invalidation is sent
226 # we use hard cleanup if invalidation is sent
227 rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)
227 rc_cache.clear_cache_namespace(region, namespace_key, method=rc_cache.CLEAR_DELETE)
228
228
229 def get_cache_call_method(self, cache=True):
229 def get_cache_call_method(self, cache=True):
230 region, cache_key = self.get_cache_region()
230 region, cache_key = self.get_cache_region()
231
231
232 @region.conditional_cache_on_arguments(condition=cache)
232 @region.conditional_cache_on_arguments(condition=cache)
233 def _get_all_settings(name, key):
233 def _get_all_settings(name, key):
234 q = self._get_settings_query()
234 q = self._get_settings_query()
235 if not q:
235 if not q:
236 raise Exception('Could not get application settings !')
236 raise Exception('Could not get application settings !')
237
237
238 settings = {
238 settings = {
239 self.get_keyname(res.app_settings_name): res.app_settings_value
239 self.get_keyname(res.app_settings_name): res.app_settings_value
240 for res in q
240 for res in q
241 }
241 }
242 return settings
242 return settings
243 return _get_all_settings
243 return _get_all_settings
244
244
245 def get_all_settings(self, cache=False, from_request=True):
245 def get_all_settings(self, cache=False, from_request=True):
246 # defines if we use GLOBAL, or PER_REPO
246 # defines if we use GLOBAL, or PER_REPO
247 repo = self._get_repo(self.repo) if self.repo else None
247 repo = self._get_repo(self.repo) if self.repo else None
248
248
249 # initially try the request context; this is the fastest
249 # initially try the request context; this is the fastest
250 # we only fetch global config, NOT for repo-specific
250 # we only fetch global config, NOT for repo-specific
251 if from_request and not repo:
251 if from_request and not repo:
252 request = get_current_request()
252 request = get_current_request()
253
253
254 if request and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
254 if request and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
255 rc_config = request.call_context.rc_config
255 rc_config = request.call_context.rc_config
256 if rc_config:
256 if rc_config:
257 return rc_config
257 return rc_config
258
258
259 _region, cache_key = self.get_cache_region()
259 _region, cache_key = self.get_cache_region()
260 _get_all_settings = self.get_cache_call_method(cache=cache)
260 _get_all_settings = self.get_cache_call_method(cache=cache)
261
261
262 start = time.time()
262 start = time.time()
263 result = _get_all_settings('rhodecode_settings', cache_key)
263 result = _get_all_settings('rhodecode_settings', cache_key)
264 compute_time = time.time() - start
264 compute_time = time.time() - start
265 log.debug('cached method:%s took %.4fs', _get_all_settings.__name__, compute_time)
265 log.debug('cached method:%s took %.4fs', _get_all_settings.__name__, compute_time)
266
266
267 statsd = StatsdClient.statsd
267 statsd = StatsdClient.statsd
268 if statsd:
268 if statsd:
269 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
269 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
270 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
270 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
271 use_decimals=False)
271 use_decimals=False)
272
272
273 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
273 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
274
274
275 return result
275 return result
276
276
277 def get_auth_settings(self):
277 def get_auth_settings(self):
278 q = self._get_settings_query()
278 q = self._get_settings_query()
279 q = q.filter(
279 q = q.filter(
280 self.SettingsDbModel.app_settings_name.startswith('auth_'))
280 self.SettingsDbModel.app_settings_name.startswith('auth_'))
281 rows = q.all()
281 rows = q.all()
282 auth_settings = {
282 auth_settings = {
283 row.app_settings_name: row.app_settings_value for row in rows}
283 row.app_settings_name: row.app_settings_value for row in rows}
284 return auth_settings
284 return auth_settings
285
285
286 def get_auth_plugins(self):
286 def get_auth_plugins(self):
287 auth_plugins = self.get_setting_by_name("auth_plugins")
287 auth_plugins = self.get_setting_by_name("auth_plugins")
288 return auth_plugins.app_settings_value
288 return auth_plugins.app_settings_value
289
289
290 def get_default_repo_settings(self, strip_prefix=False):
290 def get_default_repo_settings(self, strip_prefix=False):
291 q = self._get_settings_query()
291 q = self._get_settings_query()
292 q = q.filter(
292 q = q.filter(
293 self.SettingsDbModel.app_settings_name.startswith('default_'))
293 self.SettingsDbModel.app_settings_name.startswith('default_'))
294 rows = q.all()
294 rows = q.all()
295
295
296 result = {}
296 result = {}
297 for row in rows:
297 for row in rows:
298 key = row.app_settings_name
298 key = row.app_settings_name
299 if strip_prefix:
299 if strip_prefix:
300 key = remove_prefix(key, prefix='default_')
300 key = remove_prefix(key, prefix='default_')
301 result.update({key: row.app_settings_value})
301 result.update({key: row.app_settings_value})
302 return result
302 return result
303
303
304 def get_repo(self):
304 def get_repo(self):
305 repo = self._get_repo(self.repo)
305 repo = self._get_repo(self.repo)
306 if not repo:
306 if not repo:
307 raise Exception(
307 raise Exception(
308 f'Repository `{self.repo}` cannot be found inside the database')
308 f'Repository `{self.repo}` cannot be found inside the database')
309 return repo
309 return repo
310
310
311 def _filter_by_repo(self, model, query):
311 def _filter_by_repo(self, model, query):
312 if self.repo:
312 if self.repo:
313 repo = self.get_repo()
313 repo = self.get_repo()
314 query = query.filter(model.repository_id == repo.repo_id)
314 query = query.filter(model.repository_id == repo.repo_id)
315 return query
315 return query
316
316
317 def _get_hooks(self, query):
317 def _get_hooks(self, query):
318 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
318 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
319 query = self._filter_by_repo(RepoRhodeCodeUi, query)
319 query = self._filter_by_repo(RepoRhodeCodeUi, query)
320 return query.all()
320 return query.all()
321
321
322 def _get_settings_query(self):
322 def _get_settings_query(self):
323 q = self.SettingsDbModel.query()
323 q = self.SettingsDbModel.query()
324 return self._filter_by_repo(RepoRhodeCodeSetting, q)
324 return self._filter_by_repo(RepoRhodeCodeSetting, q)
325
325
326 def list_enabled_social_plugins(self, settings):
326 def list_enabled_social_plugins(self, settings):
327 enabled = []
327 enabled = []
328 for plug in SOCIAL_PLUGINS_LIST:
328 for plug in SOCIAL_PLUGINS_LIST:
329 if str2bool(settings.get(f'rhodecode_auth_{plug}_enabled')):
329 if str2bool(settings.get(f'rhodecode_auth_{plug}_enabled')):
330 enabled.append(plug)
330 enabled.append(plug)
331 return enabled
331 return enabled
332
332
333
333
334 def assert_repo_settings(func):
334 def assert_repo_settings(func):
335 @functools.wraps(func)
335 @functools.wraps(func)
336 def _wrapper(self, *args, **kwargs):
336 def _wrapper(self, *args, **kwargs):
337 if not self.repo_settings:
337 if not self.repo_settings:
338 raise Exception('Repository is not specified')
338 raise Exception('Repository is not specified')
339 return func(self, *args, **kwargs)
339 return func(self, *args, **kwargs)
340 return _wrapper
340 return _wrapper
341
341
342
342
343 class IssueTrackerSettingsModel(object):
343 class IssueTrackerSettingsModel(object):
344 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
344 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
345 SETTINGS_PREFIX = 'issuetracker_'
345 SETTINGS_PREFIX = 'issuetracker_'
346
346
347 def __init__(self, sa=None, repo=None):
347 def __init__(self, sa=None, repo=None):
348 self.global_settings = SettingsModel(sa=sa)
348 self.global_settings = SettingsModel(sa=sa)
349 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
349 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
350
350
351 @property
351 @property
352 def inherit_global_settings(self):
352 def inherit_global_settings(self):
353 if not self.repo_settings:
353 if not self.repo_settings:
354 return True
354 return True
355 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
355 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
356 return setting.app_settings_value if setting else True
356 return setting.app_settings_value if setting else True
357
357
358 @inherit_global_settings.setter
358 @inherit_global_settings.setter
359 def inherit_global_settings(self, value):
359 def inherit_global_settings(self, value):
360 if self.repo_settings:
360 if self.repo_settings:
361 settings = self.repo_settings.create_or_update_setting(
361 settings = self.repo_settings.create_or_update_setting(
362 self.INHERIT_SETTINGS, value, type_='bool')
362 self.INHERIT_SETTINGS, value, type_='bool')
363 Session().add(settings)
363 Session().add(settings)
364
364
365 def _get_keyname(self, key, uid, prefix='rhodecode_'):
365 def _get_keyname(self, key, uid, prefix='rhodecode_'):
366 return f'{prefix}{self.SETTINGS_PREFIX}{key}_{uid}'
366 return f'{prefix}{self.SETTINGS_PREFIX}{key}_{uid}'
367
367
368 def _make_dict_for_settings(self, qs):
368 def _make_dict_for_settings(self, qs):
369 prefix_match = self._get_keyname('pat', '',)
369 prefix_match = self._get_keyname('pat', '',)
370
370
371 issuetracker_entries = {}
371 issuetracker_entries = {}
372 # create keys
372 # create keys
373 for k, v in qs.items():
373 for k, v in qs.items():
374 if k.startswith(prefix_match):
374 if k.startswith(prefix_match):
375 uid = k[len(prefix_match):]
375 uid = k[len(prefix_match):]
376 issuetracker_entries[uid] = None
376 issuetracker_entries[uid] = None
377
377
378 def url_cleaner(input_str):
378 def url_cleaner(input_str):
379 input_str = input_str.replace('"', '').replace("'", '')
379 input_str = input_str.replace('"', '').replace("'", '')
380 input_str = sanitize_html(input_str, strip=True)
380 input_str = sanitize_html(input_str, strip=True)
381 return input_str
381 return input_str
382
382
383 # populate
383 # populate
384 for uid in issuetracker_entries:
384 for uid in issuetracker_entries:
385 url_data = qs.get(self._get_keyname('url', uid))
385 url_data = qs.get(self._get_keyname('url', uid))
386
386
387 pat = qs.get(self._get_keyname('pat', uid))
387 pat = qs.get(self._get_keyname('pat', uid))
388 try:
388 try:
389 pat_compiled = re.compile(r'%s' % pat)
389 pat_compiled = re.compile(r'%s' % pat)
390 except re.error:
390 except re.error:
391 pat_compiled = None
391 pat_compiled = None
392
392
393 issuetracker_entries[uid] = AttributeDict({
393 issuetracker_entries[uid] = AttributeDict({
394 'pat': pat,
394 'pat': pat,
395 'pat_compiled': pat_compiled,
395 'pat_compiled': pat_compiled,
396 'url': url_cleaner(
396 'url': url_cleaner(
397 qs.get(self._get_keyname('url', uid)) or ''),
397 qs.get(self._get_keyname('url', uid)) or ''),
398 'pref': sanitize_html(
398 'pref': sanitize_html(
399 qs.get(self._get_keyname('pref', uid)) or ''),
399 qs.get(self._get_keyname('pref', uid)) or ''),
400 'desc': qs.get(
400 'desc': qs.get(
401 self._get_keyname('desc', uid)),
401 self._get_keyname('desc', uid)),
402 })
402 })
403
403
404 return issuetracker_entries
404 return issuetracker_entries
405
405
406 def get_global_settings(self, cache=False):
406 def get_global_settings(self, cache=False):
407 """
407 """
408 Returns list of global issue tracker settings
408 Returns list of global issue tracker settings
409 """
409 """
410 defaults = self.global_settings.get_all_settings(cache=cache)
410 defaults = self.global_settings.get_all_settings(cache=cache)
411 settings = self._make_dict_for_settings(defaults)
411 settings = self._make_dict_for_settings(defaults)
412 return settings
412 return settings
413
413
414 def get_repo_settings(self, cache=False):
414 def get_repo_settings(self, cache=False):
415 """
415 """
416 Returns list of issue tracker settings per repository
416 Returns list of issue tracker settings per repository
417 """
417 """
418 if not self.repo_settings:
418 if not self.repo_settings:
419 raise Exception('Repository is not specified')
419 raise Exception('Repository is not specified')
420 all_settings = self.repo_settings.get_all_settings(cache=cache)
420 all_settings = self.repo_settings.get_all_settings(cache=cache)
421 settings = self._make_dict_for_settings(all_settings)
421 settings = self._make_dict_for_settings(all_settings)
422 return settings
422 return settings
423
423
424 def get_settings(self, cache=False):
424 def get_settings(self, cache=False):
425 if self.inherit_global_settings:
425 if self.inherit_global_settings:
426 return self.get_global_settings(cache=cache)
426 return self.get_global_settings(cache=cache)
427 else:
427 else:
428 return self.get_repo_settings(cache=cache)
428 return self.get_repo_settings(cache=cache)
429
429
430 def delete_entries(self, uid):
430 def delete_entries(self, uid):
431 if self.repo_settings:
431 if self.repo_settings:
432 all_patterns = self.get_repo_settings()
432 all_patterns = self.get_repo_settings()
433 settings_model = self.repo_settings
433 settings_model = self.repo_settings
434 else:
434 else:
435 all_patterns = self.get_global_settings()
435 all_patterns = self.get_global_settings()
436 settings_model = self.global_settings
436 settings_model = self.global_settings
437 entries = all_patterns.get(uid, [])
437 entries = all_patterns.get(uid, [])
438
438
439 for del_key in entries:
439 for del_key in entries:
440 setting_name = self._get_keyname(del_key, uid, prefix='')
440 setting_name = self._get_keyname(del_key, uid, prefix='')
441 entry = settings_model.get_setting_by_name(setting_name)
441 entry = settings_model.get_setting_by_name(setting_name)
442 if entry:
442 if entry:
443 Session().delete(entry)
443 Session().delete(entry)
444
444
445 Session().commit()
445 Session().commit()
446
446
447 def create_or_update_setting(
447 def create_or_update_setting(
448 self, name, val=Optional(''), type_=Optional('unicode')):
448 self, name, val=Optional(''), type_=Optional('unicode')):
449 if self.repo_settings:
449 if self.repo_settings:
450 setting = self.repo_settings.create_or_update_setting(
450 setting = self.repo_settings.create_or_update_setting(
451 name, val, type_)
451 name, val, type_)
452 else:
452 else:
453 setting = self.global_settings.create_or_update_setting(
453 setting = self.global_settings.create_or_update_setting(
454 name, val, type_)
454 name, val, type_)
455 return setting
455 return setting
456
456
457
457
458 class VcsSettingsModel(object):
458 class VcsSettingsModel(object):
459
459
460 INHERIT_SETTINGS = 'inherit_vcs_settings'
460 INHERIT_SETTINGS = 'inherit_vcs_settings'
461 GENERAL_SETTINGS = (
461 GENERAL_SETTINGS = (
462 'use_outdated_comments',
462 'use_outdated_comments',
463 'pr_merge_enabled',
463 'pr_merge_enabled',
464 'hg_use_rebase_for_merging',
464 'hg_use_rebase_for_merging',
465 'hg_close_branch_before_merging',
465 'hg_close_branch_before_merging',
466 'git_use_rebase_for_merging',
466 'git_use_rebase_for_merging',
467 'git_close_branch_before_merging',
467 'git_close_branch_before_merging',
468 'diff_cache',
468 'diff_cache',
469 )
469 )
470
470
471 HOOKS_SETTINGS = (
471 HOOKS_SETTINGS = (
472 ('hooks', 'changegroup.repo_size'),
472 ('hooks', 'changegroup.repo_size'),
473 ('hooks', 'changegroup.push_logger'),
473 ('hooks', 'changegroup.push_logger'),
474 ('hooks', 'outgoing.pull_logger'),
474 ('hooks', 'outgoing.pull_logger'),
475 )
475 )
476 HG_SETTINGS = (
476 HG_SETTINGS = (
477 ('extensions', 'largefiles'),
477 ('extensions', 'largefiles'),
478 ('phases', 'publish'),
478 ('phases', 'publish'),
479 ('extensions', 'evolve'),
479 ('extensions', 'evolve'),
480 ('extensions', 'topic'),
480 ('extensions', 'topic'),
481 ('experimental', 'evolution'),
481 ('experimental', 'evolution'),
482 ('experimental', 'evolution.exchange'),
482 ('experimental', 'evolution.exchange'),
483 )
483 )
484 GIT_SETTINGS = (
484 GIT_SETTINGS = (
485 ('vcs_git_lfs', 'enabled'),
485 ('vcs_git_lfs', 'enabled'),
486 )
486 )
487 GLOBAL_HG_SETTINGS = (
487 GLOBAL_HG_SETTINGS = (
488 ('extensions', 'largefiles'),
488 ('extensions', 'largefiles'),
489 ('largefiles', 'usercache'),
489 ('largefiles', 'usercache'),
490 ('phases', 'publish'),
490 ('phases', 'publish'),
491 ('extensions', 'evolve'),
491 ('extensions', 'evolve'),
492 ('extensions', 'topic'),
492 ('extensions', 'topic'),
493 ('experimental', 'evolution'),
493 ('experimental', 'evolution'),
494 ('experimental', 'evolution.exchange'),
494 ('experimental', 'evolution.exchange'),
495 )
495 )
496
496
497 GLOBAL_GIT_SETTINGS = (
497 GLOBAL_GIT_SETTINGS = (
498 ('vcs_git_lfs', 'enabled'),
498 ('vcs_git_lfs', 'enabled'),
499 ('vcs_git_lfs', 'store_location')
499 ('vcs_git_lfs', 'store_location')
500 )
500 )
501
501
502 SVN_BRANCH_SECTION = 'vcs_svn_branch'
502 SVN_BRANCH_SECTION = 'vcs_svn_branch'
503 SVN_TAG_SECTION = 'vcs_svn_tag'
503 SVN_TAG_SECTION = 'vcs_svn_tag'
504 SSL_SETTING = ('web', 'push_ssl')
505 PATH_SETTING = ('paths', '/')
504 PATH_SETTING = ('paths', '/')
506
505
507 def __init__(self, sa=None, repo=None):
506 def __init__(self, sa=None, repo=None):
508 self.global_settings = SettingsModel(sa=sa)
507 self.global_settings = SettingsModel(sa=sa)
509 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
508 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
510 self._ui_settings = (
509 self._ui_settings = (
511 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
510 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
512 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
511 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
513
512
514 @property
513 @property
515 @assert_repo_settings
514 @assert_repo_settings
516 def inherit_global_settings(self):
515 def inherit_global_settings(self):
517 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
516 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
518 return setting.app_settings_value if setting else True
517 return setting.app_settings_value if setting else True
519
518
520 @inherit_global_settings.setter
519 @inherit_global_settings.setter
521 @assert_repo_settings
520 @assert_repo_settings
522 def inherit_global_settings(self, value):
521 def inherit_global_settings(self, value):
523 self.repo_settings.create_or_update_setting(
522 self.repo_settings.create_or_update_setting(
524 self.INHERIT_SETTINGS, value, type_='bool')
523 self.INHERIT_SETTINGS, value, type_='bool')
525
524
526 def get_keyname(self, key_name, prefix='rhodecode_'):
525 def get_keyname(self, key_name, prefix='rhodecode_'):
527 return f'{prefix}{key_name}'
526 return f'{prefix}{key_name}'
528
527
529 def get_global_svn_branch_patterns(self):
528 def get_global_svn_branch_patterns(self):
530 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
529 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
531
530
532 @assert_repo_settings
531 @assert_repo_settings
533 def get_repo_svn_branch_patterns(self):
532 def get_repo_svn_branch_patterns(self):
534 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
533 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
535
534
536 def get_global_svn_tag_patterns(self):
535 def get_global_svn_tag_patterns(self):
537 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
536 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
538
537
539 @assert_repo_settings
538 @assert_repo_settings
540 def get_repo_svn_tag_patterns(self):
539 def get_repo_svn_tag_patterns(self):
541 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
540 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
542
541
543 def get_global_settings(self):
542 def get_global_settings(self):
544 return self._collect_all_settings(global_=True)
543 return self._collect_all_settings(global_=True)
545
544
546 @assert_repo_settings
545 @assert_repo_settings
547 def get_repo_settings(self):
546 def get_repo_settings(self):
548 return self._collect_all_settings(global_=False)
547 return self._collect_all_settings(global_=False)
549
548
550 @assert_repo_settings
549 @assert_repo_settings
551 def get_repo_settings_inherited(self):
550 def get_repo_settings_inherited(self):
552 global_settings = self.get_global_settings()
551 global_settings = self.get_global_settings()
553 global_settings.update(self.get_repo_settings())
552 global_settings.update(self.get_repo_settings())
554 return global_settings
553 return global_settings
555
554
556 @assert_repo_settings
555 @assert_repo_settings
557 def create_or_update_repo_settings(
556 def create_or_update_repo_settings(
558 self, data, inherit_global_settings=False):
557 self, data, inherit_global_settings=False):
559 from rhodecode.model.scm import ScmModel
558 from rhodecode.model.scm import ScmModel
560
559
561 self.inherit_global_settings = inherit_global_settings
560 self.inherit_global_settings = inherit_global_settings
562
561
563 repo = self.repo_settings.get_repo()
562 repo = self.repo_settings.get_repo()
564 if not inherit_global_settings:
563 if not inherit_global_settings:
565 if repo.repo_type == 'svn':
564 if repo.repo_type == 'svn':
566 self.create_repo_svn_settings(data)
565 self.create_repo_svn_settings(data)
567 else:
566 else:
568 self.create_or_update_repo_hook_settings(data)
567 self.create_or_update_repo_hook_settings(data)
569 self.create_or_update_repo_pr_settings(data)
568 self.create_or_update_repo_pr_settings(data)
570
569
571 if repo.repo_type == 'hg':
570 if repo.repo_type == 'hg':
572 self.create_or_update_repo_hg_settings(data)
571 self.create_or_update_repo_hg_settings(data)
573
572
574 if repo.repo_type == 'git':
573 if repo.repo_type == 'git':
575 self.create_or_update_repo_git_settings(data)
574 self.create_or_update_repo_git_settings(data)
576
575
577 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
576 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
578
577
579 @assert_repo_settings
578 @assert_repo_settings
580 def create_or_update_repo_hook_settings(self, data):
579 def create_or_update_repo_hook_settings(self, data):
581 for section, key in self.HOOKS_SETTINGS:
580 for section, key in self.HOOKS_SETTINGS:
582 data_key = self._get_form_ui_key(section, key)
581 data_key = self._get_form_ui_key(section, key)
583 if data_key not in data:
582 if data_key not in data:
584 raise ValueError(
583 raise ValueError(
585 f'The given data does not contain {data_key} key')
584 f'The given data does not contain {data_key} key')
586
585
587 active = data.get(data_key)
586 active = data.get(data_key)
588 repo_setting = self.repo_settings.get_ui_by_section_and_key(
587 repo_setting = self.repo_settings.get_ui_by_section_and_key(
589 section, key)
588 section, key)
590 if not repo_setting:
589 if not repo_setting:
591 global_setting = self.global_settings.\
590 global_setting = self.global_settings.\
592 get_ui_by_section_and_key(section, key)
591 get_ui_by_section_and_key(section, key)
593 self.repo_settings.create_ui_section_value(
592 self.repo_settings.create_ui_section_value(
594 section, global_setting.ui_value, key=key, active=active)
593 section, global_setting.ui_value, key=key, active=active)
595 else:
594 else:
596 repo_setting.ui_active = active
595 repo_setting.ui_active = active
597 Session().add(repo_setting)
596 Session().add(repo_setting)
598
597
599 def update_global_hook_settings(self, data):
598 def update_global_hook_settings(self, data):
600 for section, key in self.HOOKS_SETTINGS:
599 for section, key in self.HOOKS_SETTINGS:
601 data_key = self._get_form_ui_key(section, key)
600 data_key = self._get_form_ui_key(section, key)
602 if data_key not in data:
601 if data_key not in data:
603 raise ValueError(
602 raise ValueError(
604 f'The given data does not contain {data_key} key')
603 f'The given data does not contain {data_key} key')
605 active = data.get(data_key)
604 active = data.get(data_key)
606 repo_setting = self.global_settings.get_ui_by_section_and_key(
605 repo_setting = self.global_settings.get_ui_by_section_and_key(
607 section, key)
606 section, key)
608 repo_setting.ui_active = active
607 repo_setting.ui_active = active
609 Session().add(repo_setting)
608 Session().add(repo_setting)
610
609
611 @assert_repo_settings
610 @assert_repo_settings
612 def create_or_update_repo_pr_settings(self, data):
611 def create_or_update_repo_pr_settings(self, data):
613 return self._create_or_update_general_settings(
612 return self._create_or_update_general_settings(
614 self.repo_settings, data)
613 self.repo_settings, data)
615
614
616 def create_or_update_global_pr_settings(self, data):
615 def create_or_update_global_pr_settings(self, data):
617 return self._create_or_update_general_settings(
616 return self._create_or_update_general_settings(
618 self.global_settings, data)
617 self.global_settings, data)
619
618
620 @assert_repo_settings
619 @assert_repo_settings
621 def create_repo_svn_settings(self, data):
620 def create_repo_svn_settings(self, data):
622 return self._create_svn_settings(self.repo_settings, data)
621 return self._create_svn_settings(self.repo_settings, data)
623
622
624 def _set_evolution(self, settings, is_enabled):
623 def _set_evolution(self, settings, is_enabled):
625 if is_enabled:
624 if is_enabled:
626 # if evolve is active set evolution=all
625 # if evolve is active set evolution=all
627
626
628 self._create_or_update_ui(
627 self._create_or_update_ui(
629 settings, *('experimental', 'evolution'), value='all',
628 settings, *('experimental', 'evolution'), value='all',
630 active=True)
629 active=True)
631 self._create_or_update_ui(
630 self._create_or_update_ui(
632 settings, *('experimental', 'evolution.exchange'), value='yes',
631 settings, *('experimental', 'evolution.exchange'), value='yes',
633 active=True)
632 active=True)
634 # if evolve is active set topics server support
633 # if evolve is active set topics server support
635 self._create_or_update_ui(
634 self._create_or_update_ui(
636 settings, *('extensions', 'topic'), value='',
635 settings, *('extensions', 'topic'), value='',
637 active=True)
636 active=True)
638
637
639 else:
638 else:
640 self._create_or_update_ui(
639 self._create_or_update_ui(
641 settings, *('experimental', 'evolution'), value='',
640 settings, *('experimental', 'evolution'), value='',
642 active=False)
641 active=False)
643 self._create_or_update_ui(
642 self._create_or_update_ui(
644 settings, *('experimental', 'evolution.exchange'), value='no',
643 settings, *('experimental', 'evolution.exchange'), value='no',
645 active=False)
644 active=False)
646 self._create_or_update_ui(
645 self._create_or_update_ui(
647 settings, *('extensions', 'topic'), value='',
646 settings, *('extensions', 'topic'), value='',
648 active=False)
647 active=False)
649
648
650 @assert_repo_settings
649 @assert_repo_settings
651 def create_or_update_repo_hg_settings(self, data):
650 def create_or_update_repo_hg_settings(self, data):
652 largefiles, phases, evolve = \
651 largefiles, phases, evolve = \
653 self.HG_SETTINGS[:3]
652 self.HG_SETTINGS[:3]
654 largefiles_key, phases_key, evolve_key = \
653 largefiles_key, phases_key, evolve_key = \
655 self._get_settings_keys(self.HG_SETTINGS[:3], data)
654 self._get_settings_keys(self.HG_SETTINGS[:3], data)
656
655
657 self._create_or_update_ui(
656 self._create_or_update_ui(
658 self.repo_settings, *largefiles, value='',
657 self.repo_settings, *largefiles, value='',
659 active=data[largefiles_key])
658 active=data[largefiles_key])
660 self._create_or_update_ui(
659 self._create_or_update_ui(
661 self.repo_settings, *evolve, value='',
660 self.repo_settings, *evolve, value='',
662 active=data[evolve_key])
661 active=data[evolve_key])
663 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
662 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
664
663
665 self._create_or_update_ui(
664 self._create_or_update_ui(
666 self.repo_settings, *phases, value=safe_str(data[phases_key]))
665 self.repo_settings, *phases, value=safe_str(data[phases_key]))
667
666
668 def create_or_update_global_hg_settings(self, data):
667 def create_or_update_global_hg_settings(self, data):
669 opts_len = 4
668 opts_len = 4
670 largefiles, largefiles_store, phases, evolve \
669 largefiles, largefiles_store, phases, evolve \
671 = self.GLOBAL_HG_SETTINGS[:opts_len]
670 = self.GLOBAL_HG_SETTINGS[:opts_len]
672 largefiles_key, largefiles_store_key, phases_key, evolve_key \
671 largefiles_key, largefiles_store_key, phases_key, evolve_key \
673 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:opts_len], data)
672 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:opts_len], data)
674
673
675 self._create_or_update_ui(
674 self._create_or_update_ui(
676 self.global_settings, *largefiles, value='',
675 self.global_settings, *largefiles, value='',
677 active=data[largefiles_key])
676 active=data[largefiles_key])
678 self._create_or_update_ui(
677 self._create_or_update_ui(
679 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
678 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
680 self._create_or_update_ui(
679 self._create_or_update_ui(
681 self.global_settings, *phases, value=safe_str(data[phases_key]))
680 self.global_settings, *phases, value=safe_str(data[phases_key]))
682 self._create_or_update_ui(
681 self._create_or_update_ui(
683 self.global_settings, *evolve, value='',
682 self.global_settings, *evolve, value='',
684 active=data[evolve_key])
683 active=data[evolve_key])
685 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
684 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
686
685
687 def create_or_update_repo_git_settings(self, data):
686 def create_or_update_repo_git_settings(self, data):
688 # NOTE(marcink): # comma makes unpack work properly
687 # NOTE(marcink): # comma makes unpack work properly
689 lfs_enabled, \
688 lfs_enabled, \
690 = self.GIT_SETTINGS
689 = self.GIT_SETTINGS
691
690
692 lfs_enabled_key, \
691 lfs_enabled_key, \
693 = self._get_settings_keys(self.GIT_SETTINGS, data)
692 = self._get_settings_keys(self.GIT_SETTINGS, data)
694
693
695 self._create_or_update_ui(
694 self._create_or_update_ui(
696 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
695 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
697 active=data[lfs_enabled_key])
696 active=data[lfs_enabled_key])
698
697
699 def create_or_update_global_git_settings(self, data):
698 def create_or_update_global_git_settings(self, data):
700 lfs_enabled, lfs_store_location \
699 lfs_enabled, lfs_store_location \
701 = self.GLOBAL_GIT_SETTINGS
700 = self.GLOBAL_GIT_SETTINGS
702 lfs_enabled_key, lfs_store_location_key \
701 lfs_enabled_key, lfs_store_location_key \
703 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
702 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
704
703
705 self._create_or_update_ui(
704 self._create_or_update_ui(
706 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
705 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
707 active=data[lfs_enabled_key])
706 active=data[lfs_enabled_key])
708 self._create_or_update_ui(
707 self._create_or_update_ui(
709 self.global_settings, *lfs_store_location,
708 self.global_settings, *lfs_store_location,
710 value=data[lfs_store_location_key])
709 value=data[lfs_store_location_key])
711
710
712 def create_or_update_global_svn_settings(self, data):
711 def create_or_update_global_svn_settings(self, data):
713 # branch/tags patterns
712 # branch/tags patterns
714 self._create_svn_settings(self.global_settings, data)
713 self._create_svn_settings(self.global_settings, data)
715
714
716 def update_global_ssl_setting(self, value):
717 self._create_or_update_ui(
718 self.global_settings, *self.SSL_SETTING, value=value)
719
720 @assert_repo_settings
715 @assert_repo_settings
721 def delete_repo_svn_pattern(self, id_):
716 def delete_repo_svn_pattern(self, id_):
722 ui = self.repo_settings.UiDbModel.get(id_)
717 ui = self.repo_settings.UiDbModel.get(id_)
723 if ui and ui.repository.repo_name == self.repo_settings.repo:
718 if ui and ui.repository.repo_name == self.repo_settings.repo:
724 # only delete if it's the same repo as initialized settings
719 # only delete if it's the same repo as initialized settings
725 self.repo_settings.delete_ui(id_)
720 self.repo_settings.delete_ui(id_)
726 else:
721 else:
727 # raise error as if we wouldn't find this option
722 # raise error as if we wouldn't find this option
728 self.repo_settings.delete_ui(-1)
723 self.repo_settings.delete_ui(-1)
729
724
730 def delete_global_svn_pattern(self, id_):
725 def delete_global_svn_pattern(self, id_):
731 self.global_settings.delete_ui(id_)
726 self.global_settings.delete_ui(id_)
732
727
733 @assert_repo_settings
728 @assert_repo_settings
734 def get_repo_ui_settings(self, section=None, key=None):
729 def get_repo_ui_settings(self, section=None, key=None):
735 global_uis = self.global_settings.get_ui(section, key)
730 global_uis = self.global_settings.get_ui(section, key)
736 repo_uis = self.repo_settings.get_ui(section, key)
731 repo_uis = self.repo_settings.get_ui(section, key)
737
732
738 filtered_repo_uis = self._filter_ui_settings(repo_uis)
733 filtered_repo_uis = self._filter_ui_settings(repo_uis)
739 filtered_repo_uis_keys = [
734 filtered_repo_uis_keys = [
740 (s.section, s.key) for s in filtered_repo_uis]
735 (s.section, s.key) for s in filtered_repo_uis]
741
736
742 def _is_global_ui_filtered(ui):
737 def _is_global_ui_filtered(ui):
743 return (
738 return (
744 (ui.section, ui.key) in filtered_repo_uis_keys
739 (ui.section, ui.key) in filtered_repo_uis_keys
745 or ui.section in self._svn_sections)
740 or ui.section in self._svn_sections)
746
741
747 filtered_global_uis = [
742 filtered_global_uis = [
748 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
743 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
749
744
750 return filtered_global_uis + filtered_repo_uis
745 return filtered_global_uis + filtered_repo_uis
751
746
752 def get_global_ui_settings(self, section=None, key=None):
747 def get_global_ui_settings(self, section=None, key=None):
753 return self.global_settings.get_ui(section, key)
748 return self.global_settings.get_ui(section, key)
754
749
755 def get_ui_settings_as_config_obj(self, section=None, key=None):
750 def get_ui_settings_as_config_obj(self, section=None, key=None):
756 config = base.Config()
751 config = base.Config()
757
752
758 ui_settings = self.get_ui_settings(section=section, key=key)
753 ui_settings = self.get_ui_settings(section=section, key=key)
759
754
760 for entry in ui_settings:
755 for entry in ui_settings:
761 config.set(entry.section, entry.key, entry.value)
756 config.set(entry.section, entry.key, entry.value)
762
757
763 return config
758 return config
764
759
765 def get_ui_settings(self, section=None, key=None):
760 def get_ui_settings(self, section=None, key=None):
766 if not self.repo_settings or self.inherit_global_settings:
761 if not self.repo_settings or self.inherit_global_settings:
767 return self.get_global_ui_settings(section, key)
762 return self.get_global_ui_settings(section, key)
768 else:
763 else:
769 return self.get_repo_ui_settings(section, key)
764 return self.get_repo_ui_settings(section, key)
770
765
771 def get_svn_patterns(self, section=None):
766 def get_svn_patterns(self, section=None):
772 if not self.repo_settings:
767 if not self.repo_settings:
773 return self.get_global_ui_settings(section)
768 return self.get_global_ui_settings(section)
774 else:
769 else:
775 return self.get_repo_ui_settings(section)
770 return self.get_repo_ui_settings(section)
776
771
777 @assert_repo_settings
772 @assert_repo_settings
778 def get_repo_general_settings(self):
773 def get_repo_general_settings(self):
779 global_settings = self.global_settings.get_all_settings()
774 global_settings = self.global_settings.get_all_settings()
780 repo_settings = self.repo_settings.get_all_settings()
775 repo_settings = self.repo_settings.get_all_settings()
781 filtered_repo_settings = self._filter_general_settings(repo_settings)
776 filtered_repo_settings = self._filter_general_settings(repo_settings)
782 global_settings.update(filtered_repo_settings)
777 global_settings.update(filtered_repo_settings)
783 return global_settings
778 return global_settings
784
779
785 def get_global_general_settings(self):
780 def get_global_general_settings(self):
786 return self.global_settings.get_all_settings()
781 return self.global_settings.get_all_settings()
787
782
788 def get_general_settings(self):
783 def get_general_settings(self):
789 if not self.repo_settings or self.inherit_global_settings:
784 if not self.repo_settings or self.inherit_global_settings:
790 return self.get_global_general_settings()
785 return self.get_global_general_settings()
791 else:
786 else:
792 return self.get_repo_general_settings()
787 return self.get_repo_general_settings()
793
788
794 def _filter_ui_settings(self, settings):
789 def _filter_ui_settings(self, settings):
795 filtered_settings = [
790 filtered_settings = [
796 s for s in settings if self._should_keep_setting(s)]
791 s for s in settings if self._should_keep_setting(s)]
797 return filtered_settings
792 return filtered_settings
798
793
799 def _should_keep_setting(self, setting):
794 def _should_keep_setting(self, setting):
800 keep = (
795 keep = (
801 (setting.section, setting.key) in self._ui_settings or
796 (setting.section, setting.key) in self._ui_settings or
802 setting.section in self._svn_sections)
797 setting.section in self._svn_sections)
803 return keep
798 return keep
804
799
805 def _filter_general_settings(self, settings):
800 def _filter_general_settings(self, settings):
806 keys = [self.get_keyname(key) for key in self.GENERAL_SETTINGS]
801 keys = [self.get_keyname(key) for key in self.GENERAL_SETTINGS]
807 return {
802 return {
808 k: settings[k]
803 k: settings[k]
809 for k in settings if k in keys}
804 for k in settings if k in keys}
810
805
811 def _collect_all_settings(self, global_=False):
806 def _collect_all_settings(self, global_=False):
812 settings = self.global_settings if global_ else self.repo_settings
807 settings = self.global_settings if global_ else self.repo_settings
813 result = {}
808 result = {}
814
809
815 for section, key in self._ui_settings:
810 for section, key in self._ui_settings:
816 ui = settings.get_ui_by_section_and_key(section, key)
811 ui = settings.get_ui_by_section_and_key(section, key)
817 result_key = self._get_form_ui_key(section, key)
812 result_key = self._get_form_ui_key(section, key)
818
813
819 if ui:
814 if ui:
820 if section in ('hooks', 'extensions'):
815 if section in ('hooks', 'extensions'):
821 result[result_key] = ui.ui_active
816 result[result_key] = ui.ui_active
822 elif result_key in ['vcs_git_lfs_enabled']:
817 elif result_key in ['vcs_git_lfs_enabled']:
823 result[result_key] = ui.ui_active
818 result[result_key] = ui.ui_active
824 else:
819 else:
825 result[result_key] = ui.ui_value
820 result[result_key] = ui.ui_value
826
821
827 for name in self.GENERAL_SETTINGS:
822 for name in self.GENERAL_SETTINGS:
828 setting = settings.get_setting_by_name(name)
823 setting = settings.get_setting_by_name(name)
829 if setting:
824 if setting:
830 result_key = self.get_keyname(name)
825 result_key = self.get_keyname(name)
831 result[result_key] = setting.app_settings_value
826 result[result_key] = setting.app_settings_value
832
827
833 return result
828 return result
834
829
835 def _get_form_ui_key(self, section, key):
830 def _get_form_ui_key(self, section, key):
836 return '{section}_{key}'.format(
831 return '{section}_{key}'.format(
837 section=section, key=key.replace('.', '_'))
832 section=section, key=key.replace('.', '_'))
838
833
839 def _create_or_update_ui(
834 def _create_or_update_ui(
840 self, settings, section, key, value=None, active=None):
835 self, settings, section, key, value=None, active=None):
841 ui = settings.get_ui_by_section_and_key(section, key)
836 ui = settings.get_ui_by_section_and_key(section, key)
842 if not ui:
837 if not ui:
843 active = True if active is None else active
838 active = True if active is None else active
844 settings.create_ui_section_value(
839 settings.create_ui_section_value(
845 section, value, key=key, active=active)
840 section, value, key=key, active=active)
846 else:
841 else:
847 if active is not None:
842 if active is not None:
848 ui.ui_active = active
843 ui.ui_active = active
849 if value is not None:
844 if value is not None:
850 ui.ui_value = value
845 ui.ui_value = value
851 Session().add(ui)
846 Session().add(ui)
852
847
853 def _create_svn_settings(self, settings, data):
848 def _create_svn_settings(self, settings, data):
854 svn_settings = {
849 svn_settings = {
855 'new_svn_branch': self.SVN_BRANCH_SECTION,
850 'new_svn_branch': self.SVN_BRANCH_SECTION,
856 'new_svn_tag': self.SVN_TAG_SECTION
851 'new_svn_tag': self.SVN_TAG_SECTION
857 }
852 }
858 for key in svn_settings:
853 for key in svn_settings:
859 if data.get(key):
854 if data.get(key):
860 settings.create_ui_section_value(svn_settings[key], data[key])
855 settings.create_ui_section_value(svn_settings[key], data[key])
861
856
862 def _create_or_update_general_settings(self, settings, data):
857 def _create_or_update_general_settings(self, settings, data):
863 for name in self.GENERAL_SETTINGS:
858 for name in self.GENERAL_SETTINGS:
864 data_key = self.get_keyname(name)
859 data_key = self.get_keyname(name)
865 if data_key not in data:
860 if data_key not in data:
866 raise ValueError(
861 raise ValueError(
867 f'The given data does not contain {data_key} key')
862 f'The given data does not contain {data_key} key')
868 setting = settings.create_or_update_setting(
863 setting = settings.create_or_update_setting(
869 name, data[data_key], 'bool')
864 name, data[data_key], 'bool')
870 Session().add(setting)
865 Session().add(setting)
871
866
872 def _get_settings_keys(self, settings, data):
867 def _get_settings_keys(self, settings, data):
873 data_keys = [self._get_form_ui_key(*s) for s in settings]
868 data_keys = [self._get_form_ui_key(*s) for s in settings]
874 for data_key in data_keys:
869 for data_key in data_keys:
875 if data_key not in data:
870 if data_key not in data:
876 raise ValueError(
871 raise ValueError(
877 f'The given data does not contain {data_key} key')
872 f'The given data does not contain {data_key} key')
878 return data_keys
873 return data_keys
879
874
880 def create_largeobjects_dirs_if_needed(self, repo_store_path):
875 def create_largeobjects_dirs_if_needed(self, repo_store_path):
881 """
876 """
882 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
877 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
883 does a repository scan if enabled in the settings.
878 does a repository scan if enabled in the settings.
884 """
879 """
885
880
886 from rhodecode.lib.vcs.backends.hg import largefiles_store
881 from rhodecode.lib.vcs.backends.hg import largefiles_store
887 from rhodecode.lib.vcs.backends.git import lfs_store
882 from rhodecode.lib.vcs.backends.git import lfs_store
888
883
889 paths = [
884 paths = [
890 largefiles_store(repo_store_path),
885 largefiles_store(repo_store_path),
891 lfs_store(repo_store_path)]
886 lfs_store(repo_store_path)]
892
887
893 for path in paths:
888 for path in paths:
894 if os.path.isdir(path):
889 if os.path.isdir(path):
895 continue
890 continue
896 if os.path.isfile(path):
891 if os.path.isfile(path):
897 continue
892 continue
898 # not a file nor dir, we try to create it
893 # not a file nor dir, we try to create it
899 try:
894 try:
900 os.makedirs(path)
895 os.makedirs(path)
901 except Exception:
896 except Exception:
902 log.warning('Failed to create largefiles dir:%s', path)
897 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,345 +1,330 b''
1 ## snippet for displaying vcs settings
1 ## snippet for displaying vcs settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
3 ## <%namespace name="vcss" file="/base/vcssettings.mako"/>
4 ## ${vcss.vcs_settings_fields()}
4 ## ${vcss.vcs_settings_fields()}
5
5
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, **kwargs)">
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, **kwargs)">
7 % if display_globals:
7 % if display_globals:
8 <div class="panel panel-default">
8
9 <div class="panel-heading" id="general">
10 <h3 class="panel-title">${_('General')}<a class="permalink" href="#general"></a></h3>
11 </div>
12 <div class="panel-body">
13 <div class="field">
14 <div class="checkbox">
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 </div>
18 <div class="label">
19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 </div>
21 </div>
22 </div>
23 </div>
24 % endif
9 % endif
25
10
26 % if display_globals or repo_type in ['git', 'hg']:
11 % if display_globals or repo_type in ['git', 'hg']:
27 <div class="panel panel-default">
12 <div class="panel panel-default">
28 <div class="panel-heading" id="vcs-hooks-options">
13 <div class="panel-heading" id="vcs-hooks-options">
29 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"></a></h3>
14 <h3 class="panel-title">${_('Internal Hooks')}<a class="permalink" href="#vcs-hooks-options"></a></h3>
30 </div>
15 </div>
31 <div class="panel-body">
16 <div class="panel-body">
32 <div class="field">
17 <div class="field">
33 <div class="checkbox">
18 <div class="checkbox">
34 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
19 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
35 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
20 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
36 </div>
21 </div>
37
22
38 <div class="label">
23 <div class="label">
39 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
24 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
40 </div>
25 </div>
41 <div class="checkbox">
26 <div class="checkbox">
42 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
27 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
43 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
28 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
44 </div>
29 </div>
45 <div class="label">
30 <div class="label">
46 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
31 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
47 </div>
32 </div>
48 <div class="checkbox">
33 <div class="checkbox">
49 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
34 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
50 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
35 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
51 </div>
36 </div>
52 <div class="label">
37 <div class="label">
53 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
38 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
54 </div>
39 </div>
55 </div>
40 </div>
56 </div>
41 </div>
57 </div>
42 </div>
58 % endif
43 % endif
59
44
60 % if display_globals or repo_type in ['hg']:
45 % if display_globals or repo_type in ['hg']:
61 <div class="panel panel-default">
46 <div class="panel panel-default">
62 <div class="panel-heading" id="vcs-hg-options">
47 <div class="panel-heading" id="vcs-hg-options">
63 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"></a></h3>
48 <h3 class="panel-title">${_('Mercurial Settings')}<a class="permalink" href="#vcs-hg-options"></a></h3>
64 </div>
49 </div>
65 <div class="panel-body">
50 <div class="panel-body">
66 <div class="checkbox">
51 <div class="checkbox">
67 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
52 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
68 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
53 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
69 </div>
54 </div>
70 <div class="label">
55 <div class="label">
71 % if display_globals:
56 % if display_globals:
72 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
57 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
73 % else:
58 % else:
74 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
59 <span class="help-block">${_('Enable Largefiles extensions for this repository.')}</span>
75 % endif
60 % endif
76 </div>
61 </div>
77
62
78 % if display_globals:
63 % if display_globals:
79 <div class="field">
64 <div class="field">
80 <div class="input">
65 <div class="input">
81 ${h.text('largefiles_usercache' + suffix, size=59)}
66 ${h.text('largefiles_usercache' + suffix, size=59)}
82 </div>
67 </div>
83 </div>
68 </div>
84 <div class="label">
69 <div class="label">
85 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
70 <span class="help-block">${_('Filesystem location where Mercurial largefile objects should be stored.')}</span>
86 </div>
71 </div>
87 % endif
72 % endif
88
73
89 <div class="checkbox">
74 <div class="checkbox">
90 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
75 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
91 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
76 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
92 </div>
77 </div>
93 <div class="label">
78 <div class="label">
94 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
79 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
95 </div>
80 </div>
96
81
97 <div class="checkbox">
82 <div class="checkbox">
98 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
83 ${h.checkbox('extensions_evolve' + suffix, 'True', **kwargs)}
99 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
84 <label for="extensions_evolve${suffix}">${_('Enable Evolve and Topic extension')}</label>
100 </div>
85 </div>
101 <div class="label">
86 <div class="label">
102 % if display_globals:
87 % if display_globals:
103 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
88 <span class="help-block">${_('Enable Evolve and Topic extensions for all repositories.')}</span>
104 % else:
89 % else:
105 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
90 <span class="help-block">${_('Enable Evolve and Topic extensions for this repository.')}</span>
106 % endif
91 % endif
107 </div>
92 </div>
108
93
109 </div>
94 </div>
110 </div>
95 </div>
111 % endif
96 % endif
112
97
113 % if display_globals or repo_type in ['git']:
98 % if display_globals or repo_type in ['git']:
114 <div class="panel panel-default">
99 <div class="panel panel-default">
115 <div class="panel-heading" id="vcs-git-options">
100 <div class="panel-heading" id="vcs-git-options">
116 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"></a></h3>
101 <h3 class="panel-title">${_('Git Settings')}<a class="permalink" href="#vcs-git-options"></a></h3>
117 </div>
102 </div>
118 <div class="panel-body">
103 <div class="panel-body">
119 <div class="checkbox">
104 <div class="checkbox">
120 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
105 ${h.checkbox('vcs_git_lfs_enabled' + suffix, 'True', **kwargs)}
121 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
106 <label for="vcs_git_lfs_enabled${suffix}">${_('Enable lfs extension')}</label>
122 </div>
107 </div>
123 <div class="label">
108 <div class="label">
124 % if display_globals:
109 % if display_globals:
125 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
110 <span class="help-block">${_('Enable lfs extensions for all repositories.')}</span>
126 % else:
111 % else:
127 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
112 <span class="help-block">${_('Enable lfs extensions for this repository.')}</span>
128 % endif
113 % endif
129 </div>
114 </div>
130
115
131 % if display_globals:
116 % if display_globals:
132 <div class="field">
117 <div class="field">
133 <div class="input">
118 <div class="input">
134 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
119 ${h.text('vcs_git_lfs_store_location' + suffix, size=59)}
135 </div>
120 </div>
136 </div>
121 </div>
137 <div class="label">
122 <div class="label">
138 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
123 <span class="help-block">${_('Filesystem location where Git lfs objects should be stored.')}</span>
139 </div>
124 </div>
140 % endif
125 % endif
141 </div>
126 </div>
142 </div>
127 </div>
143 % endif
128 % endif
144
129
145 % if display_globals or repo_type in ['svn']:
130 % if display_globals or repo_type in ['svn']:
146 <div class="panel panel-default">
131 <div class="panel panel-default">
147 <div class="panel-heading" id="vcs-svn-options">
132 <div class="panel-heading" id="vcs-svn-options">
148 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"></a></h3>
133 <h3 class="panel-title">${_('Subversion Settings')}<a class="permalink" href="#vcs-svn-options"></a></h3>
149 </div>
134 </div>
150 <div class="panel-body">
135 <div class="panel-body">
151 % if display_globals:
136 % if display_globals:
152 <div class="field">
137 <div class="field">
153 <div class="content" >
138 <div class="content" >
154 <label>${_('mod_dav config')}</label><br/>
139 <label>${_('mod_dav config')}</label><br/>
155 <code>path: ${c.svn_config_path}</code>
140 <code>path: ${c.svn_config_path}</code>
156 </div>
141 </div>
157 <br/>
142 <br/>
158
143
159 <div>
144 <div>
160
145
161 % if c.svn_generate_config:
146 % if c.svn_generate_config:
162 <span class="buttons">
147 <span class="buttons">
163 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Re-generate Apache Config')}</button>
148 <button class="btn btn-primary" id="vcs_svn_generate_cfg">${_('Re-generate Apache Config')}</button>
164 </span>
149 </span>
165 % endif
150 % endif
166 </div>
151 </div>
167 </div>
152 </div>
168 % endif
153 % endif
169
154
170 <div class="field">
155 <div class="field">
171 <div class="content" >
156 <div class="content" >
172 <label>${_('Repository patterns')}</label><br/>
157 <label>${_('Repository patterns')}</label><br/>
173 </div>
158 </div>
174 </div>
159 </div>
175 <div class="label">
160 <div class="label">
176 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
161 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
177 </div>
162 </div>
178
163
179 <div class="field branch_patterns">
164 <div class="field branch_patterns">
180 <div class="input" >
165 <div class="input" >
181 <label>${_('Branches')}:</label><br/>
166 <label>${_('Branches')}:</label><br/>
182 </div>
167 </div>
183 % if svn_branch_patterns:
168 % if svn_branch_patterns:
184 % for branch in svn_branch_patterns:
169 % for branch in svn_branch_patterns:
185 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
170 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
186 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
171 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
187 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
172 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
188 % if kwargs.get('disabled') != 'disabled':
173 % if kwargs.get('disabled') != 'disabled':
189 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
174 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
190 ${_('Delete')}
175 ${_('Delete')}
191 </span>
176 </span>
192 % endif
177 % endif
193 </div>
178 </div>
194 % endfor
179 % endfor
195 %endif
180 %endif
196 </div>
181 </div>
197 % if kwargs.get('disabled') != 'disabled':
182 % if kwargs.get('disabled') != 'disabled':
198 <div class="field branch_patterns">
183 <div class="field branch_patterns">
199 <div class="input" >
184 <div class="input" >
200 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
185 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
201 </div>
186 </div>
202 </div>
187 </div>
203 % endif
188 % endif
204 <div class="field tag_patterns">
189 <div class="field tag_patterns">
205 <div class="input" >
190 <div class="input" >
206 <label>${_('Tags')}:</label><br/>
191 <label>${_('Tags')}:</label><br/>
207 </div>
192 </div>
208 % if svn_tag_patterns:
193 % if svn_tag_patterns:
209 % for tag in svn_tag_patterns:
194 % for tag in svn_tag_patterns:
210 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
195 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
211 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
196 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
212 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
197 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
213 % if kwargs.get('disabled') != 'disabled':
198 % if kwargs.get('disabled') != 'disabled':
214 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
199 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
215 ${_('Delete')}
200 ${_('Delete')}
216 </span>
201 </span>
217 %endif
202 %endif
218 </div>
203 </div>
219 % endfor
204 % endfor
220 % endif
205 % endif
221 </div>
206 </div>
222 % if kwargs.get('disabled') != 'disabled':
207 % if kwargs.get('disabled') != 'disabled':
223 <div class="field tag_patterns">
208 <div class="field tag_patterns">
224 <div class="input" >
209 <div class="input" >
225 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
210 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
226 </div>
211 </div>
227 </div>
212 </div>
228 %endif
213 %endif
229 </div>
214 </div>
230 </div>
215 </div>
231 % else:
216 % else:
232 ${h.hidden('new_svn_branch' + suffix, '')}
217 ${h.hidden('new_svn_branch' + suffix, '')}
233 ${h.hidden('new_svn_tag' + suffix, '')}
218 ${h.hidden('new_svn_tag' + suffix, '')}
234 % endif
219 % endif
235
220
236
221
237 % if display_globals or repo_type in ['hg', 'git']:
222 % if display_globals or repo_type in ['hg', 'git']:
238 <div class="panel panel-default">
223 <div class="panel panel-default">
239 <div class="panel-heading" id="vcs-pull-requests-options">
224 <div class="panel-heading" id="vcs-pull-requests-options">
240 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
225 <h3 class="panel-title">${_('Pull Request Settings')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
241 </div>
226 </div>
242 <div class="panel-body">
227 <div class="panel-body">
243 <div class="checkbox">
228 <div class="checkbox">
244 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
229 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
245 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
230 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
246 </div>
231 </div>
247 <div class="label">
232 <div class="label">
248 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
233 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
249 </div>
234 </div>
250 <div class="checkbox">
235 <div class="checkbox">
251 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
236 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
252 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
237 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
253 </div>
238 </div>
254 <div class="label">
239 <div class="label">
255 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
240 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
256 </div>
241 </div>
257 </div>
242 </div>
258 </div>
243 </div>
259 % endif
244 % endif
260
245
261 % if display_globals or repo_type in ['hg', 'git', 'svn']:
246 % if display_globals or repo_type in ['hg', 'git', 'svn']:
262 <div class="panel panel-default">
247 <div class="panel panel-default">
263 <div class="panel-heading" id="vcs-pull-requests-options">
248 <div class="panel-heading" id="vcs-pull-requests-options">
264 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
249 <h3 class="panel-title">${_('Diff cache')}<a class="permalink" href="#vcs-pull-requests-options"></a></h3>
265 </div>
250 </div>
266 <div class="panel-body">
251 <div class="panel-body">
267 <div class="checkbox">
252 <div class="checkbox">
268 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
253 ${h.checkbox('rhodecode_diff_cache' + suffix, 'True', **kwargs)}
269 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
254 <label for="rhodecode_diff_cache${suffix}">${_('Enable caching diffs for pull requests cache and commits')}</label>
270 </div>
255 </div>
271 </div>
256 </div>
272 </div>
257 </div>
273 % endif
258 % endif
274
259
275 % if display_globals or repo_type in ['hg',]:
260 % if display_globals or repo_type in ['hg',]:
276 <div class="panel panel-default">
261 <div class="panel panel-default">
277 <div class="panel-heading" id="vcs-pull-requests-options">
262 <div class="panel-heading" id="vcs-pull-requests-options">
278 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"></a></h3>
263 <h3 class="panel-title">${_('Mercurial Pull Request Settings')}<a class="permalink" href="#vcs-hg-pull-requests-options"></a></h3>
279 </div>
264 </div>
280 <div class="panel-body">
265 <div class="panel-body">
281 ## Specific HG settings
266 ## Specific HG settings
282 <div class="checkbox">
267 <div class="checkbox">
283 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
268 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
284 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
269 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
285 </div>
270 </div>
286 <div class="label">
271 <div class="label">
287 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
272 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
288 </div>
273 </div>
289
274
290 <div class="checkbox">
275 <div class="checkbox">
291 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
276 ${h.checkbox('rhodecode_hg_close_branch_before_merging' + suffix, 'True', **kwargs)}
292 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
277 <label for="rhodecode_hg_close_branch_before_merging{suffix}">${_('Close branch before merging it')}</label>
293 </div>
278 </div>
294 <div class="label">
279 <div class="label">
295 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
280 <span class="help-block">${_('Close branch before merging it into destination branch. No effect when rebase strategy is use.')}</span>
296 </div>
281 </div>
297
282
298
283
299 </div>
284 </div>
300 </div>
285 </div>
301 % endif
286 % endif
302
287
303 % if display_globals or repo_type in ['git']:
288 % if display_globals or repo_type in ['git']:
304 <div class="panel panel-default">
289 <div class="panel panel-default">
305 <div class="panel-heading" id="vcs-pull-requests-options">
290 <div class="panel-heading" id="vcs-pull-requests-options">
306 <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"></a></h3>
291 <h3 class="panel-title">${_('Git Pull Request Settings')}<a class="permalink" href="#vcs-git-pull-requests-options"></a></h3>
307 </div>
292 </div>
308 <div class="panel-body">
293 <div class="panel-body">
309 ## <div class="checkbox">
294 ## <div class="checkbox">
310 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
295 ## ${h.checkbox('rhodecode_git_use_rebase_for_merging' + suffix, 'True', **kwargs)}
311 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
296 ## <label for="rhodecode_git_use_rebase_for_merging${suffix}">${_('Use rebase as merge strategy')}</label>
312 ## </div>
297 ## </div>
313 ## <div class="label">
298 ## <div class="label">
314 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
299 ## <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
315 ## </div>
300 ## </div>
316
301
317 <div class="checkbox">
302 <div class="checkbox">
318 ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
303 ${h.checkbox('rhodecode_git_close_branch_before_merging' + suffix, 'True', **kwargs)}
319 <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
304 <label for="rhodecode_git_close_branch_before_merging{suffix}">${_('Delete branch after merging it')}</label>
320 </div>
305 </div>
321 <div class="label">
306 <div class="label">
322 <span class="help-block">${_('Delete branch after merging it into destination branch.')}</span>
307 <span class="help-block">${_('Delete branch after merging it into destination branch.')}</span>
323 </div>
308 </div>
324 </div>
309 </div>
325 </div>
310 </div>
326 % endif
311 % endif
327
312
328 <script type="text/javascript">
313 <script type="text/javascript">
329
314
330 $(document).ready(function() {
315 $(document).ready(function() {
331 /* On click handler for the `Generate Apache Config` button. It sends a
316 /* On click handler for the `Generate Apache Config` button. It sends a
332 POST request to trigger the (re)generation of the mod_dav_svn config. */
317 POST request to trigger the (re)generation of the mod_dav_svn config. */
333 $('#vcs_svn_generate_cfg').on('click', function(event) {
318 $('#vcs_svn_generate_cfg').on('click', function(event) {
334 event.preventDefault();
319 event.preventDefault();
335 var url = "${h.route_path('admin_settings_vcs_svn_generate_cfg')}";
320 var url = "${h.route_path('admin_settings_vcs_svn_generate_cfg')}";
336 var jqxhr = $.post(url, {'csrf_token': CSRF_TOKEN});
321 var jqxhr = $.post(url, {'csrf_token': CSRF_TOKEN});
337 jqxhr.done(function(data) {
322 jqxhr.done(function(data) {
338 $.Topic('/notifications').publish(data);
323 $.Topic('/notifications').publish(data);
339 });
324 });
340 });
325 });
341 });
326 });
342
327
343 </script>
328 </script>
344 </%def>
329 </%def>
345
330
@@ -1,155 +1,154 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import urllib.parse
20 import urllib.parse
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24 import simplejson as json
24 import simplejson as json
25
25
26 from rhodecode.lib.vcs.backends.base import Config
26 from rhodecode.lib.vcs.backends.base import Config
27 from rhodecode.tests.lib.middleware import mock_scm_app
27 from rhodecode.tests.lib.middleware import mock_scm_app
28 import rhodecode.lib.middleware.simplehg as simplehg
28 import rhodecode.lib.middleware.simplehg as simplehg
29
29
30
30
31 def get_environ(url):
31 def get_environ(url):
32 """Construct a minimum WSGI environ based on the URL."""
32 """Construct a minimum WSGI environ based on the URL."""
33 parsed_url = urllib.parse.urlparse(url)
33 parsed_url = urllib.parse.urlparse(url)
34 environ = {
34 environ = {
35 'PATH_INFO': parsed_url.path,
35 'PATH_INFO': parsed_url.path,
36 'QUERY_STRING': parsed_url.query,
36 'QUERY_STRING': parsed_url.query,
37 }
37 }
38
38
39 return environ
39 return environ
40
40
41
41
42 @pytest.mark.parametrize(
42 @pytest.mark.parametrize(
43 'url, expected_action',
43 'url, expected_action',
44 [
44 [
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
50 ('/foo/bar?cmd=batch', 'push'),
50 ('/foo/bar?cmd=batch', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
52 # Edge case: unknown argument: assume push
52 # Edge case: unknown argument: assume push
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
55 # Edge case: not cmd argument
55 # Edge case: not cmd argument
56 ('/foo/bar?key=tip', 'push'),
56 ('/foo/bar?key=tip', 'push'),
57 ])
57 ])
58 def test_get_action(url, expected_action, request_stub):
58 def test_get_action(url, expected_action, request_stub):
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
60 registry=request_stub.registry)
60 registry=request_stub.registry)
61 assert expected_action == app._get_action(get_environ(url))
61 assert expected_action == app._get_action(get_environ(url))
62
62
63
63
64 @pytest.mark.parametrize(
64 @pytest.mark.parametrize(
65 'environ, expected_xargs, expected_batch',
65 'environ, expected_xargs, expected_batch',
66 [
66 [
67 ({},
67 ({},
68 [''], ['push']),
68 [''], ['push']),
69
69
70 ({'HTTP_X_HGARG_1': ''},
70 ({'HTTP_X_HGARG_1': ''},
71 [''], ['push']),
71 [''], ['push']),
72
72
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
74 ['listkeys namespace=phases'], ['pull']),
74 ['listkeys namespace=phases'], ['pull']),
75
75
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
78
78
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
80 ['namespace=phases'], ['push']),
80 ['namespace=phases'], ['push']),
81
81
82 ])
82 ])
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
84 app = simplehg.SimpleHg
84 app = simplehg.SimpleHg
85
85
86 result = app._get_xarg_headers(environ)
86 result = app._get_xarg_headers(environ)
87 result_batch = app._get_batch_cmd(environ)
87 result_batch = app._get_batch_cmd(environ)
88 assert expected_xargs == result
88 assert expected_xargs == result
89 assert expected_batch == result_batch
89 assert expected_batch == result_batch
90
90
91
91
92 @pytest.mark.parametrize(
92 @pytest.mark.parametrize(
93 'url, expected_repo_name',
93 'url, expected_repo_name',
94 [
94 [
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
98 # Repos with trailing slashes.
98 # Repos with trailing slashes.
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
102 ])
102 ])
103 def test_get_repository_name(url, expected_repo_name, request_stub):
103 def test_get_repository_name(url, expected_repo_name, request_stub):
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
105 registry=request_stub.registry)
105 registry=request_stub.registry)
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
107
107
108
108
109 def test_get_config(user_util, baseapp, request_stub):
109 def test_get_config(user_util, baseapp, request_stub):
110 repo = user_util.create_repo(repo_type='git')
110 repo = user_util.create_repo(repo_type='git')
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
112 registry=request_stub.registry)
112 registry=request_stub.registry)
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
114
114
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
116
116
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
119 hg_config_org = config
119 hg_config_org = config
120
120
121 expected_config = [
121 expected_config = [
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
123 ('web', 'push_ssl', 'False'),
124 ('web', 'allow_push', '*'),
123 ('web', 'allow_push', '*'),
125 ('web', 'allow_archive', 'gz zip bz2'),
124 ('web', 'allow_archive', 'gz zip bz2'),
126 ('web', 'baseurl', '/'),
125 ('web', 'baseurl', '/'),
127 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
126 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
128 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
127 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
129 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
128 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
130 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
129 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
131 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
130 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
132 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
131 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
133 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
132 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
134 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
133 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
135 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
134 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
136 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
135 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
137 ('phases', 'publish', 'True'),
136 ('phases', 'publish', 'True'),
138 ('extensions', 'largefiles', ''),
137 ('extensions', 'largefiles', ''),
139 ('paths', '/', hg_config_org.get('paths', '/')),
138 ('paths', '/', hg_config_org.get('paths', '/')),
140 ('rhodecode', 'RC_SCM_DATA', '[["foo","FOO","bar","BAR"]]')
139 ('rhodecode', 'RC_SCM_DATA', '[["foo","FOO","bar","BAR"]]')
141 ]
140 ]
142 for entry in expected_config:
141 for entry in expected_config:
143 assert entry in hg_config
142 assert entry in hg_config
144
143
145
144
146 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
145 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
147 config = {
146 config = {
148 'auth_ret_code': '',
147 'auth_ret_code': '',
149 'base_path': '',
148 'base_path': '',
150 'vcs.scm_app_implementation':
149 'vcs.scm_app_implementation':
151 'rhodecode.tests.lib.middleware.mock_scm_app',
150 'rhodecode.tests.lib.middleware.mock_scm_app',
152 }
151 }
153 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
152 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
154 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
153 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
155 assert wsgi_app is mock_scm_app.mock_hg_wsgi
154 assert wsgi_app is mock_scm_app.mock_hg_wsgi
@@ -1,451 +1,448 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.str_utils import base64_to_str
23 from rhodecode.lib.str_utils import base64_to_str
24 from rhodecode.lib.utils2 import AttributeDict
24 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.tests.utils import CustomTestApp
25 from rhodecode.tests.utils import CustomTestApp
26
26
27 from rhodecode.lib.caching_query import FromCache
27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.middleware import simplevcs
28 from rhodecode.lib.middleware import simplevcs
29 from rhodecode.lib.middleware.https_fixup import HttpsFixup
29 from rhodecode.lib.middleware.https_fixup import HttpsFixup
30 from rhodecode.lib.middleware.utils import scm_app_http
30 from rhodecode.lib.middleware.utils import scm_app_http
31 from rhodecode.model.db import User, _hash_key
31 from rhodecode.model.db import User, _hash_key
32 from rhodecode.model.meta import Session, cache as db_cache
32 from rhodecode.model.meta import Session, cache as db_cache
33 from rhodecode.tests import (
33 from rhodecode.tests import (
34 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
34 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
35 from rhodecode.tests.lib.middleware import mock_scm_app
35 from rhodecode.tests.lib.middleware import mock_scm_app
36
36
37
37
38 class StubVCSController(simplevcs.SimpleVCS):
38 class StubVCSController(simplevcs.SimpleVCS):
39
39
40 SCM = 'hg'
40 SCM = 'hg'
41 stub_response_body = tuple()
41 stub_response_body = tuple()
42
42
43 def __init__(self, *args, **kwargs):
43 def __init__(self, *args, **kwargs):
44 super(StubVCSController, self).__init__(*args, **kwargs)
44 super(StubVCSController, self).__init__(*args, **kwargs)
45 self._action = 'pull'
45 self._action = 'pull'
46 self._is_shadow_repo_dir = True
46 self._is_shadow_repo_dir = True
47 self._name = HG_REPO
47 self._name = HG_REPO
48 self.set_repo_names(None)
48 self.set_repo_names(None)
49
49
50 @property
50 @property
51 def is_shadow_repo_dir(self):
51 def is_shadow_repo_dir(self):
52 return self._is_shadow_repo_dir
52 return self._is_shadow_repo_dir
53
53
54 def _get_repository_name(self, environ):
54 def _get_repository_name(self, environ):
55 return self._name
55 return self._name
56
56
57 def _get_action(self, environ):
57 def _get_action(self, environ):
58 return self._action
58 return self._action
59
59
60 def _create_wsgi_app(self, repo_path, repo_name, config):
60 def _create_wsgi_app(self, repo_path, repo_name, config):
61 def fake_app(environ, start_response):
61 def fake_app(environ, start_response):
62 headers = [
62 headers = [
63 ('Http-Accept', 'application/mercurial')
63 ('Http-Accept', 'application/mercurial')
64 ]
64 ]
65 start_response('200 OK', headers)
65 start_response('200 OK', headers)
66 return self.stub_response_body
66 return self.stub_response_body
67 return fake_app
67 return fake_app
68
68
69 def _create_config(self, extras, repo_name, scheme='http'):
69 def _create_config(self, extras, repo_name, scheme='http'):
70 return None
70 return None
71
71
72
72
73 @pytest.fixture()
73 @pytest.fixture()
74 def vcscontroller(baseapp, config_stub, request_stub):
74 def vcscontroller(baseapp, config_stub, request_stub):
75 from rhodecode.config.middleware import ce_auth_resources
75 from rhodecode.config.middleware import ce_auth_resources
76
76
77 config_stub.testing_securitypolicy()
77 config_stub.testing_securitypolicy()
78 config_stub.include('rhodecode.authentication')
78 config_stub.include('rhodecode.authentication')
79
79
80 for resource in ce_auth_resources:
80 for resource in ce_auth_resources:
81 config_stub.include(resource)
81 config_stub.include(resource)
82
82
83 controller = StubVCSController(
83 controller = StubVCSController(
84 baseapp.config.get_settings(), request_stub.registry)
84 baseapp.config.get_settings(), request_stub.registry)
85 app = HttpsFixup(controller, baseapp.config.get_settings())
85 app = HttpsFixup(controller, baseapp.config.get_settings())
86 app = CustomTestApp(app)
86 app = CustomTestApp(app)
87
87
88 _remove_default_user_from_query_cache()
88 _remove_default_user_from_query_cache()
89
89
90 # Sanity checks that things are set up correctly
90 # Sanity checks that things are set up correctly
91 app.get('/' + HG_REPO, status=200)
91 app.get('/' + HG_REPO, status=200)
92
92
93 app.controller = controller
93 app.controller = controller
94 return app
94 return app
95
95
96
96
97 def _remove_default_user_from_query_cache():
97 def _remove_default_user_from_query_cache():
98 user = User.get_default_user(cache=True)
98 user = User.get_default_user(cache=True)
99 query = Session().query(User).filter(User.username == user.username)
99 query = Session().query(User).filter(User.username == user.username)
100 query = query.options(
100 query = query.options(
101 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
101 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
102
102
103 db_cache.invalidate(
103 db_cache.invalidate(
104 query, {},
104 query, {},
105 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
105 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
106
106
107 Session().expire(user)
107 Session().expire(user)
108
108
109
109
110 def test_handles_exceptions_during_permissions_checks(
110 def test_handles_exceptions_during_permissions_checks(
111 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
111 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
112
112
113 test_password = 'qweqwe'
113 test_password = 'qweqwe'
114 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
114 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
115 test_username = test_user.username
115 test_username = test_user.username
116
116
117 enable_auth_plugins.enable([
117 enable_auth_plugins.enable([
118 'egg:rhodecode-enterprise-ce#headers',
118 'egg:rhodecode-enterprise-ce#headers',
119 'egg:rhodecode-enterprise-ce#token',
119 'egg:rhodecode-enterprise-ce#token',
120 'egg:rhodecode-enterprise-ce#rhodecode'],
120 'egg:rhodecode-enterprise-ce#rhodecode'],
121 override={
121 override={
122 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
122 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
123 })
123 })
124
124
125 user_and_pass = f'{test_username}:{test_password}'
125 user_and_pass = f'{test_username}:{test_password}'
126 auth_password = base64_to_str(user_and_pass)
126 auth_password = base64_to_str(user_and_pass)
127
127
128 extra_environ = {
128 extra_environ = {
129 'AUTH_TYPE': 'Basic',
129 'AUTH_TYPE': 'Basic',
130 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
130 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
131 'REMOTE_USER': test_username,
131 'REMOTE_USER': test_username,
132 }
132 }
133
133
134 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
134 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
135 vcscontroller.get('/', status=200, extra_environ=extra_environ)
135 vcscontroller.get('/', status=200, extra_environ=extra_environ)
136
136
137 # Simulate trouble during permission checks
137 # Simulate trouble during permission checks
138 with mock.patch('rhodecode.model.db.User.get_by_username',
138 with mock.patch('rhodecode.model.db.User.get_by_username',
139 side_effect=Exception('permission_error_test')) as get_user:
139 side_effect=Exception('permission_error_test')) as get_user:
140 # Verify that a correct 500 is returned and check that the expected
140 # Verify that a correct 500 is returned and check that the expected
141 # code path was hit.
141 # code path was hit.
142 vcscontroller.get('/', status=500, extra_environ=extra_environ)
142 vcscontroller.get('/', status=500, extra_environ=extra_environ)
143 assert get_user.called
143 assert get_user.called
144
144
145
145
146 class StubFailVCSController(simplevcs.SimpleVCS):
146 class StubFailVCSController(simplevcs.SimpleVCS):
147 def _handle_request(self, environ, start_response):
147 def _handle_request(self, environ, start_response):
148 raise Exception("BOOM")
148 raise Exception("BOOM")
149
149
150
150
151 @pytest.fixture(scope='module')
151 @pytest.fixture(scope='module')
152 def fail_controller(baseapp):
152 def fail_controller(baseapp):
153 controller = StubFailVCSController(
153 controller = StubFailVCSController(
154 baseapp.config.get_settings(), baseapp.config)
154 baseapp.config.get_settings(), baseapp.config)
155 controller = HttpsFixup(controller, baseapp.config.get_settings())
155 controller = HttpsFixup(controller, baseapp.config.get_settings())
156 controller = CustomTestApp(controller)
156 controller = CustomTestApp(controller)
157 return controller
157 return controller
158
158
159
159
160 def test_handles_exceptions_as_internal_server_error(fail_controller):
160 def test_handles_exceptions_as_internal_server_error(fail_controller):
161 fail_controller.get('/', status=500)
161 fail_controller.get('/', status=500)
162
162
163
163
164 def test_provides_traceback_for_appenlight(fail_controller):
164 def test_provides_traceback_for_appenlight(fail_controller):
165 response = fail_controller.get(
165 response = fail_controller.get(
166 '/', status=500, extra_environ={'appenlight.client': 'fake'})
166 '/', status=500, extra_environ={'appenlight.client': 'fake'})
167 assert 'appenlight.__traceback' in response.request.environ
167 assert 'appenlight.__traceback' in response.request.environ
168
168
169
169
170 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
170 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
171 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
171 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
172 assert controller.scm_app is scm_app_http
172 assert controller.scm_app is scm_app_http
173
173
174
174
175 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
175 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
176 config = baseapp.config.get_settings().copy()
176 config = baseapp.config.get_settings().copy()
177 config['vcs.scm_app_implementation'] = (
177 config['vcs.scm_app_implementation'] = (
178 'rhodecode.tests.lib.middleware.mock_scm_app')
178 'rhodecode.tests.lib.middleware.mock_scm_app')
179 controller = StubVCSController(config, request_stub.registry)
179 controller = StubVCSController(config, request_stub.registry)
180 assert controller.scm_app is mock_scm_app
180 assert controller.scm_app is mock_scm_app
181
181
182
182
183 @pytest.mark.parametrize('query_string, expected', [
183 @pytest.mark.parametrize('query_string, expected', [
184 ('cmd=stub_command', True),
184 ('cmd=stub_command', True),
185 ('cmd=listkeys', False),
185 ('cmd=listkeys', False),
186 ])
186 ])
187 def test_should_check_locking(query_string, expected):
187 def test_should_check_locking(query_string, expected):
188 result = simplevcs._should_check_locking(query_string)
188 result = simplevcs._should_check_locking(query_string)
189 assert result == expected
189 assert result == expected
190
190
191
191
192 class TestShadowRepoRegularExpression(object):
192 class TestShadowRepoRegularExpression(object):
193 pr_segment = 'pull-request'
193 pr_segment = 'pull-request'
194 shadow_segment = 'repository'
194 shadow_segment = 'repository'
195
195
196 @pytest.mark.parametrize('url, expected', [
196 @pytest.mark.parametrize('url, expected', [
197 # repo with/without groups
197 # repo with/without groups
198 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
198 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
199 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
199 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
200 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
200 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
201 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
201 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
202
202
203 # pull request ID
203 # pull request ID
204 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
204 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
205 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
205 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
206 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
206 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
207 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
207 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
208
208
209 # unicode
209 # unicode
210 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
210 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
211 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
211 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
212
212
213 # trailing/leading slash
213 # trailing/leading slash
214 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
214 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
215 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
215 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
216 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
216 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
217
217
218 # misc
218 # misc
219 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
219 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
220 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
220 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
221 ])
221 ])
222 def test_shadow_repo_regular_expression(self, url, expected):
222 def test_shadow_repo_regular_expression(self, url, expected):
223 from rhodecode.lib.middleware.simplevcs import SimpleVCS
223 from rhodecode.lib.middleware.simplevcs import SimpleVCS
224 url = url.format(
224 url = url.format(
225 pr_segment=self.pr_segment,
225 pr_segment=self.pr_segment,
226 shadow_segment=self.shadow_segment)
226 shadow_segment=self.shadow_segment)
227 match_obj = SimpleVCS.shadow_repo_re.match(url)
227 match_obj = SimpleVCS.shadow_repo_re.match(url)
228 assert (match_obj is not None) == expected
228 assert (match_obj is not None) == expected
229
229
230
230
231 @pytest.mark.backends('git', 'hg')
231 @pytest.mark.backends('git', 'hg')
232 class TestShadowRepoExposure(object):
232 class TestShadowRepoExposure(object):
233
233
234 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
234 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
235 self, baseapp, request_stub):
235 self, baseapp, request_stub):
236 """
236 """
237 Check that a pull action to a shadow repo is propagated to the
237 Check that a pull action to a shadow repo is propagated to the
238 underlying wsgi app.
238 underlying wsgi app.
239 """
239 """
240 controller = StubVCSController(
240 controller = StubVCSController(
241 baseapp.config.get_settings(), request_stub.registry)
241 baseapp.config.get_settings(), request_stub.registry)
242 controller._check_ssl = mock.Mock()
243 controller.is_shadow_repo = True
242 controller.is_shadow_repo = True
244 controller._action = 'pull'
243 controller._action = 'pull'
245 controller._is_shadow_repo_dir = True
244 controller._is_shadow_repo_dir = True
246 controller.stub_response_body = (b'dummy body value',)
245 controller.stub_response_body = (b'dummy body value',)
247 controller._get_default_cache_ttl = mock.Mock(
246 controller._get_default_cache_ttl = mock.Mock(
248 return_value=(False, 0))
247 return_value=(False, 0))
249
248
250 environ_stub = {
249 environ_stub = {
251 'HTTP_HOST': 'test.example.com',
250 'HTTP_HOST': 'test.example.com',
252 'HTTP_ACCEPT': 'application/mercurial',
251 'HTTP_ACCEPT': 'application/mercurial',
253 'REQUEST_METHOD': 'GET',
252 'REQUEST_METHOD': 'GET',
254 'wsgi.url_scheme': 'http',
253 'wsgi.url_scheme': 'http',
255 }
254 }
256
255
257 response = controller(environ_stub, mock.Mock())
256 response = controller(environ_stub, mock.Mock())
258 response_body = b''.join(response)
257 response_body = b''.join(response)
259
258
260 # Assert that we got the response from the wsgi app.
259 # Assert that we got the response from the wsgi app.
261 assert response_body == b''.join(controller.stub_response_body)
260 assert response_body == b''.join(controller.stub_response_body)
262
261
263 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
262 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
264 """
263 """
265 Check that a pull action to a shadow repo is propagated to the
264 Check that a pull action to a shadow repo is propagated to the
266 underlying wsgi app.
265 underlying wsgi app.
267 """
266 """
268 controller = StubVCSController(
267 controller = StubVCSController(
269 baseapp.config.get_settings(), request_stub.registry)
268 baseapp.config.get_settings(), request_stub.registry)
270 controller._check_ssl = mock.Mock()
271 controller.is_shadow_repo = True
269 controller.is_shadow_repo = True
272 controller._action = 'pull'
270 controller._action = 'pull'
273 controller._is_shadow_repo_dir = False
271 controller._is_shadow_repo_dir = False
274 controller.stub_response_body = (b'dummy body value',)
272 controller.stub_response_body = (b'dummy body value',)
275 environ_stub = {
273 environ_stub = {
276 'HTTP_HOST': 'test.example.com',
274 'HTTP_HOST': 'test.example.com',
277 'HTTP_ACCEPT': 'application/mercurial',
275 'HTTP_ACCEPT': 'application/mercurial',
278 'REQUEST_METHOD': 'GET',
276 'REQUEST_METHOD': 'GET',
279 'wsgi.url_scheme': 'http',
277 'wsgi.url_scheme': 'http',
280 }
278 }
281
279
282 response = controller(environ_stub, mock.Mock())
280 response = controller(environ_stub, mock.Mock())
283 response_body = b''.join(response)
281 response_body = b''.join(response)
284
282
285 # Assert that we got the response from the wsgi app.
283 # Assert that we got the response from the wsgi app.
286 assert b'404 Not Found' in response_body
284 assert b'404 Not Found' in response_body
287
285
288 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
286 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
289 """
287 """
290 Check that a push action to a shadow repo is aborted.
288 Check that a push action to a shadow repo is aborted.
291 """
289 """
292 controller = StubVCSController(
290 controller = StubVCSController(
293 baseapp.config.get_settings(), request_stub.registry)
291 baseapp.config.get_settings(), request_stub.registry)
294 controller._check_ssl = mock.Mock()
295 controller.is_shadow_repo = True
292 controller.is_shadow_repo = True
296 controller._action = 'push'
293 controller._action = 'push'
297 controller.stub_response_body = (b'dummy body value',)
294 controller.stub_response_body = (b'dummy body value',)
298 environ_stub = {
295 environ_stub = {
299 'HTTP_HOST': 'test.example.com',
296 'HTTP_HOST': 'test.example.com',
300 'HTTP_ACCEPT': 'application/mercurial',
297 'HTTP_ACCEPT': 'application/mercurial',
301 'REQUEST_METHOD': 'GET',
298 'REQUEST_METHOD': 'GET',
302 'wsgi.url_scheme': 'http',
299 'wsgi.url_scheme': 'http',
303 }
300 }
304
301
305 response = controller(environ_stub, mock.Mock())
302 response = controller(environ_stub, mock.Mock())
306 response_body = b''.join(response)
303 response_body = b''.join(response)
307
304
308 assert response_body != controller.stub_response_body
305 assert response_body != controller.stub_response_body
309 # Assert that a 406 error is returned.
306 # Assert that a 406 error is returned.
310 assert b'406 Not Acceptable' in response_body
307 assert b'406 Not Acceptable' in response_body
311
308
312 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
309 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
313 """
310 """
314 Check that the set_repo_names method sets all names to the one returned
311 Check that the set_repo_names method sets all names to the one returned
315 by the _get_repository_name method on a request to a non shadow repo.
312 by the _get_repository_name method on a request to a non shadow repo.
316 """
313 """
317 environ_stub = {}
314 environ_stub = {}
318 controller = StubVCSController(
315 controller = StubVCSController(
319 baseapp.config.get_settings(), request_stub.registry)
316 baseapp.config.get_settings(), request_stub.registry)
320 controller._name = 'RepoGroup/MyRepo'
317 controller._name = 'RepoGroup/MyRepo'
321 controller.set_repo_names(environ_stub)
318 controller.set_repo_names(environ_stub)
322 assert not controller.is_shadow_repo
319 assert not controller.is_shadow_repo
323 assert (controller.url_repo_name ==
320 assert (controller.url_repo_name ==
324 controller.acl_repo_name ==
321 controller.acl_repo_name ==
325 controller.vcs_repo_name ==
322 controller.vcs_repo_name ==
326 controller._get_repository_name(environ_stub))
323 controller._get_repository_name(environ_stub))
327
324
328 def test_set_repo_names_with_shadow(
325 def test_set_repo_names_with_shadow(
329 self, baseapp, pr_util, config_stub, request_stub):
326 self, baseapp, pr_util, config_stub, request_stub):
330 """
327 """
331 Check that the set_repo_names method sets correct names on a request
328 Check that the set_repo_names method sets correct names on a request
332 to a shadow repo.
329 to a shadow repo.
333 """
330 """
334 from rhodecode.model.pull_request import PullRequestModel
331 from rhodecode.model.pull_request import PullRequestModel
335
332
336 pull_request = pr_util.create_pull_request()
333 pull_request = pr_util.create_pull_request()
337 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
334 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
338 target=pull_request.target_repo.repo_name,
335 target=pull_request.target_repo.repo_name,
339 pr_id=pull_request.pull_request_id,
336 pr_id=pull_request.pull_request_id,
340 pr_segment=TestShadowRepoRegularExpression.pr_segment,
337 pr_segment=TestShadowRepoRegularExpression.pr_segment,
341 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
338 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
342 controller = StubVCSController(
339 controller = StubVCSController(
343 baseapp.config.get_settings(), request_stub.registry)
340 baseapp.config.get_settings(), request_stub.registry)
344 controller._name = shadow_url
341 controller._name = shadow_url
345 controller.set_repo_names({})
342 controller.set_repo_names({})
346
343
347 # Get file system path to shadow repo for assertions.
344 # Get file system path to shadow repo for assertions.
348 workspace_id = PullRequestModel()._workspace_id(pull_request)
345 workspace_id = PullRequestModel()._workspace_id(pull_request)
349 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
346 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
350
347
351 assert controller.vcs_repo_name == vcs_repo_name
348 assert controller.vcs_repo_name == vcs_repo_name
352 assert controller.url_repo_name == shadow_url
349 assert controller.url_repo_name == shadow_url
353 assert controller.acl_repo_name == pull_request.target_repo.repo_name
350 assert controller.acl_repo_name == pull_request.target_repo.repo_name
354 assert controller.is_shadow_repo
351 assert controller.is_shadow_repo
355
352
356 def test_set_repo_names_with_shadow_but_missing_pr(
353 def test_set_repo_names_with_shadow_but_missing_pr(
357 self, baseapp, pr_util, config_stub, request_stub):
354 self, baseapp, pr_util, config_stub, request_stub):
358 """
355 """
359 Checks that the set_repo_names method enforces matching target repos
356 Checks that the set_repo_names method enforces matching target repos
360 and pull request IDs.
357 and pull request IDs.
361 """
358 """
362 pull_request = pr_util.create_pull_request()
359 pull_request = pr_util.create_pull_request()
363 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
360 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
364 target=pull_request.target_repo.repo_name,
361 target=pull_request.target_repo.repo_name,
365 pr_id=999999999,
362 pr_id=999999999,
366 pr_segment=TestShadowRepoRegularExpression.pr_segment,
363 pr_segment=TestShadowRepoRegularExpression.pr_segment,
367 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
364 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
368 controller = StubVCSController(
365 controller = StubVCSController(
369 baseapp.config.get_settings(), request_stub.registry)
366 baseapp.config.get_settings(), request_stub.registry)
370 controller._name = shadow_url
367 controller._name = shadow_url
371 controller.set_repo_names({})
368 controller.set_repo_names({})
372
369
373 assert not controller.is_shadow_repo
370 assert not controller.is_shadow_repo
374 assert (controller.url_repo_name ==
371 assert (controller.url_repo_name ==
375 controller.acl_repo_name ==
372 controller.acl_repo_name ==
376 controller.vcs_repo_name)
373 controller.vcs_repo_name)
377
374
378
375
379 @pytest.mark.usefixtures('baseapp')
376 @pytest.mark.usefixtures('baseapp')
380 class TestGenerateVcsResponse(object):
377 class TestGenerateVcsResponse(object):
381
378
382 def test_ensures_that_start_response_is_called_early_enough(self):
379 def test_ensures_that_start_response_is_called_early_enough(self):
383 self.call_controller_with_response_body(iter(['a', 'b']))
380 self.call_controller_with_response_body(iter(['a', 'b']))
384 assert self.start_response.called
381 assert self.start_response.called
385
382
386 def test_invalidates_cache_after_body_is_consumed(self):
383 def test_invalidates_cache_after_body_is_consumed(self):
387 result = self.call_controller_with_response_body(iter(['a', 'b']))
384 result = self.call_controller_with_response_body(iter(['a', 'b']))
388 assert not self.was_cache_invalidated()
385 assert not self.was_cache_invalidated()
389 # Consume the result
386 # Consume the result
390 list(result)
387 list(result)
391 assert self.was_cache_invalidated()
388 assert self.was_cache_invalidated()
392
389
393 def test_raises_unknown_exceptions(self):
390 def test_raises_unknown_exceptions(self):
394 result = self.call_controller_with_response_body(
391 result = self.call_controller_with_response_body(
395 self.raise_result_iter(vcs_kind='unknown'))
392 self.raise_result_iter(vcs_kind='unknown'))
396 with pytest.raises(Exception):
393 with pytest.raises(Exception):
397 list(result)
394 list(result)
398
395
399 def call_controller_with_response_body(self, response_body):
396 def call_controller_with_response_body(self, response_body):
400 settings = {
397 settings = {
401 'base_path': 'fake_base_path',
398 'base_path': 'fake_base_path',
402 'vcs.hooks.protocol.v2': 'celery',
399 'vcs.hooks.protocol.v2': 'celery',
403 'vcs.hooks.direct_calls': False,
400 'vcs.hooks.direct_calls': False,
404 }
401 }
405 registry = AttributeDict()
402 registry = AttributeDict()
406 controller = StubVCSController(settings, registry)
403 controller = StubVCSController(settings, registry)
407 controller._invalidate_cache = mock.Mock()
404 controller._invalidate_cache = mock.Mock()
408 controller.stub_response_body = response_body
405 controller.stub_response_body = response_body
409 self.start_response = mock.Mock()
406 self.start_response = mock.Mock()
410 result = controller._generate_vcs_response(
407 result = controller._generate_vcs_response(
411 environ={}, start_response=self.start_response,
408 environ={}, start_response=self.start_response,
412 repo_path='fake_repo_path',
409 repo_path='fake_repo_path',
413 extras={}, action='push')
410 extras={}, action='push')
414 self.controller = controller
411 self.controller = controller
415 return result
412 return result
416
413
417 def raise_result_iter(self, vcs_kind='repo_locked'):
414 def raise_result_iter(self, vcs_kind='repo_locked'):
418 """
415 """
419 Simulates an exception due to a vcs raised exception if kind vcs_kind
416 Simulates an exception due to a vcs raised exception if kind vcs_kind
420 """
417 """
421 raise self.vcs_exception(vcs_kind=vcs_kind)
418 raise self.vcs_exception(vcs_kind=vcs_kind)
422 yield "never_reached"
419 yield "never_reached"
423
420
424 def vcs_exception(self, vcs_kind='repo_locked'):
421 def vcs_exception(self, vcs_kind='repo_locked'):
425 locked_exception = Exception('TEST_MESSAGE')
422 locked_exception = Exception('TEST_MESSAGE')
426 locked_exception._vcs_kind = vcs_kind
423 locked_exception._vcs_kind = vcs_kind
427 return locked_exception
424 return locked_exception
428
425
429 def was_cache_invalidated(self):
426 def was_cache_invalidated(self):
430 return self.controller._invalidate_cache.called
427 return self.controller._invalidate_cache.called
431
428
432
429
433 class TestInitializeGenerator(object):
430 class TestInitializeGenerator(object):
434
431
435 def test_drains_first_element(self):
432 def test_drains_first_element(self):
436 gen = self.factory(['__init__', 1, 2])
433 gen = self.factory(['__init__', 1, 2])
437 result = list(gen)
434 result = list(gen)
438 assert result == [1, 2]
435 assert result == [1, 2]
439
436
440 @pytest.mark.parametrize('values', [
437 @pytest.mark.parametrize('values', [
441 [],
438 [],
442 [1, 2],
439 [1, 2],
443 ])
440 ])
444 def test_raises_value_error(self, values):
441 def test_raises_value_error(self, values):
445 with pytest.raises(ValueError):
442 with pytest.raises(ValueError):
446 self.factory(values)
443 self.factory(values)
447
444
448 @simplevcs.initialize_generator
445 @simplevcs.initialize_generator
449 def factory(self, iterable):
446 def factory(self, iterable):
450 for elem in iterable:
447 for elem in iterable:
451 yield elem
448 yield elem
@@ -1,1114 +1,1103 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import str2bool
23 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.settings import VcsSettingsModel, UiSetting
25 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26
26
27
27
28 HOOKS_FORM_DATA = {
28 HOOKS_FORM_DATA = {
29 'hooks_changegroup_repo_size': True,
29 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_push_logger': True,
30 'hooks_changegroup_push_logger': True,
31 'hooks_outgoing_pull_logger': True
31 'hooks_outgoing_pull_logger': True
32 }
32 }
33
33
34 SVN_FORM_DATA = {
34 SVN_FORM_DATA = {
35 'new_svn_branch': 'test-branch',
35 'new_svn_branch': 'test-branch',
36 'new_svn_tag': 'test-tag'
36 'new_svn_tag': 'test-tag'
37 }
37 }
38
38
39 GENERAL_FORM_DATA = {
39 GENERAL_FORM_DATA = {
40 'rhodecode_pr_merge_enabled': True,
40 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_use_outdated_comments': True,
41 'rhodecode_use_outdated_comments': True,
42 'rhodecode_hg_use_rebase_for_merging': True,
42 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_close_branch_before_merging': True,
43 'rhodecode_hg_close_branch_before_merging': True,
44 'rhodecode_git_use_rebase_for_merging': True,
44 'rhodecode_git_use_rebase_for_merging': True,
45 'rhodecode_git_close_branch_before_merging': True,
45 'rhodecode_git_close_branch_before_merging': True,
46 'rhodecode_diff_cache': True,
46 'rhodecode_diff_cache': True,
47 }
47 }
48
48
49
49
50 class TestInheritGlobalSettingsProperty(object):
50 class TestInheritGlobalSettingsProperty(object):
51 def test_get_raises_exception_when_repository_not_specified(self):
51 def test_get_raises_exception_when_repository_not_specified(self):
52 model = VcsSettingsModel()
52 model = VcsSettingsModel()
53 with pytest.raises(Exception) as exc_info:
53 with pytest.raises(Exception) as exc_info:
54 model.inherit_global_settings
54 model.inherit_global_settings
55 assert str(exc_info.value) == 'Repository is not specified'
55 assert str(exc_info.value) == 'Repository is not specified'
56
56
57 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
57 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
58 model = VcsSettingsModel(repo=repo_stub.repo_name)
58 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 assert model.inherit_global_settings is True
59 assert model.inherit_global_settings is True
60
60
61 def test_value_is_returned(self, repo_stub, settings_util):
61 def test_value_is_returned(self, repo_stub, settings_util):
62 model = VcsSettingsModel(repo=repo_stub.repo_name)
62 model = VcsSettingsModel(repo=repo_stub.repo_name)
63 settings_util.create_repo_rhodecode_setting(
63 settings_util.create_repo_rhodecode_setting(
64 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
64 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
65 assert model.inherit_global_settings is False
65 assert model.inherit_global_settings is False
66
66
67 def test_value_is_set(self, repo_stub):
67 def test_value_is_set(self, repo_stub):
68 model = VcsSettingsModel(repo=repo_stub.repo_name)
68 model = VcsSettingsModel(repo=repo_stub.repo_name)
69 model.inherit_global_settings = False
69 model.inherit_global_settings = False
70 setting = model.repo_settings.get_setting_by_name(
70 setting = model.repo_settings.get_setting_by_name(
71 VcsSettingsModel.INHERIT_SETTINGS)
71 VcsSettingsModel.INHERIT_SETTINGS)
72 try:
72 try:
73 assert setting.app_settings_type == 'bool'
73 assert setting.app_settings_type == 'bool'
74 assert setting.app_settings_value is False
74 assert setting.app_settings_value is False
75 finally:
75 finally:
76 Session().delete(setting)
76 Session().delete(setting)
77 Session().commit()
77 Session().commit()
78
78
79 def test_set_raises_exception_when_repository_not_specified(self):
79 def test_set_raises_exception_when_repository_not_specified(self):
80 model = VcsSettingsModel()
80 model = VcsSettingsModel()
81 with pytest.raises(Exception) as exc_info:
81 with pytest.raises(Exception) as exc_info:
82 model.inherit_global_settings = False
82 model.inherit_global_settings = False
83 assert str(exc_info.value) == 'Repository is not specified'
83 assert str(exc_info.value) == 'Repository is not specified'
84
84
85
85
86 class TestVcsSettingsModel(object):
86 class TestVcsSettingsModel(object):
87 def test_global_svn_branch_patterns(self):
87 def test_global_svn_branch_patterns(self):
88 model = VcsSettingsModel()
88 model = VcsSettingsModel()
89 expected_result = {'test': 'test'}
89 expected_result = {'test': 'test'}
90 with mock.patch.object(model, 'global_settings') as settings_mock:
90 with mock.patch.object(model, 'global_settings') as settings_mock:
91 get_settings = settings_mock.get_ui_by_section
91 get_settings = settings_mock.get_ui_by_section
92 get_settings.return_value = expected_result
92 get_settings.return_value = expected_result
93 settings_mock.return_value = expected_result
93 settings_mock.return_value = expected_result
94 result = model.get_global_svn_branch_patterns()
94 result = model.get_global_svn_branch_patterns()
95
95
96 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
96 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
97 assert expected_result == result
97 assert expected_result == result
98
98
99 def test_repo_svn_branch_patterns(self):
99 def test_repo_svn_branch_patterns(self):
100 model = VcsSettingsModel()
100 model = VcsSettingsModel()
101 expected_result = {'test': 'test'}
101 expected_result = {'test': 'test'}
102 with mock.patch.object(model, 'repo_settings') as settings_mock:
102 with mock.patch.object(model, 'repo_settings') as settings_mock:
103 get_settings = settings_mock.get_ui_by_section
103 get_settings = settings_mock.get_ui_by_section
104 get_settings.return_value = expected_result
104 get_settings.return_value = expected_result
105 settings_mock.return_value = expected_result
105 settings_mock.return_value = expected_result
106 result = model.get_repo_svn_branch_patterns()
106 result = model.get_repo_svn_branch_patterns()
107
107
108 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
108 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
109 assert expected_result == result
109 assert expected_result == result
110
110
111 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
111 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
112 self):
112 self):
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 with pytest.raises(Exception) as exc_info:
114 with pytest.raises(Exception) as exc_info:
115 model.get_repo_svn_branch_patterns()
115 model.get_repo_svn_branch_patterns()
116 assert str(exc_info.value) == 'Repository is not specified'
116 assert str(exc_info.value) == 'Repository is not specified'
117
117
118 def test_global_svn_tag_patterns(self):
118 def test_global_svn_tag_patterns(self):
119 model = VcsSettingsModel()
119 model = VcsSettingsModel()
120 expected_result = {'test': 'test'}
120 expected_result = {'test': 'test'}
121 with mock.patch.object(model, 'global_settings') as settings_mock:
121 with mock.patch.object(model, 'global_settings') as settings_mock:
122 get_settings = settings_mock.get_ui_by_section
122 get_settings = settings_mock.get_ui_by_section
123 get_settings.return_value = expected_result
123 get_settings.return_value = expected_result
124 settings_mock.return_value = expected_result
124 settings_mock.return_value = expected_result
125 result = model.get_global_svn_tag_patterns()
125 result = model.get_global_svn_tag_patterns()
126
126
127 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
127 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
128 assert expected_result == result
128 assert expected_result == result
129
129
130 def test_repo_svn_tag_patterns(self):
130 def test_repo_svn_tag_patterns(self):
131 model = VcsSettingsModel()
131 model = VcsSettingsModel()
132 expected_result = {'test': 'test'}
132 expected_result = {'test': 'test'}
133 with mock.patch.object(model, 'repo_settings') as settings_mock:
133 with mock.patch.object(model, 'repo_settings') as settings_mock:
134 get_settings = settings_mock.get_ui_by_section
134 get_settings = settings_mock.get_ui_by_section
135 get_settings.return_value = expected_result
135 get_settings.return_value = expected_result
136 settings_mock.return_value = expected_result
136 settings_mock.return_value = expected_result
137 result = model.get_repo_svn_tag_patterns()
137 result = model.get_repo_svn_tag_patterns()
138
138
139 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
139 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
140 assert expected_result == result
140 assert expected_result == result
141
141
142 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
142 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
143 model = VcsSettingsModel()
143 model = VcsSettingsModel()
144 with pytest.raises(Exception) as exc_info:
144 with pytest.raises(Exception) as exc_info:
145 model.get_repo_svn_tag_patterns()
145 model.get_repo_svn_tag_patterns()
146 assert str(exc_info.value) == 'Repository is not specified'
146 assert str(exc_info.value) == 'Repository is not specified'
147
147
148 def test_get_global_settings(self):
148 def test_get_global_settings(self):
149 expected_result = {'test': 'test'}
149 expected_result = {'test': 'test'}
150 model = VcsSettingsModel()
150 model = VcsSettingsModel()
151 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
151 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
152 collect_mock.return_value = expected_result
152 collect_mock.return_value = expected_result
153 result = model.get_global_settings()
153 result = model.get_global_settings()
154
154
155 collect_mock.assert_called_once_with(global_=True)
155 collect_mock.assert_called_once_with(global_=True)
156 assert result == expected_result
156 assert result == expected_result
157
157
158 def test_get_repo_settings(self, repo_stub):
158 def test_get_repo_settings(self, repo_stub):
159 model = VcsSettingsModel(repo=repo_stub.repo_name)
159 model = VcsSettingsModel(repo=repo_stub.repo_name)
160 expected_result = {'test': 'test'}
160 expected_result = {'test': 'test'}
161 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
161 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
162 collect_mock.return_value = expected_result
162 collect_mock.return_value = expected_result
163 result = model.get_repo_settings()
163 result = model.get_repo_settings()
164
164
165 collect_mock.assert_called_once_with(global_=False)
165 collect_mock.assert_called_once_with(global_=False)
166 assert result == expected_result
166 assert result == expected_result
167
167
168 @pytest.mark.parametrize('settings, global_', [
168 @pytest.mark.parametrize('settings, global_', [
169 ('global_settings', True),
169 ('global_settings', True),
170 ('repo_settings', False)
170 ('repo_settings', False)
171 ])
171 ])
172 def test_collect_all_settings(self, settings, global_):
172 def test_collect_all_settings(self, settings, global_):
173 model = VcsSettingsModel()
173 model = VcsSettingsModel()
174 result_mock = self._mock_result()
174 result_mock = self._mock_result()
175
175
176 settings_patch = mock.patch.object(model, settings)
176 settings_patch = mock.patch.object(model, settings)
177 with settings_patch as settings_mock:
177 with settings_patch as settings_mock:
178 settings_mock.get_ui_by_section_and_key.return_value = result_mock
178 settings_mock.get_ui_by_section_and_key.return_value = result_mock
179 settings_mock.get_setting_by_name.return_value = result_mock
179 settings_mock.get_setting_by_name.return_value = result_mock
180 result = model._collect_all_settings(global_=global_)
180 result = model._collect_all_settings(global_=global_)
181
181
182 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
182 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
183 self._assert_get_settings_calls(
183 self._assert_get_settings_calls(
184 settings_mock, ui_settings, model.GENERAL_SETTINGS)
184 settings_mock, ui_settings, model.GENERAL_SETTINGS)
185 self._assert_collect_all_settings_result(
185 self._assert_collect_all_settings_result(
186 ui_settings, model.GENERAL_SETTINGS, result)
186 ui_settings, model.GENERAL_SETTINGS, result)
187
187
188 @pytest.mark.parametrize('settings, global_', [
188 @pytest.mark.parametrize('settings, global_', [
189 ('global_settings', True),
189 ('global_settings', True),
190 ('repo_settings', False)
190 ('repo_settings', False)
191 ])
191 ])
192 def test_collect_all_settings_without_empty_value(self, settings, global_):
192 def test_collect_all_settings_without_empty_value(self, settings, global_):
193 model = VcsSettingsModel()
193 model = VcsSettingsModel()
194
194
195 settings_patch = mock.patch.object(model, settings)
195 settings_patch = mock.patch.object(model, settings)
196 with settings_patch as settings_mock:
196 with settings_patch as settings_mock:
197 settings_mock.get_ui_by_section_and_key.return_value = None
197 settings_mock.get_ui_by_section_and_key.return_value = None
198 settings_mock.get_setting_by_name.return_value = None
198 settings_mock.get_setting_by_name.return_value = None
199 result = model._collect_all_settings(global_=global_)
199 result = model._collect_all_settings(global_=global_)
200
200
201 assert result == {}
201 assert result == {}
202
202
203 def _mock_result(self):
203 def _mock_result(self):
204 result_mock = mock.Mock()
204 result_mock = mock.Mock()
205 result_mock.ui_value = 'ui_value'
205 result_mock.ui_value = 'ui_value'
206 result_mock.ui_active = True
206 result_mock.ui_active = True
207 result_mock.app_settings_value = 'setting_value'
207 result_mock.app_settings_value = 'setting_value'
208 return result_mock
208 return result_mock
209
209
210 def _assert_get_settings_calls(
210 def _assert_get_settings_calls(
211 self, settings_mock, ui_settings, general_settings):
211 self, settings_mock, ui_settings, general_settings):
212 assert (
212 assert (
213 settings_mock.get_ui_by_section_and_key.call_count ==
213 settings_mock.get_ui_by_section_and_key.call_count ==
214 len(ui_settings))
214 len(ui_settings))
215 assert (
215 assert (
216 settings_mock.get_setting_by_name.call_count ==
216 settings_mock.get_setting_by_name.call_count ==
217 len(general_settings))
217 len(general_settings))
218
218
219 for section, key in ui_settings:
219 for section, key in ui_settings:
220 expected_call = mock.call(section, key)
220 expected_call = mock.call(section, key)
221 assert (
221 assert (
222 expected_call in
222 expected_call in
223 settings_mock.get_ui_by_section_and_key.call_args_list)
223 settings_mock.get_ui_by_section_and_key.call_args_list)
224
224
225 for name in general_settings:
225 for name in general_settings:
226 expected_call = mock.call(name)
226 expected_call = mock.call(name)
227 assert (
227 assert (
228 expected_call in
228 expected_call in
229 settings_mock.get_setting_by_name.call_args_list)
229 settings_mock.get_setting_by_name.call_args_list)
230
230
231 def _assert_collect_all_settings_result(
231 def _assert_collect_all_settings_result(
232 self, ui_settings, general_settings, result):
232 self, ui_settings, general_settings, result):
233 expected_result = {}
233 expected_result = {}
234 for section, key in ui_settings:
234 for section, key in ui_settings:
235 key = '{}_{}'.format(section, key.replace('.', '_'))
235 key = '{}_{}'.format(section, key.replace('.', '_'))
236
236
237 if section in ('extensions', 'hooks'):
237 if section in ('extensions', 'hooks'):
238 value = True
238 value = True
239 elif key in ['vcs_git_lfs_enabled']:
239 elif key in ['vcs_git_lfs_enabled']:
240 value = True
240 value = True
241 else:
241 else:
242 value = 'ui_value'
242 value = 'ui_value'
243 expected_result[key] = value
243 expected_result[key] = value
244
244
245 for name in general_settings:
245 for name in general_settings:
246 key = 'rhodecode_' + name
246 key = 'rhodecode_' + name
247 expected_result[key] = 'setting_value'
247 expected_result[key] = 'setting_value'
248
248
249 assert expected_result == result
249 assert expected_result == result
250
250
251
251
252 class TestCreateOrUpdateRepoHookSettings(object):
252 class TestCreateOrUpdateRepoHookSettings(object):
253 def test_create_when_no_repo_object_found(self, repo_stub):
253 def test_create_when_no_repo_object_found(self, repo_stub):
254 model = VcsSettingsModel(repo=repo_stub.repo_name)
254 model = VcsSettingsModel(repo=repo_stub.repo_name)
255
255
256 self._create_settings(model, HOOKS_FORM_DATA)
256 self._create_settings(model, HOOKS_FORM_DATA)
257
257
258 cleanup = []
258 cleanup = []
259 try:
259 try:
260 for section, key in model.HOOKS_SETTINGS:
260 for section, key in model.HOOKS_SETTINGS:
261 ui = model.repo_settings.get_ui_by_section_and_key(
261 ui = model.repo_settings.get_ui_by_section_and_key(
262 section, key)
262 section, key)
263 assert ui.ui_active is True
263 assert ui.ui_active is True
264 cleanup.append(ui)
264 cleanup.append(ui)
265 finally:
265 finally:
266 for ui in cleanup:
266 for ui in cleanup:
267 Session().delete(ui)
267 Session().delete(ui)
268 Session().commit()
268 Session().commit()
269
269
270 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
270 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
271 model = VcsSettingsModel(repo=repo_stub.repo_name)
271 model = VcsSettingsModel(repo=repo_stub.repo_name)
272
272
273 deleted_key = 'hooks_changegroup_repo_size'
273 deleted_key = 'hooks_changegroup_repo_size'
274 data = HOOKS_FORM_DATA.copy()
274 data = HOOKS_FORM_DATA.copy()
275 data.pop(deleted_key)
275 data.pop(deleted_key)
276
276
277 with pytest.raises(ValueError) as exc_info:
277 with pytest.raises(ValueError) as exc_info:
278 model.create_or_update_repo_hook_settings(data)
278 model.create_or_update_repo_hook_settings(data)
279 Session().commit()
279 Session().commit()
280
280
281 msg = 'The given data does not contain {} key'.format(deleted_key)
281 msg = 'The given data does not contain {} key'.format(deleted_key)
282 assert str(exc_info.value) == msg
282 assert str(exc_info.value) == msg
283
283
284 def test_update_when_repo_object_found(self, repo_stub, settings_util):
284 def test_update_when_repo_object_found(self, repo_stub, settings_util):
285 model = VcsSettingsModel(repo=repo_stub.repo_name)
285 model = VcsSettingsModel(repo=repo_stub.repo_name)
286 for section, key in model.HOOKS_SETTINGS:
286 for section, key in model.HOOKS_SETTINGS:
287 settings_util.create_repo_rhodecode_ui(
287 settings_util.create_repo_rhodecode_ui(
288 repo_stub, section, None, key=key, active=False)
288 repo_stub, section, None, key=key, active=False)
289 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
289 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
290 Session().commit()
290 Session().commit()
291
291
292 for section, key in model.HOOKS_SETTINGS:
292 for section, key in model.HOOKS_SETTINGS:
293 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
293 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
294 assert ui.ui_active is True
294 assert ui.ui_active is True
295
295
296 def _create_settings(self, model, data):
296 def _create_settings(self, model, data):
297 global_patch = mock.patch.object(model, 'global_settings')
297 global_patch = mock.patch.object(model, 'global_settings')
298 global_setting = mock.Mock()
298 global_setting = mock.Mock()
299 global_setting.ui_value = 'Test value'
299 global_setting.ui_value = 'Test value'
300 with global_patch as global_mock:
300 with global_patch as global_mock:
301 global_mock.get_ui_by_section_and_key.return_value = global_setting
301 global_mock.get_ui_by_section_and_key.return_value = global_setting
302 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
302 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
303 Session().commit()
303 Session().commit()
304
304
305
305
306 class TestUpdateGlobalHookSettings(object):
306 class TestUpdateGlobalHookSettings(object):
307 def test_update_raises_exception_when_data_incomplete(self):
307 def test_update_raises_exception_when_data_incomplete(self):
308 model = VcsSettingsModel()
308 model = VcsSettingsModel()
309
309
310 deleted_key = 'hooks_changegroup_repo_size'
310 deleted_key = 'hooks_changegroup_repo_size'
311 data = HOOKS_FORM_DATA.copy()
311 data = HOOKS_FORM_DATA.copy()
312 data.pop(deleted_key)
312 data.pop(deleted_key)
313
313
314 with pytest.raises(ValueError) as exc_info:
314 with pytest.raises(ValueError) as exc_info:
315 model.update_global_hook_settings(data)
315 model.update_global_hook_settings(data)
316 Session().commit()
316 Session().commit()
317
317
318 msg = 'The given data does not contain {} key'.format(deleted_key)
318 msg = 'The given data does not contain {} key'.format(deleted_key)
319 assert str(exc_info.value) == msg
319 assert str(exc_info.value) == msg
320
320
321 def test_update_global_hook_settings(self, settings_util):
321 def test_update_global_hook_settings(self, settings_util):
322 model = VcsSettingsModel()
322 model = VcsSettingsModel()
323 setting_mock = mock.MagicMock()
323 setting_mock = mock.MagicMock()
324 setting_mock.ui_active = False
324 setting_mock.ui_active = False
325 get_settings_patcher = mock.patch.object(
325 get_settings_patcher = mock.patch.object(
326 model.global_settings, 'get_ui_by_section_and_key',
326 model.global_settings, 'get_ui_by_section_and_key',
327 return_value=setting_mock)
327 return_value=setting_mock)
328 session_patcher = mock.patch('rhodecode.model.settings.Session')
328 session_patcher = mock.patch('rhodecode.model.settings.Session')
329 with get_settings_patcher as get_settings_mock, session_patcher:
329 with get_settings_patcher as get_settings_mock, session_patcher:
330 model.update_global_hook_settings(HOOKS_FORM_DATA)
330 model.update_global_hook_settings(HOOKS_FORM_DATA)
331 Session().commit()
331 Session().commit()
332
332
333 assert setting_mock.ui_active is True
333 assert setting_mock.ui_active is True
334 assert get_settings_mock.call_count == 3
334 assert get_settings_mock.call_count == 3
335
335
336
336
337 class TestCreateOrUpdateRepoGeneralSettings(object):
337 class TestCreateOrUpdateRepoGeneralSettings(object):
338 def test_calls_create_or_update_general_settings(self, repo_stub):
338 def test_calls_create_or_update_general_settings(self, repo_stub):
339 model = VcsSettingsModel(repo=repo_stub.repo_name)
339 model = VcsSettingsModel(repo=repo_stub.repo_name)
340 create_patch = mock.patch.object(
340 create_patch = mock.patch.object(
341 model, '_create_or_update_general_settings')
341 model, '_create_or_update_general_settings')
342 with create_patch as create_mock:
342 with create_patch as create_mock:
343 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
343 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
344 Session().commit()
344 Session().commit()
345
345
346 create_mock.assert_called_once_with(
346 create_mock.assert_called_once_with(
347 model.repo_settings, GENERAL_FORM_DATA)
347 model.repo_settings, GENERAL_FORM_DATA)
348
348
349 def test_raises_exception_when_repository_is_not_specified(self):
349 def test_raises_exception_when_repository_is_not_specified(self):
350 model = VcsSettingsModel()
350 model = VcsSettingsModel()
351 with pytest.raises(Exception) as exc_info:
351 with pytest.raises(Exception) as exc_info:
352 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
352 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
353 assert str(exc_info.value) == 'Repository is not specified'
353 assert str(exc_info.value) == 'Repository is not specified'
354
354
355
355
356 class TestCreateOrUpdatGlobalGeneralSettings(object):
356 class TestCreateOrUpdatGlobalGeneralSettings(object):
357 def test_calls_create_or_update_general_settings(self):
357 def test_calls_create_or_update_general_settings(self):
358 model = VcsSettingsModel()
358 model = VcsSettingsModel()
359 create_patch = mock.patch.object(
359 create_patch = mock.patch.object(
360 model, '_create_or_update_general_settings')
360 model, '_create_or_update_general_settings')
361 with create_patch as create_mock:
361 with create_patch as create_mock:
362 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
362 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
363 create_mock.assert_called_once_with(
363 create_mock.assert_called_once_with(
364 model.global_settings, GENERAL_FORM_DATA)
364 model.global_settings, GENERAL_FORM_DATA)
365
365
366
366
367 class TestCreateOrUpdateGeneralSettings(object):
367 class TestCreateOrUpdateGeneralSettings(object):
368 def test_create_when_no_repo_settings_found(self, repo_stub):
368 def test_create_when_no_repo_settings_found(self, repo_stub):
369 model = VcsSettingsModel(repo=repo_stub.repo_name)
369 model = VcsSettingsModel(repo=repo_stub.repo_name)
370 model._create_or_update_general_settings(
370 model._create_or_update_general_settings(
371 model.repo_settings, GENERAL_FORM_DATA)
371 model.repo_settings, GENERAL_FORM_DATA)
372
372
373 cleanup = []
373 cleanup = []
374 try:
374 try:
375 for name in model.GENERAL_SETTINGS:
375 for name in model.GENERAL_SETTINGS:
376 setting = model.repo_settings.get_setting_by_name(name)
376 setting = model.repo_settings.get_setting_by_name(name)
377 assert setting.app_settings_value is True
377 assert setting.app_settings_value is True
378 cleanup.append(setting)
378 cleanup.append(setting)
379 finally:
379 finally:
380 for setting in cleanup:
380 for setting in cleanup:
381 Session().delete(setting)
381 Session().delete(setting)
382 Session().commit()
382 Session().commit()
383
383
384 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
384 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
385 model = VcsSettingsModel(repo=repo_stub.repo_name)
385 model = VcsSettingsModel(repo=repo_stub.repo_name)
386
386
387 deleted_key = 'rhodecode_pr_merge_enabled'
387 deleted_key = 'rhodecode_pr_merge_enabled'
388 data = GENERAL_FORM_DATA.copy()
388 data = GENERAL_FORM_DATA.copy()
389 data.pop(deleted_key)
389 data.pop(deleted_key)
390
390
391 with pytest.raises(ValueError) as exc_info:
391 with pytest.raises(ValueError) as exc_info:
392 model._create_or_update_general_settings(model.repo_settings, data)
392 model._create_or_update_general_settings(model.repo_settings, data)
393 Session().commit()
393 Session().commit()
394
394
395 msg = 'The given data does not contain {} key'.format(deleted_key)
395 msg = 'The given data does not contain {} key'.format(deleted_key)
396 assert str(exc_info.value) == msg
396 assert str(exc_info.value) == msg
397
397
398 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
398 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
399 model = VcsSettingsModel(repo=repo_stub.repo_name)
399 model = VcsSettingsModel(repo=repo_stub.repo_name)
400 for name in model.GENERAL_SETTINGS:
400 for name in model.GENERAL_SETTINGS:
401 settings_util.create_repo_rhodecode_setting(
401 settings_util.create_repo_rhodecode_setting(
402 repo_stub, name, False, 'bool')
402 repo_stub, name, False, 'bool')
403
403
404 model._create_or_update_general_settings(
404 model._create_or_update_general_settings(
405 model.repo_settings, GENERAL_FORM_DATA)
405 model.repo_settings, GENERAL_FORM_DATA)
406 Session().commit()
406 Session().commit()
407
407
408 for name in model.GENERAL_SETTINGS:
408 for name in model.GENERAL_SETTINGS:
409 setting = model.repo_settings.get_setting_by_name(name)
409 setting = model.repo_settings.get_setting_by_name(name)
410 assert setting.app_settings_value is True
410 assert setting.app_settings_value is True
411
411
412
412
413 class TestCreateRepoSvnSettings(object):
413 class TestCreateRepoSvnSettings(object):
414 def test_calls_create_svn_settings(self, repo_stub):
414 def test_calls_create_svn_settings(self, repo_stub):
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
415 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 with mock.patch.object(model, '_create_svn_settings') as create_mock:
416 with mock.patch.object(model, '_create_svn_settings') as create_mock:
417 model.create_repo_svn_settings(SVN_FORM_DATA)
417 model.create_repo_svn_settings(SVN_FORM_DATA)
418 Session().commit()
418 Session().commit()
419
419
420 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
420 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
421
421
422 def test_raises_exception_when_repository_is_not_specified(self):
422 def test_raises_exception_when_repository_is_not_specified(self):
423 model = VcsSettingsModel()
423 model = VcsSettingsModel()
424 with pytest.raises(Exception) as exc_info:
424 with pytest.raises(Exception) as exc_info:
425 model.create_repo_svn_settings(SVN_FORM_DATA)
425 model.create_repo_svn_settings(SVN_FORM_DATA)
426 Session().commit()
426 Session().commit()
427
427
428 assert str(exc_info.value) == 'Repository is not specified'
428 assert str(exc_info.value) == 'Repository is not specified'
429
429
430
430
431 class TestCreateSvnSettings(object):
431 class TestCreateSvnSettings(object):
432 def test_create(self, repo_stub):
432 def test_create(self, repo_stub):
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
433 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
434 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
435 Session().commit()
435 Session().commit()
436
436
437 branch_ui = model.repo_settings.get_ui_by_section(
437 branch_ui = model.repo_settings.get_ui_by_section(
438 model.SVN_BRANCH_SECTION)
438 model.SVN_BRANCH_SECTION)
439 tag_ui = model.repo_settings.get_ui_by_section(
439 tag_ui = model.repo_settings.get_ui_by_section(
440 model.SVN_TAG_SECTION)
440 model.SVN_TAG_SECTION)
441
441
442 try:
442 try:
443 assert len(branch_ui) == 1
443 assert len(branch_ui) == 1
444 assert len(tag_ui) == 1
444 assert len(tag_ui) == 1
445 finally:
445 finally:
446 Session().delete(branch_ui[0])
446 Session().delete(branch_ui[0])
447 Session().delete(tag_ui[0])
447 Session().delete(tag_ui[0])
448 Session().commit()
448 Session().commit()
449
449
450 def test_create_tag(self, repo_stub):
450 def test_create_tag(self, repo_stub):
451 model = VcsSettingsModel(repo=repo_stub.repo_name)
451 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 data = SVN_FORM_DATA.copy()
452 data = SVN_FORM_DATA.copy()
453 data.pop('new_svn_branch')
453 data.pop('new_svn_branch')
454 model._create_svn_settings(model.repo_settings, data)
454 model._create_svn_settings(model.repo_settings, data)
455 Session().commit()
455 Session().commit()
456
456
457 branch_ui = model.repo_settings.get_ui_by_section(
457 branch_ui = model.repo_settings.get_ui_by_section(
458 model.SVN_BRANCH_SECTION)
458 model.SVN_BRANCH_SECTION)
459 tag_ui = model.repo_settings.get_ui_by_section(
459 tag_ui = model.repo_settings.get_ui_by_section(
460 model.SVN_TAG_SECTION)
460 model.SVN_TAG_SECTION)
461
461
462 try:
462 try:
463 assert len(branch_ui) == 0
463 assert len(branch_ui) == 0
464 assert len(tag_ui) == 1
464 assert len(tag_ui) == 1
465 finally:
465 finally:
466 Session().delete(tag_ui[0])
466 Session().delete(tag_ui[0])
467 Session().commit()
467 Session().commit()
468
468
469 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
469 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
470 model = VcsSettingsModel(repo=repo_stub.repo_name)
470 model = VcsSettingsModel(repo=repo_stub.repo_name)
471 model._create_svn_settings(model.repo_settings, {})
471 model._create_svn_settings(model.repo_settings, {})
472 Session().commit()
472 Session().commit()
473
473
474 branch_ui = model.repo_settings.get_ui_by_section(
474 branch_ui = model.repo_settings.get_ui_by_section(
475 model.SVN_BRANCH_SECTION)
475 model.SVN_BRANCH_SECTION)
476 tag_ui = model.repo_settings.get_ui_by_section(
476 tag_ui = model.repo_settings.get_ui_by_section(
477 model.SVN_TAG_SECTION)
477 model.SVN_TAG_SECTION)
478
478
479 assert len(branch_ui) == 0
479 assert len(branch_ui) == 0
480 assert len(tag_ui) == 0
480 assert len(tag_ui) == 0
481
481
482 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
482 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
483 model = VcsSettingsModel(repo=repo_stub.repo_name)
483 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 data = {
484 data = {
485 'new_svn_branch': '',
485 'new_svn_branch': '',
486 'new_svn_tag': ''
486 'new_svn_tag': ''
487 }
487 }
488 model._create_svn_settings(model.repo_settings, data)
488 model._create_svn_settings(model.repo_settings, data)
489 Session().commit()
489 Session().commit()
490
490
491 branch_ui = model.repo_settings.get_ui_by_section(
491 branch_ui = model.repo_settings.get_ui_by_section(
492 model.SVN_BRANCH_SECTION)
492 model.SVN_BRANCH_SECTION)
493 tag_ui = model.repo_settings.get_ui_by_section(
493 tag_ui = model.repo_settings.get_ui_by_section(
494 model.SVN_TAG_SECTION)
494 model.SVN_TAG_SECTION)
495
495
496 assert len(branch_ui) == 0
496 assert len(branch_ui) == 0
497 assert len(tag_ui) == 0
497 assert len(tag_ui) == 0
498
498
499
499
500 class TestCreateOrUpdateUi(object):
500 class TestCreateOrUpdateUi(object):
501 def test_create(self, repo_stub):
501 def test_create(self, repo_stub):
502 model = VcsSettingsModel(repo=repo_stub.repo_name)
502 model = VcsSettingsModel(repo=repo_stub.repo_name)
503 model._create_or_update_ui(
503 model._create_or_update_ui(
504 model.repo_settings, 'test-section', 'test-key', active=False,
504 model.repo_settings, 'test-section', 'test-key', active=False,
505 value='False')
505 value='False')
506 Session().commit()
506 Session().commit()
507
507
508 created_ui = model.repo_settings.get_ui_by_section_and_key(
508 created_ui = model.repo_settings.get_ui_by_section_and_key(
509 'test-section', 'test-key')
509 'test-section', 'test-key')
510
510
511 try:
511 try:
512 assert created_ui.ui_active is False
512 assert created_ui.ui_active is False
513 assert str2bool(created_ui.ui_value) is False
513 assert str2bool(created_ui.ui_value) is False
514 finally:
514 finally:
515 Session().delete(created_ui)
515 Session().delete(created_ui)
516 Session().commit()
516 Session().commit()
517
517
518 def test_update(self, repo_stub, settings_util):
518 def test_update(self, repo_stub, settings_util):
519 model = VcsSettingsModel(repo=repo_stub.repo_name)
519 model = VcsSettingsModel(repo=repo_stub.repo_name)
520 # care about only 3 first settings
520 # care about only 3 first settings
521 largefiles, phases, evolve = model.HG_SETTINGS[:3]
521 largefiles, phases, evolve = model.HG_SETTINGS[:3]
522
522
523 section = 'test-section'
523 section = 'test-section'
524 key = 'test-key'
524 key = 'test-key'
525 settings_util.create_repo_rhodecode_ui(
525 settings_util.create_repo_rhodecode_ui(
526 repo_stub, section, 'True', key=key, active=True)
526 repo_stub, section, 'True', key=key, active=True)
527
527
528 model._create_or_update_ui(
528 model._create_or_update_ui(
529 model.repo_settings, section, key, active=False, value='False')
529 model.repo_settings, section, key, active=False, value='False')
530 Session().commit()
530 Session().commit()
531
531
532 created_ui = model.repo_settings.get_ui_by_section_and_key(
532 created_ui = model.repo_settings.get_ui_by_section_and_key(
533 section, key)
533 section, key)
534 assert created_ui.ui_active is False
534 assert created_ui.ui_active is False
535 assert str2bool(created_ui.ui_value) is False
535 assert str2bool(created_ui.ui_value) is False
536
536
537
537
538 class TestCreateOrUpdateRepoHgSettings(object):
538 class TestCreateOrUpdateRepoHgSettings(object):
539 FORM_DATA = {
539 FORM_DATA = {
540 'extensions_largefiles': False,
540 'extensions_largefiles': False,
541 'extensions_evolve': False,
541 'extensions_evolve': False,
542 'phases_publish': False
542 'phases_publish': False
543 }
543 }
544
544
545 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
545 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
546 model = VcsSettingsModel(repo=repo_stub.repo_name)
546 model = VcsSettingsModel(repo=repo_stub.repo_name)
547 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
547 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
548 model.create_or_update_repo_hg_settings(self.FORM_DATA)
548 model.create_or_update_repo_hg_settings(self.FORM_DATA)
549 expected_calls = [
549 expected_calls = [
550 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
550 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
551 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
551 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
552 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
552 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
553 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
553 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
554 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
554 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
555 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
555 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
556 ]
556 ]
557 assert expected_calls == create_mock.call_args_list
557 assert expected_calls == create_mock.call_args_list
558
558
559 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
559 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
560 def test_key_is_not_found(self, repo_stub, field_to_remove):
560 def test_key_is_not_found(self, repo_stub, field_to_remove):
561 model = VcsSettingsModel(repo=repo_stub.repo_name)
561 model = VcsSettingsModel(repo=repo_stub.repo_name)
562 data = self.FORM_DATA.copy()
562 data = self.FORM_DATA.copy()
563 data.pop(field_to_remove)
563 data.pop(field_to_remove)
564 with pytest.raises(ValueError) as exc_info:
564 with pytest.raises(ValueError) as exc_info:
565 model.create_or_update_repo_hg_settings(data)
565 model.create_or_update_repo_hg_settings(data)
566 Session().commit()
566 Session().commit()
567
567
568 expected_message = 'The given data does not contain {} key'.format(
568 expected_message = 'The given data does not contain {} key'.format(
569 field_to_remove)
569 field_to_remove)
570 assert str(exc_info.value) == expected_message
570 assert str(exc_info.value) == expected_message
571
571
572 def test_create_raises_exception_when_repository_not_specified(self):
572 def test_create_raises_exception_when_repository_not_specified(self):
573 model = VcsSettingsModel()
573 model = VcsSettingsModel()
574 with pytest.raises(Exception) as exc_info:
574 with pytest.raises(Exception) as exc_info:
575 model.create_or_update_repo_hg_settings(self.FORM_DATA)
575 model.create_or_update_repo_hg_settings(self.FORM_DATA)
576 Session().commit()
576 Session().commit()
577
577
578 assert str(exc_info.value) == 'Repository is not specified'
578 assert str(exc_info.value) == 'Repository is not specified'
579
579
580
580
581 class TestUpdateGlobalSslSetting(object):
582 def test_updates_global_hg_settings(self):
583 model = VcsSettingsModel()
584 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
585 model.update_global_ssl_setting('False')
586 Session().commit()
587
588 create_mock.assert_called_once_with(
589 model.global_settings, 'web', 'push_ssl', value='False')
590
591
592 class TestCreateOrUpdateGlobalHgSettings(object):
581 class TestCreateOrUpdateGlobalHgSettings(object):
593 FORM_DATA = {
582 FORM_DATA = {
594 'extensions_largefiles': False,
583 'extensions_largefiles': False,
595 'largefiles_usercache': '/example/largefiles-store',
584 'largefiles_usercache': '/example/largefiles-store',
596 'phases_publish': False,
585 'phases_publish': False,
597 'extensions_evolve': False
586 'extensions_evolve': False
598 }
587 }
599
588
600 def test_creates_repo_hg_settings_when_data_is_correct(self):
589 def test_creates_repo_hg_settings_when_data_is_correct(self):
601 model = VcsSettingsModel()
590 model = VcsSettingsModel()
602 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
591 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
603 model.create_or_update_global_hg_settings(self.FORM_DATA)
592 model.create_or_update_global_hg_settings(self.FORM_DATA)
604 Session().commit()
593 Session().commit()
605
594
606 expected_calls = [
595 expected_calls = [
607 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
596 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
608 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
597 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
609 mock.call(model.global_settings, 'phases', 'publish', value='False'),
598 mock.call(model.global_settings, 'phases', 'publish', value='False'),
610 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
599 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
611 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
600 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
612 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
601 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
613 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
602 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
614 ]
603 ]
615
604
616 assert expected_calls == create_mock.call_args_list
605 assert expected_calls == create_mock.call_args_list
617
606
618 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
607 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
619 def test_key_is_not_found(self, repo_stub, field_to_remove):
608 def test_key_is_not_found(self, repo_stub, field_to_remove):
620 model = VcsSettingsModel(repo=repo_stub.repo_name)
609 model = VcsSettingsModel(repo=repo_stub.repo_name)
621 data = self.FORM_DATA.copy()
610 data = self.FORM_DATA.copy()
622 data.pop(field_to_remove)
611 data.pop(field_to_remove)
623 with pytest.raises(Exception) as exc_info:
612 with pytest.raises(Exception) as exc_info:
624 model.create_or_update_global_hg_settings(data)
613 model.create_or_update_global_hg_settings(data)
625 Session().commit()
614 Session().commit()
626
615
627 expected_message = 'The given data does not contain {} key'.format(
616 expected_message = 'The given data does not contain {} key'.format(
628 field_to_remove)
617 field_to_remove)
629 assert str(exc_info.value) == expected_message
618 assert str(exc_info.value) == expected_message
630
619
631
620
632 class TestCreateOrUpdateGlobalGitSettings(object):
621 class TestCreateOrUpdateGlobalGitSettings(object):
633 FORM_DATA = {
622 FORM_DATA = {
634 'vcs_git_lfs_enabled': False,
623 'vcs_git_lfs_enabled': False,
635 'vcs_git_lfs_store_location': '/example/lfs-store',
624 'vcs_git_lfs_store_location': '/example/lfs-store',
636 }
625 }
637
626
638 def test_creates_repo_hg_settings_when_data_is_correct(self):
627 def test_creates_repo_hg_settings_when_data_is_correct(self):
639 model = VcsSettingsModel()
628 model = VcsSettingsModel()
640 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
629 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
641 model.create_or_update_global_git_settings(self.FORM_DATA)
630 model.create_or_update_global_git_settings(self.FORM_DATA)
642 Session().commit()
631 Session().commit()
643
632
644 expected_calls = [
633 expected_calls = [
645 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
634 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
646 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
635 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
647 ]
636 ]
648 assert expected_calls == create_mock.call_args_list
637 assert expected_calls == create_mock.call_args_list
649
638
650
639
651 class TestDeleteRepoSvnPattern(object):
640 class TestDeleteRepoSvnPattern(object):
652 def test_success_when_repo_is_set(self, backend_svn, settings_util):
641 def test_success_when_repo_is_set(self, backend_svn, settings_util):
653 repo = backend_svn.create_repo()
642 repo = backend_svn.create_repo()
654 repo_name = repo.repo_name
643 repo_name = repo.repo_name
655
644
656 model = VcsSettingsModel(repo=repo_name)
645 model = VcsSettingsModel(repo=repo_name)
657 entry = settings_util.create_repo_rhodecode_ui(
646 entry = settings_util.create_repo_rhodecode_ui(
658 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
647 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
659 Session().commit()
648 Session().commit()
660
649
661 model.delete_repo_svn_pattern(entry.ui_id)
650 model.delete_repo_svn_pattern(entry.ui_id)
662
651
663 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
652 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
664 repo_name = backend_svn.repo_name
653 repo_name = backend_svn.repo_name
665 model = VcsSettingsModel(repo=repo_name)
654 model = VcsSettingsModel(repo=repo_name)
666 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
655 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
667 with delete_ui_patch as delete_ui_mock:
656 with delete_ui_patch as delete_ui_mock:
668 model.delete_repo_svn_pattern(123)
657 model.delete_repo_svn_pattern(123)
669 Session().commit()
658 Session().commit()
670
659
671 delete_ui_mock.assert_called_once_with(-1)
660 delete_ui_mock.assert_called_once_with(-1)
672
661
673 def test_raises_exception_when_repository_is_not_specified(self):
662 def test_raises_exception_when_repository_is_not_specified(self):
674 model = VcsSettingsModel()
663 model = VcsSettingsModel()
675 with pytest.raises(Exception) as exc_info:
664 with pytest.raises(Exception) as exc_info:
676 model.delete_repo_svn_pattern(123)
665 model.delete_repo_svn_pattern(123)
677 assert str(exc_info.value) == 'Repository is not specified'
666 assert str(exc_info.value) == 'Repository is not specified'
678
667
679
668
680 class TestDeleteGlobalSvnPattern(object):
669 class TestDeleteGlobalSvnPattern(object):
681 def test_delete_global_svn_pattern_calls_delete_ui(self):
670 def test_delete_global_svn_pattern_calls_delete_ui(self):
682 model = VcsSettingsModel()
671 model = VcsSettingsModel()
683 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
672 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
684 with delete_ui_patch as delete_ui_mock:
673 with delete_ui_patch as delete_ui_mock:
685 model.delete_global_svn_pattern(123)
674 model.delete_global_svn_pattern(123)
686 delete_ui_mock.assert_called_once_with(123)
675 delete_ui_mock.assert_called_once_with(123)
687
676
688
677
689 class TestFilterUiSettings(object):
678 class TestFilterUiSettings(object):
690 def test_settings_are_filtered(self):
679 def test_settings_are_filtered(self):
691 model = VcsSettingsModel()
680 model = VcsSettingsModel()
692 repo_settings = [
681 repo_settings = [
693 UiSetting('extensions', 'largefiles', '', True),
682 UiSetting('extensions', 'largefiles', '', True),
694 UiSetting('phases', 'publish', 'True', True),
683 UiSetting('phases', 'publish', 'True', True),
695 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
684 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
696 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
685 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
697 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
686 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
698 UiSetting(
687 UiSetting(
699 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
688 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
700 'test_branch', True),
689 'test_branch', True),
701 UiSetting(
690 UiSetting(
702 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
691 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
703 'test_tag', True),
692 'test_tag', True),
704 ]
693 ]
705 non_repo_settings = [
694 non_repo_settings = [
706 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
695 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
707 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
696 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
708 UiSetting('hooks', 'test2', 'hook', True),
697 UiSetting('hooks', 'test2', 'hook', True),
709 UiSetting(
698 UiSetting(
710 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
699 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
711 'test_tag', True),
700 'test_tag', True),
712 ]
701 ]
713 settings = repo_settings + non_repo_settings
702 settings = repo_settings + non_repo_settings
714 filtered_settings = model._filter_ui_settings(settings)
703 filtered_settings = model._filter_ui_settings(settings)
715 assert sorted(filtered_settings) == sorted(repo_settings)
704 assert sorted(filtered_settings) == sorted(repo_settings)
716
705
717
706
718 class TestFilterGeneralSettings(object):
707 class TestFilterGeneralSettings(object):
719 def test_settings_are_filtered(self):
708 def test_settings_are_filtered(self):
720 model = VcsSettingsModel()
709 model = VcsSettingsModel()
721 settings = {
710 settings = {
722 'rhodecode_abcde': 'value1',
711 'rhodecode_abcde': 'value1',
723 'rhodecode_vwxyz': 'value2',
712 'rhodecode_vwxyz': 'value2',
724 }
713 }
725 general_settings = {
714 general_settings = {
726 'rhodecode_{}'.format(key): 'value'
715 'rhodecode_{}'.format(key): 'value'
727 for key in VcsSettingsModel.GENERAL_SETTINGS
716 for key in VcsSettingsModel.GENERAL_SETTINGS
728 }
717 }
729 settings.update(general_settings)
718 settings.update(general_settings)
730
719
731 filtered_settings = model._filter_general_settings(general_settings)
720 filtered_settings = model._filter_general_settings(general_settings)
732 assert sorted(filtered_settings) == sorted(general_settings)
721 assert sorted(filtered_settings) == sorted(general_settings)
733
722
734
723
735 class TestGetRepoUiSettings(object):
724 class TestGetRepoUiSettings(object):
736 def test_global_uis_are_returned_when_no_repo_uis_found(
725 def test_global_uis_are_returned_when_no_repo_uis_found(
737 self, repo_stub):
726 self, repo_stub):
738 model = VcsSettingsModel(repo=repo_stub.repo_name)
727 model = VcsSettingsModel(repo=repo_stub.repo_name)
739 result = model.get_repo_ui_settings()
728 result = model.get_repo_ui_settings()
740 svn_sections = (
729 svn_sections = (
741 VcsSettingsModel.SVN_TAG_SECTION,
730 VcsSettingsModel.SVN_TAG_SECTION,
742 VcsSettingsModel.SVN_BRANCH_SECTION)
731 VcsSettingsModel.SVN_BRANCH_SECTION)
743 expected_result = [
732 expected_result = [
744 s for s in model.global_settings.get_ui()
733 s for s in model.global_settings.get_ui()
745 if s.section not in svn_sections]
734 if s.section not in svn_sections]
746 assert sorted(result) == sorted(expected_result)
735 assert sorted(result) == sorted(expected_result)
747
736
748 def test_repo_uis_are_overriding_global_uis(
737 def test_repo_uis_are_overriding_global_uis(
749 self, repo_stub, settings_util):
738 self, repo_stub, settings_util):
750 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
739 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
751 settings_util.create_repo_rhodecode_ui(
740 settings_util.create_repo_rhodecode_ui(
752 repo_stub, section, 'repo', key=key, active=False)
741 repo_stub, section, 'repo', key=key, active=False)
753 model = VcsSettingsModel(repo=repo_stub.repo_name)
742 model = VcsSettingsModel(repo=repo_stub.repo_name)
754 result = model.get_repo_ui_settings()
743 result = model.get_repo_ui_settings()
755 for setting in result:
744 for setting in result:
756 locator = (setting.section, setting.key)
745 locator = (setting.section, setting.key)
757 if locator in VcsSettingsModel.HOOKS_SETTINGS:
746 if locator in VcsSettingsModel.HOOKS_SETTINGS:
758 assert setting.value == 'repo'
747 assert setting.value == 'repo'
759
748
760 assert setting.active is False
749 assert setting.active is False
761
750
762 def test_global_svn_patterns_are_not_in_list(
751 def test_global_svn_patterns_are_not_in_list(
763 self, repo_stub, settings_util):
752 self, repo_stub, settings_util):
764 svn_sections = (
753 svn_sections = (
765 VcsSettingsModel.SVN_TAG_SECTION,
754 VcsSettingsModel.SVN_TAG_SECTION,
766 VcsSettingsModel.SVN_BRANCH_SECTION)
755 VcsSettingsModel.SVN_BRANCH_SECTION)
767 for section in svn_sections:
756 for section in svn_sections:
768 settings_util.create_rhodecode_ui(
757 settings_util.create_rhodecode_ui(
769 section, 'repo', key='deadbeef' + section, active=False)
758 section, 'repo', key='deadbeef' + section, active=False)
770 Session().commit()
759 Session().commit()
771
760
772 model = VcsSettingsModel(repo=repo_stub.repo_name)
761 model = VcsSettingsModel(repo=repo_stub.repo_name)
773 result = model.get_repo_ui_settings()
762 result = model.get_repo_ui_settings()
774 for setting in result:
763 for setting in result:
775 assert setting.section not in svn_sections
764 assert setting.section not in svn_sections
776
765
777 def test_repo_uis_filtered_by_section_are_returned(
766 def test_repo_uis_filtered_by_section_are_returned(
778 self, repo_stub, settings_util):
767 self, repo_stub, settings_util):
779 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
768 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
780 settings_util.create_repo_rhodecode_ui(
769 settings_util.create_repo_rhodecode_ui(
781 repo_stub, section, 'repo', key=key, active=False)
770 repo_stub, section, 'repo', key=key, active=False)
782 model = VcsSettingsModel(repo=repo_stub.repo_name)
771 model = VcsSettingsModel(repo=repo_stub.repo_name)
783 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
772 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
784 result = model.get_repo_ui_settings(section=section)
773 result = model.get_repo_ui_settings(section=section)
785 for setting in result:
774 for setting in result:
786 assert setting.section == section
775 assert setting.section == section
787
776
788 def test_repo_uis_filtered_by_key_are_returned(
777 def test_repo_uis_filtered_by_key_are_returned(
789 self, repo_stub, settings_util):
778 self, repo_stub, settings_util):
790 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
779 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
791 settings_util.create_repo_rhodecode_ui(
780 settings_util.create_repo_rhodecode_ui(
792 repo_stub, section, 'repo', key=key, active=False)
781 repo_stub, section, 'repo', key=key, active=False)
793 model = VcsSettingsModel(repo=repo_stub.repo_name)
782 model = VcsSettingsModel(repo=repo_stub.repo_name)
794 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
783 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
795 result = model.get_repo_ui_settings(key=key)
784 result = model.get_repo_ui_settings(key=key)
796 for setting in result:
785 for setting in result:
797 assert setting.key == key
786 assert setting.key == key
798
787
799 def test_raises_exception_when_repository_is_not_specified(self):
788 def test_raises_exception_when_repository_is_not_specified(self):
800 model = VcsSettingsModel()
789 model = VcsSettingsModel()
801 with pytest.raises(Exception) as exc_info:
790 with pytest.raises(Exception) as exc_info:
802 model.get_repo_ui_settings()
791 model.get_repo_ui_settings()
803 assert str(exc_info.value) == 'Repository is not specified'
792 assert str(exc_info.value) == 'Repository is not specified'
804
793
805
794
806 class TestGetRepoGeneralSettings(object):
795 class TestGetRepoGeneralSettings(object):
807 def test_global_settings_are_returned_when_no_repo_settings_found(
796 def test_global_settings_are_returned_when_no_repo_settings_found(
808 self, repo_stub):
797 self, repo_stub):
809 model = VcsSettingsModel(repo=repo_stub.repo_name)
798 model = VcsSettingsModel(repo=repo_stub.repo_name)
810 result = model.get_repo_general_settings()
799 result = model.get_repo_general_settings()
811 expected_result = model.global_settings.get_all_settings()
800 expected_result = model.global_settings.get_all_settings()
812 assert sorted(result) == sorted(expected_result)
801 assert sorted(result) == sorted(expected_result)
813
802
814 def test_repo_uis_are_overriding_global_uis(
803 def test_repo_uis_are_overriding_global_uis(
815 self, repo_stub, settings_util):
804 self, repo_stub, settings_util):
816 for key in VcsSettingsModel.GENERAL_SETTINGS:
805 for key in VcsSettingsModel.GENERAL_SETTINGS:
817 settings_util.create_repo_rhodecode_setting(
806 settings_util.create_repo_rhodecode_setting(
818 repo_stub, key, 'abcde', type_='unicode')
807 repo_stub, key, 'abcde', type_='unicode')
819 Session().commit()
808 Session().commit()
820
809
821 model = VcsSettingsModel(repo=repo_stub.repo_name)
810 model = VcsSettingsModel(repo=repo_stub.repo_name)
822 result = model.get_repo_ui_settings()
811 result = model.get_repo_ui_settings()
823 for key in result:
812 for key in result:
824 if key in VcsSettingsModel.GENERAL_SETTINGS:
813 if key in VcsSettingsModel.GENERAL_SETTINGS:
825 assert result[key] == 'abcde'
814 assert result[key] == 'abcde'
826
815
827 def test_raises_exception_when_repository_is_not_specified(self):
816 def test_raises_exception_when_repository_is_not_specified(self):
828 model = VcsSettingsModel()
817 model = VcsSettingsModel()
829 with pytest.raises(Exception) as exc_info:
818 with pytest.raises(Exception) as exc_info:
830 model.get_repo_general_settings()
819 model.get_repo_general_settings()
831 assert str(exc_info.value) == 'Repository is not specified'
820 assert str(exc_info.value) == 'Repository is not specified'
832
821
833
822
834 class TestGetGlobalGeneralSettings(object):
823 class TestGetGlobalGeneralSettings(object):
835 def test_global_settings_are_returned(self, repo_stub):
824 def test_global_settings_are_returned(self, repo_stub):
836 model = VcsSettingsModel()
825 model = VcsSettingsModel()
837 result = model.get_global_general_settings()
826 result = model.get_global_general_settings()
838 expected_result = model.global_settings.get_all_settings()
827 expected_result = model.global_settings.get_all_settings()
839 assert sorted(result) == sorted(expected_result)
828 assert sorted(result) == sorted(expected_result)
840
829
841 def test_repo_uis_are_not_overriding_global_uis(
830 def test_repo_uis_are_not_overriding_global_uis(
842 self, repo_stub, settings_util):
831 self, repo_stub, settings_util):
843 for key in VcsSettingsModel.GENERAL_SETTINGS:
832 for key in VcsSettingsModel.GENERAL_SETTINGS:
844 settings_util.create_repo_rhodecode_setting(
833 settings_util.create_repo_rhodecode_setting(
845 repo_stub, key, 'abcde', type_='unicode')
834 repo_stub, key, 'abcde', type_='unicode')
846 Session().commit()
835 Session().commit()
847
836
848 model = VcsSettingsModel(repo=repo_stub.repo_name)
837 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 result = model.get_global_general_settings()
838 result = model.get_global_general_settings()
850 expected_result = model.global_settings.get_all_settings()
839 expected_result = model.global_settings.get_all_settings()
851 assert sorted(result) == sorted(expected_result)
840 assert sorted(result) == sorted(expected_result)
852
841
853
842
854 class TestGetGlobalUiSettings(object):
843 class TestGetGlobalUiSettings(object):
855 def test_global_uis_are_returned(self, repo_stub):
844 def test_global_uis_are_returned(self, repo_stub):
856 model = VcsSettingsModel()
845 model = VcsSettingsModel()
857 result = model.get_global_ui_settings()
846 result = model.get_global_ui_settings()
858 expected_result = model.global_settings.get_ui()
847 expected_result = model.global_settings.get_ui()
859 assert sorted(result) == sorted(expected_result)
848 assert sorted(result) == sorted(expected_result)
860
849
861 def test_repo_uis_are_not_overriding_global_uis(
850 def test_repo_uis_are_not_overriding_global_uis(
862 self, repo_stub, settings_util):
851 self, repo_stub, settings_util):
863 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
852 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
864 settings_util.create_repo_rhodecode_ui(
853 settings_util.create_repo_rhodecode_ui(
865 repo_stub, section, 'repo', key=key, active=False)
854 repo_stub, section, 'repo', key=key, active=False)
866 Session().commit()
855 Session().commit()
867
856
868 model = VcsSettingsModel(repo=repo_stub.repo_name)
857 model = VcsSettingsModel(repo=repo_stub.repo_name)
869 result = model.get_global_ui_settings()
858 result = model.get_global_ui_settings()
870 expected_result = model.global_settings.get_ui()
859 expected_result = model.global_settings.get_ui()
871 assert sorted(result) == sorted(expected_result)
860 assert sorted(result) == sorted(expected_result)
872
861
873 def test_ui_settings_filtered_by_section(
862 def test_ui_settings_filtered_by_section(
874 self, repo_stub, settings_util):
863 self, repo_stub, settings_util):
875 model = VcsSettingsModel(repo=repo_stub.repo_name)
864 model = VcsSettingsModel(repo=repo_stub.repo_name)
876 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
865 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
877 result = model.get_global_ui_settings(section=section)
866 result = model.get_global_ui_settings(section=section)
878 expected_result = model.global_settings.get_ui(section=section)
867 expected_result = model.global_settings.get_ui(section=section)
879 assert sorted(result) == sorted(expected_result)
868 assert sorted(result) == sorted(expected_result)
880
869
881 def test_ui_settings_filtered_by_key(
870 def test_ui_settings_filtered_by_key(
882 self, repo_stub, settings_util):
871 self, repo_stub, settings_util):
883 model = VcsSettingsModel(repo=repo_stub.repo_name)
872 model = VcsSettingsModel(repo=repo_stub.repo_name)
884 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
873 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
885 result = model.get_global_ui_settings(key=key)
874 result = model.get_global_ui_settings(key=key)
886 expected_result = model.global_settings.get_ui(key=key)
875 expected_result = model.global_settings.get_ui(key=key)
887 assert sorted(result) == sorted(expected_result)
876 assert sorted(result) == sorted(expected_result)
888
877
889
878
890 class TestGetGeneralSettings(object):
879 class TestGetGeneralSettings(object):
891 def test_global_settings_are_returned_when_inherited_is_true(
880 def test_global_settings_are_returned_when_inherited_is_true(
892 self, repo_stub, settings_util):
881 self, repo_stub, settings_util):
893 model = VcsSettingsModel(repo=repo_stub.repo_name)
882 model = VcsSettingsModel(repo=repo_stub.repo_name)
894 model.inherit_global_settings = True
883 model.inherit_global_settings = True
895 for key in VcsSettingsModel.GENERAL_SETTINGS:
884 for key in VcsSettingsModel.GENERAL_SETTINGS:
896 settings_util.create_repo_rhodecode_setting(
885 settings_util.create_repo_rhodecode_setting(
897 repo_stub, key, 'abcde', type_='unicode')
886 repo_stub, key, 'abcde', type_='unicode')
898 Session().commit()
887 Session().commit()
899
888
900 result = model.get_general_settings()
889 result = model.get_general_settings()
901 expected_result = model.get_global_general_settings()
890 expected_result = model.get_global_general_settings()
902 assert sorted(result) == sorted(expected_result)
891 assert sorted(result) == sorted(expected_result)
903
892
904 def test_repo_settings_are_returned_when_inherited_is_false(
893 def test_repo_settings_are_returned_when_inherited_is_false(
905 self, repo_stub, settings_util):
894 self, repo_stub, settings_util):
906 model = VcsSettingsModel(repo=repo_stub.repo_name)
895 model = VcsSettingsModel(repo=repo_stub.repo_name)
907 model.inherit_global_settings = False
896 model.inherit_global_settings = False
908 for key in VcsSettingsModel.GENERAL_SETTINGS:
897 for key in VcsSettingsModel.GENERAL_SETTINGS:
909 settings_util.create_repo_rhodecode_setting(
898 settings_util.create_repo_rhodecode_setting(
910 repo_stub, key, 'abcde', type_='unicode')
899 repo_stub, key, 'abcde', type_='unicode')
911 Session().commit()
900 Session().commit()
912
901
913 result = model.get_general_settings()
902 result = model.get_general_settings()
914 expected_result = model.get_repo_general_settings()
903 expected_result = model.get_repo_general_settings()
915 assert sorted(result) == sorted(expected_result)
904 assert sorted(result) == sorted(expected_result)
916
905
917 def test_global_settings_are_returned_when_no_repository_specified(self):
906 def test_global_settings_are_returned_when_no_repository_specified(self):
918 model = VcsSettingsModel()
907 model = VcsSettingsModel()
919 result = model.get_general_settings()
908 result = model.get_general_settings()
920 expected_result = model.get_global_general_settings()
909 expected_result = model.get_global_general_settings()
921 assert sorted(result) == sorted(expected_result)
910 assert sorted(result) == sorted(expected_result)
922
911
923
912
924 class TestGetUiSettings(object):
913 class TestGetUiSettings(object):
925 def test_global_settings_are_returned_when_inherited_is_true(
914 def test_global_settings_are_returned_when_inherited_is_true(
926 self, repo_stub, settings_util):
915 self, repo_stub, settings_util):
927 model = VcsSettingsModel(repo=repo_stub.repo_name)
916 model = VcsSettingsModel(repo=repo_stub.repo_name)
928 model.inherit_global_settings = True
917 model.inherit_global_settings = True
929 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
918 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
930 settings_util.create_repo_rhodecode_ui(
919 settings_util.create_repo_rhodecode_ui(
931 repo_stub, section, 'repo', key=key, active=True)
920 repo_stub, section, 'repo', key=key, active=True)
932 Session().commit()
921 Session().commit()
933
922
934 result = model.get_ui_settings()
923 result = model.get_ui_settings()
935 expected_result = model.get_global_ui_settings()
924 expected_result = model.get_global_ui_settings()
936 assert sorted(result) == sorted(expected_result)
925 assert sorted(result) == sorted(expected_result)
937
926
938 def test_repo_settings_are_returned_when_inherited_is_false(
927 def test_repo_settings_are_returned_when_inherited_is_false(
939 self, repo_stub, settings_util):
928 self, repo_stub, settings_util):
940 model = VcsSettingsModel(repo=repo_stub.repo_name)
929 model = VcsSettingsModel(repo=repo_stub.repo_name)
941 model.inherit_global_settings = False
930 model.inherit_global_settings = False
942 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
931 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
943 settings_util.create_repo_rhodecode_ui(
932 settings_util.create_repo_rhodecode_ui(
944 repo_stub, section, 'repo', key=key, active=True)
933 repo_stub, section, 'repo', key=key, active=True)
945 Session().commit()
934 Session().commit()
946
935
947 result = model.get_ui_settings()
936 result = model.get_ui_settings()
948 expected_result = model.get_repo_ui_settings()
937 expected_result = model.get_repo_ui_settings()
949 assert sorted(result) == sorted(expected_result)
938 assert sorted(result) == sorted(expected_result)
950
939
951 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
940 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
952 model = VcsSettingsModel(repo=repo_stub.repo_name)
941 model = VcsSettingsModel(repo=repo_stub.repo_name)
953 model.inherit_global_settings = False
942 model.inherit_global_settings = False
954
943
955 args = ('section', 'key')
944 args = ('section', 'key')
956 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
945 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
957 model.get_ui_settings(*args)
946 model.get_ui_settings(*args)
958 Session().commit()
947 Session().commit()
959
948
960 settings_mock.assert_called_once_with(*args)
949 settings_mock.assert_called_once_with(*args)
961
950
962 def test_global_settings_filtered_by_section_and_key(self):
951 def test_global_settings_filtered_by_section_and_key(self):
963 model = VcsSettingsModel()
952 model = VcsSettingsModel()
964 args = ('section', 'key')
953 args = ('section', 'key')
965 with mock.patch.object(model, 'get_global_ui_settings') as (
954 with mock.patch.object(model, 'get_global_ui_settings') as (
966 settings_mock):
955 settings_mock):
967 model.get_ui_settings(*args)
956 model.get_ui_settings(*args)
968 settings_mock.assert_called_once_with(*args)
957 settings_mock.assert_called_once_with(*args)
969
958
970 def test_global_settings_are_returned_when_no_repository_specified(self):
959 def test_global_settings_are_returned_when_no_repository_specified(self):
971 model = VcsSettingsModel()
960 model = VcsSettingsModel()
972 result = model.get_ui_settings()
961 result = model.get_ui_settings()
973 expected_result = model.get_global_ui_settings()
962 expected_result = model.get_global_ui_settings()
974 assert sorted(result) == sorted(expected_result)
963 assert sorted(result) == sorted(expected_result)
975
964
976
965
977 class TestGetSvnPatterns(object):
966 class TestGetSvnPatterns(object):
978 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
967 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
979 model = VcsSettingsModel(repo=repo_stub.repo_name)
968 model = VcsSettingsModel(repo=repo_stub.repo_name)
980 args = ('section', )
969 args = ('section', )
981 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
970 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
982 model.get_svn_patterns(*args)
971 model.get_svn_patterns(*args)
983
972
984 Session().commit()
973 Session().commit()
985 settings_mock.assert_called_once_with(*args)
974 settings_mock.assert_called_once_with(*args)
986
975
987 def test_global_settings_filtered_by_section_and_key(self):
976 def test_global_settings_filtered_by_section_and_key(self):
988 model = VcsSettingsModel()
977 model = VcsSettingsModel()
989 args = ('section', )
978 args = ('section', )
990 with mock.patch.object(model, 'get_global_ui_settings') as (
979 with mock.patch.object(model, 'get_global_ui_settings') as (
991 settings_mock):
980 settings_mock):
992 model.get_svn_patterns(*args)
981 model.get_svn_patterns(*args)
993 settings_mock.assert_called_once_with(*args)
982 settings_mock.assert_called_once_with(*args)
994
983
995
984
996 class TestCreateOrUpdateRepoSettings(object):
985 class TestCreateOrUpdateRepoSettings(object):
997 FORM_DATA = {
986 FORM_DATA = {
998 'inherit_global_settings': False,
987 'inherit_global_settings': False,
999 'hooks_changegroup_repo_size': False,
988 'hooks_changegroup_repo_size': False,
1000 'hooks_changegroup_push_logger': False,
989 'hooks_changegroup_push_logger': False,
1001 'hooks_outgoing_pull_logger': False,
990 'hooks_outgoing_pull_logger': False,
1002 'extensions_largefiles': False,
991 'extensions_largefiles': False,
1003 'extensions_evolve': False,
992 'extensions_evolve': False,
1004 'largefiles_usercache': '/example/largefiles-store',
993 'largefiles_usercache': '/example/largefiles-store',
1005 'vcs_git_lfs_enabled': False,
994 'vcs_git_lfs_enabled': False,
1006 'vcs_git_lfs_store_location': '/',
995 'vcs_git_lfs_store_location': '/',
1007 'phases_publish': 'False',
996 'phases_publish': 'False',
1008 'rhodecode_pr_merge_enabled': False,
997 'rhodecode_pr_merge_enabled': False,
1009 'rhodecode_use_outdated_comments': False,
998 'rhodecode_use_outdated_comments': False,
1010 'new_svn_branch': '',
999 'new_svn_branch': '',
1011 'new_svn_tag': ''
1000 'new_svn_tag': ''
1012 }
1001 }
1013
1002
1014 def test_get_raises_exception_when_repository_not_specified(self):
1003 def test_get_raises_exception_when_repository_not_specified(self):
1015 model = VcsSettingsModel()
1004 model = VcsSettingsModel()
1016 with pytest.raises(Exception) as exc_info:
1005 with pytest.raises(Exception) as exc_info:
1017 model.create_or_update_repo_settings(data=self.FORM_DATA)
1006 model.create_or_update_repo_settings(data=self.FORM_DATA)
1018 Session().commit()
1007 Session().commit()
1019
1008
1020 assert str(exc_info.value) == 'Repository is not specified'
1009 assert str(exc_info.value) == 'Repository is not specified'
1021
1010
1022 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1011 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1023 repo = backend_svn.create_repo()
1012 repo = backend_svn.create_repo()
1024 model = VcsSettingsModel(repo=repo)
1013 model = VcsSettingsModel(repo=repo)
1025 with self._patch_model(model) as mocks:
1014 with self._patch_model(model) as mocks:
1026 model.create_or_update_repo_settings(
1015 model.create_or_update_repo_settings(
1027 data=self.FORM_DATA, inherit_global_settings=False)
1016 data=self.FORM_DATA, inherit_global_settings=False)
1028 Session().commit()
1017 Session().commit()
1029
1018
1030 mocks['create_repo_svn_settings'].assert_called_once_with(
1019 mocks['create_repo_svn_settings'].assert_called_once_with(
1031 self.FORM_DATA)
1020 self.FORM_DATA)
1032 non_called_methods = (
1021 non_called_methods = (
1033 'create_or_update_repo_hook_settings',
1022 'create_or_update_repo_hook_settings',
1034 'create_or_update_repo_pr_settings',
1023 'create_or_update_repo_pr_settings',
1035 'create_or_update_repo_hg_settings')
1024 'create_or_update_repo_hg_settings')
1036 for method in non_called_methods:
1025 for method in non_called_methods:
1037 assert mocks[method].call_count == 0
1026 assert mocks[method].call_count == 0
1038
1027
1039 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1028 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1040 repo = backend_hg.create_repo()
1029 repo = backend_hg.create_repo()
1041 model = VcsSettingsModel(repo=repo)
1030 model = VcsSettingsModel(repo=repo)
1042 with self._patch_model(model) as mocks:
1031 with self._patch_model(model) as mocks:
1043 model.create_or_update_repo_settings(
1032 model.create_or_update_repo_settings(
1044 data=self.FORM_DATA, inherit_global_settings=False)
1033 data=self.FORM_DATA, inherit_global_settings=False)
1045 Session().commit()
1034 Session().commit()
1046
1035
1047 assert mocks['create_repo_svn_settings'].call_count == 0
1036 assert mocks['create_repo_svn_settings'].call_count == 0
1048 called_methods = (
1037 called_methods = (
1049 'create_or_update_repo_hook_settings',
1038 'create_or_update_repo_hook_settings',
1050 'create_or_update_repo_pr_settings',
1039 'create_or_update_repo_pr_settings',
1051 'create_or_update_repo_hg_settings')
1040 'create_or_update_repo_hg_settings')
1052 for method in called_methods:
1041 for method in called_methods:
1053 mocks[method].assert_called_once_with(self.FORM_DATA)
1042 mocks[method].assert_called_once_with(self.FORM_DATA)
1054
1043
1055 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1044 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1056 self, backend_git):
1045 self, backend_git):
1057 repo = backend_git.create_repo()
1046 repo = backend_git.create_repo()
1058 model = VcsSettingsModel(repo=repo)
1047 model = VcsSettingsModel(repo=repo)
1059 with self._patch_model(model) as mocks:
1048 with self._patch_model(model) as mocks:
1060 model.create_or_update_repo_settings(
1049 model.create_or_update_repo_settings(
1061 data=self.FORM_DATA, inherit_global_settings=False)
1050 data=self.FORM_DATA, inherit_global_settings=False)
1062
1051
1063 assert mocks['create_repo_svn_settings'].call_count == 0
1052 assert mocks['create_repo_svn_settings'].call_count == 0
1064 called_methods = (
1053 called_methods = (
1065 'create_or_update_repo_hook_settings',
1054 'create_or_update_repo_hook_settings',
1066 'create_or_update_repo_pr_settings')
1055 'create_or_update_repo_pr_settings')
1067 non_called_methods = (
1056 non_called_methods = (
1068 'create_repo_svn_settings',
1057 'create_repo_svn_settings',
1069 'create_or_update_repo_hg_settings'
1058 'create_or_update_repo_hg_settings'
1070 )
1059 )
1071 for method in called_methods:
1060 for method in called_methods:
1072 mocks[method].assert_called_once_with(self.FORM_DATA)
1061 mocks[method].assert_called_once_with(self.FORM_DATA)
1073 for method in non_called_methods:
1062 for method in non_called_methods:
1074 assert mocks[method].call_count == 0
1063 assert mocks[method].call_count == 0
1075
1064
1076 def test_no_methods_are_called_when_settings_are_inherited(
1065 def test_no_methods_are_called_when_settings_are_inherited(
1077 self, backend):
1066 self, backend):
1078 repo = backend.create_repo()
1067 repo = backend.create_repo()
1079 model = VcsSettingsModel(repo=repo)
1068 model = VcsSettingsModel(repo=repo)
1080 with self._patch_model(model) as mocks:
1069 with self._patch_model(model) as mocks:
1081 model.create_or_update_repo_settings(
1070 model.create_or_update_repo_settings(
1082 data=self.FORM_DATA, inherit_global_settings=True)
1071 data=self.FORM_DATA, inherit_global_settings=True)
1083 for method_name in mocks:
1072 for method_name in mocks:
1084 assert mocks[method_name].call_count == 0
1073 assert mocks[method_name].call_count == 0
1085
1074
1086 def test_cache_is_marked_for_invalidation(self, repo_stub):
1075 def test_cache_is_marked_for_invalidation(self, repo_stub):
1087 model = VcsSettingsModel(repo=repo_stub)
1076 model = VcsSettingsModel(repo=repo_stub)
1088 invalidation_patcher = mock.patch(
1077 invalidation_patcher = mock.patch(
1089 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1078 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1090 with invalidation_patcher as invalidation_mock:
1079 with invalidation_patcher as invalidation_mock:
1091 model.create_or_update_repo_settings(
1080 model.create_or_update_repo_settings(
1092 data=self.FORM_DATA, inherit_global_settings=True)
1081 data=self.FORM_DATA, inherit_global_settings=True)
1093 Session().commit()
1082 Session().commit()
1094
1083
1095 invalidation_mock.assert_called_once_with(
1084 invalidation_mock.assert_called_once_with(
1096 repo_stub.repo_name, delete=True)
1085 repo_stub.repo_name, delete=True)
1097
1086
1098 def test_inherit_flag_is_saved(self, repo_stub):
1087 def test_inherit_flag_is_saved(self, repo_stub):
1099 model = VcsSettingsModel(repo=repo_stub)
1088 model = VcsSettingsModel(repo=repo_stub)
1100 model.inherit_global_settings = True
1089 model.inherit_global_settings = True
1101 with self._patch_model(model):
1090 with self._patch_model(model):
1102 model.create_or_update_repo_settings(
1091 model.create_or_update_repo_settings(
1103 data=self.FORM_DATA, inherit_global_settings=False)
1092 data=self.FORM_DATA, inherit_global_settings=False)
1104 Session().commit()
1093 Session().commit()
1105
1094
1106 assert model.inherit_global_settings is False
1095 assert model.inherit_global_settings is False
1107
1096
1108 def _patch_model(self, model):
1097 def _patch_model(self, model):
1109 return mock.patch.multiple(
1098 return mock.patch.multiple(
1110 model,
1099 model,
1111 create_repo_svn_settings=mock.DEFAULT,
1100 create_repo_svn_settings=mock.DEFAULT,
1112 create_or_update_repo_hook_settings=mock.DEFAULT,
1101 create_or_update_repo_hook_settings=mock.DEFAULT,
1113 create_or_update_repo_pr_settings=mock.DEFAULT,
1102 create_or_update_repo_pr_settings=mock.DEFAULT,
1114 create_or_update_repo_hg_settings=mock.DEFAULT)
1103 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now