##// END OF EJS Templates
issue-trackers: enforce a http or / patterns to avoid JS injections.
marcink -
r2334:0804fe0e default
parent child Browse files
Show More
@@ -1,730 +1,730 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests import assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = (
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36
36
37
37
38 def route_path(name, params=None, **kwargs):
38 def route_path(name, params=None, **kwargs):
39 import urllib
39 import urllib
40 from rhodecode.apps._base import ADMIN_PREFIX
40 from rhodecode.apps._base import ADMIN_PREFIX
41
41
42 base_url = {
42 base_url = {
43
43
44 'admin_settings':
44 'admin_settings':
45 ADMIN_PREFIX +'/settings',
45 ADMIN_PREFIX +'/settings',
46 'admin_settings_update':
46 'admin_settings_update':
47 ADMIN_PREFIX + '/settings/update',
47 ADMIN_PREFIX + '/settings/update',
48 'admin_settings_global':
48 'admin_settings_global':
49 ADMIN_PREFIX + '/settings/global',
49 ADMIN_PREFIX + '/settings/global',
50 'admin_settings_global_update':
50 'admin_settings_global_update':
51 ADMIN_PREFIX + '/settings/global/update',
51 ADMIN_PREFIX + '/settings/global/update',
52 'admin_settings_vcs':
52 'admin_settings_vcs':
53 ADMIN_PREFIX + '/settings/vcs',
53 ADMIN_PREFIX + '/settings/vcs',
54 'admin_settings_vcs_update':
54 'admin_settings_vcs_update':
55 ADMIN_PREFIX + '/settings/vcs/update',
55 ADMIN_PREFIX + '/settings/vcs/update',
56 'admin_settings_vcs_svn_pattern_delete':
56 'admin_settings_vcs_svn_pattern_delete':
57 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
58 'admin_settings_mapping':
58 'admin_settings_mapping':
59 ADMIN_PREFIX + '/settings/mapping',
59 ADMIN_PREFIX + '/settings/mapping',
60 'admin_settings_mapping_update':
60 'admin_settings_mapping_update':
61 ADMIN_PREFIX + '/settings/mapping/update',
61 ADMIN_PREFIX + '/settings/mapping/update',
62 'admin_settings_visual':
62 'admin_settings_visual':
63 ADMIN_PREFIX + '/settings/visual',
63 ADMIN_PREFIX + '/settings/visual',
64 'admin_settings_visual_update':
64 'admin_settings_visual_update':
65 ADMIN_PREFIX + '/settings/visual/update',
65 ADMIN_PREFIX + '/settings/visual/update',
66 'admin_settings_issuetracker':
66 'admin_settings_issuetracker':
67 ADMIN_PREFIX + '/settings/issue-tracker',
67 ADMIN_PREFIX + '/settings/issue-tracker',
68 'admin_settings_issuetracker_update':
68 'admin_settings_issuetracker_update':
69 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 ADMIN_PREFIX + '/settings/issue-tracker/update',
70 'admin_settings_issuetracker_test':
70 'admin_settings_issuetracker_test':
71 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 ADMIN_PREFIX + '/settings/issue-tracker/test',
72 'admin_settings_issuetracker_delete':
72 'admin_settings_issuetracker_delete':
73 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 ADMIN_PREFIX + '/settings/issue-tracker/delete',
74 'admin_settings_email':
74 'admin_settings_email':
75 ADMIN_PREFIX + '/settings/email',
75 ADMIN_PREFIX + '/settings/email',
76 'admin_settings_email_update':
76 'admin_settings_email_update':
77 ADMIN_PREFIX + '/settings/email/update',
77 ADMIN_PREFIX + '/settings/email/update',
78 'admin_settings_hooks':
78 'admin_settings_hooks':
79 ADMIN_PREFIX + '/settings/hooks',
79 ADMIN_PREFIX + '/settings/hooks',
80 'admin_settings_hooks_update':
80 'admin_settings_hooks_update':
81 ADMIN_PREFIX + '/settings/hooks/update',
81 ADMIN_PREFIX + '/settings/hooks/update',
82 'admin_settings_hooks_delete':
82 'admin_settings_hooks_delete':
83 ADMIN_PREFIX + '/settings/hooks/delete',
83 ADMIN_PREFIX + '/settings/hooks/delete',
84 'admin_settings_search':
84 'admin_settings_search':
85 ADMIN_PREFIX + '/settings/search',
85 ADMIN_PREFIX + '/settings/search',
86 'admin_settings_labs':
86 'admin_settings_labs':
87 ADMIN_PREFIX + '/settings/labs',
87 ADMIN_PREFIX + '/settings/labs',
88 'admin_settings_labs_update':
88 'admin_settings_labs_update':
89 ADMIN_PREFIX + '/settings/labs/update',
89 ADMIN_PREFIX + '/settings/labs/update',
90
90
91 'admin_settings_sessions':
91 'admin_settings_sessions':
92 ADMIN_PREFIX + '/settings/sessions',
92 ADMIN_PREFIX + '/settings/sessions',
93 'admin_settings_sessions_cleanup':
93 'admin_settings_sessions_cleanup':
94 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 ADMIN_PREFIX + '/settings/sessions/cleanup',
95 'admin_settings_system':
95 'admin_settings_system':
96 ADMIN_PREFIX + '/settings/system',
96 ADMIN_PREFIX + '/settings/system',
97 'admin_settings_system_update':
97 'admin_settings_system_update':
98 ADMIN_PREFIX + '/settings/system/updates',
98 ADMIN_PREFIX + '/settings/system/updates',
99 'admin_settings_open_source':
99 'admin_settings_open_source':
100 ADMIN_PREFIX + '/settings/open_source',
100 ADMIN_PREFIX + '/settings/open_source',
101
101
102
102
103 }[name].format(**kwargs)
103 }[name].format(**kwargs)
104
104
105 if params:
105 if params:
106 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
106 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
107 return base_url
107 return base_url
108
108
109
109
110 @pytest.mark.usefixtures('autologin_user', 'app')
110 @pytest.mark.usefixtures('autologin_user', 'app')
111 class TestAdminSettingsController(object):
111 class TestAdminSettingsController(object):
112
112
113 @pytest.mark.parametrize('urlname', [
113 @pytest.mark.parametrize('urlname', [
114 'admin_settings_vcs',
114 'admin_settings_vcs',
115 'admin_settings_mapping',
115 'admin_settings_mapping',
116 'admin_settings_global',
116 'admin_settings_global',
117 'admin_settings_visual',
117 'admin_settings_visual',
118 'admin_settings_email',
118 'admin_settings_email',
119 'admin_settings_hooks',
119 'admin_settings_hooks',
120 'admin_settings_search',
120 'admin_settings_search',
121 ])
121 ])
122 def test_simple_get(self, urlname):
122 def test_simple_get(self, urlname):
123 self.app.get(route_path(urlname))
123 self.app.get(route_path(urlname))
124
124
125 def test_create_custom_hook(self, csrf_token):
125 def test_create_custom_hook(self, csrf_token):
126 response = self.app.post(
126 response = self.app.post(
127 route_path('admin_settings_hooks_update'),
127 route_path('admin_settings_hooks_update'),
128 params={
128 params={
129 'new_hook_ui_key': 'test_hooks_1',
129 'new_hook_ui_key': 'test_hooks_1',
130 'new_hook_ui_value': 'cd /tmp',
130 'new_hook_ui_value': 'cd /tmp',
131 'csrf_token': csrf_token})
131 'csrf_token': csrf_token})
132
132
133 response = response.follow()
133 response = response.follow()
134 response.mustcontain('test_hooks_1')
134 response.mustcontain('test_hooks_1')
135 response.mustcontain('cd /tmp')
135 response.mustcontain('cd /tmp')
136
136
137 def test_create_custom_hook_delete(self, csrf_token):
137 def test_create_custom_hook_delete(self, csrf_token):
138 response = self.app.post(
138 response = self.app.post(
139 route_path('admin_settings_hooks_update'),
139 route_path('admin_settings_hooks_update'),
140 params={
140 params={
141 'new_hook_ui_key': 'test_hooks_2',
141 'new_hook_ui_key': 'test_hooks_2',
142 'new_hook_ui_value': 'cd /tmp2',
142 'new_hook_ui_value': 'cd /tmp2',
143 'csrf_token': csrf_token})
143 'csrf_token': csrf_token})
144
144
145 response = response.follow()
145 response = response.follow()
146 response.mustcontain('test_hooks_2')
146 response.mustcontain('test_hooks_2')
147 response.mustcontain('cd /tmp2')
147 response.mustcontain('cd /tmp2')
148
148
149 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
150
150
151 # delete
151 # delete
152 self.app.post(
152 self.app.post(
153 route_path('admin_settings_hooks_delete'),
153 route_path('admin_settings_hooks_delete'),
154 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 params={'hook_id': hook_id, 'csrf_token': csrf_token})
155 response = self.app.get(route_path('admin_settings_hooks'))
155 response = self.app.get(route_path('admin_settings_hooks'))
156 response.mustcontain(no=['test_hooks_2'])
156 response.mustcontain(no=['test_hooks_2'])
157 response.mustcontain(no=['cd /tmp2'])
157 response.mustcontain(no=['cd /tmp2'])
158
158
159
159
160 @pytest.mark.usefixtures('autologin_user', 'app')
160 @pytest.mark.usefixtures('autologin_user', 'app')
161 class TestAdminSettingsGlobal(object):
161 class TestAdminSettingsGlobal(object):
162
162
163 def test_pre_post_code_code_active(self, csrf_token):
163 def test_pre_post_code_code_active(self, csrf_token):
164 pre_code = 'rc-pre-code-187652122'
164 pre_code = 'rc-pre-code-187652122'
165 post_code = 'rc-postcode-98165231'
165 post_code = 'rc-postcode-98165231'
166
166
167 response = self.post_and_verify_settings({
167 response = self.post_and_verify_settings({
168 'rhodecode_pre_code': pre_code,
168 'rhodecode_pre_code': pre_code,
169 'rhodecode_post_code': post_code,
169 'rhodecode_post_code': post_code,
170 'csrf_token': csrf_token,
170 'csrf_token': csrf_token,
171 })
171 })
172
172
173 response = response.follow()
173 response = response.follow()
174 response.mustcontain(pre_code, post_code)
174 response.mustcontain(pre_code, post_code)
175
175
176 def test_pre_post_code_code_inactive(self, csrf_token):
176 def test_pre_post_code_code_inactive(self, csrf_token):
177 pre_code = 'rc-pre-code-187652122'
177 pre_code = 'rc-pre-code-187652122'
178 post_code = 'rc-postcode-98165231'
178 post_code = 'rc-postcode-98165231'
179 response = self.post_and_verify_settings({
179 response = self.post_and_verify_settings({
180 'rhodecode_pre_code': '',
180 'rhodecode_pre_code': '',
181 'rhodecode_post_code': '',
181 'rhodecode_post_code': '',
182 'csrf_token': csrf_token,
182 'csrf_token': csrf_token,
183 })
183 })
184
184
185 response = response.follow()
185 response = response.follow()
186 response.mustcontain(no=[pre_code, post_code])
186 response.mustcontain(no=[pre_code, post_code])
187
187
188 def test_captcha_activate(self, csrf_token):
188 def test_captcha_activate(self, csrf_token):
189 self.post_and_verify_settings({
189 self.post_and_verify_settings({
190 'rhodecode_captcha_private_key': '1234567890',
190 'rhodecode_captcha_private_key': '1234567890',
191 'rhodecode_captcha_public_key': '1234567890',
191 'rhodecode_captcha_public_key': '1234567890',
192 'csrf_token': csrf_token,
192 'csrf_token': csrf_token,
193 })
193 })
194
194
195 response = self.app.get(ADMIN_PREFIX + '/register')
195 response = self.app.get(ADMIN_PREFIX + '/register')
196 response.mustcontain('captcha')
196 response.mustcontain('captcha')
197
197
198 def test_captcha_deactivate(self, csrf_token):
198 def test_captcha_deactivate(self, csrf_token):
199 self.post_and_verify_settings({
199 self.post_and_verify_settings({
200 'rhodecode_captcha_private_key': '',
200 'rhodecode_captcha_private_key': '',
201 'rhodecode_captcha_public_key': '1234567890',
201 'rhodecode_captcha_public_key': '1234567890',
202 'csrf_token': csrf_token,
202 'csrf_token': csrf_token,
203 })
203 })
204
204
205 response = self.app.get(ADMIN_PREFIX + '/register')
205 response = self.app.get(ADMIN_PREFIX + '/register')
206 response.mustcontain(no=['captcha'])
206 response.mustcontain(no=['captcha'])
207
207
208 def test_title_change(self, csrf_token):
208 def test_title_change(self, csrf_token):
209 old_title = 'RhodeCode'
209 old_title = 'RhodeCode'
210
210
211 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
211 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
212 response = self.post_and_verify_settings({
212 response = self.post_and_verify_settings({
213 'rhodecode_title': new_title,
213 'rhodecode_title': new_title,
214 'csrf_token': csrf_token,
214 'csrf_token': csrf_token,
215 })
215 })
216
216
217 response = response.follow()
217 response = response.follow()
218 response.mustcontain(
218 response.mustcontain(
219 """<div class="branding">- %s</div>""" % new_title)
219 """<div class="branding">- %s</div>""" % new_title)
220
220
221 def post_and_verify_settings(self, settings):
221 def post_and_verify_settings(self, settings):
222 old_title = 'RhodeCode'
222 old_title = 'RhodeCode'
223 old_realm = 'RhodeCode authentication'
223 old_realm = 'RhodeCode authentication'
224 params = {
224 params = {
225 'rhodecode_title': old_title,
225 'rhodecode_title': old_title,
226 'rhodecode_realm': old_realm,
226 'rhodecode_realm': old_realm,
227 'rhodecode_pre_code': '',
227 'rhodecode_pre_code': '',
228 'rhodecode_post_code': '',
228 'rhodecode_post_code': '',
229 'rhodecode_captcha_private_key': '',
229 'rhodecode_captcha_private_key': '',
230 'rhodecode_captcha_public_key': '',
230 'rhodecode_captcha_public_key': '',
231 'rhodecode_create_personal_repo_group': False,
231 'rhodecode_create_personal_repo_group': False,
232 'rhodecode_personal_repo_group_pattern': '${username}',
232 'rhodecode_personal_repo_group_pattern': '${username}',
233 }
233 }
234 params.update(settings)
234 params.update(settings)
235 response = self.app.post(
235 response = self.app.post(
236 route_path('admin_settings_global_update'), params=params)
236 route_path('admin_settings_global_update'), params=params)
237
237
238 assert_session_flash(response, 'Updated application settings')
238 assert_session_flash(response, 'Updated application settings')
239 app_settings = SettingsModel().get_all_settings()
239 app_settings = SettingsModel().get_all_settings()
240 del settings['csrf_token']
240 del settings['csrf_token']
241 for key, value in settings.iteritems():
241 for key, value in settings.iteritems():
242 assert app_settings[key] == value.decode('utf-8')
242 assert app_settings[key] == value.decode('utf-8')
243
243
244 return response
244 return response
245
245
246
246
247 @pytest.mark.usefixtures('autologin_user', 'app')
247 @pytest.mark.usefixtures('autologin_user', 'app')
248 class TestAdminSettingsVcs(object):
248 class TestAdminSettingsVcs(object):
249
249
250 def test_contains_svn_default_patterns(self):
250 def test_contains_svn_default_patterns(self):
251 response = self.app.get(route_path('admin_settings_vcs'))
251 response = self.app.get(route_path('admin_settings_vcs'))
252 expected_patterns = [
252 expected_patterns = [
253 '/trunk',
253 '/trunk',
254 '/branches/*',
254 '/branches/*',
255 '/tags/*',
255 '/tags/*',
256 ]
256 ]
257 for pattern in expected_patterns:
257 for pattern in expected_patterns:
258 response.mustcontain(pattern)
258 response.mustcontain(pattern)
259
259
260 def test_add_new_svn_branch_and_tag_pattern(
260 def test_add_new_svn_branch_and_tag_pattern(
261 self, backend_svn, form_defaults, disable_sql_cache,
261 self, backend_svn, form_defaults, disable_sql_cache,
262 csrf_token):
262 csrf_token):
263 form_defaults.update({
263 form_defaults.update({
264 'new_svn_branch': '/exp/branches/*',
264 'new_svn_branch': '/exp/branches/*',
265 'new_svn_tag': '/important_tags/*',
265 'new_svn_tag': '/important_tags/*',
266 'csrf_token': csrf_token,
266 'csrf_token': csrf_token,
267 })
267 })
268
268
269 response = self.app.post(
269 response = self.app.post(
270 route_path('admin_settings_vcs_update'),
270 route_path('admin_settings_vcs_update'),
271 params=form_defaults, status=302)
271 params=form_defaults, status=302)
272 response = response.follow()
272 response = response.follow()
273
273
274 # Expect to find the new values on the page
274 # Expect to find the new values on the page
275 response.mustcontain('/exp/branches/*')
275 response.mustcontain('/exp/branches/*')
276 response.mustcontain('/important_tags/*')
276 response.mustcontain('/important_tags/*')
277
277
278 # Expect that those patterns are used to match branches and tags now
278 # Expect that those patterns are used to match branches and tags now
279 repo = backend_svn['svn-simple-layout'].scm_instance()
279 repo = backend_svn['svn-simple-layout'].scm_instance()
280 assert 'exp/branches/exp-sphinx-docs' in repo.branches
280 assert 'exp/branches/exp-sphinx-docs' in repo.branches
281 assert 'important_tags/v0.5' in repo.tags
281 assert 'important_tags/v0.5' in repo.tags
282
282
283 def test_add_same_svn_value_twice_shows_an_error_message(
283 def test_add_same_svn_value_twice_shows_an_error_message(
284 self, form_defaults, csrf_token, settings_util):
284 self, form_defaults, csrf_token, settings_util):
285 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
285 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
286 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
286 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
287
287
288 response = self.app.post(
288 response = self.app.post(
289 route_path('admin_settings_vcs_update'),
289 route_path('admin_settings_vcs_update'),
290 params={
290 params={
291 'paths_root_path': form_defaults['paths_root_path'],
291 'paths_root_path': form_defaults['paths_root_path'],
292 'new_svn_branch': '/test',
292 'new_svn_branch': '/test',
293 'new_svn_tag': '/test',
293 'new_svn_tag': '/test',
294 'csrf_token': csrf_token,
294 'csrf_token': csrf_token,
295 },
295 },
296 status=200)
296 status=200)
297
297
298 response.mustcontain("Pattern already exists")
298 response.mustcontain("Pattern already exists")
299 response.mustcontain("Some form inputs contain invalid data.")
299 response.mustcontain("Some form inputs contain invalid data.")
300
300
301 @pytest.mark.parametrize('section', [
301 @pytest.mark.parametrize('section', [
302 'vcs_svn_branch',
302 'vcs_svn_branch',
303 'vcs_svn_tag',
303 'vcs_svn_tag',
304 ])
304 ])
305 def test_delete_svn_patterns(
305 def test_delete_svn_patterns(
306 self, section, csrf_token, settings_util):
306 self, section, csrf_token, settings_util):
307 setting = settings_util.create_rhodecode_ui(
307 setting = settings_util.create_rhodecode_ui(
308 section, '/test_delete', cleanup=False)
308 section, '/test_delete', cleanup=False)
309
309
310 self.app.post(
310 self.app.post(
311 route_path('admin_settings_vcs_svn_pattern_delete'),
311 route_path('admin_settings_vcs_svn_pattern_delete'),
312 params={
312 params={
313 'delete_svn_pattern': setting.ui_id,
313 'delete_svn_pattern': setting.ui_id,
314 'csrf_token': csrf_token},
314 'csrf_token': csrf_token},
315 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
315 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
316
316
317 @pytest.mark.parametrize('section', [
317 @pytest.mark.parametrize('section', [
318 'vcs_svn_branch',
318 'vcs_svn_branch',
319 'vcs_svn_tag',
319 'vcs_svn_tag',
320 ])
320 ])
321 def test_delete_svn_patterns_raises_404_when_no_xhr(
321 def test_delete_svn_patterns_raises_404_when_no_xhr(
322 self, section, csrf_token, settings_util):
322 self, section, csrf_token, settings_util):
323 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
323 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
324
324
325 self.app.post(
325 self.app.post(
326 route_path('admin_settings_vcs_svn_pattern_delete'),
326 route_path('admin_settings_vcs_svn_pattern_delete'),
327 params={
327 params={
328 'delete_svn_pattern': setting.ui_id,
328 'delete_svn_pattern': setting.ui_id,
329 'csrf_token': csrf_token},
329 'csrf_token': csrf_token},
330 status=404)
330 status=404)
331
331
332 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
332 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
333 form_defaults.update({
333 form_defaults.update({
334 'csrf_token': csrf_token,
334 'csrf_token': csrf_token,
335 'extensions_hgsubversion': 'True',
335 'extensions_hgsubversion': 'True',
336 })
336 })
337 response = self.app.post(
337 response = self.app.post(
338 route_path('admin_settings_vcs_update'),
338 route_path('admin_settings_vcs_update'),
339 params=form_defaults,
339 params=form_defaults,
340 status=302)
340 status=302)
341
341
342 response = response.follow()
342 response = response.follow()
343 extensions_input = (
343 extensions_input = (
344 '<input id="extensions_hgsubversion" '
344 '<input id="extensions_hgsubversion" '
345 'name="extensions_hgsubversion" type="checkbox" '
345 'name="extensions_hgsubversion" type="checkbox" '
346 'value="True" checked="checked" />')
346 'value="True" checked="checked" />')
347 response.mustcontain(extensions_input)
347 response.mustcontain(extensions_input)
348
348
349 def test_extensions_hgevolve(self, form_defaults, csrf_token):
349 def test_extensions_hgevolve(self, form_defaults, csrf_token):
350 form_defaults.update({
350 form_defaults.update({
351 'csrf_token': csrf_token,
351 'csrf_token': csrf_token,
352 'extensions_evolve': 'True',
352 'extensions_evolve': 'True',
353 })
353 })
354 response = self.app.post(
354 response = self.app.post(
355 route_path('admin_settings_vcs_update'),
355 route_path('admin_settings_vcs_update'),
356 params=form_defaults,
356 params=form_defaults,
357 status=302)
357 status=302)
358
358
359 response = response.follow()
359 response = response.follow()
360 extensions_input = (
360 extensions_input = (
361 '<input id="extensions_evolve" '
361 '<input id="extensions_evolve" '
362 'name="extensions_evolve" type="checkbox" '
362 'name="extensions_evolve" type="checkbox" '
363 'value="True" checked="checked" />')
363 'value="True" checked="checked" />')
364 response.mustcontain(extensions_input)
364 response.mustcontain(extensions_input)
365
365
366 def test_has_a_section_for_pull_request_settings(self):
366 def test_has_a_section_for_pull_request_settings(self):
367 response = self.app.get(route_path('admin_settings_vcs'))
367 response = self.app.get(route_path('admin_settings_vcs'))
368 response.mustcontain('Pull Request Settings')
368 response.mustcontain('Pull Request Settings')
369
369
370 def test_has_an_input_for_invalidation_of_inline_comments(self):
370 def test_has_an_input_for_invalidation_of_inline_comments(self):
371 response = self.app.get(route_path('admin_settings_vcs'))
371 response = self.app.get(route_path('admin_settings_vcs'))
372 assert_response = AssertResponse(response)
372 assert_response = AssertResponse(response)
373 assert_response.one_element_exists(
373 assert_response.one_element_exists(
374 '[name=rhodecode_use_outdated_comments]')
374 '[name=rhodecode_use_outdated_comments]')
375
375
376 @pytest.mark.parametrize('new_value', [True, False])
376 @pytest.mark.parametrize('new_value', [True, False])
377 def test_allows_to_change_invalidation_of_inline_comments(
377 def test_allows_to_change_invalidation_of_inline_comments(
378 self, form_defaults, csrf_token, new_value):
378 self, form_defaults, csrf_token, new_value):
379 setting_key = 'use_outdated_comments'
379 setting_key = 'use_outdated_comments'
380 setting = SettingsModel().create_or_update_setting(
380 setting = SettingsModel().create_or_update_setting(
381 setting_key, not new_value, 'bool')
381 setting_key, not new_value, 'bool')
382 Session().add(setting)
382 Session().add(setting)
383 Session().commit()
383 Session().commit()
384
384
385 form_defaults.update({
385 form_defaults.update({
386 'csrf_token': csrf_token,
386 'csrf_token': csrf_token,
387 'rhodecode_use_outdated_comments': str(new_value),
387 'rhodecode_use_outdated_comments': str(new_value),
388 })
388 })
389 response = self.app.post(
389 response = self.app.post(
390 route_path('admin_settings_vcs_update'),
390 route_path('admin_settings_vcs_update'),
391 params=form_defaults,
391 params=form_defaults,
392 status=302)
392 status=302)
393 response = response.follow()
393 response = response.follow()
394 setting = SettingsModel().get_setting_by_name(setting_key)
394 setting = SettingsModel().get_setting_by_name(setting_key)
395 assert setting.app_settings_value is new_value
395 assert setting.app_settings_value is new_value
396
396
397 @pytest.mark.parametrize('new_value', [True, False])
397 @pytest.mark.parametrize('new_value', [True, False])
398 def test_allows_to_change_hg_rebase_merge_strategy(
398 def test_allows_to_change_hg_rebase_merge_strategy(
399 self, form_defaults, csrf_token, new_value):
399 self, form_defaults, csrf_token, new_value):
400 setting_key = 'hg_use_rebase_for_merging'
400 setting_key = 'hg_use_rebase_for_merging'
401
401
402 form_defaults.update({
402 form_defaults.update({
403 'csrf_token': csrf_token,
403 'csrf_token': csrf_token,
404 'rhodecode_' + setting_key: str(new_value),
404 'rhodecode_' + setting_key: str(new_value),
405 })
405 })
406
406
407 with mock.patch.dict(
407 with mock.patch.dict(
408 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
408 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
409 self.app.post(
409 self.app.post(
410 route_path('admin_settings_vcs_update'),
410 route_path('admin_settings_vcs_update'),
411 params=form_defaults,
411 params=form_defaults,
412 status=302)
412 status=302)
413
413
414 setting = SettingsModel().get_setting_by_name(setting_key)
414 setting = SettingsModel().get_setting_by_name(setting_key)
415 assert setting.app_settings_value is new_value
415 assert setting.app_settings_value is new_value
416
416
417 @pytest.fixture
417 @pytest.fixture
418 def disable_sql_cache(self, request):
418 def disable_sql_cache(self, request):
419 patcher = mock.patch(
419 patcher = mock.patch(
420 'rhodecode.lib.caching_query.FromCache.process_query')
420 'rhodecode.lib.caching_query.FromCache.process_query')
421 request.addfinalizer(patcher.stop)
421 request.addfinalizer(patcher.stop)
422 patcher.start()
422 patcher.start()
423
423
424 @pytest.fixture
424 @pytest.fixture
425 def form_defaults(self):
425 def form_defaults(self):
426 from rhodecode.apps.admin.views.settings import AdminSettingsView
426 from rhodecode.apps.admin.views.settings import AdminSettingsView
427 return AdminSettingsView._form_defaults()
427 return AdminSettingsView._form_defaults()
428
428
429 # TODO: johbo: What we really want is to checkpoint before a test run and
429 # TODO: johbo: What we really want is to checkpoint before a test run and
430 # reset the session afterwards.
430 # reset the session afterwards.
431 @pytest.fixture(scope='class', autouse=True)
431 @pytest.fixture(scope='class', autouse=True)
432 def cleanup_settings(self, request, pylonsapp):
432 def cleanup_settings(self, request, pylonsapp):
433 ui_id = RhodeCodeUi.ui_id
433 ui_id = RhodeCodeUi.ui_id
434 original_ids = list(
434 original_ids = list(
435 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
435 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
436
436
437 @request.addfinalizer
437 @request.addfinalizer
438 def cleanup():
438 def cleanup():
439 RhodeCodeUi.query().filter(
439 RhodeCodeUi.query().filter(
440 ui_id.notin_(original_ids)).delete(False)
440 ui_id.notin_(original_ids)).delete(False)
441
441
442
442
443 @pytest.mark.usefixtures('autologin_user', 'app')
443 @pytest.mark.usefixtures('autologin_user', 'app')
444 class TestLabsSettings(object):
444 class TestLabsSettings(object):
445 def test_get_settings_page_disabled(self):
445 def test_get_settings_page_disabled(self):
446 with mock.patch.dict(
446 with mock.patch.dict(
447 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
447 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
448
448
449 response = self.app.get(
449 response = self.app.get(
450 route_path('admin_settings_labs'), status=302)
450 route_path('admin_settings_labs'), status=302)
451
451
452 assert response.location.endswith(route_path('admin_settings'))
452 assert response.location.endswith(route_path('admin_settings'))
453
453
454 def test_get_settings_page_enabled(self):
454 def test_get_settings_page_enabled(self):
455 from rhodecode.apps.admin.views import settings
455 from rhodecode.apps.admin.views import settings
456 lab_settings = [
456 lab_settings = [
457 settings.LabSetting(
457 settings.LabSetting(
458 key='rhodecode_bool',
458 key='rhodecode_bool',
459 type='bool',
459 type='bool',
460 group='bool group',
460 group='bool group',
461 label='bool label',
461 label='bool label',
462 help='bool help'
462 help='bool help'
463 ),
463 ),
464 settings.LabSetting(
464 settings.LabSetting(
465 key='rhodecode_text',
465 key='rhodecode_text',
466 type='unicode',
466 type='unicode',
467 group='text group',
467 group='text group',
468 label='text label',
468 label='text label',
469 help='text help'
469 help='text help'
470 ),
470 ),
471 ]
471 ]
472 with mock.patch.dict(rhodecode.CONFIG,
472 with mock.patch.dict(rhodecode.CONFIG,
473 {'labs_settings_active': 'true'}):
473 {'labs_settings_active': 'true'}):
474 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
474 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
475 response = self.app.get(route_path('admin_settings_labs'))
475 response = self.app.get(route_path('admin_settings_labs'))
476
476
477 assert '<label>bool group:</label>' in response
477 assert '<label>bool group:</label>' in response
478 assert '<label for="rhodecode_bool">bool label</label>' in response
478 assert '<label for="rhodecode_bool">bool label</label>' in response
479 assert '<p class="help-block">bool help</p>' in response
479 assert '<p class="help-block">bool help</p>' in response
480 assert 'name="rhodecode_bool" type="checkbox"' in response
480 assert 'name="rhodecode_bool" type="checkbox"' in response
481
481
482 assert '<label>text group:</label>' in response
482 assert '<label>text group:</label>' in response
483 assert '<label for="rhodecode_text">text label</label>' in response
483 assert '<label for="rhodecode_text">text label</label>' in response
484 assert '<p class="help-block">text help</p>' in response
484 assert '<p class="help-block">text help</p>' in response
485 assert 'name="rhodecode_text" size="60" type="text"' in response
485 assert 'name="rhodecode_text" size="60" type="text"' in response
486
486
487
487
488 @pytest.mark.usefixtures('app')
488 @pytest.mark.usefixtures('app')
489 class TestOpenSourceLicenses(object):
489 class TestOpenSourceLicenses(object):
490
490
491 def test_records_are_displayed(self, autologin_user):
491 def test_records_are_displayed(self, autologin_user):
492 sample_licenses = {
492 sample_licenses = {
493 "python2.7-pytest-2.7.1": {
493 "python2.7-pytest-2.7.1": {
494 "UNKNOWN": None
494 "UNKNOWN": None
495 },
495 },
496 "python2.7-Markdown-2.6.2": {
496 "python2.7-Markdown-2.6.2": {
497 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
497 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
498 }
498 }
499 }
499 }
500 read_licenses_patch = mock.patch(
500 read_licenses_patch = mock.patch(
501 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
501 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
502 return_value=sample_licenses)
502 return_value=sample_licenses)
503 with read_licenses_patch:
503 with read_licenses_patch:
504 response = self.app.get(
504 response = self.app.get(
505 route_path('admin_settings_open_source'), status=200)
505 route_path('admin_settings_open_source'), status=200)
506
506
507 assert_response = AssertResponse(response)
507 assert_response = AssertResponse(response)
508 assert_response.element_contains(
508 assert_response.element_contains(
509 '.panel-heading', 'Licenses of Third Party Packages')
509 '.panel-heading', 'Licenses of Third Party Packages')
510 for name in sample_licenses:
510 for name in sample_licenses:
511 response.mustcontain(name)
511 response.mustcontain(name)
512 for license in sample_licenses[name]:
512 for license in sample_licenses[name]:
513 assert_response.element_contains('.panel-body', license)
513 assert_response.element_contains('.panel-body', license)
514
514
515 def test_records_can_be_read(self, autologin_user):
515 def test_records_can_be_read(self, autologin_user):
516 response = self.app.get(
516 response = self.app.get(
517 route_path('admin_settings_open_source'), status=200)
517 route_path('admin_settings_open_source'), status=200)
518 assert_response = AssertResponse(response)
518 assert_response = AssertResponse(response)
519 assert_response.element_contains(
519 assert_response.element_contains(
520 '.panel-heading', 'Licenses of Third Party Packages')
520 '.panel-heading', 'Licenses of Third Party Packages')
521
521
522 def test_forbidden_when_normal_user(self, autologin_regular_user):
522 def test_forbidden_when_normal_user(self, autologin_regular_user):
523 self.app.get(
523 self.app.get(
524 route_path('admin_settings_open_source'), status=404)
524 route_path('admin_settings_open_source'), status=404)
525
525
526
526
527 @pytest.mark.usefixtures('app')
527 @pytest.mark.usefixtures('app')
528 class TestUserSessions(object):
528 class TestUserSessions(object):
529
529
530 def test_forbidden_when_normal_user(self, autologin_regular_user):
530 def test_forbidden_when_normal_user(self, autologin_regular_user):
531 self.app.get(route_path('admin_settings_sessions'), status=404)
531 self.app.get(route_path('admin_settings_sessions'), status=404)
532
532
533 def test_show_sessions_page(self, autologin_user):
533 def test_show_sessions_page(self, autologin_user):
534 response = self.app.get(route_path('admin_settings_sessions'), status=200)
534 response = self.app.get(route_path('admin_settings_sessions'), status=200)
535 response.mustcontain('file')
535 response.mustcontain('file')
536
536
537 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
537 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
538
538
539 post_data = {
539 post_data = {
540 'csrf_token': csrf_token,
540 'csrf_token': csrf_token,
541 'expire_days': '60'
541 'expire_days': '60'
542 }
542 }
543 response = self.app.post(
543 response = self.app.post(
544 route_path('admin_settings_sessions_cleanup'), params=post_data,
544 route_path('admin_settings_sessions_cleanup'), params=post_data,
545 status=302)
545 status=302)
546 assert_session_flash(response, 'Cleaned up old sessions')
546 assert_session_flash(response, 'Cleaned up old sessions')
547
547
548
548
549 @pytest.mark.usefixtures('app')
549 @pytest.mark.usefixtures('app')
550 class TestAdminSystemInfo(object):
550 class TestAdminSystemInfo(object):
551
551
552 def test_forbidden_when_normal_user(self, autologin_regular_user):
552 def test_forbidden_when_normal_user(self, autologin_regular_user):
553 self.app.get(route_path('admin_settings_system'), status=404)
553 self.app.get(route_path('admin_settings_system'), status=404)
554
554
555 def test_system_info_page(self, autologin_user):
555 def test_system_info_page(self, autologin_user):
556 response = self.app.get(route_path('admin_settings_system'))
556 response = self.app.get(route_path('admin_settings_system'))
557 response.mustcontain('RhodeCode Community Edition, version {}'.format(
557 response.mustcontain('RhodeCode Community Edition, version {}'.format(
558 rhodecode.__version__))
558 rhodecode.__version__))
559
559
560 def test_system_update_new_version(self, autologin_user):
560 def test_system_update_new_version(self, autologin_user):
561 update_data = {
561 update_data = {
562 'versions': [
562 'versions': [
563 {
563 {
564 'version': '100.3.1415926535',
564 'version': '100.3.1415926535',
565 'general': 'The latest version we are ever going to ship'
565 'general': 'The latest version we are ever going to ship'
566 },
566 },
567 {
567 {
568 'version': '0.0.0',
568 'version': '0.0.0',
569 'general': 'The first version we ever shipped'
569 'general': 'The first version we ever shipped'
570 }
570 }
571 ]
571 ]
572 }
572 }
573 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
573 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
574 response = self.app.get(route_path('admin_settings_system_update'))
574 response = self.app.get(route_path('admin_settings_system_update'))
575 response.mustcontain('A <b>new version</b> is available')
575 response.mustcontain('A <b>new version</b> is available')
576
576
577 def test_system_update_nothing_new(self, autologin_user):
577 def test_system_update_nothing_new(self, autologin_user):
578 update_data = {
578 update_data = {
579 'versions': [
579 'versions': [
580 {
580 {
581 'version': '0.0.0',
581 'version': '0.0.0',
582 'general': 'The first version we ever shipped'
582 'general': 'The first version we ever shipped'
583 }
583 }
584 ]
584 ]
585 }
585 }
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 response = self.app.get(route_path('admin_settings_system_update'))
587 response = self.app.get(route_path('admin_settings_system_update'))
588 response.mustcontain(
588 response.mustcontain(
589 'You already have the <b>latest</b> stable version.')
589 'You already have the <b>latest</b> stable version.')
590
590
591 def test_system_update_bad_response(self, autologin_user):
591 def test_system_update_bad_response(self, autologin_user):
592 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
592 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
593 response = self.app.get(route_path('admin_settings_system_update'))
593 response = self.app.get(route_path('admin_settings_system_update'))
594 response.mustcontain(
594 response.mustcontain(
595 'Bad data sent from update server')
595 'Bad data sent from update server')
596
596
597
597
598 @pytest.mark.usefixtures("app")
598 @pytest.mark.usefixtures("app")
599 class TestAdminSettingsIssueTracker(object):
599 class TestAdminSettingsIssueTracker(object):
600 RC_PREFIX = 'rhodecode_'
600 RC_PREFIX = 'rhodecode_'
601 SHORT_PATTERN_KEY = 'issuetracker_pat_'
601 SHORT_PATTERN_KEY = 'issuetracker_pat_'
602 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
602 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
603
603
604 def test_issuetracker_index(self, autologin_user):
604 def test_issuetracker_index(self, autologin_user):
605 response = self.app.get(route_path('admin_settings_issuetracker'))
605 response = self.app.get(route_path('admin_settings_issuetracker'))
606 assert response.status_code == 200
606 assert response.status_code == 200
607
607
608 def test_add_empty_issuetracker_pattern(
608 def test_add_empty_issuetracker_pattern(
609 self, request, autologin_user, csrf_token):
609 self, request, autologin_user, csrf_token):
610 post_url = route_path('admin_settings_issuetracker_update')
610 post_url = route_path('admin_settings_issuetracker_update')
611 post_data = {
611 post_data = {
612 'csrf_token': csrf_token
612 'csrf_token': csrf_token
613 }
613 }
614 self.app.post(post_url, post_data, status=302)
614 self.app.post(post_url, post_data, status=302)
615
615
616 def test_add_issuetracker_pattern(
616 def test_add_issuetracker_pattern(
617 self, request, autologin_user, csrf_token):
617 self, request, autologin_user, csrf_token):
618 pattern = 'issuetracker_pat'
618 pattern = 'issuetracker_pat'
619 another_pattern = pattern+'1'
619 another_pattern = pattern+'1'
620 post_url = route_path('admin_settings_issuetracker_update')
620 post_url = route_path('admin_settings_issuetracker_update')
621 post_data = {
621 post_data = {
622 'new_pattern_pattern_0': pattern,
622 'new_pattern_pattern_0': pattern,
623 'new_pattern_url_0': 'url',
623 'new_pattern_url_0': 'http://url',
624 'new_pattern_prefix_0': 'prefix',
624 'new_pattern_prefix_0': 'prefix',
625 'new_pattern_description_0': 'description',
625 'new_pattern_description_0': 'description',
626 'new_pattern_pattern_1': another_pattern,
626 'new_pattern_pattern_1': another_pattern,
627 'new_pattern_url_1': 'url1',
627 'new_pattern_url_1': 'https://url1',
628 'new_pattern_prefix_1': 'prefix1',
628 'new_pattern_prefix_1': 'prefix1',
629 'new_pattern_description_1': 'description1',
629 'new_pattern_description_1': 'description1',
630 'csrf_token': csrf_token
630 'csrf_token': csrf_token
631 }
631 }
632 self.app.post(post_url, post_data, status=302)
632 self.app.post(post_url, post_data, status=302)
633 settings = SettingsModel().get_all_settings()
633 settings = SettingsModel().get_all_settings()
634 self.uid = md5(pattern)
634 self.uid = md5(pattern)
635 assert settings[self.PATTERN_KEY+self.uid] == pattern
635 assert settings[self.PATTERN_KEY+self.uid] == pattern
636 self.another_uid = md5(another_pattern)
636 self.another_uid = md5(another_pattern)
637 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
637 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
638
638
639 @request.addfinalizer
639 @request.addfinalizer
640 def cleanup():
640 def cleanup():
641 defaults = SettingsModel().get_all_settings()
641 defaults = SettingsModel().get_all_settings()
642
642
643 entries = [name for name in defaults if (
643 entries = [name for name in defaults if (
644 (self.uid in name) or (self.another_uid) in name)]
644 (self.uid in name) or (self.another_uid) in name)]
645 start = len(self.RC_PREFIX)
645 start = len(self.RC_PREFIX)
646 for del_key in entries:
646 for del_key in entries:
647 # TODO: anderson: get_by_name needs name without prefix
647 # TODO: anderson: get_by_name needs name without prefix
648 entry = SettingsModel().get_setting_by_name(del_key[start:])
648 entry = SettingsModel().get_setting_by_name(del_key[start:])
649 Session().delete(entry)
649 Session().delete(entry)
650
650
651 Session().commit()
651 Session().commit()
652
652
653 def test_edit_issuetracker_pattern(
653 def test_edit_issuetracker_pattern(
654 self, autologin_user, backend, csrf_token, request):
654 self, autologin_user, backend, csrf_token, request):
655 old_pattern = 'issuetracker_pat'
655 old_pattern = 'issuetracker_pat'
656 old_uid = md5(old_pattern)
656 old_uid = md5(old_pattern)
657 pattern = 'issuetracker_pat_new'
657 pattern = 'issuetracker_pat_new'
658 self.new_uid = md5(pattern)
658 self.new_uid = md5(pattern)
659
659
660 SettingsModel().create_or_update_setting(
660 SettingsModel().create_or_update_setting(
661 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
661 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
662
662
663 post_url = route_path('admin_settings_issuetracker_update')
663 post_url = route_path('admin_settings_issuetracker_update')
664 post_data = {
664 post_data = {
665 'new_pattern_pattern_0': pattern,
665 'new_pattern_pattern_0': pattern,
666 'new_pattern_url_0': 'url',
666 'new_pattern_url_0': 'https://url',
667 'new_pattern_prefix_0': 'prefix',
667 'new_pattern_prefix_0': 'prefix',
668 'new_pattern_description_0': 'description',
668 'new_pattern_description_0': 'description',
669 'uid': old_uid,
669 'uid': old_uid,
670 'csrf_token': csrf_token
670 'csrf_token': csrf_token
671 }
671 }
672 self.app.post(post_url, post_data, status=302)
672 self.app.post(post_url, post_data, status=302)
673 settings = SettingsModel().get_all_settings()
673 settings = SettingsModel().get_all_settings()
674 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
674 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
675 assert self.PATTERN_KEY+old_uid not in settings
675 assert self.PATTERN_KEY+old_uid not in settings
676
676
677 @request.addfinalizer
677 @request.addfinalizer
678 def cleanup():
678 def cleanup():
679 IssueTrackerSettingsModel().delete_entries(self.new_uid)
679 IssueTrackerSettingsModel().delete_entries(self.new_uid)
680
680
681 def test_replace_issuetracker_pattern_description(
681 def test_replace_issuetracker_pattern_description(
682 self, autologin_user, csrf_token, request, settings_util):
682 self, autologin_user, csrf_token, request, settings_util):
683 prefix = 'issuetracker'
683 prefix = 'issuetracker'
684 pattern = 'issuetracker_pat'
684 pattern = 'issuetracker_pat'
685 self.uid = md5(pattern)
685 self.uid = md5(pattern)
686 pattern_key = '_'.join([prefix, 'pat', self.uid])
686 pattern_key = '_'.join([prefix, 'pat', self.uid])
687 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
687 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
688 desc_key = '_'.join([prefix, 'desc', self.uid])
688 desc_key = '_'.join([prefix, 'desc', self.uid])
689 rc_desc_key = '_'.join(['rhodecode', desc_key])
689 rc_desc_key = '_'.join(['rhodecode', desc_key])
690 new_description = 'new_description'
690 new_description = 'new_description'
691
691
692 settings_util.create_rhodecode_setting(
692 settings_util.create_rhodecode_setting(
693 pattern_key, pattern, 'unicode', cleanup=False)
693 pattern_key, pattern, 'unicode', cleanup=False)
694 settings_util.create_rhodecode_setting(
694 settings_util.create_rhodecode_setting(
695 desc_key, 'old description', 'unicode', cleanup=False)
695 desc_key, 'old description', 'unicode', cleanup=False)
696
696
697 post_url = route_path('admin_settings_issuetracker_update')
697 post_url = route_path('admin_settings_issuetracker_update')
698 post_data = {
698 post_data = {
699 'new_pattern_pattern_0': pattern,
699 'new_pattern_pattern_0': pattern,
700 'new_pattern_url_0': 'url',
700 'new_pattern_url_0': 'https://url',
701 'new_pattern_prefix_0': 'prefix',
701 'new_pattern_prefix_0': 'prefix',
702 'new_pattern_description_0': new_description,
702 'new_pattern_description_0': new_description,
703 'uid': self.uid,
703 'uid': self.uid,
704 'csrf_token': csrf_token
704 'csrf_token': csrf_token
705 }
705 }
706 self.app.post(post_url, post_data, status=302)
706 self.app.post(post_url, post_data, status=302)
707 settings = SettingsModel().get_all_settings()
707 settings = SettingsModel().get_all_settings()
708 assert settings[rc_pattern_key] == pattern
708 assert settings[rc_pattern_key] == pattern
709 assert settings[rc_desc_key] == new_description
709 assert settings[rc_desc_key] == new_description
710
710
711 @request.addfinalizer
711 @request.addfinalizer
712 def cleanup():
712 def cleanup():
713 IssueTrackerSettingsModel().delete_entries(self.uid)
713 IssueTrackerSettingsModel().delete_entries(self.uid)
714
714
715 def test_delete_issuetracker_pattern(
715 def test_delete_issuetracker_pattern(
716 self, autologin_user, backend, csrf_token, settings_util):
716 self, autologin_user, backend, csrf_token, settings_util):
717 pattern = 'issuetracker_pat'
717 pattern = 'issuetracker_pat'
718 uid = md5(pattern)
718 uid = md5(pattern)
719 settings_util.create_rhodecode_setting(
719 settings_util.create_rhodecode_setting(
720 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
720 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
721
721
722 post_url = route_path('admin_settings_issuetracker_delete')
722 post_url = route_path('admin_settings_issuetracker_delete')
723 post_data = {
723 post_data = {
724 '_method': 'delete',
724 '_method': 'delete',
725 'uid': uid,
725 'uid': uid,
726 'csrf_token': csrf_token
726 'csrf_token': csrf_token
727 }
727 }
728 self.app.post(post_url, post_data, status=302)
728 self.app.post(post_url, post_data, status=302)
729 settings = SettingsModel().get_all_settings()
729 settings = SettingsModel().get_all_settings()
730 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
730 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,754 +1,762 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30 from pyramid.view import view_config
30 from pyramid.view import view_config
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps.admin.navigation import navigation_list
36 from rhodecode.apps.admin.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
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 self._register_global_c(c)
70 self._register_global_c(c)
71 return c
71 return c
72
72
73 @classmethod
73 @classmethod
74 def _get_ui_settings(cls):
74 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
75 ret = RhodeCodeUi.query().all()
76
76
77 if not ret:
77 if not ret:
78 raise Exception('Could not get application ui settings !')
78 raise Exception('Could not get application ui settings !')
79 settings = {}
79 settings = {}
80 for each in ret:
80 for each in ret:
81 k = each.ui_key
81 k = each.ui_key
82 v = each.ui_value
82 v = each.ui_value
83 if k == '/':
83 if k == '/':
84 k = 'root_path'
84 k = 'root_path'
85
85
86 if k in ['push_ssl', 'publish', 'enabled']:
86 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
87 v = str2bool(v)
88
88
89 if k.find('.') != -1:
89 if k.find('.') != -1:
90 k = k.replace('.', '_')
90 k = k.replace('.', '_')
91
91
92 if each.ui_section in ['hooks', 'extensions']:
92 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
93 v = each.ui_active
94
94
95 settings[each.ui_section + '_' + k] = v
95 settings[each.ui_section + '_' + k] = v
96 return settings
96 return settings
97
97
98 @classmethod
98 @classmethod
99 def _form_defaults(cls):
99 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
100 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
101 defaults.update(cls._get_ui_settings())
102
102
103 defaults.update({
103 defaults.update({
104 'new_svn_branch': '',
104 'new_svn_branch': '',
105 'new_svn_tag': '',
105 'new_svn_tag': '',
106 })
106 })
107 return defaults
107 return defaults
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
110 @HasPermissionAllDecorator('hg.admin')
111 @view_config(
111 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
112 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
114 def settings_vcs(self):
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'vcs'
116 c.active = 'vcs'
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
120
121 settings = self.request.registry.settings
121 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
122 c.svn_proxy_generate_config = settings[generate_config]
123
123
124 defaults = self._form_defaults()
124 defaults = self._form_defaults()
125
125
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
127
128 data = render('rhodecode:templates/admin/settings/settings.mako',
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 encoding="UTF-8",
133 encoding="UTF-8",
134 force_defaults=False
134 force_defaults=False
135 )
135 )
136 return Response(html)
136 return Response(html)
137
137
138 @LoginRequired()
138 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
140 @CSRFRequired()
141 @view_config(
141 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
142 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
144 def settings_vcs_update(self):
145 _ = self.request.translate
145 _ = self.request.translate
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.active = 'vcs'
147 c.active = 'vcs'
148
148
149 model = VcsSettingsModel()
149 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
152
153 settings = self.request.registry.settings
153 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
154 c.svn_proxy_generate_config = settings[generate_config]
155
155
156 application_form = ApplicationUiSettingsForm()()
156 application_form = ApplicationUiSettingsForm()()
157
157
158 try:
158 try:
159 form_result = application_form.to_python(dict(self.request.POST))
159 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
160 except formencode.Invalid as errors:
161 h.flash(
161 h.flash(
162 _("Some form inputs contain invalid data."),
162 _("Some form inputs contain invalid data."),
163 category='error')
163 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=errors.value,
168 defaults=errors.value,
169 errors=errors.error_dict or {},
169 errors=errors.error_dict or {},
170 prefix_error=False,
170 prefix_error=False,
171 encoding="UTF-8",
171 encoding="UTF-8",
172 force_defaults=False
172 force_defaults=False
173 )
173 )
174 return Response(html)
174 return Response(html)
175
175
176 try:
176 try:
177 if c.visual.allow_repo_location_change:
177 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(
178 model.update_global_path_setting(
179 form_result['paths_root_path'])
179 form_result['paths_root_path'])
180
180
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
182 model.update_global_hook_settings(form_result)
182 model.update_global_hook_settings(form_result)
183
183
184 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_svn_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
188 except Exception:
188 except Exception:
189 log.exception("Exception while updating settings")
189 log.exception("Exception while updating settings")
190 h.flash(_('Error occurred during updating '
190 h.flash(_('Error occurred during updating '
191 'application settings'), category='error')
191 'application settings'), category='error')
192 else:
192 else:
193 Session().commit()
193 Session().commit()
194 h.flash(_('Updated VCS settings'), category='success')
194 h.flash(_('Updated VCS settings'), category='success')
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
196
196
197 data = render('rhodecode:templates/admin/settings/settings.mako',
197 data = render('rhodecode:templates/admin/settings/settings.mako',
198 self._get_template_context(c), self.request)
198 self._get_template_context(c), self.request)
199 html = formencode.htmlfill.render(
199 html = formencode.htmlfill.render(
200 data,
200 data,
201 defaults=self._form_defaults(),
201 defaults=self._form_defaults(),
202 encoding="UTF-8",
202 encoding="UTF-8",
203 force_defaults=False
203 force_defaults=False
204 )
204 )
205 return Response(html)
205 return Response(html)
206
206
207 @LoginRequired()
207 @LoginRequired()
208 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
209 @CSRFRequired()
209 @CSRFRequired()
210 @view_config(
210 @view_config(
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
212 renderer='json_ext', xhr=True)
212 renderer='json_ext', xhr=True)
213 def settings_vcs_delete_svn_pattern(self):
213 def settings_vcs_delete_svn_pattern(self):
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
215 model = VcsSettingsModel()
215 model = VcsSettingsModel()
216 try:
216 try:
217 model.delete_global_svn_pattern(delete_pattern_id)
217 model.delete_global_svn_pattern(delete_pattern_id)
218 except SettingNotFound:
218 except SettingNotFound:
219 log.exception(
219 log.exception(
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
221 raise HTTPNotFound()
221 raise HTTPNotFound()
222
222
223 Session().commit()
223 Session().commit()
224 return True
224 return True
225
225
226 @LoginRequired()
226 @LoginRequired()
227 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
228 @view_config(
229 route_name='admin_settings_mapping', request_method='GET',
229 route_name='admin_settings_mapping', request_method='GET',
230 renderer='rhodecode:templates/admin/settings/settings.mako')
230 renderer='rhodecode:templates/admin/settings/settings.mako')
231 def settings_mapping(self):
231 def settings_mapping(self):
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'mapping'
233 c.active = 'mapping'
234
234
235 data = render('rhodecode:templates/admin/settings/settings.mako',
235 data = render('rhodecode:templates/admin/settings/settings.mako',
236 self._get_template_context(c), self.request)
236 self._get_template_context(c), self.request)
237 html = formencode.htmlfill.render(
237 html = formencode.htmlfill.render(
238 data,
238 data,
239 defaults=self._form_defaults(),
239 defaults=self._form_defaults(),
240 encoding="UTF-8",
240 encoding="UTF-8",
241 force_defaults=False
241 force_defaults=False
242 )
242 )
243 return Response(html)
243 return Response(html)
244
244
245 @LoginRequired()
245 @LoginRequired()
246 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
247 @CSRFRequired()
247 @CSRFRequired()
248 @view_config(
248 @view_config(
249 route_name='admin_settings_mapping_update', request_method='POST',
249 route_name='admin_settings_mapping_update', request_method='POST',
250 renderer='rhodecode:templates/admin/settings/settings.mako')
250 renderer='rhodecode:templates/admin/settings/settings.mako')
251 def settings_mapping_update(self):
251 def settings_mapping_update(self):
252 _ = self.request.translate
252 _ = self.request.translate
253 c = self.load_default_context()
253 c = self.load_default_context()
254 c.active = 'mapping'
254 c.active = 'mapping'
255 rm_obsolete = self.request.POST.get('destroy', False)
255 rm_obsolete = self.request.POST.get('destroy', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
257 log.debug(
257 log.debug(
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
259
259
260 if invalidate_cache:
260 if invalidate_cache:
261 log.debug('invalidating all repositories cache')
261 log.debug('invalidating all repositories cache')
262 for repo in Repository.get_all():
262 for repo in Repository.get_all():
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
264
264
265 filesystem_repos = ScmModel().repo_scan()
265 filesystem_repos = ScmModel().repo_scan()
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
268 h.flash(_('Repositories successfully '
268 h.flash(_('Repositories successfully '
269 'rescanned added: %s ; removed: %s') %
269 'rescanned added: %s ; removed: %s') %
270 (_repr(added), _repr(removed)),
270 (_repr(added), _repr(removed)),
271 category='success')
271 category='success')
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
273
273
274 @LoginRequired()
274 @LoginRequired()
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 @view_config(
276 @view_config(
277 route_name='admin_settings', request_method='GET',
277 route_name='admin_settings', request_method='GET',
278 renderer='rhodecode:templates/admin/settings/settings.mako')
278 renderer='rhodecode:templates/admin/settings/settings.mako')
279 @view_config(
279 @view_config(
280 route_name='admin_settings_global', request_method='GET',
280 route_name='admin_settings_global', request_method='GET',
281 renderer='rhodecode:templates/admin/settings/settings.mako')
281 renderer='rhodecode:templates/admin/settings/settings.mako')
282 def settings_global(self):
282 def settings_global(self):
283 c = self.load_default_context()
283 c = self.load_default_context()
284 c.active = 'global'
284 c.active = 'global'
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 .get_personal_group_name_pattern()
286 .get_personal_group_name_pattern()
287
287
288 data = render('rhodecode:templates/admin/settings/settings.mako',
288 data = render('rhodecode:templates/admin/settings/settings.mako',
289 self._get_template_context(c), self.request)
289 self._get_template_context(c), self.request)
290 html = formencode.htmlfill.render(
290 html = formencode.htmlfill.render(
291 data,
291 data,
292 defaults=self._form_defaults(),
292 defaults=self._form_defaults(),
293 encoding="UTF-8",
293 encoding="UTF-8",
294 force_defaults=False
294 force_defaults=False
295 )
295 )
296 return Response(html)
296 return Response(html)
297
297
298 @LoginRequired()
298 @LoginRequired()
299 @HasPermissionAllDecorator('hg.admin')
299 @HasPermissionAllDecorator('hg.admin')
300 @CSRFRequired()
300 @CSRFRequired()
301 @view_config(
301 @view_config(
302 route_name='admin_settings_update', request_method='POST',
302 route_name='admin_settings_update', request_method='POST',
303 renderer='rhodecode:templates/admin/settings/settings.mako')
303 renderer='rhodecode:templates/admin/settings/settings.mako')
304 @view_config(
304 @view_config(
305 route_name='admin_settings_global_update', request_method='POST',
305 route_name='admin_settings_global_update', request_method='POST',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
306 renderer='rhodecode:templates/admin/settings/settings.mako')
307 def settings_global_update(self):
307 def settings_global_update(self):
308 _ = self.request.translate
308 _ = self.request.translate
309 c = self.load_default_context()
309 c = self.load_default_context()
310 c.active = 'global'
310 c.active = 'global'
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
312 .get_personal_group_name_pattern()
312 .get_personal_group_name_pattern()
313 application_form = ApplicationSettingsForm()()
313 application_form = ApplicationSettingsForm()()
314 try:
314 try:
315 form_result = application_form.to_python(dict(self.request.POST))
315 form_result = application_form.to_python(dict(self.request.POST))
316 except formencode.Invalid as errors:
316 except formencode.Invalid as errors:
317 data = render('rhodecode:templates/admin/settings/settings.mako',
317 data = render('rhodecode:templates/admin/settings/settings.mako',
318 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
319 html = formencode.htmlfill.render(
319 html = formencode.htmlfill.render(
320 data,
320 data,
321 defaults=errors.value,
321 defaults=errors.value,
322 errors=errors.error_dict or {},
322 errors=errors.error_dict or {},
323 prefix_error=False,
323 prefix_error=False,
324 encoding="UTF-8",
324 encoding="UTF-8",
325 force_defaults=False
325 force_defaults=False
326 )
326 )
327 return Response(html)
327 return Response(html)
328
328
329 settings = [
329 settings = [
330 ('title', 'rhodecode_title', 'unicode'),
330 ('title', 'rhodecode_title', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
338 ]
338 ]
339 try:
339 try:
340 for setting, form_key, type_ in settings:
340 for setting, form_key, type_ in settings:
341 sett = SettingsModel().create_or_update_setting(
341 sett = SettingsModel().create_or_update_setting(
342 setting, form_result[form_key], type_)
342 setting, form_result[form_key], type_)
343 Session().add(sett)
343 Session().add(sett)
344
344
345 Session().commit()
345 Session().commit()
346 SettingsModel().invalidate_settings_cache()
346 SettingsModel().invalidate_settings_cache()
347 h.flash(_('Updated application settings'), category='success')
347 h.flash(_('Updated application settings'), category='success')
348 except Exception:
348 except Exception:
349 log.exception("Exception while updating application settings")
349 log.exception("Exception while updating application settings")
350 h.flash(
350 h.flash(
351 _('Error occurred during updating application settings'),
351 _('Error occurred during updating application settings'),
352 category='error')
352 category='error')
353
353
354 raise HTTPFound(h.route_path('admin_settings_global'))
354 raise HTTPFound(h.route_path('admin_settings_global'))
355
355
356 @LoginRequired()
356 @LoginRequired()
357 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
358 @view_config(
358 @view_config(
359 route_name='admin_settings_visual', request_method='GET',
359 route_name='admin_settings_visual', request_method='GET',
360 renderer='rhodecode:templates/admin/settings/settings.mako')
360 renderer='rhodecode:templates/admin/settings/settings.mako')
361 def settings_visual(self):
361 def settings_visual(self):
362 c = self.load_default_context()
362 c = self.load_default_context()
363 c.active = 'visual'
363 c.active = 'visual'
364
364
365 data = render('rhodecode:templates/admin/settings/settings.mako',
365 data = render('rhodecode:templates/admin/settings/settings.mako',
366 self._get_template_context(c), self.request)
366 self._get_template_context(c), self.request)
367 html = formencode.htmlfill.render(
367 html = formencode.htmlfill.render(
368 data,
368 data,
369 defaults=self._form_defaults(),
369 defaults=self._form_defaults(),
370 encoding="UTF-8",
370 encoding="UTF-8",
371 force_defaults=False
371 force_defaults=False
372 )
372 )
373 return Response(html)
373 return Response(html)
374
374
375 @LoginRequired()
375 @LoginRequired()
376 @HasPermissionAllDecorator('hg.admin')
376 @HasPermissionAllDecorator('hg.admin')
377 @CSRFRequired()
377 @CSRFRequired()
378 @view_config(
378 @view_config(
379 route_name='admin_settings_visual_update', request_method='POST',
379 route_name='admin_settings_visual_update', request_method='POST',
380 renderer='rhodecode:templates/admin/settings/settings.mako')
380 renderer='rhodecode:templates/admin/settings/settings.mako')
381 def settings_visual_update(self):
381 def settings_visual_update(self):
382 _ = self.request.translate
382 _ = self.request.translate
383 c = self.load_default_context()
383 c = self.load_default_context()
384 c.active = 'visual'
384 c.active = 'visual'
385 application_form = ApplicationVisualisationForm()()
385 application_form = ApplicationVisualisationForm()()
386 try:
386 try:
387 form_result = application_form.to_python(dict(self.request.POST))
387 form_result = application_form.to_python(dict(self.request.POST))
388 except formencode.Invalid as errors:
388 except formencode.Invalid as errors:
389 data = render('rhodecode:templates/admin/settings/settings.mako',
389 data = render('rhodecode:templates/admin/settings/settings.mako',
390 self._get_template_context(c), self.request)
390 self._get_template_context(c), self.request)
391 html = formencode.htmlfill.render(
391 html = formencode.htmlfill.render(
392 data,
392 data,
393 defaults=errors.value,
393 defaults=errors.value,
394 errors=errors.error_dict or {},
394 errors=errors.error_dict or {},
395 prefix_error=False,
395 prefix_error=False,
396 encoding="UTF-8",
396 encoding="UTF-8",
397 force_defaults=False
397 force_defaults=False
398 )
398 )
399 return Response(html)
399 return Response(html)
400
400
401 try:
401 try:
402 settings = [
402 settings = [
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
414 ('support_url', 'rhodecode_support_url', 'unicode'),
414 ('support_url', 'rhodecode_support_url', 'unicode'),
415 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
415 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
416 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
416 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
417 ]
417 ]
418 for setting, form_key, type_ in settings:
418 for setting, form_key, type_ in settings:
419 sett = SettingsModel().create_or_update_setting(
419 sett = SettingsModel().create_or_update_setting(
420 setting, form_result[form_key], type_)
420 setting, form_result[form_key], type_)
421 Session().add(sett)
421 Session().add(sett)
422
422
423 Session().commit()
423 Session().commit()
424 SettingsModel().invalidate_settings_cache()
424 SettingsModel().invalidate_settings_cache()
425 h.flash(_('Updated visualisation settings'), category='success')
425 h.flash(_('Updated visualisation settings'), category='success')
426 except Exception:
426 except Exception:
427 log.exception("Exception updating visualization settings")
427 log.exception("Exception updating visualization settings")
428 h.flash(_('Error occurred during updating '
428 h.flash(_('Error occurred during updating '
429 'visualisation settings'),
429 'visualisation settings'),
430 category='error')
430 category='error')
431
431
432 raise HTTPFound(h.route_path('admin_settings_visual'))
432 raise HTTPFound(h.route_path('admin_settings_visual'))
433
433
434 @LoginRequired()
434 @LoginRequired()
435 @HasPermissionAllDecorator('hg.admin')
435 @HasPermissionAllDecorator('hg.admin')
436 @view_config(
436 @view_config(
437 route_name='admin_settings_issuetracker', request_method='GET',
437 route_name='admin_settings_issuetracker', request_method='GET',
438 renderer='rhodecode:templates/admin/settings/settings.mako')
438 renderer='rhodecode:templates/admin/settings/settings.mako')
439 def settings_issuetracker(self):
439 def settings_issuetracker(self):
440 c = self.load_default_context()
440 c = self.load_default_context()
441 c.active = 'issuetracker'
441 c.active = 'issuetracker'
442 defaults = SettingsModel().get_all_settings()
442 defaults = SettingsModel().get_all_settings()
443
443
444 entry_key = 'rhodecode_issuetracker_pat_'
444 entry_key = 'rhodecode_issuetracker_pat_'
445
445
446 c.issuetracker_entries = {}
446 c.issuetracker_entries = {}
447 for k, v in defaults.items():
447 for k, v in defaults.items():
448 if k.startswith(entry_key):
448 if k.startswith(entry_key):
449 uid = k[len(entry_key):]
449 uid = k[len(entry_key):]
450 c.issuetracker_entries[uid] = None
450 c.issuetracker_entries[uid] = None
451
451
452 for uid in c.issuetracker_entries:
452 for uid in c.issuetracker_entries:
453 c.issuetracker_entries[uid] = AttributeDict({
453 c.issuetracker_entries[uid] = AttributeDict({
454 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
454 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
455 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
455 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
456 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
456 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
457 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
457 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
458 })
458 })
459
459
460 return self._get_template_context(c)
460 return self._get_template_context(c)
461
461
462 @LoginRequired()
462 @LoginRequired()
463 @HasPermissionAllDecorator('hg.admin')
463 @HasPermissionAllDecorator('hg.admin')
464 @CSRFRequired()
464 @CSRFRequired()
465 @view_config(
465 @view_config(
466 route_name='admin_settings_issuetracker_test', request_method='POST',
466 route_name='admin_settings_issuetracker_test', request_method='POST',
467 renderer='string', xhr=True)
467 renderer='string', xhr=True)
468 def settings_issuetracker_test(self):
468 def settings_issuetracker_test(self):
469 return h.urlify_commit_message(
469 return h.urlify_commit_message(
470 self.request.POST.get('test_text', ''),
470 self.request.POST.get('test_text', ''),
471 'repo_group/test_repo1')
471 'repo_group/test_repo1')
472
472
473 @LoginRequired()
473 @LoginRequired()
474 @HasPermissionAllDecorator('hg.admin')
474 @HasPermissionAllDecorator('hg.admin')
475 @CSRFRequired()
475 @CSRFRequired()
476 @view_config(
476 @view_config(
477 route_name='admin_settings_issuetracker_update', request_method='POST',
477 route_name='admin_settings_issuetracker_update', request_method='POST',
478 renderer='rhodecode:templates/admin/settings/settings.mako')
478 renderer='rhodecode:templates/admin/settings/settings.mako')
479 def settings_issuetracker_update(self):
479 def settings_issuetracker_update(self):
480 _ = self.request.translate
480 _ = self.request.translate
481 self.load_default_context()
481 self.load_default_context()
482 settings_model = IssueTrackerSettingsModel()
482 settings_model = IssueTrackerSettingsModel()
483
483
484 try:
484 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
485 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
486 except formencode.Invalid as errors:
487 log.exception('Failed to add new pattern')
488 error = errors
489 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
490 category='error')
491 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
492
485 if form:
493 if form:
486 for uid in form.get('delete_patterns', []):
494 for uid in form.get('delete_patterns', []):
487 settings_model.delete_entries(uid)
495 settings_model.delete_entries(uid)
488
496
489 for pattern in form.get('patterns', []):
497 for pattern in form.get('patterns', []):
490 for setting, value, type_ in pattern:
498 for setting, value, type_ in pattern:
491 sett = settings_model.create_or_update_setting(
499 sett = settings_model.create_or_update_setting(
492 setting, value, type_)
500 setting, value, type_)
493 Session().add(sett)
501 Session().add(sett)
494
502
495 Session().commit()
503 Session().commit()
496
504
497 SettingsModel().invalidate_settings_cache()
505 SettingsModel().invalidate_settings_cache()
498 h.flash(_('Updated issue tracker entries'), category='success')
506 h.flash(_('Updated issue tracker entries'), category='success')
499 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
507 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
500
508
501 @LoginRequired()
509 @LoginRequired()
502 @HasPermissionAllDecorator('hg.admin')
510 @HasPermissionAllDecorator('hg.admin')
503 @CSRFRequired()
511 @CSRFRequired()
504 @view_config(
512 @view_config(
505 route_name='admin_settings_issuetracker_delete', request_method='POST',
513 route_name='admin_settings_issuetracker_delete', request_method='POST',
506 renderer='rhodecode:templates/admin/settings/settings.mako')
514 renderer='rhodecode:templates/admin/settings/settings.mako')
507 def settings_issuetracker_delete(self):
515 def settings_issuetracker_delete(self):
508 _ = self.request.translate
516 _ = self.request.translate
509 self.load_default_context()
517 self.load_default_context()
510 uid = self.request.POST.get('uid')
518 uid = self.request.POST.get('uid')
511 try:
519 try:
512 IssueTrackerSettingsModel().delete_entries(uid)
520 IssueTrackerSettingsModel().delete_entries(uid)
513 except Exception:
521 except Exception:
514 log.exception('Failed to delete issue tracker setting %s', uid)
522 log.exception('Failed to delete issue tracker setting %s', uid)
515 raise HTTPNotFound()
523 raise HTTPNotFound()
516 h.flash(_('Removed issue tracker entry'), category='success')
524 h.flash(_('Removed issue tracker entry'), category='success')
517 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
525 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
518
526
519 @LoginRequired()
527 @LoginRequired()
520 @HasPermissionAllDecorator('hg.admin')
528 @HasPermissionAllDecorator('hg.admin')
521 @view_config(
529 @view_config(
522 route_name='admin_settings_email', request_method='GET',
530 route_name='admin_settings_email', request_method='GET',
523 renderer='rhodecode:templates/admin/settings/settings.mako')
531 renderer='rhodecode:templates/admin/settings/settings.mako')
524 def settings_email(self):
532 def settings_email(self):
525 c = self.load_default_context()
533 c = self.load_default_context()
526 c.active = 'email'
534 c.active = 'email'
527 c.rhodecode_ini = rhodecode.CONFIG
535 c.rhodecode_ini = rhodecode.CONFIG
528
536
529 data = render('rhodecode:templates/admin/settings/settings.mako',
537 data = render('rhodecode:templates/admin/settings/settings.mako',
530 self._get_template_context(c), self.request)
538 self._get_template_context(c), self.request)
531 html = formencode.htmlfill.render(
539 html = formencode.htmlfill.render(
532 data,
540 data,
533 defaults=self._form_defaults(),
541 defaults=self._form_defaults(),
534 encoding="UTF-8",
542 encoding="UTF-8",
535 force_defaults=False
543 force_defaults=False
536 )
544 )
537 return Response(html)
545 return Response(html)
538
546
539 @LoginRequired()
547 @LoginRequired()
540 @HasPermissionAllDecorator('hg.admin')
548 @HasPermissionAllDecorator('hg.admin')
541 @CSRFRequired()
549 @CSRFRequired()
542 @view_config(
550 @view_config(
543 route_name='admin_settings_email_update', request_method='POST',
551 route_name='admin_settings_email_update', request_method='POST',
544 renderer='rhodecode:templates/admin/settings/settings.mako')
552 renderer='rhodecode:templates/admin/settings/settings.mako')
545 def settings_email_update(self):
553 def settings_email_update(self):
546 _ = self.request.translate
554 _ = self.request.translate
547 c = self.load_default_context()
555 c = self.load_default_context()
548 c.active = 'email'
556 c.active = 'email'
549
557
550 test_email = self.request.POST.get('test_email')
558 test_email = self.request.POST.get('test_email')
551
559
552 if not test_email:
560 if not test_email:
553 h.flash(_('Please enter email address'), category='error')
561 h.flash(_('Please enter email address'), category='error')
554 raise HTTPFound(h.route_path('admin_settings_email'))
562 raise HTTPFound(h.route_path('admin_settings_email'))
555
563
556 email_kwargs = {
564 email_kwargs = {
557 'date': datetime.datetime.now(),
565 'date': datetime.datetime.now(),
558 'user': c.rhodecode_user,
566 'user': c.rhodecode_user,
559 'rhodecode_version': c.rhodecode_version
567 'rhodecode_version': c.rhodecode_version
560 }
568 }
561
569
562 (subject, headers, email_body,
570 (subject, headers, email_body,
563 email_body_plaintext) = EmailNotificationModel().render_email(
571 email_body_plaintext) = EmailNotificationModel().render_email(
564 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
572 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
565
573
566 recipients = [test_email] if test_email else None
574 recipients = [test_email] if test_email else None
567
575
568 run_task(tasks.send_email, recipients, subject,
576 run_task(tasks.send_email, recipients, subject,
569 email_body_plaintext, email_body)
577 email_body_plaintext, email_body)
570
578
571 h.flash(_('Send email task created'), category='success')
579 h.flash(_('Send email task created'), category='success')
572 raise HTTPFound(h.route_path('admin_settings_email'))
580 raise HTTPFound(h.route_path('admin_settings_email'))
573
581
574 @LoginRequired()
582 @LoginRequired()
575 @HasPermissionAllDecorator('hg.admin')
583 @HasPermissionAllDecorator('hg.admin')
576 @view_config(
584 @view_config(
577 route_name='admin_settings_hooks', request_method='GET',
585 route_name='admin_settings_hooks', request_method='GET',
578 renderer='rhodecode:templates/admin/settings/settings.mako')
586 renderer='rhodecode:templates/admin/settings/settings.mako')
579 def settings_hooks(self):
587 def settings_hooks(self):
580 c = self.load_default_context()
588 c = self.load_default_context()
581 c.active = 'hooks'
589 c.active = 'hooks'
582
590
583 model = SettingsModel()
591 model = SettingsModel()
584 c.hooks = model.get_builtin_hooks()
592 c.hooks = model.get_builtin_hooks()
585 c.custom_hooks = model.get_custom_hooks()
593 c.custom_hooks = model.get_custom_hooks()
586
594
587 data = render('rhodecode:templates/admin/settings/settings.mako',
595 data = render('rhodecode:templates/admin/settings/settings.mako',
588 self._get_template_context(c), self.request)
596 self._get_template_context(c), self.request)
589 html = formencode.htmlfill.render(
597 html = formencode.htmlfill.render(
590 data,
598 data,
591 defaults=self._form_defaults(),
599 defaults=self._form_defaults(),
592 encoding="UTF-8",
600 encoding="UTF-8",
593 force_defaults=False
601 force_defaults=False
594 )
602 )
595 return Response(html)
603 return Response(html)
596
604
597 @LoginRequired()
605 @LoginRequired()
598 @HasPermissionAllDecorator('hg.admin')
606 @HasPermissionAllDecorator('hg.admin')
599 @CSRFRequired()
607 @CSRFRequired()
600 @view_config(
608 @view_config(
601 route_name='admin_settings_hooks_update', request_method='POST',
609 route_name='admin_settings_hooks_update', request_method='POST',
602 renderer='rhodecode:templates/admin/settings/settings.mako')
610 renderer='rhodecode:templates/admin/settings/settings.mako')
603 @view_config(
611 @view_config(
604 route_name='admin_settings_hooks_delete', request_method='POST',
612 route_name='admin_settings_hooks_delete', request_method='POST',
605 renderer='rhodecode:templates/admin/settings/settings.mako')
613 renderer='rhodecode:templates/admin/settings/settings.mako')
606 def settings_hooks_update(self):
614 def settings_hooks_update(self):
607 _ = self.request.translate
615 _ = self.request.translate
608 c = self.load_default_context()
616 c = self.load_default_context()
609 c.active = 'hooks'
617 c.active = 'hooks'
610 if c.visual.allow_custom_hooks_settings:
618 if c.visual.allow_custom_hooks_settings:
611 ui_key = self.request.POST.get('new_hook_ui_key')
619 ui_key = self.request.POST.get('new_hook_ui_key')
612 ui_value = self.request.POST.get('new_hook_ui_value')
620 ui_value = self.request.POST.get('new_hook_ui_value')
613
621
614 hook_id = self.request.POST.get('hook_id')
622 hook_id = self.request.POST.get('hook_id')
615 new_hook = False
623 new_hook = False
616
624
617 model = SettingsModel()
625 model = SettingsModel()
618 try:
626 try:
619 if ui_value and ui_key:
627 if ui_value and ui_key:
620 model.create_or_update_hook(ui_key, ui_value)
628 model.create_or_update_hook(ui_key, ui_value)
621 h.flash(_('Added new hook'), category='success')
629 h.flash(_('Added new hook'), category='success')
622 new_hook = True
630 new_hook = True
623 elif hook_id:
631 elif hook_id:
624 RhodeCodeUi.delete(hook_id)
632 RhodeCodeUi.delete(hook_id)
625 Session().commit()
633 Session().commit()
626
634
627 # check for edits
635 # check for edits
628 update = False
636 update = False
629 _d = self.request.POST.dict_of_lists()
637 _d = self.request.POST.dict_of_lists()
630 for k, v in zip(_d.get('hook_ui_key', []),
638 for k, v in zip(_d.get('hook_ui_key', []),
631 _d.get('hook_ui_value_new', [])):
639 _d.get('hook_ui_value_new', [])):
632 model.create_or_update_hook(k, v)
640 model.create_or_update_hook(k, v)
633 update = True
641 update = True
634
642
635 if update and not new_hook:
643 if update and not new_hook:
636 h.flash(_('Updated hooks'), category='success')
644 h.flash(_('Updated hooks'), category='success')
637 Session().commit()
645 Session().commit()
638 except Exception:
646 except Exception:
639 log.exception("Exception during hook creation")
647 log.exception("Exception during hook creation")
640 h.flash(_('Error occurred during hook creation'),
648 h.flash(_('Error occurred during hook creation'),
641 category='error')
649 category='error')
642
650
643 raise HTTPFound(h.route_path('admin_settings_hooks'))
651 raise HTTPFound(h.route_path('admin_settings_hooks'))
644
652
645 @LoginRequired()
653 @LoginRequired()
646 @HasPermissionAllDecorator('hg.admin')
654 @HasPermissionAllDecorator('hg.admin')
647 @view_config(
655 @view_config(
648 route_name='admin_settings_search', request_method='GET',
656 route_name='admin_settings_search', request_method='GET',
649 renderer='rhodecode:templates/admin/settings/settings.mako')
657 renderer='rhodecode:templates/admin/settings/settings.mako')
650 def settings_search(self):
658 def settings_search(self):
651 c = self.load_default_context()
659 c = self.load_default_context()
652 c.active = 'search'
660 c.active = 'search'
653
661
654 searcher = searcher_from_config(self.request.registry.settings)
662 searcher = searcher_from_config(self.request.registry.settings)
655 c.statistics = searcher.statistics()
663 c.statistics = searcher.statistics()
656
664
657 return self._get_template_context(c)
665 return self._get_template_context(c)
658
666
659 @LoginRequired()
667 @LoginRequired()
660 @HasPermissionAllDecorator('hg.admin')
668 @HasPermissionAllDecorator('hg.admin')
661 @view_config(
669 @view_config(
662 route_name='admin_settings_labs', request_method='GET',
670 route_name='admin_settings_labs', request_method='GET',
663 renderer='rhodecode:templates/admin/settings/settings.mako')
671 renderer='rhodecode:templates/admin/settings/settings.mako')
664 def settings_labs(self):
672 def settings_labs(self):
665 c = self.load_default_context()
673 c = self.load_default_context()
666 if not c.labs_active:
674 if not c.labs_active:
667 raise HTTPFound(h.route_path('admin_settings'))
675 raise HTTPFound(h.route_path('admin_settings'))
668
676
669 c.active = 'labs'
677 c.active = 'labs'
670 c.lab_settings = _LAB_SETTINGS
678 c.lab_settings = _LAB_SETTINGS
671
679
672 data = render('rhodecode:templates/admin/settings/settings.mako',
680 data = render('rhodecode:templates/admin/settings/settings.mako',
673 self._get_template_context(c), self.request)
681 self._get_template_context(c), self.request)
674 html = formencode.htmlfill.render(
682 html = formencode.htmlfill.render(
675 data,
683 data,
676 defaults=self._form_defaults(),
684 defaults=self._form_defaults(),
677 encoding="UTF-8",
685 encoding="UTF-8",
678 force_defaults=False
686 force_defaults=False
679 )
687 )
680 return Response(html)
688 return Response(html)
681
689
682 @LoginRequired()
690 @LoginRequired()
683 @HasPermissionAllDecorator('hg.admin')
691 @HasPermissionAllDecorator('hg.admin')
684 @CSRFRequired()
692 @CSRFRequired()
685 @view_config(
693 @view_config(
686 route_name='admin_settings_labs_update', request_method='POST',
694 route_name='admin_settings_labs_update', request_method='POST',
687 renderer='rhodecode:templates/admin/settings/settings.mako')
695 renderer='rhodecode:templates/admin/settings/settings.mako')
688 def settings_labs_update(self):
696 def settings_labs_update(self):
689 _ = self.request.translate
697 _ = self.request.translate
690 c = self.load_default_context()
698 c = self.load_default_context()
691 c.active = 'labs'
699 c.active = 'labs'
692
700
693 application_form = LabsSettingsForm()()
701 application_form = LabsSettingsForm()()
694 try:
702 try:
695 form_result = application_form.to_python(dict(self.request.POST))
703 form_result = application_form.to_python(dict(self.request.POST))
696 except formencode.Invalid as errors:
704 except formencode.Invalid as errors:
697 h.flash(
705 h.flash(
698 _('Some form inputs contain invalid data.'),
706 _('Some form inputs contain invalid data.'),
699 category='error')
707 category='error')
700 data = render('rhodecode:templates/admin/settings/settings.mako',
708 data = render('rhodecode:templates/admin/settings/settings.mako',
701 self._get_template_context(c), self.request)
709 self._get_template_context(c), self.request)
702 html = formencode.htmlfill.render(
710 html = formencode.htmlfill.render(
703 data,
711 data,
704 defaults=errors.value,
712 defaults=errors.value,
705 errors=errors.error_dict or {},
713 errors=errors.error_dict or {},
706 prefix_error=False,
714 prefix_error=False,
707 encoding="UTF-8",
715 encoding="UTF-8",
708 force_defaults=False
716 force_defaults=False
709 )
717 )
710 return Response(html)
718 return Response(html)
711
719
712 try:
720 try:
713 session = Session()
721 session = Session()
714 for setting in _LAB_SETTINGS:
722 for setting in _LAB_SETTINGS:
715 setting_name = setting.key[len('rhodecode_'):]
723 setting_name = setting.key[len('rhodecode_'):]
716 sett = SettingsModel().create_or_update_setting(
724 sett = SettingsModel().create_or_update_setting(
717 setting_name, form_result[setting.key], setting.type)
725 setting_name, form_result[setting.key], setting.type)
718 session.add(sett)
726 session.add(sett)
719
727
720 except Exception:
728 except Exception:
721 log.exception('Exception while updating lab settings')
729 log.exception('Exception while updating lab settings')
722 h.flash(_('Error occurred during updating labs settings'),
730 h.flash(_('Error occurred during updating labs settings'),
723 category='error')
731 category='error')
724 else:
732 else:
725 Session().commit()
733 Session().commit()
726 SettingsModel().invalidate_settings_cache()
734 SettingsModel().invalidate_settings_cache()
727 h.flash(_('Updated Labs settings'), category='success')
735 h.flash(_('Updated Labs settings'), category='success')
728 raise HTTPFound(h.route_path('admin_settings_labs'))
736 raise HTTPFound(h.route_path('admin_settings_labs'))
729
737
730 data = render('rhodecode:templates/admin/settings/settings.mako',
738 data = render('rhodecode:templates/admin/settings/settings.mako',
731 self._get_template_context(c), self.request)
739 self._get_template_context(c), self.request)
732 html = formencode.htmlfill.render(
740 html = formencode.htmlfill.render(
733 data,
741 data,
734 defaults=self._form_defaults(),
742 defaults=self._form_defaults(),
735 encoding="UTF-8",
743 encoding="UTF-8",
736 force_defaults=False
744 force_defaults=False
737 )
745 )
738 return Response(html)
746 return Response(html)
739
747
740
748
741 # :param key: name of the setting including the 'rhodecode_' prefix
749 # :param key: name of the setting including the 'rhodecode_' prefix
742 # :param type: the RhodeCodeSetting type to use.
750 # :param type: the RhodeCodeSetting type to use.
743 # :param group: the i18ned group in which we should dispaly this setting
751 # :param group: the i18ned group in which we should dispaly this setting
744 # :param label: the i18ned label we should display for this setting
752 # :param label: the i18ned label we should display for this setting
745 # :param help: the i18ned help we should dispaly for this setting
753 # :param help: the i18ned help we should dispaly for this setting
746 LabSetting = collections.namedtuple(
754 LabSetting = collections.namedtuple(
747 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
755 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
748
756
749
757
750 # This list has to be kept in sync with the form
758 # This list has to be kept in sync with the form
751 # rhodecode.model.forms.LabsSettingsForm.
759 # rhodecode.model.forms.LabsSettingsForm.
752 _LAB_SETTINGS = [
760 _LAB_SETTINGS = [
753
761
754 ]
762 ]
@@ -1,148 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.utils2 import md5
23 from rhodecode.lib.utils2 import md5
24 from rhodecode.model.db import Repository
24 from rhodecode.model.db import Repository
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
26 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
27
27
28
28
29 def route_path(name, params=None, **kwargs):
29 def route_path(name, params=None, **kwargs):
30 import urllib
30 import urllib
31
31
32 base_url = {
32 base_url = {
33 'repo_summary': '/{repo_name}',
33 'repo_summary': '/{repo_name}',
34 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
34 'edit_repo_issuetracker': '/{repo_name}/settings/issue_trackers',
35 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test',
35 'edit_repo_issuetracker_test': '/{repo_name}/settings/issue_trackers/test',
36 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete',
36 'edit_repo_issuetracker_delete': '/{repo_name}/settings/issue_trackers/delete',
37 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update',
37 'edit_repo_issuetracker_update': '/{repo_name}/settings/issue_trackers/update',
38 }[name].format(**kwargs)
38 }[name].format(**kwargs)
39
39
40 if params:
40 if params:
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
41 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 return base_url
42 return base_url
43
43
44
44
45 @pytest.mark.usefixtures("app")
45 @pytest.mark.usefixtures("app")
46 class TestRepoIssueTracker(object):
46 class TestRepoIssueTracker(object):
47 def test_issuetracker_index(self, autologin_user, backend):
47 def test_issuetracker_index(self, autologin_user, backend):
48 repo = backend.create_repo()
48 repo = backend.create_repo()
49 response = self.app.get(route_path('edit_repo_issuetracker',
49 response = self.app.get(route_path('edit_repo_issuetracker',
50 repo_name=repo.repo_name))
50 repo_name=repo.repo_name))
51 assert response.status_code == 200
51 assert response.status_code == 200
52
52
53 def test_add_and_test_issuetracker_patterns(
53 def test_add_and_test_issuetracker_patterns(
54 self, autologin_user, backend, csrf_token, request, xhr_header):
54 self, autologin_user, backend, csrf_token, request, xhr_header):
55 pattern = 'issuetracker_pat'
55 pattern = 'issuetracker_pat'
56 another_pattern = pattern+'1'
56 another_pattern = pattern+'1'
57 post_url = route_path(
57 post_url = route_path(
58 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
58 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
59 post_data = {
59 post_data = {
60 'new_pattern_pattern_0': pattern,
60 'new_pattern_pattern_0': pattern,
61 'new_pattern_url_0': 'url',
61 'new_pattern_url_0': 'http://url',
62 'new_pattern_prefix_0': 'prefix',
62 'new_pattern_prefix_0': 'prefix',
63 'new_pattern_description_0': 'description',
63 'new_pattern_description_0': 'description',
64 'new_pattern_pattern_1': another_pattern,
64 'new_pattern_pattern_1': another_pattern,
65 'new_pattern_url_1': 'url1',
65 'new_pattern_url_1': '/url1',
66 'new_pattern_prefix_1': 'prefix1',
66 'new_pattern_prefix_1': 'prefix1',
67 'new_pattern_description_1': 'description1',
67 'new_pattern_description_1': 'description1',
68 'csrf_token': csrf_token
68 'csrf_token': csrf_token
69 }
69 }
70 self.app.post(post_url, post_data, status=302)
70 self.app.post(post_url, post_data, status=302)
71 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
71 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
72 settings = self.settings_model.get_repo_settings()
72 settings = self.settings_model.get_repo_settings()
73 self.uid = md5(pattern)
73 self.uid = md5(pattern)
74 assert settings[self.uid]['pat'] == pattern
74 assert settings[self.uid]['pat'] == pattern
75 self.another_uid = md5(another_pattern)
75 self.another_uid = md5(another_pattern)
76 assert settings[self.another_uid]['pat'] == another_pattern
76 assert settings[self.another_uid]['pat'] == another_pattern
77
77
78 # test pattern
78 # test pattern
79 data = {'test_text': 'example of issuetracker_pat replacement',
79 data = {'test_text': 'example of issuetracker_pat replacement',
80 'csrf_token': csrf_token}
80 'csrf_token': csrf_token}
81 response = self.app.post(
81 response = self.app.post(
82 route_path('edit_repo_issuetracker_test',
82 route_path('edit_repo_issuetracker_test',
83 repo_name=backend.repo.repo_name),
83 repo_name=backend.repo.repo_name),
84 extra_environ=xhr_header, params=data)
84 extra_environ=xhr_header, params=data)
85
85
86 assert response.body == \
86 assert response.body == \
87 'example of <a class="issue-tracker-link" href="url">prefix</a> replacement'
87 'example of <a class="issue-tracker-link" href="http://url">prefix</a> replacement'
88
88
89 @request.addfinalizer
89 @request.addfinalizer
90 def cleanup():
90 def cleanup():
91 self.settings_model.delete_entries(self.uid)
91 self.settings_model.delete_entries(self.uid)
92 self.settings_model.delete_entries(self.another_uid)
92 self.settings_model.delete_entries(self.another_uid)
93
93
94 def test_edit_issuetracker_pattern(
94 def test_edit_issuetracker_pattern(
95 self, autologin_user, backend, csrf_token, request):
95 self, autologin_user, backend, csrf_token, request):
96 entry_key = 'issuetracker_pat_'
96 entry_key = 'issuetracker_pat_'
97 pattern = 'issuetracker_pat2'
97 pattern = 'issuetracker_pat2'
98 old_pattern = 'issuetracker_pat'
98 old_pattern = 'issuetracker_pat'
99 old_uid = md5(old_pattern)
99 old_uid = md5(old_pattern)
100
100
101 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
101 sett = SettingsModel(repo=backend.repo).create_or_update_setting(
102 entry_key+old_uid, old_pattern, 'unicode')
102 entry_key+old_uid, old_pattern, 'unicode')
103 Session().add(sett)
103 Session().add(sett)
104 Session().commit()
104 Session().commit()
105 post_url = route_path(
105 post_url = route_path(
106 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
106 'edit_repo_issuetracker_update', repo_name=backend.repo.repo_name)
107 post_data = {
107 post_data = {
108 'new_pattern_pattern_0': pattern,
108 'new_pattern_pattern_0': pattern,
109 'new_pattern_url_0': 'url',
109 'new_pattern_url_0': '/url',
110 'new_pattern_prefix_0': 'prefix',
110 'new_pattern_prefix_0': 'prefix',
111 'new_pattern_description_0': 'description',
111 'new_pattern_description_0': 'description',
112 'uid': old_uid,
112 'uid': old_uid,
113 'csrf_token': csrf_token
113 'csrf_token': csrf_token
114 }
114 }
115 self.app.post(post_url, post_data, status=302)
115 self.app.post(post_url, post_data, status=302)
116 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
116 self.settings_model = IssueTrackerSettingsModel(repo=backend.repo)
117 settings = self.settings_model.get_repo_settings()
117 settings = self.settings_model.get_repo_settings()
118 self.uid = md5(pattern)
118 self.uid = md5(pattern)
119 assert settings[self.uid]['pat'] == pattern
119 assert settings[self.uid]['pat'] == pattern
120 with pytest.raises(KeyError):
120 with pytest.raises(KeyError):
121 key = settings[old_uid]
121 key = settings[old_uid]
122
122
123 @request.addfinalizer
123 @request.addfinalizer
124 def cleanup():
124 def cleanup():
125 self.settings_model.delete_entries(self.uid)
125 self.settings_model.delete_entries(self.uid)
126
126
127 def test_delete_issuetracker_pattern(
127 def test_delete_issuetracker_pattern(
128 self, autologin_user, backend, csrf_token, settings_util):
128 self, autologin_user, backend, csrf_token, settings_util):
129 repo = backend.create_repo()
129 repo = backend.create_repo()
130 repo_name = repo.repo_name
130 repo_name = repo.repo_name
131 entry_key = 'issuetracker_pat_'
131 entry_key = 'issuetracker_pat_'
132 pattern = 'issuetracker_pat3'
132 pattern = 'issuetracker_pat3'
133 uid = md5(pattern)
133 uid = md5(pattern)
134 settings_util.create_repo_rhodecode_setting(
134 settings_util.create_repo_rhodecode_setting(
135 repo=backend.repo, name=entry_key+uid,
135 repo=backend.repo, name=entry_key+uid,
136 value=entry_key, type_='unicode', cleanup=False)
136 value=entry_key, type_='unicode', cleanup=False)
137
137
138 self.app.post(
138 self.app.post(
139 route_path(
139 route_path(
140 'edit_repo_issuetracker_delete',
140 'edit_repo_issuetracker_delete',
141 repo_name=backend.repo.repo_name),
141 repo_name=backend.repo.repo_name),
142 {
142 {
143 'uid': uid,
143 'uid': uid,
144 'csrf_token': csrf_token
144 'csrf_token': csrf_token
145 }, status=302)
145 }, status=302)
146 settings = IssueTrackerSettingsModel(
146 settings = IssueTrackerSettingsModel(
147 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
147 repo=Repository.get_by_repo_name(repo_name)).get_repo_settings()
148 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
148 assert 'rhodecode_%s%s' % (entry_key, uid) not in settings
@@ -1,126 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 import formencode
25
26
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
33 from rhodecode.model.settings import IssueTrackerSettingsModel
34 from rhodecode.model.settings import IssueTrackerSettingsModel
34
35
35 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
36
37
37
38
38 class RepoSettingsIssueTrackersView(RepoAppView):
39 class RepoSettingsIssueTrackersView(RepoAppView):
39 def load_default_context(self):
40 def load_default_context(self):
40 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
41
42
42 self._register_global_c(c)
43 self._register_global_c(c)
43 return c
44 return c
44
45
45 @LoginRequired()
46 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.admin')
47 @HasRepoPermissionAnyDecorator('repository.admin')
47 @view_config(
48 @view_config(
48 route_name='edit_repo_issuetracker', request_method='GET',
49 route_name='edit_repo_issuetracker', request_method='GET',
49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 def repo_issuetracker(self):
51 def repo_issuetracker(self):
51 c = self.load_default_context()
52 c = self.load_default_context()
52 c.active = 'issuetracker'
53 c.active = 'issuetracker'
53 c.data = 'data'
54 c.data = 'data'
54
55
55 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
56 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
56 c.global_patterns = c.settings_model.get_global_settings()
57 c.global_patterns = c.settings_model.get_global_settings()
57 c.repo_patterns = c.settings_model.get_repo_settings()
58 c.repo_patterns = c.settings_model.get_repo_settings()
58
59
59 return self._get_template_context(c)
60 return self._get_template_context(c)
60
61
61 @LoginRequired()
62 @LoginRequired()
62 @HasRepoPermissionAnyDecorator('repository.admin')
63 @HasRepoPermissionAnyDecorator('repository.admin')
63 @CSRFRequired()
64 @CSRFRequired()
64 @view_config(
65 @view_config(
65 route_name='edit_repo_issuetracker_test', request_method='POST',
66 route_name='edit_repo_issuetracker_test', request_method='POST',
66 xhr=True, renderer='string')
67 xhr=True, renderer='string')
67 def repo_issuetracker_test(self):
68 def repo_issuetracker_test(self):
68 return h.urlify_commit_message(
69 return h.urlify_commit_message(
69 self.request.POST.get('test_text', ''),
70 self.request.POST.get('test_text', ''),
70 self.db_repo_name)
71 self.db_repo_name)
71
72
72 @LoginRequired()
73 @LoginRequired()
73 @HasRepoPermissionAnyDecorator('repository.admin')
74 @HasRepoPermissionAnyDecorator('repository.admin')
74 @CSRFRequired()
75 @CSRFRequired()
75 @view_config(
76 @view_config(
76 route_name='edit_repo_issuetracker_delete', request_method='POST',
77 route_name='edit_repo_issuetracker_delete', request_method='POST',
77 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
78 def repo_issuetracker_delete(self):
79 def repo_issuetracker_delete(self):
79 _ = self.request.translate
80 _ = self.request.translate
80 uid = self.request.POST.get('uid')
81 uid = self.request.POST.get('uid')
81 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
82 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
82 try:
83 try:
83 repo_settings.delete_entries(uid)
84 repo_settings.delete_entries(uid)
84 except Exception:
85 except Exception:
85 h.flash(_('Error occurred during deleting issue tracker entry'),
86 h.flash(_('Error occurred during deleting issue tracker entry'),
86 category='error')
87 category='error')
87 else:
88 else:
88 h.flash(_('Removed issue tracker entry'), category='success')
89 h.flash(_('Removed issue tracker entry'), category='success')
89 raise HTTPFound(
90 raise HTTPFound(
90 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
91 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
91
92
92 def _update_patterns(self, form, repo_settings):
93 def _update_patterns(self, form, repo_settings):
93 for uid in form['delete_patterns']:
94 for uid in form['delete_patterns']:
94 repo_settings.delete_entries(uid)
95 repo_settings.delete_entries(uid)
95
96
96 for pattern_data in form['patterns']:
97 for pattern_data in form['patterns']:
97 for setting_key, pattern, type_ in pattern_data:
98 for setting_key, pattern, type_ in pattern_data:
98 sett = repo_settings.create_or_update_setting(
99 sett = repo_settings.create_or_update_setting(
99 setting_key, pattern.strip(), type_)
100 setting_key, pattern.strip(), type_)
100 Session().add(sett)
101 Session().add(sett)
101
102
102 Session().commit()
103 Session().commit()
103
104
104 @LoginRequired()
105 @LoginRequired()
105 @HasRepoPermissionAnyDecorator('repository.admin')
106 @HasRepoPermissionAnyDecorator('repository.admin')
106 @CSRFRequired()
107 @CSRFRequired()
107 @view_config(
108 @view_config(
108 route_name='edit_repo_issuetracker_update', request_method='POST',
109 route_name='edit_repo_issuetracker_update', request_method='POST',
109 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
110 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
110 def repo_issuetracker_update(self):
111 def repo_issuetracker_update(self):
111 _ = self.request.translate
112 _ = self.request.translate
112 # Save inheritance
113 # Save inheritance
113 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
114 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
114 inherited = (
115 inherited = (
115 self.request.POST.get('inherit_global_issuetracker') == "inherited")
116 self.request.POST.get('inherit_global_issuetracker') == "inherited")
116 repo_settings.inherit_global_settings = inherited
117 repo_settings.inherit_global_settings = inherited
117 Session().commit()
118 Session().commit()
118
119
120 try:
119 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
121 form = IssueTrackerPatternsForm()().to_python(self.request.POST)
122 except formencode.Invalid as errors:
123 log.exception('Failed to add new pattern')
124 error = errors
125 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
126 category='error')
127 raise HTTPFound(
128 h.route_path('edit_repo_issuetracker',
129 repo_name=self.db_repo_name))
130
120 if form:
131 if form:
121 self._update_patterns(form, repo_settings)
132 self._update_patterns(form, repo_settings)
122
133
123 h.flash(_('Updated issue tracker entries'), category='success')
134 h.flash(_('Updated issue tracker entries'), category='success')
124 raise HTTPFound(
135 raise HTTPFound(
125 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
136 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
126
137
@@ -1,1122 +1,1132 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Set of generic validators
22 Set of generic validators
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 from collections import defaultdict
28 from collections import defaultdict
29
29
30 import formencode
30 import formencode
31 import ipaddress
31 import ipaddress
32 from formencode.validators import (
32 from formencode.validators import (
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 )
35 )
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.sql.expression import true
37 from sqlalchemy.sql.expression import true
38 from sqlalchemy.util import OrderedSet
38 from sqlalchemy.util import OrderedSet
39 from webhelpers.pylonslib.secure_form import authentication_token
39 from webhelpers.pylonslib.secure_form import authentication_token
40
40
41 from rhodecode.authentication import (
41 from rhodecode.authentication import (
42 legacy_plugin_prefix, _import_legacy_plugin)
42 legacy_plugin_prefix, _import_legacy_plugin)
43 from rhodecode.authentication.base import loadplugin
43 from rhodecode.authentication.base import loadplugin
44 from rhodecode.apps._base import ADMIN_PREFIX
44 from rhodecode.apps._base import ADMIN_PREFIX
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 # silence warnings and pylint
55 # silence warnings and pylint
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class _Missing(object):
62 class _Missing(object):
63 pass
63 pass
64
64
65 Missing = _Missing()
65 Missing = _Missing()
66
66
67
67
68 class StateObj(object):
68 class StateObj(object):
69 """
69 """
70 this is needed to translate the messages using _() in validators
70 this is needed to translate the messages using _() in validators
71 """
71 """
72 _ = staticmethod(_)
72 _ = staticmethod(_)
73
73
74
74
75 def M(self, key, state=None, **kwargs):
75 def M(self, key, state=None, **kwargs):
76 """
76 """
77 returns string from self.message based on given key,
77 returns string from self.message based on given key,
78 passed kw params are used to substitute %(named)s params inside
78 passed kw params are used to substitute %(named)s params inside
79 translated strings
79 translated strings
80
80
81 :param msg:
81 :param msg:
82 :param state:
82 :param state:
83 """
83 """
84 if state is None:
84 if state is None:
85 state = StateObj()
85 state = StateObj()
86 else:
86 else:
87 state._ = staticmethod(_)
87 state._ = staticmethod(_)
88 # inject validator into state object
88 # inject validator into state object
89 return self.message(key, state, **kwargs)
89 return self.message(key, state, **kwargs)
90
90
91
91
92 def UniqueList(convert=None):
92 def UniqueList(convert=None):
93 class _UniqueList(formencode.FancyValidator):
93 class _UniqueList(formencode.FancyValidator):
94 """
94 """
95 Unique List !
95 Unique List !
96 """
96 """
97 messages = {
97 messages = {
98 'empty': _(u'Value cannot be an empty list'),
98 'empty': _(u'Value cannot be an empty list'),
99 'missing_value': _(u'Value cannot be an empty list'),
99 'missing_value': _(u'Value cannot be an empty list'),
100 }
100 }
101
101
102 def _to_python(self, value, state):
102 def _to_python(self, value, state):
103 ret_val = []
103 ret_val = []
104
104
105 def make_unique(value):
105 def make_unique(value):
106 seen = []
106 seen = []
107 return [c for c in value if not (c in seen or seen.append(c))]
107 return [c for c in value if not (c in seen or seen.append(c))]
108
108
109 if isinstance(value, list):
109 if isinstance(value, list):
110 ret_val = make_unique(value)
110 ret_val = make_unique(value)
111 elif isinstance(value, set):
111 elif isinstance(value, set):
112 ret_val = make_unique(list(value))
112 ret_val = make_unique(list(value))
113 elif isinstance(value, tuple):
113 elif isinstance(value, tuple):
114 ret_val = make_unique(list(value))
114 ret_val = make_unique(list(value))
115 elif value is None:
115 elif value is None:
116 ret_val = []
116 ret_val = []
117 else:
117 else:
118 ret_val = [value]
118 ret_val = [value]
119
119
120 if convert:
120 if convert:
121 ret_val = map(convert, ret_val)
121 ret_val = map(convert, ret_val)
122 return ret_val
122 return ret_val
123
123
124 def empty_value(self, value):
124 def empty_value(self, value):
125 return []
125 return []
126
126
127 return _UniqueList
127 return _UniqueList
128
128
129
129
130 def UniqueListFromString():
130 def UniqueListFromString():
131 class _UniqueListFromString(UniqueList()):
131 class _UniqueListFromString(UniqueList()):
132 def _to_python(self, value, state):
132 def _to_python(self, value, state):
133 if isinstance(value, basestring):
133 if isinstance(value, basestring):
134 value = aslist(value, ',')
134 value = aslist(value, ',')
135 return super(_UniqueListFromString, self)._to_python(value, state)
135 return super(_UniqueListFromString, self)._to_python(value, state)
136 return _UniqueListFromString
136 return _UniqueListFromString
137
137
138
138
139 def ValidSvnPattern(section, repo_name=None):
139 def ValidSvnPattern(section, repo_name=None):
140 class _validator(formencode.validators.FancyValidator):
140 class _validator(formencode.validators.FancyValidator):
141 messages = {
141 messages = {
142 'pattern_exists': _(u'Pattern already exists'),
142 'pattern_exists': _(u'Pattern already exists'),
143 }
143 }
144
144
145 def validate_python(self, value, state):
145 def validate_python(self, value, state):
146 if not value:
146 if not value:
147 return
147 return
148 model = VcsSettingsModel(repo=repo_name)
148 model = VcsSettingsModel(repo=repo_name)
149 ui_settings = model.get_svn_patterns(section=section)
149 ui_settings = model.get_svn_patterns(section=section)
150 for entry in ui_settings:
150 for entry in ui_settings:
151 if value == entry.value:
151 if value == entry.value:
152 msg = M(self, 'pattern_exists', state)
152 msg = M(self, 'pattern_exists', state)
153 raise formencode.Invalid(msg, value, state)
153 raise formencode.Invalid(msg, value, state)
154 return _validator
154 return _validator
155
155
156
156
157 def ValidUsername(edit=False, old_data={}):
157 def ValidUsername(edit=False, old_data={}):
158 class _validator(formencode.validators.FancyValidator):
158 class _validator(formencode.validators.FancyValidator):
159 messages = {
159 messages = {
160 'username_exists': _(u'Username "%(username)s" already exists'),
160 'username_exists': _(u'Username "%(username)s" already exists'),
161 'system_invalid_username':
161 'system_invalid_username':
162 _(u'Username "%(username)s" is forbidden'),
162 _(u'Username "%(username)s" is forbidden'),
163 'invalid_username':
163 'invalid_username':
164 _(u'Username may only contain alphanumeric characters '
164 _(u'Username may only contain alphanumeric characters '
165 u'underscores, periods or dashes and must begin with '
165 u'underscores, periods or dashes and must begin with '
166 u'alphanumeric character or underscore')
166 u'alphanumeric character or underscore')
167 }
167 }
168
168
169 def validate_python(self, value, state):
169 def validate_python(self, value, state):
170 if value in ['default', 'new_user']:
170 if value in ['default', 'new_user']:
171 msg = M(self, 'system_invalid_username', state, username=value)
171 msg = M(self, 'system_invalid_username', state, username=value)
172 raise formencode.Invalid(msg, value, state)
172 raise formencode.Invalid(msg, value, state)
173 # check if user is unique
173 # check if user is unique
174 old_un = None
174 old_un = None
175 if edit:
175 if edit:
176 old_un = User.get(old_data.get('user_id')).username
176 old_un = User.get(old_data.get('user_id')).username
177
177
178 if old_un != value or not edit:
178 if old_un != value or not edit:
179 if User.get_by_username(value, case_insensitive=True):
179 if User.get_by_username(value, case_insensitive=True):
180 msg = M(self, 'username_exists', state, username=value)
180 msg = M(self, 'username_exists', state, username=value)
181 raise formencode.Invalid(msg, value, state)
181 raise formencode.Invalid(msg, value, state)
182
182
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 is None):
184 is None):
185 msg = M(self, 'invalid_username', state)
185 msg = M(self, 'invalid_username', state)
186 raise formencode.Invalid(msg, value, state)
186 raise formencode.Invalid(msg, value, state)
187 return _validator
187 return _validator
188
188
189
189
190 def ValidRegex(msg=None):
190 def ValidRegex(msg=None):
191 class _validator(formencode.validators.Regex):
191 class _validator(formencode.validators.Regex):
192 messages = {'invalid': msg or _(u'The input is not valid')}
192 messages = {'invalid': msg or _(u'The input is not valid')}
193 return _validator
193 return _validator
194
194
195
195
196 def ValidRepoUser(allow_disabled=False):
196 def ValidRepoUser(allow_disabled=False):
197 class _validator(formencode.validators.FancyValidator):
197 class _validator(formencode.validators.FancyValidator):
198 messages = {
198 messages = {
199 'invalid_username': _(u'Username %(username)s is not valid'),
199 'invalid_username': _(u'Username %(username)s is not valid'),
200 'disabled_username': _(u'Username %(username)s is disabled')
200 'disabled_username': _(u'Username %(username)s is disabled')
201 }
201 }
202
202
203 def validate_python(self, value, state):
203 def validate_python(self, value, state):
204 try:
204 try:
205 user = User.query().filter(User.username == value).one()
205 user = User.query().filter(User.username == value).one()
206 except Exception:
206 except Exception:
207 msg = M(self, 'invalid_username', state, username=value)
207 msg = M(self, 'invalid_username', state, username=value)
208 raise formencode.Invalid(
208 raise formencode.Invalid(
209 msg, value, state, error_dict={'username': msg}
209 msg, value, state, error_dict={'username': msg}
210 )
210 )
211 if user and (not allow_disabled and not user.active):
211 if user and (not allow_disabled and not user.active):
212 msg = M(self, 'disabled_username', state, username=value)
212 msg = M(self, 'disabled_username', state, username=value)
213 raise formencode.Invalid(
213 raise formencode.Invalid(
214 msg, value, state, error_dict={'username': msg}
214 msg, value, state, error_dict={'username': msg}
215 )
215 )
216
216
217 return _validator
217 return _validator
218
218
219
219
220 def ValidUserGroup(edit=False, old_data={}):
220 def ValidUserGroup(edit=False, old_data={}):
221 class _validator(formencode.validators.FancyValidator):
221 class _validator(formencode.validators.FancyValidator):
222 messages = {
222 messages = {
223 'invalid_group': _(u'Invalid user group name'),
223 'invalid_group': _(u'Invalid user group name'),
224 'group_exist': _(u'User group `%(usergroup)s` already exists'),
224 'group_exist': _(u'User group `%(usergroup)s` already exists'),
225 'invalid_usergroup_name':
225 'invalid_usergroup_name':
226 _(u'user group name may only contain alphanumeric '
226 _(u'user group name may only contain alphanumeric '
227 u'characters underscores, periods or dashes and must begin '
227 u'characters underscores, periods or dashes and must begin '
228 u'with alphanumeric character')
228 u'with alphanumeric character')
229 }
229 }
230
230
231 def validate_python(self, value, state):
231 def validate_python(self, value, state):
232 if value in ['default']:
232 if value in ['default']:
233 msg = M(self, 'invalid_group', state)
233 msg = M(self, 'invalid_group', state)
234 raise formencode.Invalid(
234 raise formencode.Invalid(
235 msg, value, state, error_dict={'users_group_name': msg}
235 msg, value, state, error_dict={'users_group_name': msg}
236 )
236 )
237 # check if group is unique
237 # check if group is unique
238 old_ugname = None
238 old_ugname = None
239 if edit:
239 if edit:
240 old_id = old_data.get('users_group_id')
240 old_id = old_data.get('users_group_id')
241 old_ugname = UserGroup.get(old_id).users_group_name
241 old_ugname = UserGroup.get(old_id).users_group_name
242
242
243 if old_ugname != value or not edit:
243 if old_ugname != value or not edit:
244 is_existing_group = UserGroup.get_by_group_name(
244 is_existing_group = UserGroup.get_by_group_name(
245 value, case_insensitive=True)
245 value, case_insensitive=True)
246 if is_existing_group:
246 if is_existing_group:
247 msg = M(self, 'group_exist', state, usergroup=value)
247 msg = M(self, 'group_exist', state, usergroup=value)
248 raise formencode.Invalid(
248 raise formencode.Invalid(
249 msg, value, state, error_dict={'users_group_name': msg}
249 msg, value, state, error_dict={'users_group_name': msg}
250 )
250 )
251
251
252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
253 msg = M(self, 'invalid_usergroup_name', state)
253 msg = M(self, 'invalid_usergroup_name', state)
254 raise formencode.Invalid(
254 raise formencode.Invalid(
255 msg, value, state, error_dict={'users_group_name': msg}
255 msg, value, state, error_dict={'users_group_name': msg}
256 )
256 )
257
257
258 return _validator
258 return _validator
259
259
260
260
261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
262 class _validator(formencode.validators.FancyValidator):
262 class _validator(formencode.validators.FancyValidator):
263 messages = {
263 messages = {
264 'group_parent_id': _(u'Cannot assign this group as parent'),
264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 u'already exists'),
267 u'already exists'),
268 'permission_denied': _(u"no permission to store repository group"
268 'permission_denied': _(u"no permission to store repository group"
269 u"in this location"),
269 u"in this location"),
270 'permission_denied_root': _(
270 'permission_denied_root': _(
271 u"no permission to store repository group "
271 u"no permission to store repository group "
272 u"in root location")
272 u"in root location")
273 }
273 }
274
274
275 def _to_python(self, value, state):
275 def _to_python(self, value, state):
276 group_name = repo_name_slug(value.get('group_name', ''))
276 group_name = repo_name_slug(value.get('group_name', ''))
277 group_parent_id = safe_int(value.get('group_parent_id'))
277 group_parent_id = safe_int(value.get('group_parent_id'))
278 gr = RepoGroup.get(group_parent_id)
278 gr = RepoGroup.get(group_parent_id)
279 if gr:
279 if gr:
280 parent_group_path = gr.full_path
280 parent_group_path = gr.full_path
281 # value needs to be aware of group name in order to check
281 # value needs to be aware of group name in order to check
282 # db key This is an actual just the name to store in the
282 # db key This is an actual just the name to store in the
283 # database
283 # database
284 group_name_full = (
284 group_name_full = (
285 parent_group_path + RepoGroup.url_sep() + group_name)
285 parent_group_path + RepoGroup.url_sep() + group_name)
286 else:
286 else:
287 group_name_full = group_name
287 group_name_full = group_name
288
288
289 value['group_name'] = group_name
289 value['group_name'] = group_name
290 value['group_name_full'] = group_name_full
290 value['group_name_full'] = group_name_full
291 value['group_parent_id'] = group_parent_id
291 value['group_parent_id'] = group_parent_id
292 return value
292 return value
293
293
294 def validate_python(self, value, state):
294 def validate_python(self, value, state):
295
295
296 old_group_name = None
296 old_group_name = None
297 group_name = value.get('group_name')
297 group_name = value.get('group_name')
298 group_name_full = value.get('group_name_full')
298 group_name_full = value.get('group_name_full')
299 group_parent_id = safe_int(value.get('group_parent_id'))
299 group_parent_id = safe_int(value.get('group_parent_id'))
300 if group_parent_id == -1:
300 if group_parent_id == -1:
301 group_parent_id = None
301 group_parent_id = None
302
302
303 group_obj = RepoGroup.get(old_data.get('group_id'))
303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 parent_group_changed = False
304 parent_group_changed = False
305 if edit:
305 if edit:
306 old_group_name = group_obj.group_name
306 old_group_name = group_obj.group_name
307 old_group_parent_id = group_obj.group_parent_id
307 old_group_parent_id = group_obj.group_parent_id
308
308
309 if group_parent_id != old_group_parent_id:
309 if group_parent_id != old_group_parent_id:
310 parent_group_changed = True
310 parent_group_changed = True
311
311
312 # TODO: mikhail: the following if statement is not reached
312 # TODO: mikhail: the following if statement is not reached
313 # since group_parent_id's OneOf validation fails before.
313 # since group_parent_id's OneOf validation fails before.
314 # Can be removed.
314 # Can be removed.
315
315
316 # check against setting a parent of self
316 # check against setting a parent of self
317 parent_of_self = (
317 parent_of_self = (
318 old_data['group_id'] == group_parent_id
318 old_data['group_id'] == group_parent_id
319 if group_parent_id else False
319 if group_parent_id else False
320 )
320 )
321 if parent_of_self:
321 if parent_of_self:
322 msg = M(self, 'group_parent_id', state)
322 msg = M(self, 'group_parent_id', state)
323 raise formencode.Invalid(
323 raise formencode.Invalid(
324 msg, value, state, error_dict={'group_parent_id': msg}
324 msg, value, state, error_dict={'group_parent_id': msg}
325 )
325 )
326
326
327 # group we're moving current group inside
327 # group we're moving current group inside
328 child_group = None
328 child_group = None
329 if group_parent_id:
329 if group_parent_id:
330 child_group = RepoGroup.query().filter(
330 child_group = RepoGroup.query().filter(
331 RepoGroup.group_id == group_parent_id).scalar()
331 RepoGroup.group_id == group_parent_id).scalar()
332
332
333 # do a special check that we cannot move a group to one of
333 # do a special check that we cannot move a group to one of
334 # it's children
334 # it's children
335 if edit and child_group:
335 if edit and child_group:
336 parents = [x.group_id for x in child_group.parents]
336 parents = [x.group_id for x in child_group.parents]
337 move_to_children = old_data['group_id'] in parents
337 move_to_children = old_data['group_id'] in parents
338 if move_to_children:
338 if move_to_children:
339 msg = M(self, 'group_parent_id', state)
339 msg = M(self, 'group_parent_id', state)
340 raise formencode.Invalid(
340 raise formencode.Invalid(
341 msg, value, state, error_dict={'group_parent_id': msg})
341 msg, value, state, error_dict={'group_parent_id': msg})
342
342
343 # Check if we have permission to store in the parent.
343 # Check if we have permission to store in the parent.
344 # Only check if the parent group changed.
344 # Only check if the parent group changed.
345 if parent_group_changed:
345 if parent_group_changed:
346 if child_group is None:
346 if child_group is None:
347 if not can_create_in_root:
347 if not can_create_in_root:
348 msg = M(self, 'permission_denied_root', state)
348 msg = M(self, 'permission_denied_root', state)
349 raise formencode.Invalid(
349 raise formencode.Invalid(
350 msg, value, state,
350 msg, value, state,
351 error_dict={'group_parent_id': msg})
351 error_dict={'group_parent_id': msg})
352 else:
352 else:
353 valid = HasRepoGroupPermissionAny('group.admin')
353 valid = HasRepoGroupPermissionAny('group.admin')
354 forbidden = not valid(
354 forbidden = not valid(
355 child_group.group_name, 'can create group validator')
355 child_group.group_name, 'can create group validator')
356 if forbidden:
356 if forbidden:
357 msg = M(self, 'permission_denied', state)
357 msg = M(self, 'permission_denied', state)
358 raise formencode.Invalid(
358 raise formencode.Invalid(
359 msg, value, state,
359 msg, value, state,
360 error_dict={'group_parent_id': msg})
360 error_dict={'group_parent_id': msg})
361
361
362 # if we change the name or it's new group, check for existing names
362 # if we change the name or it's new group, check for existing names
363 # or repositories with the same name
363 # or repositories with the same name
364 if old_group_name != group_name_full or not edit:
364 if old_group_name != group_name_full or not edit:
365 # check group
365 # check group
366 gr = RepoGroup.get_by_group_name(group_name_full)
366 gr = RepoGroup.get_by_group_name(group_name_full)
367 if gr:
367 if gr:
368 msg = M(self, 'group_exists', state, group_name=group_name)
368 msg = M(self, 'group_exists', state, group_name=group_name)
369 raise formencode.Invalid(
369 raise formencode.Invalid(
370 msg, value, state, error_dict={'group_name': msg})
370 msg, value, state, error_dict={'group_name': msg})
371
371
372 # check for same repo
372 # check for same repo
373 repo = Repository.get_by_repo_name(group_name_full)
373 repo = Repository.get_by_repo_name(group_name_full)
374 if repo:
374 if repo:
375 msg = M(self, 'repo_exists', state, group_name=group_name)
375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 raise formencode.Invalid(
376 raise formencode.Invalid(
377 msg, value, state, error_dict={'group_name': msg})
377 msg, value, state, error_dict={'group_name': msg})
378
378
379 return _validator
379 return _validator
380
380
381
381
382 def ValidPassword():
382 def ValidPassword():
383 class _validator(formencode.validators.FancyValidator):
383 class _validator(formencode.validators.FancyValidator):
384 messages = {
384 messages = {
385 'invalid_password':
385 'invalid_password':
386 _(u'Invalid characters (non-ascii) in password')
386 _(u'Invalid characters (non-ascii) in password')
387 }
387 }
388
388
389 def validate_python(self, value, state):
389 def validate_python(self, value, state):
390 try:
390 try:
391 (value or '').decode('ascii')
391 (value or '').decode('ascii')
392 except UnicodeError:
392 except UnicodeError:
393 msg = M(self, 'invalid_password', state)
393 msg = M(self, 'invalid_password', state)
394 raise formencode.Invalid(msg, value, state,)
394 raise formencode.Invalid(msg, value, state,)
395 return _validator
395 return _validator
396
396
397
397
398 def ValidOldPassword(username):
398 def ValidOldPassword(username):
399 class _validator(formencode.validators.FancyValidator):
399 class _validator(formencode.validators.FancyValidator):
400 messages = {
400 messages = {
401 'invalid_password': _(u'Invalid old password')
401 'invalid_password': _(u'Invalid old password')
402 }
402 }
403
403
404 def validate_python(self, value, state):
404 def validate_python(self, value, state):
405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
406 if not authenticate(username, value, '', HTTP_TYPE):
406 if not authenticate(username, value, '', HTTP_TYPE):
407 msg = M(self, 'invalid_password', state)
407 msg = M(self, 'invalid_password', state)
408 raise formencode.Invalid(
408 raise formencode.Invalid(
409 msg, value, state, error_dict={'current_password': msg}
409 msg, value, state, error_dict={'current_password': msg}
410 )
410 )
411 return _validator
411 return _validator
412
412
413
413
414 def ValidPasswordsMatch(
414 def ValidPasswordsMatch(
415 passwd='new_password', passwd_confirmation='password_confirmation'):
415 passwd='new_password', passwd_confirmation='password_confirmation'):
416 class _validator(formencode.validators.FancyValidator):
416 class _validator(formencode.validators.FancyValidator):
417 messages = {
417 messages = {
418 'password_mismatch': _(u'Passwords do not match'),
418 'password_mismatch': _(u'Passwords do not match'),
419 }
419 }
420
420
421 def validate_python(self, value, state):
421 def validate_python(self, value, state):
422
422
423 pass_val = value.get('password') or value.get(passwd)
423 pass_val = value.get('password') or value.get(passwd)
424 if pass_val != value[passwd_confirmation]:
424 if pass_val != value[passwd_confirmation]:
425 msg = M(self, 'password_mismatch', state)
425 msg = M(self, 'password_mismatch', state)
426 raise formencode.Invalid(
426 raise formencode.Invalid(
427 msg, value, state,
427 msg, value, state,
428 error_dict={passwd: msg, passwd_confirmation: msg}
428 error_dict={passwd: msg, passwd_confirmation: msg}
429 )
429 )
430 return _validator
430 return _validator
431
431
432
432
433 def ValidAuth():
433 def ValidAuth():
434 class _validator(formencode.validators.FancyValidator):
434 class _validator(formencode.validators.FancyValidator):
435 messages = {
435 messages = {
436 'invalid_password': _(u'invalid password'),
436 'invalid_password': _(u'invalid password'),
437 'invalid_username': _(u'invalid user name'),
437 'invalid_username': _(u'invalid user name'),
438 'disabled_account': _(u'Your account is disabled')
438 'disabled_account': _(u'Your account is disabled')
439 }
439 }
440
440
441 def validate_python(self, value, state):
441 def validate_python(self, value, state):
442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
443
443
444 password = value['password']
444 password = value['password']
445 username = value['username']
445 username = value['username']
446
446
447 if not authenticate(username, password, '', HTTP_TYPE,
447 if not authenticate(username, password, '', HTTP_TYPE,
448 skip_missing=True):
448 skip_missing=True):
449 user = User.get_by_username(username)
449 user = User.get_by_username(username)
450 if user and not user.active:
450 if user and not user.active:
451 log.warning('user %s is disabled', username)
451 log.warning('user %s is disabled', username)
452 msg = M(self, 'disabled_account', state)
452 msg = M(self, 'disabled_account', state)
453 raise formencode.Invalid(
453 raise formencode.Invalid(
454 msg, value, state, error_dict={'username': msg}
454 msg, value, state, error_dict={'username': msg}
455 )
455 )
456 else:
456 else:
457 log.warning('user `%s` failed to authenticate', username)
457 log.warning('user `%s` failed to authenticate', username)
458 msg = M(self, 'invalid_username', state)
458 msg = M(self, 'invalid_username', state)
459 msg2 = M(self, 'invalid_password', state)
459 msg2 = M(self, 'invalid_password', state)
460 raise formencode.Invalid(
460 raise formencode.Invalid(
461 msg, value, state,
461 msg, value, state,
462 error_dict={'username': msg, 'password': msg2}
462 error_dict={'username': msg, 'password': msg2}
463 )
463 )
464 return _validator
464 return _validator
465
465
466
466
467 def ValidAuthToken():
467 def ValidAuthToken():
468 class _validator(formencode.validators.FancyValidator):
468 class _validator(formencode.validators.FancyValidator):
469 messages = {
469 messages = {
470 'invalid_token': _(u'Token mismatch')
470 'invalid_token': _(u'Token mismatch')
471 }
471 }
472
472
473 def validate_python(self, value, state):
473 def validate_python(self, value, state):
474 if value != authentication_token():
474 if value != authentication_token():
475 msg = M(self, 'invalid_token', state)
475 msg = M(self, 'invalid_token', state)
476 raise formencode.Invalid(msg, value, state)
476 raise formencode.Invalid(msg, value, state)
477 return _validator
477 return _validator
478
478
479
479
480 def ValidRepoName(edit=False, old_data={}):
480 def ValidRepoName(edit=False, old_data={}):
481 class _validator(formencode.validators.FancyValidator):
481 class _validator(formencode.validators.FancyValidator):
482 messages = {
482 messages = {
483 'invalid_repo_name':
483 'invalid_repo_name':
484 _(u'Repository name %(repo)s is disallowed'),
484 _(u'Repository name %(repo)s is disallowed'),
485 # top level
485 # top level
486 'repository_exists': _(u'Repository with name %(repo)s '
486 'repository_exists': _(u'Repository with name %(repo)s '
487 u'already exists'),
487 u'already exists'),
488 'group_exists': _(u'Repository group with name "%(repo)s" '
488 'group_exists': _(u'Repository group with name "%(repo)s" '
489 u'already exists'),
489 u'already exists'),
490 # inside a group
490 # inside a group
491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
492 u'exists in group "%(group)s"'),
492 u'exists in group "%(group)s"'),
493 'group_in_group_exists': _(
493 'group_in_group_exists': _(
494 u'Repository group with name "%(repo)s" '
494 u'Repository group with name "%(repo)s" '
495 u'exists in group "%(group)s"'),
495 u'exists in group "%(group)s"'),
496 }
496 }
497
497
498 def _to_python(self, value, state):
498 def _to_python(self, value, state):
499 repo_name = repo_name_slug(value.get('repo_name', ''))
499 repo_name = repo_name_slug(value.get('repo_name', ''))
500 repo_group = value.get('repo_group')
500 repo_group = value.get('repo_group')
501 if repo_group:
501 if repo_group:
502 gr = RepoGroup.get(repo_group)
502 gr = RepoGroup.get(repo_group)
503 group_path = gr.full_path
503 group_path = gr.full_path
504 group_name = gr.group_name
504 group_name = gr.group_name
505 # value needs to be aware of group name in order to check
505 # value needs to be aware of group name in order to check
506 # db key This is an actual just the name to store in the
506 # db key This is an actual just the name to store in the
507 # database
507 # database
508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
509 else:
509 else:
510 group_name = group_path = ''
510 group_name = group_path = ''
511 repo_name_full = repo_name
511 repo_name_full = repo_name
512
512
513 value['repo_name'] = repo_name
513 value['repo_name'] = repo_name
514 value['repo_name_full'] = repo_name_full
514 value['repo_name_full'] = repo_name_full
515 value['group_path'] = group_path
515 value['group_path'] = group_path
516 value['group_name'] = group_name
516 value['group_name'] = group_name
517 return value
517 return value
518
518
519 def validate_python(self, value, state):
519 def validate_python(self, value, state):
520
520
521 repo_name = value.get('repo_name')
521 repo_name = value.get('repo_name')
522 repo_name_full = value.get('repo_name_full')
522 repo_name_full = value.get('repo_name_full')
523 group_path = value.get('group_path')
523 group_path = value.get('group_path')
524 group_name = value.get('group_name')
524 group_name = value.get('group_name')
525
525
526 if repo_name in [ADMIN_PREFIX, '']:
526 if repo_name in [ADMIN_PREFIX, '']:
527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
528 raise formencode.Invalid(
528 raise formencode.Invalid(
529 msg, value, state, error_dict={'repo_name': msg})
529 msg, value, state, error_dict={'repo_name': msg})
530
530
531 rename = old_data.get('repo_name') != repo_name_full
531 rename = old_data.get('repo_name') != repo_name_full
532 create = not edit
532 create = not edit
533 if rename or create:
533 if rename or create:
534
534
535 if group_path:
535 if group_path:
536 if Repository.get_by_repo_name(repo_name_full):
536 if Repository.get_by_repo_name(repo_name_full):
537 msg = M(self, 'repository_in_group_exists', state,
537 msg = M(self, 'repository_in_group_exists', state,
538 repo=repo_name, group=group_name)
538 repo=repo_name, group=group_name)
539 raise formencode.Invalid(
539 raise formencode.Invalid(
540 msg, value, state, error_dict={'repo_name': msg})
540 msg, value, state, error_dict={'repo_name': msg})
541 if RepoGroup.get_by_group_name(repo_name_full):
541 if RepoGroup.get_by_group_name(repo_name_full):
542 msg = M(self, 'group_in_group_exists', state,
542 msg = M(self, 'group_in_group_exists', state,
543 repo=repo_name, group=group_name)
543 repo=repo_name, group=group_name)
544 raise formencode.Invalid(
544 raise formencode.Invalid(
545 msg, value, state, error_dict={'repo_name': msg})
545 msg, value, state, error_dict={'repo_name': msg})
546 else:
546 else:
547 if RepoGroup.get_by_group_name(repo_name_full):
547 if RepoGroup.get_by_group_name(repo_name_full):
548 msg = M(self, 'group_exists', state, repo=repo_name)
548 msg = M(self, 'group_exists', state, repo=repo_name)
549 raise formencode.Invalid(
549 raise formencode.Invalid(
550 msg, value, state, error_dict={'repo_name': msg})
550 msg, value, state, error_dict={'repo_name': msg})
551
551
552 if Repository.get_by_repo_name(repo_name_full):
552 if Repository.get_by_repo_name(repo_name_full):
553 msg = M(
553 msg = M(
554 self, 'repository_exists', state, repo=repo_name)
554 self, 'repository_exists', state, repo=repo_name)
555 raise formencode.Invalid(
555 raise formencode.Invalid(
556 msg, value, state, error_dict={'repo_name': msg})
556 msg, value, state, error_dict={'repo_name': msg})
557 return value
557 return value
558 return _validator
558 return _validator
559
559
560
560
561 def ValidForkName(*args, **kwargs):
561 def ValidForkName(*args, **kwargs):
562 return ValidRepoName(*args, **kwargs)
562 return ValidRepoName(*args, **kwargs)
563
563
564
564
565 def SlugifyName():
565 def SlugifyName():
566 class _validator(formencode.validators.FancyValidator):
566 class _validator(formencode.validators.FancyValidator):
567
567
568 def _to_python(self, value, state):
568 def _to_python(self, value, state):
569 return repo_name_slug(value)
569 return repo_name_slug(value)
570
570
571 def validate_python(self, value, state):
571 def validate_python(self, value, state):
572 pass
572 pass
573
573
574 return _validator
574 return _validator
575
575
576
576
577 def CannotHaveGitSuffix():
577 def CannotHaveGitSuffix():
578 class _validator(formencode.validators.FancyValidator):
578 class _validator(formencode.validators.FancyValidator):
579 messages = {
579 messages = {
580 'has_git_suffix':
580 'has_git_suffix':
581 _(u'Repository name cannot end with .git'),
581 _(u'Repository name cannot end with .git'),
582 }
582 }
583
583
584 def _to_python(self, value, state):
584 def _to_python(self, value, state):
585 return value
585 return value
586
586
587 def validate_python(self, value, state):
587 def validate_python(self, value, state):
588 if value and value.endswith('.git'):
588 if value and value.endswith('.git'):
589 msg = M(
589 msg = M(
590 self, 'has_git_suffix', state)
590 self, 'has_git_suffix', state)
591 raise formencode.Invalid(
591 raise formencode.Invalid(
592 msg, value, state, error_dict={'repo_name': msg})
592 msg, value, state, error_dict={'repo_name': msg})
593
593
594 return _validator
594 return _validator
595
595
596
596
597 def ValidCloneUri():
597 def ValidCloneUri():
598 class InvalidCloneUrl(Exception):
598 class InvalidCloneUrl(Exception):
599 allowed_prefixes = ()
599 allowed_prefixes = ()
600
600
601 def url_handler(repo_type, url):
601 def url_handler(repo_type, url):
602 config = make_db_config(clear_session=False)
602 config = make_db_config(clear_session=False)
603 if repo_type == 'hg':
603 if repo_type == 'hg':
604 allowed_prefixes = ('http', 'svn+http', 'git+http')
604 allowed_prefixes = ('http', 'svn+http', 'git+http')
605
605
606 if 'http' in url[:4]:
606 if 'http' in url[:4]:
607 # initially check if it's at least the proper URL
607 # initially check if it's at least the proper URL
608 # or does it pass basic auth
608 # or does it pass basic auth
609 MercurialRepository.check_url(url, config)
609 MercurialRepository.check_url(url, config)
610 elif 'svn+http' in url[:8]: # svn->hg import
610 elif 'svn+http' in url[:8]: # svn->hg import
611 SubversionRepository.check_url(url, config)
611 SubversionRepository.check_url(url, config)
612 elif 'git+http' in url[:8]: # git->hg import
612 elif 'git+http' in url[:8]: # git->hg import
613 raise NotImplementedError()
613 raise NotImplementedError()
614 else:
614 else:
615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
616 'Allowed url must start with one of %s'
616 'Allowed url must start with one of %s'
617 % (url, ','.join(allowed_prefixes)))
617 % (url, ','.join(allowed_prefixes)))
618 exc.allowed_prefixes = allowed_prefixes
618 exc.allowed_prefixes = allowed_prefixes
619 raise exc
619 raise exc
620
620
621 elif repo_type == 'git':
621 elif repo_type == 'git':
622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
623 if 'http' in url[:4]:
623 if 'http' in url[:4]:
624 # initially check if it's at least the proper URL
624 # initially check if it's at least the proper URL
625 # or does it pass basic auth
625 # or does it pass basic auth
626 GitRepository.check_url(url, config)
626 GitRepository.check_url(url, config)
627 elif 'svn+http' in url[:8]: # svn->git import
627 elif 'svn+http' in url[:8]: # svn->git import
628 raise NotImplementedError()
628 raise NotImplementedError()
629 elif 'hg+http' in url[:8]: # hg->git import
629 elif 'hg+http' in url[:8]: # hg->git import
630 raise NotImplementedError()
630 raise NotImplementedError()
631 else:
631 else:
632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
633 'Allowed url must start with one of %s'
633 'Allowed url must start with one of %s'
634 % (url, ','.join(allowed_prefixes)))
634 % (url, ','.join(allowed_prefixes)))
635 exc.allowed_prefixes = allowed_prefixes
635 exc.allowed_prefixes = allowed_prefixes
636 raise exc
636 raise exc
637
637
638 class _validator(formencode.validators.FancyValidator):
638 class _validator(formencode.validators.FancyValidator):
639 messages = {
639 messages = {
640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
641 'invalid_clone_uri': _(
641 'invalid_clone_uri': _(
642 u'Invalid clone url, provide a valid clone '
642 u'Invalid clone url, provide a valid clone '
643 u'url starting with one of %(allowed_prefixes)s')
643 u'url starting with one of %(allowed_prefixes)s')
644 }
644 }
645
645
646 def validate_python(self, value, state):
646 def validate_python(self, value, state):
647 repo_type = value.get('repo_type')
647 repo_type = value.get('repo_type')
648 url = value.get('clone_uri')
648 url = value.get('clone_uri')
649
649
650 if url:
650 if url:
651 try:
651 try:
652 url_handler(repo_type, url)
652 url_handler(repo_type, url)
653 except InvalidCloneUrl as e:
653 except InvalidCloneUrl as e:
654 log.warning(e)
654 log.warning(e)
655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
656 allowed_prefixes=','.join(e.allowed_prefixes))
656 allowed_prefixes=','.join(e.allowed_prefixes))
657 raise formencode.Invalid(msg, value, state,
657 raise formencode.Invalid(msg, value, state,
658 error_dict={'clone_uri': msg})
658 error_dict={'clone_uri': msg})
659 except Exception:
659 except Exception:
660 log.exception('Url validation failed')
660 log.exception('Url validation failed')
661 msg = M(self, 'clone_uri', rtype=repo_type)
661 msg = M(self, 'clone_uri', rtype=repo_type)
662 raise formencode.Invalid(msg, value, state,
662 raise formencode.Invalid(msg, value, state,
663 error_dict={'clone_uri': msg})
663 error_dict={'clone_uri': msg})
664 return _validator
664 return _validator
665
665
666
666
667 def ValidForkType(old_data={}):
667 def ValidForkType(old_data={}):
668 class _validator(formencode.validators.FancyValidator):
668 class _validator(formencode.validators.FancyValidator):
669 messages = {
669 messages = {
670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
671 }
671 }
672
672
673 def validate_python(self, value, state):
673 def validate_python(self, value, state):
674 if old_data['repo_type'] != value:
674 if old_data['repo_type'] != value:
675 msg = M(self, 'invalid_fork_type', state)
675 msg = M(self, 'invalid_fork_type', state)
676 raise formencode.Invalid(
676 raise formencode.Invalid(
677 msg, value, state, error_dict={'repo_type': msg}
677 msg, value, state, error_dict={'repo_type': msg}
678 )
678 )
679 return _validator
679 return _validator
680
680
681
681
682 def CanWriteGroup(old_data=None):
682 def CanWriteGroup(old_data=None):
683 class _validator(formencode.validators.FancyValidator):
683 class _validator(formencode.validators.FancyValidator):
684 messages = {
684 messages = {
685 'permission_denied': _(
685 'permission_denied': _(
686 u"You do not have the permission "
686 u"You do not have the permission "
687 u"to create repositories in this group."),
687 u"to create repositories in this group."),
688 'permission_denied_root': _(
688 'permission_denied_root': _(
689 u"You do not have the permission to store repositories in "
689 u"You do not have the permission to store repositories in "
690 u"the root location.")
690 u"the root location.")
691 }
691 }
692
692
693 def _to_python(self, value, state):
693 def _to_python(self, value, state):
694 # root location
694 # root location
695 if value in [-1, "-1"]:
695 if value in [-1, "-1"]:
696 return None
696 return None
697 return value
697 return value
698
698
699 def validate_python(self, value, state):
699 def validate_python(self, value, state):
700 gr = RepoGroup.get(value)
700 gr = RepoGroup.get(value)
701 gr_name = gr.group_name if gr else None # None means ROOT location
701 gr_name = gr.group_name if gr else None # None means ROOT location
702 # create repositories with write permission on group is set to true
702 # create repositories with write permission on group is set to true
703 create_on_write = HasPermissionAny(
703 create_on_write = HasPermissionAny(
704 'hg.create.write_on_repogroup.true')()
704 'hg.create.write_on_repogroup.true')()
705 group_admin = HasRepoGroupPermissionAny('group.admin')(
705 group_admin = HasRepoGroupPermissionAny('group.admin')(
706 gr_name, 'can write into group validator')
706 gr_name, 'can write into group validator')
707 group_write = HasRepoGroupPermissionAny('group.write')(
707 group_write = HasRepoGroupPermissionAny('group.write')(
708 gr_name, 'can write into group validator')
708 gr_name, 'can write into group validator')
709 forbidden = not (group_admin or (group_write and create_on_write))
709 forbidden = not (group_admin or (group_write and create_on_write))
710 can_create_repos = HasPermissionAny(
710 can_create_repos = HasPermissionAny(
711 'hg.admin', 'hg.create.repository')
711 'hg.admin', 'hg.create.repository')
712 gid = (old_data['repo_group'].get('group_id')
712 gid = (old_data['repo_group'].get('group_id')
713 if (old_data and 'repo_group' in old_data) else None)
713 if (old_data and 'repo_group' in old_data) else None)
714 value_changed = gid != safe_int(value)
714 value_changed = gid != safe_int(value)
715 new = not old_data
715 new = not old_data
716 # do check if we changed the value, there's a case that someone got
716 # do check if we changed the value, there's a case that someone got
717 # revoked write permissions to a repository, he still created, we
717 # revoked write permissions to a repository, he still created, we
718 # don't need to check permission if he didn't change the value of
718 # don't need to check permission if he didn't change the value of
719 # groups in form box
719 # groups in form box
720 if value_changed or new:
720 if value_changed or new:
721 # parent group need to be existing
721 # parent group need to be existing
722 if gr and forbidden:
722 if gr and forbidden:
723 msg = M(self, 'permission_denied', state)
723 msg = M(self, 'permission_denied', state)
724 raise formencode.Invalid(
724 raise formencode.Invalid(
725 msg, value, state, error_dict={'repo_type': msg}
725 msg, value, state, error_dict={'repo_type': msg}
726 )
726 )
727 # check if we can write to root location !
727 # check if we can write to root location !
728 elif gr is None and not can_create_repos():
728 elif gr is None and not can_create_repos():
729 msg = M(self, 'permission_denied_root', state)
729 msg = M(self, 'permission_denied_root', state)
730 raise formencode.Invalid(
730 raise formencode.Invalid(
731 msg, value, state, error_dict={'repo_type': msg}
731 msg, value, state, error_dict={'repo_type': msg}
732 )
732 )
733
733
734 return _validator
734 return _validator
735
735
736
736
737 def ValidPerms(type_='repo'):
737 def ValidPerms(type_='repo'):
738 if type_ == 'repo_group':
738 if type_ == 'repo_group':
739 EMPTY_PERM = 'group.none'
739 EMPTY_PERM = 'group.none'
740 elif type_ == 'repo':
740 elif type_ == 'repo':
741 EMPTY_PERM = 'repository.none'
741 EMPTY_PERM = 'repository.none'
742 elif type_ == 'user_group':
742 elif type_ == 'user_group':
743 EMPTY_PERM = 'usergroup.none'
743 EMPTY_PERM = 'usergroup.none'
744
744
745 class _validator(formencode.validators.FancyValidator):
745 class _validator(formencode.validators.FancyValidator):
746 messages = {
746 messages = {
747 'perm_new_member_name':
747 'perm_new_member_name':
748 _(u'This username or user group name is not valid')
748 _(u'This username or user group name is not valid')
749 }
749 }
750
750
751 def _to_python(self, value, state):
751 def _to_python(self, value, state):
752 perm_updates = OrderedSet()
752 perm_updates = OrderedSet()
753 perm_additions = OrderedSet()
753 perm_additions = OrderedSet()
754 perm_deletions = OrderedSet()
754 perm_deletions = OrderedSet()
755 # build a list of permission to update/delete and new permission
755 # build a list of permission to update/delete and new permission
756
756
757 # Read the perm_new_member/perm_del_member attributes and group
757 # Read the perm_new_member/perm_del_member attributes and group
758 # them by they IDs
758 # them by they IDs
759 new_perms_group = defaultdict(dict)
759 new_perms_group = defaultdict(dict)
760 del_perms_group = defaultdict(dict)
760 del_perms_group = defaultdict(dict)
761 for k, v in value.copy().iteritems():
761 for k, v in value.copy().iteritems():
762 if k.startswith('perm_del_member'):
762 if k.startswith('perm_del_member'):
763 # delete from org storage so we don't process that later
763 # delete from org storage so we don't process that later
764 del value[k]
764 del value[k]
765 # part is `id`, `type`
765 # part is `id`, `type`
766 _type, part = k.split('perm_del_member_')
766 _type, part = k.split('perm_del_member_')
767 args = part.split('_')
767 args = part.split('_')
768 if len(args) == 2:
768 if len(args) == 2:
769 _key, pos = args
769 _key, pos = args
770 del_perms_group[pos][_key] = v
770 del_perms_group[pos][_key] = v
771 if k.startswith('perm_new_member'):
771 if k.startswith('perm_new_member'):
772 # delete from org storage so we don't process that later
772 # delete from org storage so we don't process that later
773 del value[k]
773 del value[k]
774 # part is `id`, `type`, `perm`
774 # part is `id`, `type`, `perm`
775 _type, part = k.split('perm_new_member_')
775 _type, part = k.split('perm_new_member_')
776 args = part.split('_')
776 args = part.split('_')
777 if len(args) == 2:
777 if len(args) == 2:
778 _key, pos = args
778 _key, pos = args
779 new_perms_group[pos][_key] = v
779 new_perms_group[pos][_key] = v
780
780
781 # store the deletes
781 # store the deletes
782 for k in sorted(del_perms_group.keys()):
782 for k in sorted(del_perms_group.keys()):
783 perm_dict = del_perms_group[k]
783 perm_dict = del_perms_group[k]
784 del_member = perm_dict.get('id')
784 del_member = perm_dict.get('id')
785 del_type = perm_dict.get('type')
785 del_type = perm_dict.get('type')
786 if del_member and del_type:
786 if del_member and del_type:
787 perm_deletions.add(
787 perm_deletions.add(
788 (del_member, None, del_type))
788 (del_member, None, del_type))
789
789
790 # store additions in order of how they were added in web form
790 # store additions in order of how they were added in web form
791 for k in sorted(new_perms_group.keys()):
791 for k in sorted(new_perms_group.keys()):
792 perm_dict = new_perms_group[k]
792 perm_dict = new_perms_group[k]
793 new_member = perm_dict.get('id')
793 new_member = perm_dict.get('id')
794 new_type = perm_dict.get('type')
794 new_type = perm_dict.get('type')
795 new_perm = perm_dict.get('perm')
795 new_perm = perm_dict.get('perm')
796 if new_member and new_perm and new_type:
796 if new_member and new_perm and new_type:
797 perm_additions.add(
797 perm_additions.add(
798 (new_member, new_perm, new_type))
798 (new_member, new_perm, new_type))
799
799
800 # get updates of permissions
800 # get updates of permissions
801 # (read the existing radio button states)
801 # (read the existing radio button states)
802 default_user_id = User.get_default_user().user_id
802 default_user_id = User.get_default_user().user_id
803 for k, update_value in value.iteritems():
803 for k, update_value in value.iteritems():
804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
805 member = k[7:]
805 member = k[7:]
806 update_type = {'u': 'user',
806 update_type = {'u': 'user',
807 'g': 'users_group'}[k[0]]
807 'g': 'users_group'}[k[0]]
808
808
809 if safe_int(member) == default_user_id:
809 if safe_int(member) == default_user_id:
810 if str2bool(value.get('repo_private')):
810 if str2bool(value.get('repo_private')):
811 # prevent from updating default user permissions
811 # prevent from updating default user permissions
812 # when this repository is marked as private
812 # when this repository is marked as private
813 update_value = EMPTY_PERM
813 update_value = EMPTY_PERM
814
814
815 perm_updates.add(
815 perm_updates.add(
816 (member, update_value, update_type))
816 (member, update_value, update_type))
817
817
818 value['perm_additions'] = [] # propagated later
818 value['perm_additions'] = [] # propagated later
819 value['perm_updates'] = list(perm_updates)
819 value['perm_updates'] = list(perm_updates)
820 value['perm_deletions'] = list(perm_deletions)
820 value['perm_deletions'] = list(perm_deletions)
821
821
822 updates_map = dict(
822 updates_map = dict(
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 # make sure Additions don't override updates.
824 # make sure Additions don't override updates.
825 for member_id, perm, member_type in list(perm_additions):
825 for member_id, perm, member_type in list(perm_additions):
826 if member_id in updates_map:
826 if member_id in updates_map:
827 perm = updates_map[member_id][0]
827 perm = updates_map[member_id][0]
828 value['perm_additions'].append((member_id, perm, member_type))
828 value['perm_additions'].append((member_id, perm, member_type))
829
829
830 # on new entries validate users they exist and they are active !
830 # on new entries validate users they exist and they are active !
831 # this leaves feedback to the form
831 # this leaves feedback to the form
832 try:
832 try:
833 if member_type == 'user':
833 if member_type == 'user':
834 User.query()\
834 User.query()\
835 .filter(User.active == true())\
835 .filter(User.active == true())\
836 .filter(User.user_id == member_id).one()
836 .filter(User.user_id == member_id).one()
837 if member_type == 'users_group':
837 if member_type == 'users_group':
838 UserGroup.query()\
838 UserGroup.query()\
839 .filter(UserGroup.users_group_active == true())\
839 .filter(UserGroup.users_group_active == true())\
840 .filter(UserGroup.users_group_id == member_id)\
840 .filter(UserGroup.users_group_id == member_id)\
841 .one()
841 .one()
842
842
843 except Exception:
843 except Exception:
844 log.exception('Updated permission failed: org_exc:')
844 log.exception('Updated permission failed: org_exc:')
845 msg = M(self, 'perm_new_member_type', state)
845 msg = M(self, 'perm_new_member_type', state)
846 raise formencode.Invalid(
846 raise formencode.Invalid(
847 msg, value, state, error_dict={
847 msg, value, state, error_dict={
848 'perm_new_member_name': msg}
848 'perm_new_member_name': msg}
849 )
849 )
850 return value
850 return value
851 return _validator
851 return _validator
852
852
853
853
854 def ValidSettings():
854 def ValidSettings():
855 class _validator(formencode.validators.FancyValidator):
855 class _validator(formencode.validators.FancyValidator):
856 def _to_python(self, value, state):
856 def _to_python(self, value, state):
857 # settings form for users that are not admin
857 # settings form for users that are not admin
858 # can't edit certain parameters, it's extra backup if they mangle
858 # can't edit certain parameters, it's extra backup if they mangle
859 # with forms
859 # with forms
860
860
861 forbidden_params = [
861 forbidden_params = [
862 'user', 'repo_type', 'repo_enable_locking',
862 'user', 'repo_type', 'repo_enable_locking',
863 'repo_enable_downloads', 'repo_enable_statistics'
863 'repo_enable_downloads', 'repo_enable_statistics'
864 ]
864 ]
865
865
866 for param in forbidden_params:
866 for param in forbidden_params:
867 if param in value:
867 if param in value:
868 del value[param]
868 del value[param]
869 return value
869 return value
870
870
871 def validate_python(self, value, state):
871 def validate_python(self, value, state):
872 pass
872 pass
873 return _validator
873 return _validator
874
874
875
875
876 def ValidPath():
876 def ValidPath():
877 class _validator(formencode.validators.FancyValidator):
877 class _validator(formencode.validators.FancyValidator):
878 messages = {
878 messages = {
879 'invalid_path': _(u'This is not a valid path')
879 'invalid_path': _(u'This is not a valid path')
880 }
880 }
881
881
882 def validate_python(self, value, state):
882 def validate_python(self, value, state):
883 if not os.path.isdir(value):
883 if not os.path.isdir(value):
884 msg = M(self, 'invalid_path', state)
884 msg = M(self, 'invalid_path', state)
885 raise formencode.Invalid(
885 raise formencode.Invalid(
886 msg, value, state, error_dict={'paths_root_path': msg}
886 msg, value, state, error_dict={'paths_root_path': msg}
887 )
887 )
888 return _validator
888 return _validator
889
889
890
890
891 def UniqSystemEmail(old_data={}):
891 def UniqSystemEmail(old_data={}):
892 class _validator(formencode.validators.FancyValidator):
892 class _validator(formencode.validators.FancyValidator):
893 messages = {
893 messages = {
894 'email_taken': _(u'This e-mail address is already taken')
894 'email_taken': _(u'This e-mail address is already taken')
895 }
895 }
896
896
897 def _to_python(self, value, state):
897 def _to_python(self, value, state):
898 return value.lower()
898 return value.lower()
899
899
900 def validate_python(self, value, state):
900 def validate_python(self, value, state):
901 if (old_data.get('email') or '').lower() != value:
901 if (old_data.get('email') or '').lower() != value:
902 user = User.get_by_email(value, case_insensitive=True)
902 user = User.get_by_email(value, case_insensitive=True)
903 if user:
903 if user:
904 msg = M(self, 'email_taken', state)
904 msg = M(self, 'email_taken', state)
905 raise formencode.Invalid(
905 raise formencode.Invalid(
906 msg, value, state, error_dict={'email': msg}
906 msg, value, state, error_dict={'email': msg}
907 )
907 )
908 return _validator
908 return _validator
909
909
910
910
911 def ValidSystemEmail():
911 def ValidSystemEmail():
912 class _validator(formencode.validators.FancyValidator):
912 class _validator(formencode.validators.FancyValidator):
913 messages = {
913 messages = {
914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
915 }
915 }
916
916
917 def _to_python(self, value, state):
917 def _to_python(self, value, state):
918 return value.lower()
918 return value.lower()
919
919
920 def validate_python(self, value, state):
920 def validate_python(self, value, state):
921 user = User.get_by_email(value, case_insensitive=True)
921 user = User.get_by_email(value, case_insensitive=True)
922 if user is None:
922 if user is None:
923 msg = M(self, 'non_existing_email', state, email=value)
923 msg = M(self, 'non_existing_email', state, email=value)
924 raise formencode.Invalid(
924 raise formencode.Invalid(
925 msg, value, state, error_dict={'email': msg}
925 msg, value, state, error_dict={'email': msg}
926 )
926 )
927
927
928 return _validator
928 return _validator
929
929
930
930
931 def NotReviewedRevisions(repo_id):
931 def NotReviewedRevisions(repo_id):
932 class _validator(formencode.validators.FancyValidator):
932 class _validator(formencode.validators.FancyValidator):
933 messages = {
933 messages = {
934 'rev_already_reviewed':
934 'rev_already_reviewed':
935 _(u'Revisions %(revs)s are already part of pull request '
935 _(u'Revisions %(revs)s are already part of pull request '
936 u'or have set status'),
936 u'or have set status'),
937 }
937 }
938
938
939 def validate_python(self, value, state):
939 def validate_python(self, value, state):
940 # check revisions if they are not reviewed, or a part of another
940 # check revisions if they are not reviewed, or a part of another
941 # pull request
941 # pull request
942 statuses = ChangesetStatus.query()\
942 statuses = ChangesetStatus.query()\
943 .filter(ChangesetStatus.revision.in_(value))\
943 .filter(ChangesetStatus.revision.in_(value))\
944 .filter(ChangesetStatus.repo_id == repo_id)\
944 .filter(ChangesetStatus.repo_id == repo_id)\
945 .all()
945 .all()
946
946
947 errors = []
947 errors = []
948 for status in statuses:
948 for status in statuses:
949 if status.pull_request_id:
949 if status.pull_request_id:
950 errors.append(['pull_req', status.revision[:12]])
950 errors.append(['pull_req', status.revision[:12]])
951 elif status.status:
951 elif status.status:
952 errors.append(['status', status.revision[:12]])
952 errors.append(['status', status.revision[:12]])
953
953
954 if errors:
954 if errors:
955 revs = ','.join([x[1] for x in errors])
955 revs = ','.join([x[1] for x in errors])
956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
957 raise formencode.Invalid(
957 raise formencode.Invalid(
958 msg, value, state, error_dict={'revisions': revs})
958 msg, value, state, error_dict={'revisions': revs})
959
959
960 return _validator
960 return _validator
961
961
962
962
963 def ValidIp():
963 def ValidIp():
964 class _validator(CIDR):
964 class _validator(CIDR):
965 messages = {
965 messages = {
966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
967 'illegalBits': _(
967 'illegalBits': _(
968 u'The network size (bits) must be within the range '
968 u'The network size (bits) must be within the range '
969 u'of 0-32 (not %(bits)r)'),
969 u'of 0-32 (not %(bits)r)'),
970 }
970 }
971
971
972 # we ovveride the default to_python() call
972 # we ovveride the default to_python() call
973 def to_python(self, value, state):
973 def to_python(self, value, state):
974 v = super(_validator, self).to_python(value, state)
974 v = super(_validator, self).to_python(value, state)
975 v = safe_unicode(v.strip())
975 v = safe_unicode(v.strip())
976 net = ipaddress.ip_network(address=v, strict=False)
976 net = ipaddress.ip_network(address=v, strict=False)
977 return str(net)
977 return str(net)
978
978
979 def validate_python(self, value, state):
979 def validate_python(self, value, state):
980 try:
980 try:
981 addr = safe_unicode(value.strip())
981 addr = safe_unicode(value.strip())
982 # this raises an ValueError if address is not IpV4 or IpV6
982 # this raises an ValueError if address is not IpV4 or IpV6
983 ipaddress.ip_network(addr, strict=False)
983 ipaddress.ip_network(addr, strict=False)
984 except ValueError:
984 except ValueError:
985 raise formencode.Invalid(self.message('badFormat', state),
985 raise formencode.Invalid(self.message('badFormat', state),
986 value, state)
986 value, state)
987
987
988 return _validator
988 return _validator
989
989
990
990
991 def FieldKey():
991 def FieldKey():
992 class _validator(formencode.validators.FancyValidator):
992 class _validator(formencode.validators.FancyValidator):
993 messages = {
993 messages = {
994 'badFormat': _(
994 'badFormat': _(
995 u'Key name can only consist of letters, '
995 u'Key name can only consist of letters, '
996 u'underscore, dash or numbers'),
996 u'underscore, dash or numbers'),
997 }
997 }
998
998
999 def validate_python(self, value, state):
999 def validate_python(self, value, state):
1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1001 raise formencode.Invalid(self.message('badFormat', state),
1001 raise formencode.Invalid(self.message('badFormat', state),
1002 value, state)
1002 value, state)
1003 return _validator
1003 return _validator
1004
1004
1005
1005
1006 def ValidAuthPlugins():
1006 def ValidAuthPlugins():
1007 class _validator(formencode.validators.FancyValidator):
1007 class _validator(formencode.validators.FancyValidator):
1008 messages = {
1008 messages = {
1009 'import_duplicate': _(
1009 'import_duplicate': _(
1010 u'Plugins %(loaded)s and %(next_to_load)s '
1010 u'Plugins %(loaded)s and %(next_to_load)s '
1011 u'both export the same name'),
1011 u'both export the same name'),
1012 'missing_includeme': _(
1012 'missing_includeme': _(
1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1014 u'function.'),
1014 u'function.'),
1015 'import_error': _(
1015 'import_error': _(
1016 u'Can not load plugin "%(plugin_id)s"'),
1016 u'Can not load plugin "%(plugin_id)s"'),
1017 'no_plugin': _(
1017 'no_plugin': _(
1018 u'No plugin available with ID "%(plugin_id)s"'),
1018 u'No plugin available with ID "%(plugin_id)s"'),
1019 }
1019 }
1020
1020
1021 def _to_python(self, value, state):
1021 def _to_python(self, value, state):
1022 # filter empty values
1022 # filter empty values
1023 return filter(lambda s: s not in [None, ''], value)
1023 return filter(lambda s: s not in [None, ''], value)
1024
1024
1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1026 """
1026 """
1027 Validates that the plugin import works. It also checks that the
1027 Validates that the plugin import works. It also checks that the
1028 plugin has an includeme attribute.
1028 plugin has an includeme attribute.
1029 """
1029 """
1030 try:
1030 try:
1031 plugin = _import_legacy_plugin(plugin_id)
1031 plugin = _import_legacy_plugin(plugin_id)
1032 except Exception as e:
1032 except Exception as e:
1033 log.exception(
1033 log.exception(
1034 'Exception during import of auth legacy plugin "{}"'
1034 'Exception during import of auth legacy plugin "{}"'
1035 .format(plugin_id))
1035 .format(plugin_id))
1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1037 raise formencode.Invalid(msg, value, state)
1037 raise formencode.Invalid(msg, value, state)
1038
1038
1039 if not hasattr(plugin, 'includeme'):
1039 if not hasattr(plugin, 'includeme'):
1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1041 raise formencode.Invalid(msg, value, state)
1041 raise formencode.Invalid(msg, value, state)
1042
1042
1043 return plugin
1043 return plugin
1044
1044
1045 def _validate_plugin_id(self, plugin_id, value, state):
1045 def _validate_plugin_id(self, plugin_id, value, state):
1046 """
1046 """
1047 Plugins are already imported during app start up. Therefore this
1047 Plugins are already imported during app start up. Therefore this
1048 validation only retrieves the plugin from the plugin registry and
1048 validation only retrieves the plugin from the plugin registry and
1049 if it returns something not None everything is OK.
1049 if it returns something not None everything is OK.
1050 """
1050 """
1051 plugin = loadplugin(plugin_id)
1051 plugin = loadplugin(plugin_id)
1052
1052
1053 if plugin is None:
1053 if plugin is None:
1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1055 raise formencode.Invalid(msg, value, state)
1055 raise formencode.Invalid(msg, value, state)
1056
1056
1057 return plugin
1057 return plugin
1058
1058
1059 def validate_python(self, value, state):
1059 def validate_python(self, value, state):
1060 unique_names = {}
1060 unique_names = {}
1061 for plugin_id in value:
1061 for plugin_id in value:
1062
1062
1063 # Validate legacy or normal plugin.
1063 # Validate legacy or normal plugin.
1064 if plugin_id.startswith(legacy_plugin_prefix):
1064 if plugin_id.startswith(legacy_plugin_prefix):
1065 plugin = self._validate_legacy_plugin_id(
1065 plugin = self._validate_legacy_plugin_id(
1066 plugin_id, value, state)
1066 plugin_id, value, state)
1067 else:
1067 else:
1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1069
1069
1070 # Only allow unique plugin names.
1070 # Only allow unique plugin names.
1071 if plugin.name in unique_names:
1071 if plugin.name in unique_names:
1072 msg = M(self, 'import_duplicate', state,
1072 msg = M(self, 'import_duplicate', state,
1073 loaded=unique_names[plugin.name],
1073 loaded=unique_names[plugin.name],
1074 next_to_load=plugin)
1074 next_to_load=plugin)
1075 raise formencode.Invalid(msg, value, state)
1075 raise formencode.Invalid(msg, value, state)
1076 unique_names[plugin.name] = plugin
1076 unique_names[plugin.name] = plugin
1077
1077
1078 return _validator
1078 return _validator
1079
1079
1080
1080
1081 def ValidPattern():
1081 def ValidPattern():
1082
1082
1083 class _Validator(formencode.validators.FancyValidator):
1083 class _Validator(formencode.validators.FancyValidator):
1084 messages = {
1085 'bad_format': _(u'Url must start with http or /'),
1086 }
1084
1087
1085 def _to_python(self, value, state):
1088 def _to_python(self, value, state):
1086 patterns = []
1089 patterns = []
1087
1090
1088 prefix = 'new_pattern'
1091 prefix = 'new_pattern'
1089 for name, v in value.iteritems():
1092 for name, v in value.iteritems():
1090 pattern_name = '_'.join((prefix, 'pattern'))
1093 pattern_name = '_'.join((prefix, 'pattern'))
1091 if name.startswith(pattern_name):
1094 if name.startswith(pattern_name):
1092 new_item_id = name[len(pattern_name)+1:]
1095 new_item_id = name[len(pattern_name)+1:]
1093
1096
1094 def _field(name):
1097 def _field(name):
1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1098 return '%s_%s_%s' % (prefix, name, new_item_id)
1096
1099
1097 values = {
1100 values = {
1098 'issuetracker_pat': value.get(_field('pattern')),
1101 'issuetracker_pat': value.get(_field('pattern')),
1099 'issuetracker_pat': value.get(_field('pattern')),
1100 'issuetracker_url': value.get(_field('url')),
1102 'issuetracker_url': value.get(_field('url')),
1101 'issuetracker_pref': value.get(_field('prefix')),
1103 'issuetracker_pref': value.get(_field('prefix')),
1102 'issuetracker_desc': value.get(_field('description'))
1104 'issuetracker_desc': value.get(_field('description'))
1103 }
1105 }
1104 new_uid = md5(values['issuetracker_pat'])
1106 new_uid = md5(values['issuetracker_pat'])
1105
1107
1106 has_required_fields = (
1108 has_required_fields = (
1107 values['issuetracker_pat']
1109 values['issuetracker_pat']
1108 and values['issuetracker_url'])
1110 and values['issuetracker_url'])
1109
1111
1110 if has_required_fields:
1112 if has_required_fields:
1113 # validate url that it starts with http or /
1114 # otherwise it can lead to JS injections
1115 # e.g specifig javascript:<malicios code>
1116 if not values['issuetracker_url'].startswith(('http', '/')):
1117 raise formencode.Invalid(
1118 self.message('bad_format', state),
1119 value, state)
1120
1111 settings = [
1121 settings = [
1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1122 ('_'.join((key, new_uid)), values[key], 'unicode')
1113 for key in values]
1123 for key in values]
1114 patterns.append(settings)
1124 patterns.append(settings)
1115
1125
1116 value['patterns'] = patterns
1126 value['patterns'] = patterns
1117 delete_patterns = value.get('uid') or []
1127 delete_patterns = value.get('uid') or []
1118 if not isinstance(delete_patterns, (list, tuple)):
1128 if not isinstance(delete_patterns, (list, tuple)):
1119 delete_patterns = [delete_patterns]
1129 delete_patterns = [delete_patterns]
1120 value['delete_patterns'] = delete_patterns
1130 value['delete_patterns'] = delete_patterns
1121 return value
1131 return value
1122 return _Validator
1132 return _Validator
General Comments 0
You need to be logged in to leave comments. Login now