##// END OF EJS Templates
apps: switch import of ADMIN_PREFIX from routing into base app....
marcink -
r2309:4e0e53bc default
parent child Browse files
Show More
@@ -1,325 +1,325 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.apps._base import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 Admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 33 name='admin_audit_logs',
34 34 pattern='/audit_logs')
35 35
36 36 config.add_route(
37 37 name='admin_audit_log_entry',
38 38 pattern='/audit_logs/{audit_log_id}')
39 39
40 40 config.add_route(
41 41 name='pull_requests_global_0', # backward compat
42 42 pattern='/pull_requests/{pull_request_id:\d+}')
43 43 config.add_route(
44 44 name='pull_requests_global_1', # backward compat
45 45 pattern='/pull-requests/{pull_request_id:\d+}')
46 46 config.add_route(
47 47 name='pull_requests_global',
48 48 pattern='/pull-request/{pull_request_id:\d+}')
49 49
50 50 config.add_route(
51 51 name='admin_settings_open_source',
52 52 pattern='/settings/open_source')
53 53 config.add_route(
54 54 name='admin_settings_vcs_svn_generate_cfg',
55 55 pattern='/settings/vcs/svn_generate_cfg')
56 56
57 57 config.add_route(
58 58 name='admin_settings_system',
59 59 pattern='/settings/system')
60 60 config.add_route(
61 61 name='admin_settings_system_update',
62 62 pattern='/settings/system/updates')
63 63
64 64 config.add_route(
65 65 name='admin_settings_sessions',
66 66 pattern='/settings/sessions')
67 67 config.add_route(
68 68 name='admin_settings_sessions_cleanup',
69 69 pattern='/settings/sessions/cleanup')
70 70
71 71 config.add_route(
72 72 name='admin_settings_process_management',
73 73 pattern='/settings/process_management')
74 74 config.add_route(
75 75 name='admin_settings_process_management_signal',
76 76 pattern='/settings/process_management/signal')
77 77
78 78 # default settings
79 79 config.add_route(
80 80 name='admin_defaults_repositories',
81 81 pattern='/defaults/repositories')
82 82 config.add_route(
83 83 name='admin_defaults_repositories_update',
84 84 pattern='/defaults/repositories/update')
85 85
86 86 # global permissions
87 87
88 88 config.add_route(
89 89 name='admin_permissions_application',
90 90 pattern='/permissions/application')
91 91 config.add_route(
92 92 name='admin_permissions_application_update',
93 93 pattern='/permissions/application/update')
94 94
95 95 config.add_route(
96 96 name='admin_permissions_global',
97 97 pattern='/permissions/global')
98 98 config.add_route(
99 99 name='admin_permissions_global_update',
100 100 pattern='/permissions/global/update')
101 101
102 102 config.add_route(
103 103 name='admin_permissions_object',
104 104 pattern='/permissions/object')
105 105 config.add_route(
106 106 name='admin_permissions_object_update',
107 107 pattern='/permissions/object/update')
108 108
109 109 config.add_route(
110 110 name='admin_permissions_ips',
111 111 pattern='/permissions/ips')
112 112
113 113 config.add_route(
114 114 name='admin_permissions_overview',
115 115 pattern='/permissions/overview')
116 116
117 117 config.add_route(
118 118 name='admin_permissions_auth_token_access',
119 119 pattern='/permissions/auth_token_access')
120 120
121 121 config.add_route(
122 122 name='admin_permissions_ssh_keys',
123 123 pattern='/permissions/ssh_keys')
124 124 config.add_route(
125 125 name='admin_permissions_ssh_keys_data',
126 126 pattern='/permissions/ssh_keys/data')
127 127 config.add_route(
128 128 name='admin_permissions_ssh_keys_update',
129 129 pattern='/permissions/ssh_keys/update')
130 130
131 131 # users admin
132 132 config.add_route(
133 133 name='users',
134 134 pattern='/users')
135 135
136 136 config.add_route(
137 137 name='users_data',
138 138 pattern='/users_data')
139 139
140 140 config.add_route(
141 141 name='users_create',
142 142 pattern='/users/create')
143 143
144 144 config.add_route(
145 145 name='users_new',
146 146 pattern='/users/new')
147 147
148 148 # user management
149 149 config.add_route(
150 150 name='user_edit',
151 151 pattern='/users/{user_id:\d+}/edit',
152 152 user_route=True)
153 153 config.add_route(
154 154 name='user_edit_advanced',
155 155 pattern='/users/{user_id:\d+}/edit/advanced',
156 156 user_route=True)
157 157 config.add_route(
158 158 name='user_edit_global_perms',
159 159 pattern='/users/{user_id:\d+}/edit/global_permissions',
160 160 user_route=True)
161 161 config.add_route(
162 162 name='user_edit_global_perms_update',
163 163 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
164 164 user_route=True)
165 165 config.add_route(
166 166 name='user_update',
167 167 pattern='/users/{user_id:\d+}/update',
168 168 user_route=True)
169 169 config.add_route(
170 170 name='user_delete',
171 171 pattern='/users/{user_id:\d+}/delete',
172 172 user_route=True)
173 173 config.add_route(
174 174 name='user_force_password_reset',
175 175 pattern='/users/{user_id:\d+}/password_reset',
176 176 user_route=True)
177 177 config.add_route(
178 178 name='user_create_personal_repo_group',
179 179 pattern='/users/{user_id:\d+}/create_repo_group',
180 180 user_route=True)
181 181
182 182 # user auth tokens
183 183 config.add_route(
184 184 name='edit_user_auth_tokens',
185 185 pattern='/users/{user_id:\d+}/edit/auth_tokens',
186 186 user_route=True)
187 187 config.add_route(
188 188 name='edit_user_auth_tokens_add',
189 189 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
190 190 user_route=True)
191 191 config.add_route(
192 192 name='edit_user_auth_tokens_delete',
193 193 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
194 194 user_route=True)
195 195
196 196 # user ssh keys
197 197 config.add_route(
198 198 name='edit_user_ssh_keys',
199 199 pattern='/users/{user_id:\d+}/edit/ssh_keys',
200 200 user_route=True)
201 201 config.add_route(
202 202 name='edit_user_ssh_keys_generate_keypair',
203 203 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
204 204 user_route=True)
205 205 config.add_route(
206 206 name='edit_user_ssh_keys_add',
207 207 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
208 208 user_route=True)
209 209 config.add_route(
210 210 name='edit_user_ssh_keys_delete',
211 211 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
212 212 user_route=True)
213 213
214 214 # user emails
215 215 config.add_route(
216 216 name='edit_user_emails',
217 217 pattern='/users/{user_id:\d+}/edit/emails',
218 218 user_route=True)
219 219 config.add_route(
220 220 name='edit_user_emails_add',
221 221 pattern='/users/{user_id:\d+}/edit/emails/new',
222 222 user_route=True)
223 223 config.add_route(
224 224 name='edit_user_emails_delete',
225 225 pattern='/users/{user_id:\d+}/edit/emails/delete',
226 226 user_route=True)
227 227
228 228 # user IPs
229 229 config.add_route(
230 230 name='edit_user_ips',
231 231 pattern='/users/{user_id:\d+}/edit/ips',
232 232 user_route=True)
233 233 config.add_route(
234 234 name='edit_user_ips_add',
235 235 pattern='/users/{user_id:\d+}/edit/ips/new',
236 236 user_route_with_default=True) # enabled for default user too
237 237 config.add_route(
238 238 name='edit_user_ips_delete',
239 239 pattern='/users/{user_id:\d+}/edit/ips/delete',
240 240 user_route_with_default=True) # enabled for default user too
241 241
242 242 # user perms
243 243 config.add_route(
244 244 name='edit_user_perms_summary',
245 245 pattern='/users/{user_id:\d+}/edit/permissions_summary',
246 246 user_route=True)
247 247 config.add_route(
248 248 name='edit_user_perms_summary_json',
249 249 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
250 250 user_route=True)
251 251
252 252 # user user groups management
253 253 config.add_route(
254 254 name='edit_user_groups_management',
255 255 pattern='/users/{user_id:\d+}/edit/groups_management',
256 256 user_route=True)
257 257
258 258 config.add_route(
259 259 name='edit_user_groups_management_updates',
260 260 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
261 261 user_route=True)
262 262
263 263 # user audit logs
264 264 config.add_route(
265 265 name='edit_user_audit_logs',
266 266 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
267 267
268 268 # user-groups admin
269 269 config.add_route(
270 270 name='user_groups',
271 271 pattern='/user_groups')
272 272
273 273 config.add_route(
274 274 name='user_groups_data',
275 275 pattern='/user_groups_data')
276 276
277 277 config.add_route(
278 278 name='user_groups_new',
279 279 pattern='/user_groups/new')
280 280
281 281 config.add_route(
282 282 name='user_groups_create',
283 283 pattern='/user_groups/create')
284 284
285 285 # repos admin
286 286 config.add_route(
287 287 name='repos',
288 288 pattern='/repos')
289 289
290 290 config.add_route(
291 291 name='repo_new',
292 292 pattern='/repos/new')
293 293
294 294 config.add_route(
295 295 name='repo_create',
296 296 pattern='/repos/create')
297 297
298 298 # repo groups admin
299 299 config.add_route(
300 300 name='repo_groups',
301 301 pattern='/repo_groups')
302 302
303 303 config.add_route(
304 304 name='repo_group_new',
305 305 pattern='/repo_group/new')
306 306
307 307 config.add_route(
308 308 name='repo_group_create',
309 309 pattern='/repo_group/create')
310 310
311 311
312 312 def includeme(config):
313 313 settings = config.get_settings()
314 314
315 315 # Create admin navigation registry and add it to the pyramid registry.
316 316 labs_active = str2bool(settings.get('labs_settings_active', False))
317 317 navigation_registry = NavigationRegistry(labs_active=labs_active)
318 318 config.registry.registerUtility(navigation_registry)
319 319
320 320 # main admin routes
321 321 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
322 322 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
323 323
324 324 # Scan module for configuration decorators.
325 325 config.scan('.views', ignore='.tests')
@@ -1,96 +1,96 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22
23 23 from pyramid.events import ApplicationCreated
24 24 from pyramid.settings import asbool
25 25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.apps._base import ADMIN_PREFIX
27 27 from rhodecode.lib.ext_json import json
28 28
29 29
30 30 def url_gen(request):
31 31 registry = request.registry
32 32 longpoll_url = registry.settings.get('channelstream.longpoll_url', '')
33 33 ws_url = registry.settings.get('channelstream.ws_url', '')
34 34 proxy_url = request.route_url('channelstream_proxy')
35 35 urls = {
36 36 'connect': request.route_path('channelstream_connect'),
37 37 'subscribe': request.route_path('channelstream_subscribe'),
38 38 'longpoll': longpoll_url or proxy_url,
39 39 'ws': ws_url or proxy_url.replace('http', 'ws')
40 40 }
41 41 return json.dumps(urls)
42 42
43 43
44 44 PLUGIN_DEFINITION = {
45 45 'name': 'channelstream',
46 46 'config': {
47 47 'javascript': [],
48 48 'css': [],
49 49 'template_hooks': {
50 50 'plugin_init_template': 'rhodecode:templates/channelstream/plugin_init.mako'
51 51 },
52 52 'url_gen': url_gen,
53 53 'static': None,
54 54 'enabled': False,
55 55 'server': '',
56 56 'secret': ''
57 57 }
58 58 }
59 59
60 60
61 61 def maybe_create_history_store(event):
62 62 # create plugin history location
63 63 settings = event.app.registry.settings
64 64 history_dir = settings.get('channelstream.history.location', '')
65 65 if history_dir and not os.path.exists(history_dir):
66 66 os.makedirs(history_dir, 0750)
67 67
68 68
69 69 def includeme(config):
70 70 settings = config.registry.settings
71 71 PLUGIN_DEFINITION['config']['enabled'] = asbool(
72 72 settings.get('channelstream.enabled'))
73 73 PLUGIN_DEFINITION['config']['server'] = settings.get(
74 74 'channelstream.server', '')
75 75 PLUGIN_DEFINITION['config']['secret'] = settings.get(
76 76 'channelstream.secret', '')
77 77 PLUGIN_DEFINITION['config']['history.location'] = settings.get(
78 78 'channelstream.history.location', '')
79 79 config.register_rhodecode_plugin(
80 80 PLUGIN_DEFINITION['name'],
81 81 PLUGIN_DEFINITION['config']
82 82 )
83 83 config.add_subscriber(maybe_create_history_store, ApplicationCreated)
84 84
85 85 config.add_route(
86 86 name='channelstream_connect',
87 87 pattern=ADMIN_PREFIX + '/channelstream/connect')
88 88 config.add_route(
89 89 name='channelstream_subscribe',
90 90 pattern=ADMIN_PREFIX + '/channelstream/subscribe')
91 91 config.add_route(
92 92 name='channelstream_proxy',
93 93 pattern=settings.get('channelstream.proxy_path') or '/_channelstream')
94 94
95 95 # Scan module for configuration decorators.
96 96 config.scan('.views', ignore='.tests')
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 from rhodecode.config.routing import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def includeme(config):
26 26
27 27 config.add_route(
28 28 name='login',
29 29 pattern=ADMIN_PREFIX + '/login')
30 30 config.add_route(
31 31 name='logout',
32 32 pattern=ADMIN_PREFIX + '/logout')
33 33 config.add_route(
34 34 name='register',
35 35 pattern=ADMIN_PREFIX + '/register')
36 36 config.add_route(
37 37 name='reset_password',
38 38 pattern=ADMIN_PREFIX + '/password_reset')
39 39 config.add_route(
40 40 name='reset_password_confirmation',
41 41 pattern=ADMIN_PREFIX + '/password_reset_confirmation')
42 42
43 43 # Scan module for configuration decorators.
44 44 config.scan('.views', ignore='.tests')
@@ -1,133 +1,133 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import mock
23 23 import pytest
24 24
25 from rhodecode.apps._base import ADMIN_PREFIX
25 26 from rhodecode.apps.login.views import LoginView, CaptchaData
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.model.settings import SettingsModel
27 28 from rhodecode.lib.utils2 import AttributeDict
28 from rhodecode.model.settings import SettingsModel
29 29 from rhodecode.tests.utils import AssertResponse
30 30
31 31
32 32 class RhodeCodeSetting(object):
33 33 def __init__(self, name, value):
34 34 self.name = name
35 35 self.value = value
36 36
37 37 def __enter__(self):
38 38 from rhodecode.model.settings import SettingsModel
39 39 model = SettingsModel()
40 40 self.old_setting = model.get_setting_by_name(self.name)
41 41 model.create_or_update_setting(name=self.name, val=self.value)
42 42 return self
43 43
44 44 def __exit__(self, exc_type, exc_val, exc_tb):
45 45 model = SettingsModel()
46 46 if self.old_setting:
47 47 model.create_or_update_setting(
48 48 name=self.name, val=self.old_setting.app_settings_value)
49 49 else:
50 50 model.create_or_update_setting(name=self.name)
51 51
52 52
53 53 class TestRegisterCaptcha(object):
54 54
55 55 @pytest.mark.parametrize('private_key, public_key, expected', [
56 56 ('', '', CaptchaData(False, '', '')),
57 57 ('', 'pubkey', CaptchaData(False, '', 'pubkey')),
58 58 ('privkey', '', CaptchaData(True, 'privkey', '')),
59 59 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
60 60 ])
61 61 def test_get_captcha_data(self, private_key, public_key, expected, db,
62 62 request_stub, user_util):
63 63 request_stub.user = user_util.create_user().AuthUser()
64 64 request_stub.matched_route = AttributeDict({'name': 'login'})
65 65 login_view = LoginView(mock.Mock(), request_stub)
66 66
67 67 with RhodeCodeSetting('captcha_private_key', private_key):
68 68 with RhodeCodeSetting('captcha_public_key', public_key):
69 69 captcha = login_view._get_captcha_data()
70 70 assert captcha == expected
71 71
72 72 @pytest.mark.parametrize('active', [False, True])
73 73 @mock.patch.object(LoginView, '_get_captcha_data')
74 74 def test_private_key_does_not_leak_to_html(
75 75 self, m_get_captcha_data, active, app):
76 76 captcha = CaptchaData(
77 77 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
78 78 m_get_captcha_data.return_value = captcha
79 79
80 80 response = app.get(ADMIN_PREFIX + '/register')
81 81 assert 'PRIVATE_KEY' not in response
82 82
83 83 @pytest.mark.parametrize('active', [False, True])
84 84 @mock.patch.object(LoginView, '_get_captcha_data')
85 85 def test_register_view_renders_captcha(
86 86 self, m_get_captcha_data, active, app):
87 87 captcha = CaptchaData(
88 88 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
89 89 m_get_captcha_data.return_value = captcha
90 90
91 91 response = app.get(ADMIN_PREFIX + '/register')
92 92
93 93 assertr = AssertResponse(response)
94 94 if active:
95 95 assertr.one_element_exists('#recaptcha_field')
96 96 else:
97 97 assertr.no_element_exists('#recaptcha_field')
98 98
99 99 @pytest.mark.parametrize('valid', [False, True])
100 100 @mock.patch('rhodecode.apps.login.views.submit')
101 101 @mock.patch.object(LoginView, '_get_captcha_data')
102 102 def test_register_with_active_captcha(
103 103 self, m_get_captcha_data, m_submit, valid, app, csrf_token):
104 104 captcha = CaptchaData(
105 105 active=True, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
106 106 m_get_captcha_data.return_value = captcha
107 107 m_response = mock.Mock()
108 108 m_response.is_valid = valid
109 109 m_submit.return_value = m_response
110 110
111 111 params = {
112 112 'csrf_token': csrf_token,
113 113 'email': 'pytest@example.com',
114 114 'firstname': 'pytest-firstname',
115 115 'lastname': 'pytest-lastname',
116 116 'password': 'secret',
117 117 'password_confirmation': 'secret',
118 118 'username': 'pytest',
119 119 }
120 120 response = app.post(ADMIN_PREFIX + '/register', params=params)
121 121
122 122 if valid:
123 123 # If we provided a valid captcha input we expect a successful
124 124 # registration and redirect to the login page.
125 125 assert response.status_int == 302
126 126 assert 'location' in response.headers
127 127 assert ADMIN_PREFIX + '/login' in response.headers['location']
128 128 else:
129 129 # If captche input is invalid we expect to stay on the registration
130 130 # page with an error message displayed.
131 131 assertr = AssertResponse(response)
132 132 assert response.status_int == 200
133 133 assertr.one_element_exists('#recaptcha_field ~ span.error-message')
@@ -1,46 +1,46 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 from rhodecode.config.routing import ADMIN_PREFIX
21 from rhodecode.apps._base import ADMIN_PREFIX
22 22
23 23
24 24 def admin_routes(config):
25 25 config.add_route(
26 26 name='ops_ping',
27 27 pattern='/ping')
28 28 config.add_route(
29 29 name='ops_error_test',
30 30 pattern='/error')
31 31 config.add_route(
32 32 name='ops_redirect_test',
33 33 pattern='/redirect')
34 34
35 35
36 36 def includeme(config):
37 37
38 38 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
39 39 # make OLD entries from pylons work
40 40 config.add_route(
41 41 name='ops_ping_legacy', pattern=ADMIN_PREFIX + '/ping')
42 42 config.add_route(
43 43 name='ops_error_test_legacy', pattern=ADMIN_PREFIX + '/error_test')
44 44
45 45 # Scan module for configuration decorators.
46 46 config.scan('.views', ignore='.tests')
@@ -1,101 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.apps._base import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def admin_routes(config):
28 28 """
29 29 User groups /_admin prefixed routes
30 30 """
31 31
32 32 config.add_route(
33 33 name='user_group_members_data',
34 34 pattern='/user_groups/{user_group_id:\d+}/members',
35 35 user_group_route=True)
36 36
37 37 # user groups perms
38 38 config.add_route(
39 39 name='edit_user_group_perms_summary',
40 40 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary',
41 41 user_group_route=True)
42 42 config.add_route(
43 43 name='edit_user_group_perms_summary_json',
44 44 pattern='/user_groups/{user_group_id:\d+}/edit/permissions_summary/json',
45 45 user_group_route=True)
46 46
47 47 # user groups edit
48 48 config.add_route(
49 49 name='edit_user_group',
50 50 pattern='/user_groups/{user_group_id:\d+}/edit',
51 51 user_group_route=True)
52 52
53 53 # user groups update
54 54 config.add_route(
55 55 name='user_groups_update',
56 56 pattern='/user_groups/{user_group_id:\d+}/update',
57 57 user_group_route=True)
58 58
59 59 config.add_route(
60 60 name='edit_user_group_global_perms',
61 61 pattern='/user_groups/{user_group_id:\d+}/edit/global_permissions',
62 62 user_group_route=True)
63 63
64 64 config.add_route(
65 65 name='edit_user_group_global_perms_update',
66 66 pattern='/user_groups/{user_group_id:\d+}/edit/global_permissions/update',
67 67 user_group_route=True)
68 68
69 69 config.add_route(
70 70 name='edit_user_group_perms',
71 71 pattern='/user_groups/{user_group_id:\d+}/edit/permissions',
72 72 user_group_route=True)
73 73
74 74 config.add_route(
75 75 name='edit_user_group_perms_update',
76 76 pattern='/user_groups/{user_group_id:\d+}/edit/permissions/update',
77 77 user_group_route=True)
78 78
79 79 config.add_route(
80 80 name='edit_user_group_advanced',
81 81 pattern='/user_groups/{user_group_id:\d+}/edit/advanced',
82 82 user_group_route=True)
83 83
84 84 config.add_route(
85 85 name='edit_user_group_advanced_sync',
86 86 pattern='/user_groups/{user_group_id:\d+}/edit/advanced/sync',
87 87 user_group_route=True)
88 88
89 89 # user groups delete
90 90 config.add_route(
91 91 name='user_groups_delete',
92 92 pattern='/user_groups/{user_group_id:\d+}/delete',
93 93 user_group_route=True)
94 94
95 95
96 96 def includeme(config):
97 97 # main admin routes
98 98 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
99 99
100 100 # Scan module for configuration decorators.
101 101 config.scan('.views', ignore='.tests')
@@ -1,122 +1,122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import importlib
23 23
24 24 from pkg_resources import iter_entry_points
25 25 from pyramid.authentication import SessionAuthenticationPolicy
26 26
27 27 from rhodecode.authentication.registry import AuthenticationPluginRegistry
28 28 from rhodecode.authentication.routes import root_factory
29 29 from rhodecode.authentication.routes import AuthnRootResource
30 from rhodecode.config.routing import ADMIN_PREFIX
30 from rhodecode.apps._base import ADMIN_PREFIX
31 31 from rhodecode.model.settings import SettingsModel
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36 # Plugin ID prefixes to distinct between normal and legacy plugins.
37 37 plugin_prefix = 'egg:'
38 38 legacy_plugin_prefix = 'py:'
39 39
40 40
41 41 # TODO: Currently this is only used to discover the authentication plugins.
42 42 # Later on this may be used in a generic way to look up and include all kinds
43 43 # of supported enterprise plugins. Therefore this has to be moved and
44 44 # refactored to a real 'plugin look up' machinery.
45 45 # TODO: When refactoring this think about splitting it up into distinct
46 46 # discover, load and include phases.
47 47 def _discover_plugins(config, entry_point='enterprise.plugins1'):
48 48 for ep in iter_entry_points(entry_point):
49 49 plugin_id = '{}{}#{}'.format(
50 50 plugin_prefix, ep.dist.project_name, ep.name)
51 51 log.debug('Plugin discovered: "%s"', plugin_id)
52 52 try:
53 53 module = ep.load()
54 54 plugin = module(plugin_id=plugin_id)
55 55 config.include(plugin.includeme)
56 56 except Exception as e:
57 57 log.exception(
58 58 'Exception while loading authentication plugin '
59 59 '"{}": {}'.format(plugin_id, e.message))
60 60
61 61
62 62 def _import_legacy_plugin(plugin_id):
63 63 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
64 64 module = importlib.import_module(module_name)
65 65 return module.plugin_factory(plugin_id=plugin_id)
66 66
67 67
68 68 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
69 69 """
70 70 Function that imports the legacy plugins stored in the 'auth_plugins'
71 71 setting in database which are using the specified prefix. Normally 'py:' is
72 72 used for the legacy plugins.
73 73 """
74 74 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
75 75 enabled_plugins = auth_plugins.app_settings_value
76 76 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
77 77
78 78 for plugin_id in legacy_plugins:
79 79 log.debug('Legacy plugin discovered: "%s"', plugin_id)
80 80 try:
81 81 plugin = _import_legacy_plugin(plugin_id)
82 82 config.include(plugin.includeme)
83 83 except Exception as e:
84 84 log.exception(
85 85 'Exception while loading legacy authentication plugin '
86 86 '"{}": {}'.format(plugin_id, e.message))
87 87
88 88
89 89 def includeme(config):
90 90 # Set authentication policy.
91 91 authn_policy = SessionAuthenticationPolicy()
92 92 config.set_authentication_policy(authn_policy)
93 93
94 94 # Create authentication plugin registry and add it to the pyramid registry.
95 95 authn_registry = AuthenticationPluginRegistry(config.get_settings())
96 96 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
97 97 config.registry.registerUtility(authn_registry)
98 98
99 99 # Create authentication traversal root resource.
100 100 authn_root_resource = root_factory()
101 101 config.add_directive('add_authn_resource',
102 102 authn_root_resource.add_authn_resource)
103 103
104 104 # Add the authentication traversal route.
105 105 config.add_route('auth_home',
106 106 ADMIN_PREFIX + '/auth*traverse',
107 107 factory=root_factory)
108 108 # Add the authentication settings root views.
109 109 config.add_view('rhodecode.authentication.views.AuthSettingsView',
110 110 attr='index',
111 111 request_method='GET',
112 112 route_name='auth_home',
113 113 context=AuthnRootResource)
114 114 config.add_view('rhodecode.authentication.views.AuthSettingsView',
115 115 attr='auth_settings',
116 116 request_method='POST',
117 117 route_name='auth_home',
118 118 context=AuthnRootResource)
119 119
120 120 # Auto discover authentication plugins and include their configuration.
121 121 _discover_plugins(config)
122 122 _discover_legacy_plugins(config)
@@ -1,77 +1,77 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 from rhodecode.authentication.tests.conftest import (
25 25 EnabledAuthPlugin, DisabledAuthPlugin)
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.apps._base import ADMIN_PREFIX
27 27
28 28
29 29 @pytest.mark.usefixtures('autologin_user', 'app')
30 30 class TestAuthenticationSettings:
31 31
32 32 def test_auth_settings_global_view_get(self, app):
33 33 url = '{prefix}/auth/'.format(prefix=ADMIN_PREFIX)
34 34 response = app.get(url)
35 35 assert response.status_code == 200
36 36
37 37 def test_plugin_settings_view_get(self, app, auth_plugin):
38 38 url = '{prefix}/auth/{name}'.format(
39 39 prefix=ADMIN_PREFIX,
40 40 name=auth_plugin.name)
41 41 with EnabledAuthPlugin(auth_plugin):
42 42 response = app.get(url)
43 43 assert response.status_code == 200
44 44
45 45 def test_plugin_settings_view_post(self, app, auth_plugin, csrf_token):
46 46 url = '{prefix}/auth/{name}'.format(
47 47 prefix=ADMIN_PREFIX,
48 48 name=auth_plugin.name)
49 49 params = {
50 50 'enabled': True,
51 51 'cache_ttl': 0,
52 52 'csrf_token': csrf_token,
53 53 }
54 54 with EnabledAuthPlugin(auth_plugin):
55 55 response = app.post(url, params=params)
56 56 assert response.status_code in [200, 302]
57 57
58 58 def test_plugin_settings_view_get_404(self, app, auth_plugin):
59 59 url = '{prefix}/auth/{name}'.format(
60 60 prefix=ADMIN_PREFIX,
61 61 name=auth_plugin.name)
62 62 with DisabledAuthPlugin(auth_plugin):
63 63 response = app.get(url, status=404)
64 64 assert response.status_code == 404
65 65
66 66 def test_plugin_settings_view_post_404(self, app, auth_plugin, csrf_token):
67 67 url = '{prefix}/auth/{name}'.format(
68 68 prefix=ADMIN_PREFIX,
69 69 name=auth_plugin.name)
70 70 params = {
71 71 'enabled': True,
72 72 'cache_ttl': 0,
73 73 'csrf_token': csrf_token,
74 74 }
75 75 with DisabledAuthPlugin(auth_plugin):
76 76 response = app.post(url, params=params, status=404)
77 77 assert response.status_code == 404
@@ -1,1122 +1,1122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Set of generic validators
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 from collections import defaultdict
29 29
30 30 import formencode
31 31 import ipaddress
32 32 from formencode.validators import (
33 33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 35 )
36 36 from pylons.i18n.translation import _
37 37 from sqlalchemy.sql.expression import true
38 38 from sqlalchemy.util import OrderedSet
39 39 from webhelpers.pylonslib.secure_form import authentication_token
40 40
41 41 from rhodecode.authentication import (
42 42 legacy_plugin_prefix, _import_legacy_plugin)
43 43 from rhodecode.authentication.base import loadplugin
44 from rhodecode.config.routing import ADMIN_PREFIX
44 from rhodecode.apps._base import ADMIN_PREFIX
45 45 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
46 46 from rhodecode.lib.utils import repo_name_slug, make_db_config
47 47 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5, safe_unicode
48 48 from rhodecode.lib.vcs.backends.git.repository import GitRepository
49 49 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
50 50 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
51 51 from rhodecode.model.db import (
52 52 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 # silence warnings and pylint
56 56 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
57 57 NotEmpty, IPAddress, CIDR, String, FancyValidator
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class _Missing(object):
63 63 pass
64 64
65 65 Missing = _Missing()
66 66
67 67
68 68 class StateObj(object):
69 69 """
70 70 this is needed to translate the messages using _() in validators
71 71 """
72 72 _ = staticmethod(_)
73 73
74 74
75 75 def M(self, key, state=None, **kwargs):
76 76 """
77 77 returns string from self.message based on given key,
78 78 passed kw params are used to substitute %(named)s params inside
79 79 translated strings
80 80
81 81 :param msg:
82 82 :param state:
83 83 """
84 84 if state is None:
85 85 state = StateObj()
86 86 else:
87 87 state._ = staticmethod(_)
88 88 # inject validator into state object
89 89 return self.message(key, state, **kwargs)
90 90
91 91
92 92 def UniqueList(convert=None):
93 93 class _UniqueList(formencode.FancyValidator):
94 94 """
95 95 Unique List !
96 96 """
97 97 messages = {
98 98 'empty': _(u'Value cannot be an empty list'),
99 99 'missing_value': _(u'Value cannot be an empty list'),
100 100 }
101 101
102 102 def _to_python(self, value, state):
103 103 ret_val = []
104 104
105 105 def make_unique(value):
106 106 seen = []
107 107 return [c for c in value if not (c in seen or seen.append(c))]
108 108
109 109 if isinstance(value, list):
110 110 ret_val = make_unique(value)
111 111 elif isinstance(value, set):
112 112 ret_val = make_unique(list(value))
113 113 elif isinstance(value, tuple):
114 114 ret_val = make_unique(list(value))
115 115 elif value is None:
116 116 ret_val = []
117 117 else:
118 118 ret_val = [value]
119 119
120 120 if convert:
121 121 ret_val = map(convert, ret_val)
122 122 return ret_val
123 123
124 124 def empty_value(self, value):
125 125 return []
126 126
127 127 return _UniqueList
128 128
129 129
130 130 def UniqueListFromString():
131 131 class _UniqueListFromString(UniqueList()):
132 132 def _to_python(self, value, state):
133 133 if isinstance(value, basestring):
134 134 value = aslist(value, ',')
135 135 return super(_UniqueListFromString, self)._to_python(value, state)
136 136 return _UniqueListFromString
137 137
138 138
139 139 def ValidSvnPattern(section, repo_name=None):
140 140 class _validator(formencode.validators.FancyValidator):
141 141 messages = {
142 142 'pattern_exists': _(u'Pattern already exists'),
143 143 }
144 144
145 145 def validate_python(self, value, state):
146 146 if not value:
147 147 return
148 148 model = VcsSettingsModel(repo=repo_name)
149 149 ui_settings = model.get_svn_patterns(section=section)
150 150 for entry in ui_settings:
151 151 if value == entry.value:
152 152 msg = M(self, 'pattern_exists', state)
153 153 raise formencode.Invalid(msg, value, state)
154 154 return _validator
155 155
156 156
157 157 def ValidUsername(edit=False, old_data={}):
158 158 class _validator(formencode.validators.FancyValidator):
159 159 messages = {
160 160 'username_exists': _(u'Username "%(username)s" already exists'),
161 161 'system_invalid_username':
162 162 _(u'Username "%(username)s" is forbidden'),
163 163 'invalid_username':
164 164 _(u'Username may only contain alphanumeric characters '
165 165 u'underscores, periods or dashes and must begin with '
166 166 u'alphanumeric character or underscore')
167 167 }
168 168
169 169 def validate_python(self, value, state):
170 170 if value in ['default', 'new_user']:
171 171 msg = M(self, 'system_invalid_username', state, username=value)
172 172 raise formencode.Invalid(msg, value, state)
173 173 # check if user is unique
174 174 old_un = None
175 175 if edit:
176 176 old_un = User.get(old_data.get('user_id')).username
177 177
178 178 if old_un != value or not edit:
179 179 if User.get_by_username(value, case_insensitive=True):
180 180 msg = M(self, 'username_exists', state, username=value)
181 181 raise formencode.Invalid(msg, value, state)
182 182
183 183 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
184 184 is None):
185 185 msg = M(self, 'invalid_username', state)
186 186 raise formencode.Invalid(msg, value, state)
187 187 return _validator
188 188
189 189
190 190 def ValidRegex(msg=None):
191 191 class _validator(formencode.validators.Regex):
192 192 messages = {'invalid': msg or _(u'The input is not valid')}
193 193 return _validator
194 194
195 195
196 196 def ValidRepoUser(allow_disabled=False):
197 197 class _validator(formencode.validators.FancyValidator):
198 198 messages = {
199 199 'invalid_username': _(u'Username %(username)s is not valid'),
200 200 'disabled_username': _(u'Username %(username)s is disabled')
201 201 }
202 202
203 203 def validate_python(self, value, state):
204 204 try:
205 205 user = User.query().filter(User.username == value).one()
206 206 except Exception:
207 207 msg = M(self, 'invalid_username', state, username=value)
208 208 raise formencode.Invalid(
209 209 msg, value, state, error_dict={'username': msg}
210 210 )
211 211 if user and (not allow_disabled and not user.active):
212 212 msg = M(self, 'disabled_username', state, username=value)
213 213 raise formencode.Invalid(
214 214 msg, value, state, error_dict={'username': msg}
215 215 )
216 216
217 217 return _validator
218 218
219 219
220 220 def ValidUserGroup(edit=False, old_data={}):
221 221 class _validator(formencode.validators.FancyValidator):
222 222 messages = {
223 223 'invalid_group': _(u'Invalid user group name'),
224 224 'group_exist': _(u'User group `%(usergroup)s` already exists'),
225 225 'invalid_usergroup_name':
226 226 _(u'user group name may only contain alphanumeric '
227 227 u'characters underscores, periods or dashes and must begin '
228 228 u'with alphanumeric character')
229 229 }
230 230
231 231 def validate_python(self, value, state):
232 232 if value in ['default']:
233 233 msg = M(self, 'invalid_group', state)
234 234 raise formencode.Invalid(
235 235 msg, value, state, error_dict={'users_group_name': msg}
236 236 )
237 237 # check if group is unique
238 238 old_ugname = None
239 239 if edit:
240 240 old_id = old_data.get('users_group_id')
241 241 old_ugname = UserGroup.get(old_id).users_group_name
242 242
243 243 if old_ugname != value or not edit:
244 244 is_existing_group = UserGroup.get_by_group_name(
245 245 value, case_insensitive=True)
246 246 if is_existing_group:
247 247 msg = M(self, 'group_exist', state, usergroup=value)
248 248 raise formencode.Invalid(
249 249 msg, value, state, error_dict={'users_group_name': msg}
250 250 )
251 251
252 252 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
253 253 msg = M(self, 'invalid_usergroup_name', state)
254 254 raise formencode.Invalid(
255 255 msg, value, state, error_dict={'users_group_name': msg}
256 256 )
257 257
258 258 return _validator
259 259
260 260
261 261 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
262 262 class _validator(formencode.validators.FancyValidator):
263 263 messages = {
264 264 'group_parent_id': _(u'Cannot assign this group as parent'),
265 265 'group_exists': _(u'Group "%(group_name)s" already exists'),
266 266 'repo_exists': _(u'Repository with name "%(group_name)s" '
267 267 u'already exists'),
268 268 'permission_denied': _(u"no permission to store repository group"
269 269 u"in this location"),
270 270 'permission_denied_root': _(
271 271 u"no permission to store repository group "
272 272 u"in root location")
273 273 }
274 274
275 275 def _to_python(self, value, state):
276 276 group_name = repo_name_slug(value.get('group_name', ''))
277 277 group_parent_id = safe_int(value.get('group_parent_id'))
278 278 gr = RepoGroup.get(group_parent_id)
279 279 if gr:
280 280 parent_group_path = gr.full_path
281 281 # value needs to be aware of group name in order to check
282 282 # db key This is an actual just the name to store in the
283 283 # database
284 284 group_name_full = (
285 285 parent_group_path + RepoGroup.url_sep() + group_name)
286 286 else:
287 287 group_name_full = group_name
288 288
289 289 value['group_name'] = group_name
290 290 value['group_name_full'] = group_name_full
291 291 value['group_parent_id'] = group_parent_id
292 292 return value
293 293
294 294 def validate_python(self, value, state):
295 295
296 296 old_group_name = None
297 297 group_name = value.get('group_name')
298 298 group_name_full = value.get('group_name_full')
299 299 group_parent_id = safe_int(value.get('group_parent_id'))
300 300 if group_parent_id == -1:
301 301 group_parent_id = None
302 302
303 303 group_obj = RepoGroup.get(old_data.get('group_id'))
304 304 parent_group_changed = False
305 305 if edit:
306 306 old_group_name = group_obj.group_name
307 307 old_group_parent_id = group_obj.group_parent_id
308 308
309 309 if group_parent_id != old_group_parent_id:
310 310 parent_group_changed = True
311 311
312 312 # TODO: mikhail: the following if statement is not reached
313 313 # since group_parent_id's OneOf validation fails before.
314 314 # Can be removed.
315 315
316 316 # check against setting a parent of self
317 317 parent_of_self = (
318 318 old_data['group_id'] == group_parent_id
319 319 if group_parent_id else False
320 320 )
321 321 if parent_of_self:
322 322 msg = M(self, 'group_parent_id', state)
323 323 raise formencode.Invalid(
324 324 msg, value, state, error_dict={'group_parent_id': msg}
325 325 )
326 326
327 327 # group we're moving current group inside
328 328 child_group = None
329 329 if group_parent_id:
330 330 child_group = RepoGroup.query().filter(
331 331 RepoGroup.group_id == group_parent_id).scalar()
332 332
333 333 # do a special check that we cannot move a group to one of
334 334 # it's children
335 335 if edit and child_group:
336 336 parents = [x.group_id for x in child_group.parents]
337 337 move_to_children = old_data['group_id'] in parents
338 338 if move_to_children:
339 339 msg = M(self, 'group_parent_id', state)
340 340 raise formencode.Invalid(
341 341 msg, value, state, error_dict={'group_parent_id': msg})
342 342
343 343 # Check if we have permission to store in the parent.
344 344 # Only check if the parent group changed.
345 345 if parent_group_changed:
346 346 if child_group is None:
347 347 if not can_create_in_root:
348 348 msg = M(self, 'permission_denied_root', state)
349 349 raise formencode.Invalid(
350 350 msg, value, state,
351 351 error_dict={'group_parent_id': msg})
352 352 else:
353 353 valid = HasRepoGroupPermissionAny('group.admin')
354 354 forbidden = not valid(
355 355 child_group.group_name, 'can create group validator')
356 356 if forbidden:
357 357 msg = M(self, 'permission_denied', state)
358 358 raise formencode.Invalid(
359 359 msg, value, state,
360 360 error_dict={'group_parent_id': msg})
361 361
362 362 # if we change the name or it's new group, check for existing names
363 363 # or repositories with the same name
364 364 if old_group_name != group_name_full or not edit:
365 365 # check group
366 366 gr = RepoGroup.get_by_group_name(group_name_full)
367 367 if gr:
368 368 msg = M(self, 'group_exists', state, group_name=group_name)
369 369 raise formencode.Invalid(
370 370 msg, value, state, error_dict={'group_name': msg})
371 371
372 372 # check for same repo
373 373 repo = Repository.get_by_repo_name(group_name_full)
374 374 if repo:
375 375 msg = M(self, 'repo_exists', state, group_name=group_name)
376 376 raise formencode.Invalid(
377 377 msg, value, state, error_dict={'group_name': msg})
378 378
379 379 return _validator
380 380
381 381
382 382 def ValidPassword():
383 383 class _validator(formencode.validators.FancyValidator):
384 384 messages = {
385 385 'invalid_password':
386 386 _(u'Invalid characters (non-ascii) in password')
387 387 }
388 388
389 389 def validate_python(self, value, state):
390 390 try:
391 391 (value or '').decode('ascii')
392 392 except UnicodeError:
393 393 msg = M(self, 'invalid_password', state)
394 394 raise formencode.Invalid(msg, value, state,)
395 395 return _validator
396 396
397 397
398 398 def ValidOldPassword(username):
399 399 class _validator(formencode.validators.FancyValidator):
400 400 messages = {
401 401 'invalid_password': _(u'Invalid old password')
402 402 }
403 403
404 404 def validate_python(self, value, state):
405 405 from rhodecode.authentication.base import authenticate, HTTP_TYPE
406 406 if not authenticate(username, value, '', HTTP_TYPE):
407 407 msg = M(self, 'invalid_password', state)
408 408 raise formencode.Invalid(
409 409 msg, value, state, error_dict={'current_password': msg}
410 410 )
411 411 return _validator
412 412
413 413
414 414 def ValidPasswordsMatch(
415 415 passwd='new_password', passwd_confirmation='password_confirmation'):
416 416 class _validator(formencode.validators.FancyValidator):
417 417 messages = {
418 418 'password_mismatch': _(u'Passwords do not match'),
419 419 }
420 420
421 421 def validate_python(self, value, state):
422 422
423 423 pass_val = value.get('password') or value.get(passwd)
424 424 if pass_val != value[passwd_confirmation]:
425 425 msg = M(self, 'password_mismatch', state)
426 426 raise formencode.Invalid(
427 427 msg, value, state,
428 428 error_dict={passwd: msg, passwd_confirmation: msg}
429 429 )
430 430 return _validator
431 431
432 432
433 433 def ValidAuth():
434 434 class _validator(formencode.validators.FancyValidator):
435 435 messages = {
436 436 'invalid_password': _(u'invalid password'),
437 437 'invalid_username': _(u'invalid user name'),
438 438 'disabled_account': _(u'Your account is disabled')
439 439 }
440 440
441 441 def validate_python(self, value, state):
442 442 from rhodecode.authentication.base import authenticate, HTTP_TYPE
443 443
444 444 password = value['password']
445 445 username = value['username']
446 446
447 447 if not authenticate(username, password, '', HTTP_TYPE,
448 448 skip_missing=True):
449 449 user = User.get_by_username(username)
450 450 if user and not user.active:
451 451 log.warning('user %s is disabled', username)
452 452 msg = M(self, 'disabled_account', state)
453 453 raise formencode.Invalid(
454 454 msg, value, state, error_dict={'username': msg}
455 455 )
456 456 else:
457 457 log.warning('user `%s` failed to authenticate', username)
458 458 msg = M(self, 'invalid_username', state)
459 459 msg2 = M(self, 'invalid_password', state)
460 460 raise formencode.Invalid(
461 461 msg, value, state,
462 462 error_dict={'username': msg, 'password': msg2}
463 463 )
464 464 return _validator
465 465
466 466
467 467 def ValidAuthToken():
468 468 class _validator(formencode.validators.FancyValidator):
469 469 messages = {
470 470 'invalid_token': _(u'Token mismatch')
471 471 }
472 472
473 473 def validate_python(self, value, state):
474 474 if value != authentication_token():
475 475 msg = M(self, 'invalid_token', state)
476 476 raise formencode.Invalid(msg, value, state)
477 477 return _validator
478 478
479 479
480 480 def ValidRepoName(edit=False, old_data={}):
481 481 class _validator(formencode.validators.FancyValidator):
482 482 messages = {
483 483 'invalid_repo_name':
484 484 _(u'Repository name %(repo)s is disallowed'),
485 485 # top level
486 486 'repository_exists': _(u'Repository with name %(repo)s '
487 487 u'already exists'),
488 488 'group_exists': _(u'Repository group with name "%(repo)s" '
489 489 u'already exists'),
490 490 # inside a group
491 491 'repository_in_group_exists': _(u'Repository with name %(repo)s '
492 492 u'exists in group "%(group)s"'),
493 493 'group_in_group_exists': _(
494 494 u'Repository group with name "%(repo)s" '
495 495 u'exists in group "%(group)s"'),
496 496 }
497 497
498 498 def _to_python(self, value, state):
499 499 repo_name = repo_name_slug(value.get('repo_name', ''))
500 500 repo_group = value.get('repo_group')
501 501 if repo_group:
502 502 gr = RepoGroup.get(repo_group)
503 503 group_path = gr.full_path
504 504 group_name = gr.group_name
505 505 # value needs to be aware of group name in order to check
506 506 # db key This is an actual just the name to store in the
507 507 # database
508 508 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
509 509 else:
510 510 group_name = group_path = ''
511 511 repo_name_full = repo_name
512 512
513 513 value['repo_name'] = repo_name
514 514 value['repo_name_full'] = repo_name_full
515 515 value['group_path'] = group_path
516 516 value['group_name'] = group_name
517 517 return value
518 518
519 519 def validate_python(self, value, state):
520 520
521 521 repo_name = value.get('repo_name')
522 522 repo_name_full = value.get('repo_name_full')
523 523 group_path = value.get('group_path')
524 524 group_name = value.get('group_name')
525 525
526 526 if repo_name in [ADMIN_PREFIX, '']:
527 527 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
528 528 raise formencode.Invalid(
529 529 msg, value, state, error_dict={'repo_name': msg})
530 530
531 531 rename = old_data.get('repo_name') != repo_name_full
532 532 create = not edit
533 533 if rename or create:
534 534
535 535 if group_path:
536 536 if Repository.get_by_repo_name(repo_name_full):
537 537 msg = M(self, 'repository_in_group_exists', state,
538 538 repo=repo_name, group=group_name)
539 539 raise formencode.Invalid(
540 540 msg, value, state, error_dict={'repo_name': msg})
541 541 if RepoGroup.get_by_group_name(repo_name_full):
542 542 msg = M(self, 'group_in_group_exists', state,
543 543 repo=repo_name, group=group_name)
544 544 raise formencode.Invalid(
545 545 msg, value, state, error_dict={'repo_name': msg})
546 546 else:
547 547 if RepoGroup.get_by_group_name(repo_name_full):
548 548 msg = M(self, 'group_exists', state, repo=repo_name)
549 549 raise formencode.Invalid(
550 550 msg, value, state, error_dict={'repo_name': msg})
551 551
552 552 if Repository.get_by_repo_name(repo_name_full):
553 553 msg = M(
554 554 self, 'repository_exists', state, repo=repo_name)
555 555 raise formencode.Invalid(
556 556 msg, value, state, error_dict={'repo_name': msg})
557 557 return value
558 558 return _validator
559 559
560 560
561 561 def ValidForkName(*args, **kwargs):
562 562 return ValidRepoName(*args, **kwargs)
563 563
564 564
565 565 def SlugifyName():
566 566 class _validator(formencode.validators.FancyValidator):
567 567
568 568 def _to_python(self, value, state):
569 569 return repo_name_slug(value)
570 570
571 571 def validate_python(self, value, state):
572 572 pass
573 573
574 574 return _validator
575 575
576 576
577 577 def CannotHaveGitSuffix():
578 578 class _validator(formencode.validators.FancyValidator):
579 579 messages = {
580 580 'has_git_suffix':
581 581 _(u'Repository name cannot end with .git'),
582 582 }
583 583
584 584 def _to_python(self, value, state):
585 585 return value
586 586
587 587 def validate_python(self, value, state):
588 588 if value and value.endswith('.git'):
589 589 msg = M(
590 590 self, 'has_git_suffix', state)
591 591 raise formencode.Invalid(
592 592 msg, value, state, error_dict={'repo_name': msg})
593 593
594 594 return _validator
595 595
596 596
597 597 def ValidCloneUri():
598 598 class InvalidCloneUrl(Exception):
599 599 allowed_prefixes = ()
600 600
601 601 def url_handler(repo_type, url):
602 602 config = make_db_config(clear_session=False)
603 603 if repo_type == 'hg':
604 604 allowed_prefixes = ('http', 'svn+http', 'git+http')
605 605
606 606 if 'http' in url[:4]:
607 607 # initially check if it's at least the proper URL
608 608 # or does it pass basic auth
609 609 MercurialRepository.check_url(url, config)
610 610 elif 'svn+http' in url[:8]: # svn->hg import
611 611 SubversionRepository.check_url(url, config)
612 612 elif 'git+http' in url[:8]: # git->hg import
613 613 raise NotImplementedError()
614 614 else:
615 615 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
616 616 'Allowed url must start with one of %s'
617 617 % (url, ','.join(allowed_prefixes)))
618 618 exc.allowed_prefixes = allowed_prefixes
619 619 raise exc
620 620
621 621 elif repo_type == 'git':
622 622 allowed_prefixes = ('http', 'svn+http', 'hg+http')
623 623 if 'http' in url[:4]:
624 624 # initially check if it's at least the proper URL
625 625 # or does it pass basic auth
626 626 GitRepository.check_url(url, config)
627 627 elif 'svn+http' in url[:8]: # svn->git import
628 628 raise NotImplementedError()
629 629 elif 'hg+http' in url[:8]: # hg->git import
630 630 raise NotImplementedError()
631 631 else:
632 632 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
633 633 'Allowed url must start with one of %s'
634 634 % (url, ','.join(allowed_prefixes)))
635 635 exc.allowed_prefixes = allowed_prefixes
636 636 raise exc
637 637
638 638 class _validator(formencode.validators.FancyValidator):
639 639 messages = {
640 640 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
641 641 'invalid_clone_uri': _(
642 642 u'Invalid clone url, provide a valid clone '
643 643 u'url starting with one of %(allowed_prefixes)s')
644 644 }
645 645
646 646 def validate_python(self, value, state):
647 647 repo_type = value.get('repo_type')
648 648 url = value.get('clone_uri')
649 649
650 650 if url:
651 651 try:
652 652 url_handler(repo_type, url)
653 653 except InvalidCloneUrl as e:
654 654 log.warning(e)
655 655 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
656 656 allowed_prefixes=','.join(e.allowed_prefixes))
657 657 raise formencode.Invalid(msg, value, state,
658 658 error_dict={'clone_uri': msg})
659 659 except Exception:
660 660 log.exception('Url validation failed')
661 661 msg = M(self, 'clone_uri', rtype=repo_type)
662 662 raise formencode.Invalid(msg, value, state,
663 663 error_dict={'clone_uri': msg})
664 664 return _validator
665 665
666 666
667 667 def ValidForkType(old_data={}):
668 668 class _validator(formencode.validators.FancyValidator):
669 669 messages = {
670 670 'invalid_fork_type': _(u'Fork have to be the same type as parent')
671 671 }
672 672
673 673 def validate_python(self, value, state):
674 674 if old_data['repo_type'] != value:
675 675 msg = M(self, 'invalid_fork_type', state)
676 676 raise formencode.Invalid(
677 677 msg, value, state, error_dict={'repo_type': msg}
678 678 )
679 679 return _validator
680 680
681 681
682 682 def CanWriteGroup(old_data=None):
683 683 class _validator(formencode.validators.FancyValidator):
684 684 messages = {
685 685 'permission_denied': _(
686 686 u"You do not have the permission "
687 687 u"to create repositories in this group."),
688 688 'permission_denied_root': _(
689 689 u"You do not have the permission to store repositories in "
690 690 u"the root location.")
691 691 }
692 692
693 693 def _to_python(self, value, state):
694 694 # root location
695 695 if value in [-1, "-1"]:
696 696 return None
697 697 return value
698 698
699 699 def validate_python(self, value, state):
700 700 gr = RepoGroup.get(value)
701 701 gr_name = gr.group_name if gr else None # None means ROOT location
702 702 # create repositories with write permission on group is set to true
703 703 create_on_write = HasPermissionAny(
704 704 'hg.create.write_on_repogroup.true')()
705 705 group_admin = HasRepoGroupPermissionAny('group.admin')(
706 706 gr_name, 'can write into group validator')
707 707 group_write = HasRepoGroupPermissionAny('group.write')(
708 708 gr_name, 'can write into group validator')
709 709 forbidden = not (group_admin or (group_write and create_on_write))
710 710 can_create_repos = HasPermissionAny(
711 711 'hg.admin', 'hg.create.repository')
712 712 gid = (old_data['repo_group'].get('group_id')
713 713 if (old_data and 'repo_group' in old_data) else None)
714 714 value_changed = gid != safe_int(value)
715 715 new = not old_data
716 716 # do check if we changed the value, there's a case that someone got
717 717 # revoked write permissions to a repository, he still created, we
718 718 # don't need to check permission if he didn't change the value of
719 719 # groups in form box
720 720 if value_changed or new:
721 721 # parent group need to be existing
722 722 if gr and forbidden:
723 723 msg = M(self, 'permission_denied', state)
724 724 raise formencode.Invalid(
725 725 msg, value, state, error_dict={'repo_type': msg}
726 726 )
727 727 # check if we can write to root location !
728 728 elif gr is None and not can_create_repos():
729 729 msg = M(self, 'permission_denied_root', state)
730 730 raise formencode.Invalid(
731 731 msg, value, state, error_dict={'repo_type': msg}
732 732 )
733 733
734 734 return _validator
735 735
736 736
737 737 def ValidPerms(type_='repo'):
738 738 if type_ == 'repo_group':
739 739 EMPTY_PERM = 'group.none'
740 740 elif type_ == 'repo':
741 741 EMPTY_PERM = 'repository.none'
742 742 elif type_ == 'user_group':
743 743 EMPTY_PERM = 'usergroup.none'
744 744
745 745 class _validator(formencode.validators.FancyValidator):
746 746 messages = {
747 747 'perm_new_member_name':
748 748 _(u'This username or user group name is not valid')
749 749 }
750 750
751 751 def _to_python(self, value, state):
752 752 perm_updates = OrderedSet()
753 753 perm_additions = OrderedSet()
754 754 perm_deletions = OrderedSet()
755 755 # build a list of permission to update/delete and new permission
756 756
757 757 # Read the perm_new_member/perm_del_member attributes and group
758 758 # them by they IDs
759 759 new_perms_group = defaultdict(dict)
760 760 del_perms_group = defaultdict(dict)
761 761 for k, v in value.copy().iteritems():
762 762 if k.startswith('perm_del_member'):
763 763 # delete from org storage so we don't process that later
764 764 del value[k]
765 765 # part is `id`, `type`
766 766 _type, part = k.split('perm_del_member_')
767 767 args = part.split('_')
768 768 if len(args) == 2:
769 769 _key, pos = args
770 770 del_perms_group[pos][_key] = v
771 771 if k.startswith('perm_new_member'):
772 772 # delete from org storage so we don't process that later
773 773 del value[k]
774 774 # part is `id`, `type`, `perm`
775 775 _type, part = k.split('perm_new_member_')
776 776 args = part.split('_')
777 777 if len(args) == 2:
778 778 _key, pos = args
779 779 new_perms_group[pos][_key] = v
780 780
781 781 # store the deletes
782 782 for k in sorted(del_perms_group.keys()):
783 783 perm_dict = del_perms_group[k]
784 784 del_member = perm_dict.get('id')
785 785 del_type = perm_dict.get('type')
786 786 if del_member and del_type:
787 787 perm_deletions.add(
788 788 (del_member, None, del_type))
789 789
790 790 # store additions in order of how they were added in web form
791 791 for k in sorted(new_perms_group.keys()):
792 792 perm_dict = new_perms_group[k]
793 793 new_member = perm_dict.get('id')
794 794 new_type = perm_dict.get('type')
795 795 new_perm = perm_dict.get('perm')
796 796 if new_member and new_perm and new_type:
797 797 perm_additions.add(
798 798 (new_member, new_perm, new_type))
799 799
800 800 # get updates of permissions
801 801 # (read the existing radio button states)
802 802 default_user_id = User.get_default_user().user_id
803 803 for k, update_value in value.iteritems():
804 804 if k.startswith('u_perm_') or k.startswith('g_perm_'):
805 805 member = k[7:]
806 806 update_type = {'u': 'user',
807 807 'g': 'users_group'}[k[0]]
808 808
809 809 if safe_int(member) == default_user_id:
810 810 if str2bool(value.get('repo_private')):
811 811 # prevent from updating default user permissions
812 812 # when this repository is marked as private
813 813 update_value = EMPTY_PERM
814 814
815 815 perm_updates.add(
816 816 (member, update_value, update_type))
817 817
818 818 value['perm_additions'] = [] # propagated later
819 819 value['perm_updates'] = list(perm_updates)
820 820 value['perm_deletions'] = list(perm_deletions)
821 821
822 822 updates_map = dict(
823 823 (x[0], (x[1], x[2])) for x in value['perm_updates'])
824 824 # make sure Additions don't override updates.
825 825 for member_id, perm, member_type in list(perm_additions):
826 826 if member_id in updates_map:
827 827 perm = updates_map[member_id][0]
828 828 value['perm_additions'].append((member_id, perm, member_type))
829 829
830 830 # on new entries validate users they exist and they are active !
831 831 # this leaves feedback to the form
832 832 try:
833 833 if member_type == 'user':
834 834 User.query()\
835 835 .filter(User.active == true())\
836 836 .filter(User.user_id == member_id).one()
837 837 if member_type == 'users_group':
838 838 UserGroup.query()\
839 839 .filter(UserGroup.users_group_active == true())\
840 840 .filter(UserGroup.users_group_id == member_id)\
841 841 .one()
842 842
843 843 except Exception:
844 844 log.exception('Updated permission failed: org_exc:')
845 845 msg = M(self, 'perm_new_member_type', state)
846 846 raise formencode.Invalid(
847 847 msg, value, state, error_dict={
848 848 'perm_new_member_name': msg}
849 849 )
850 850 return value
851 851 return _validator
852 852
853 853
854 854 def ValidSettings():
855 855 class _validator(formencode.validators.FancyValidator):
856 856 def _to_python(self, value, state):
857 857 # settings form for users that are not admin
858 858 # can't edit certain parameters, it's extra backup if they mangle
859 859 # with forms
860 860
861 861 forbidden_params = [
862 862 'user', 'repo_type', 'repo_enable_locking',
863 863 'repo_enable_downloads', 'repo_enable_statistics'
864 864 ]
865 865
866 866 for param in forbidden_params:
867 867 if param in value:
868 868 del value[param]
869 869 return value
870 870
871 871 def validate_python(self, value, state):
872 872 pass
873 873 return _validator
874 874
875 875
876 876 def ValidPath():
877 877 class _validator(formencode.validators.FancyValidator):
878 878 messages = {
879 879 'invalid_path': _(u'This is not a valid path')
880 880 }
881 881
882 882 def validate_python(self, value, state):
883 883 if not os.path.isdir(value):
884 884 msg = M(self, 'invalid_path', state)
885 885 raise formencode.Invalid(
886 886 msg, value, state, error_dict={'paths_root_path': msg}
887 887 )
888 888 return _validator
889 889
890 890
891 891 def UniqSystemEmail(old_data={}):
892 892 class _validator(formencode.validators.FancyValidator):
893 893 messages = {
894 894 'email_taken': _(u'This e-mail address is already taken')
895 895 }
896 896
897 897 def _to_python(self, value, state):
898 898 return value.lower()
899 899
900 900 def validate_python(self, value, state):
901 901 if (old_data.get('email') or '').lower() != value:
902 902 user = User.get_by_email(value, case_insensitive=True)
903 903 if user:
904 904 msg = M(self, 'email_taken', state)
905 905 raise formencode.Invalid(
906 906 msg, value, state, error_dict={'email': msg}
907 907 )
908 908 return _validator
909 909
910 910
911 911 def ValidSystemEmail():
912 912 class _validator(formencode.validators.FancyValidator):
913 913 messages = {
914 914 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
915 915 }
916 916
917 917 def _to_python(self, value, state):
918 918 return value.lower()
919 919
920 920 def validate_python(self, value, state):
921 921 user = User.get_by_email(value, case_insensitive=True)
922 922 if user is None:
923 923 msg = M(self, 'non_existing_email', state, email=value)
924 924 raise formencode.Invalid(
925 925 msg, value, state, error_dict={'email': msg}
926 926 )
927 927
928 928 return _validator
929 929
930 930
931 931 def NotReviewedRevisions(repo_id):
932 932 class _validator(formencode.validators.FancyValidator):
933 933 messages = {
934 934 'rev_already_reviewed':
935 935 _(u'Revisions %(revs)s are already part of pull request '
936 936 u'or have set status'),
937 937 }
938 938
939 939 def validate_python(self, value, state):
940 940 # check revisions if they are not reviewed, or a part of another
941 941 # pull request
942 942 statuses = ChangesetStatus.query()\
943 943 .filter(ChangesetStatus.revision.in_(value))\
944 944 .filter(ChangesetStatus.repo_id == repo_id)\
945 945 .all()
946 946
947 947 errors = []
948 948 for status in statuses:
949 949 if status.pull_request_id:
950 950 errors.append(['pull_req', status.revision[:12]])
951 951 elif status.status:
952 952 errors.append(['status', status.revision[:12]])
953 953
954 954 if errors:
955 955 revs = ','.join([x[1] for x in errors])
956 956 msg = M(self, 'rev_already_reviewed', state, revs=revs)
957 957 raise formencode.Invalid(
958 958 msg, value, state, error_dict={'revisions': revs})
959 959
960 960 return _validator
961 961
962 962
963 963 def ValidIp():
964 964 class _validator(CIDR):
965 965 messages = {
966 966 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
967 967 'illegalBits': _(
968 968 u'The network size (bits) must be within the range '
969 969 u'of 0-32 (not %(bits)r)'),
970 970 }
971 971
972 972 # we ovveride the default to_python() call
973 973 def to_python(self, value, state):
974 974 v = super(_validator, self).to_python(value, state)
975 975 v = safe_unicode(v.strip())
976 976 net = ipaddress.ip_network(address=v, strict=False)
977 977 return str(net)
978 978
979 979 def validate_python(self, value, state):
980 980 try:
981 981 addr = safe_unicode(value.strip())
982 982 # this raises an ValueError if address is not IpV4 or IpV6
983 983 ipaddress.ip_network(addr, strict=False)
984 984 except ValueError:
985 985 raise formencode.Invalid(self.message('badFormat', state),
986 986 value, state)
987 987
988 988 return _validator
989 989
990 990
991 991 def FieldKey():
992 992 class _validator(formencode.validators.FancyValidator):
993 993 messages = {
994 994 'badFormat': _(
995 995 u'Key name can only consist of letters, '
996 996 u'underscore, dash or numbers'),
997 997 }
998 998
999 999 def validate_python(self, value, state):
1000 1000 if not re.match('[a-zA-Z0-9_-]+$', value):
1001 1001 raise formencode.Invalid(self.message('badFormat', state),
1002 1002 value, state)
1003 1003 return _validator
1004 1004
1005 1005
1006 1006 def ValidAuthPlugins():
1007 1007 class _validator(formencode.validators.FancyValidator):
1008 1008 messages = {
1009 1009 'import_duplicate': _(
1010 1010 u'Plugins %(loaded)s and %(next_to_load)s '
1011 1011 u'both export the same name'),
1012 1012 'missing_includeme': _(
1013 1013 u'The plugin "%(plugin_id)s" is missing an includeme '
1014 1014 u'function.'),
1015 1015 'import_error': _(
1016 1016 u'Can not load plugin "%(plugin_id)s"'),
1017 1017 'no_plugin': _(
1018 1018 u'No plugin available with ID "%(plugin_id)s"'),
1019 1019 }
1020 1020
1021 1021 def _to_python(self, value, state):
1022 1022 # filter empty values
1023 1023 return filter(lambda s: s not in [None, ''], value)
1024 1024
1025 1025 def _validate_legacy_plugin_id(self, plugin_id, value, state):
1026 1026 """
1027 1027 Validates that the plugin import works. It also checks that the
1028 1028 plugin has an includeme attribute.
1029 1029 """
1030 1030 try:
1031 1031 plugin = _import_legacy_plugin(plugin_id)
1032 1032 except Exception as e:
1033 1033 log.exception(
1034 1034 'Exception during import of auth legacy plugin "{}"'
1035 1035 .format(plugin_id))
1036 1036 msg = M(self, 'import_error', plugin_id=plugin_id)
1037 1037 raise formencode.Invalid(msg, value, state)
1038 1038
1039 1039 if not hasattr(plugin, 'includeme'):
1040 1040 msg = M(self, 'missing_includeme', plugin_id=plugin_id)
1041 1041 raise formencode.Invalid(msg, value, state)
1042 1042
1043 1043 return plugin
1044 1044
1045 1045 def _validate_plugin_id(self, plugin_id, value, state):
1046 1046 """
1047 1047 Plugins are already imported during app start up. Therefore this
1048 1048 validation only retrieves the plugin from the plugin registry and
1049 1049 if it returns something not None everything is OK.
1050 1050 """
1051 1051 plugin = loadplugin(plugin_id)
1052 1052
1053 1053 if plugin is None:
1054 1054 msg = M(self, 'no_plugin', plugin_id=plugin_id)
1055 1055 raise formencode.Invalid(msg, value, state)
1056 1056
1057 1057 return plugin
1058 1058
1059 1059 def validate_python(self, value, state):
1060 1060 unique_names = {}
1061 1061 for plugin_id in value:
1062 1062
1063 1063 # Validate legacy or normal plugin.
1064 1064 if plugin_id.startswith(legacy_plugin_prefix):
1065 1065 plugin = self._validate_legacy_plugin_id(
1066 1066 plugin_id, value, state)
1067 1067 else:
1068 1068 plugin = self._validate_plugin_id(plugin_id, value, state)
1069 1069
1070 1070 # Only allow unique plugin names.
1071 1071 if plugin.name in unique_names:
1072 1072 msg = M(self, 'import_duplicate', state,
1073 1073 loaded=unique_names[plugin.name],
1074 1074 next_to_load=plugin)
1075 1075 raise formencode.Invalid(msg, value, state)
1076 1076 unique_names[plugin.name] = plugin
1077 1077
1078 1078 return _validator
1079 1079
1080 1080
1081 1081 def ValidPattern():
1082 1082
1083 1083 class _Validator(formencode.validators.FancyValidator):
1084 1084
1085 1085 def _to_python(self, value, state):
1086 1086 patterns = []
1087 1087
1088 1088 prefix = 'new_pattern'
1089 1089 for name, v in value.iteritems():
1090 1090 pattern_name = '_'.join((prefix, 'pattern'))
1091 1091 if name.startswith(pattern_name):
1092 1092 new_item_id = name[len(pattern_name)+1:]
1093 1093
1094 1094 def _field(name):
1095 1095 return '%s_%s_%s' % (prefix, name, new_item_id)
1096 1096
1097 1097 values = {
1098 1098 'issuetracker_pat': value.get(_field('pattern')),
1099 1099 'issuetracker_pat': value.get(_field('pattern')),
1100 1100 'issuetracker_url': value.get(_field('url')),
1101 1101 'issuetracker_pref': value.get(_field('prefix')),
1102 1102 'issuetracker_desc': value.get(_field('description'))
1103 1103 }
1104 1104 new_uid = md5(values['issuetracker_pat'])
1105 1105
1106 1106 has_required_fields = (
1107 1107 values['issuetracker_pat']
1108 1108 and values['issuetracker_url'])
1109 1109
1110 1110 if has_required_fields:
1111 1111 settings = [
1112 1112 ('_'.join((key, new_uid)), values[key], 'unicode')
1113 1113 for key in values]
1114 1114 patterns.append(settings)
1115 1115
1116 1116 value['patterns'] = patterns
1117 1117 delete_patterns = value.get('uid') or []
1118 1118 if not isinstance(delete_patterns, (list, tuple)):
1119 1119 delete_patterns = [delete_patterns]
1120 1120 value['delete_patterns'] = delete_patterns
1121 1121 return value
1122 1122 return _Validator
@@ -1,670 +1,670 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 import rhodecode
25 from rhodecode.config.routing import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 26 from rhodecode.lib.utils2 import md5
27 27 from rhodecode.model.db import RhodeCodeUi
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 30 from rhodecode.tests import url, assert_session_flash
31 31 from rhodecode.tests.utils import AssertResponse
32 32
33 33
34 34 UPDATE_DATA_QUALNAME = (
35 35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36 36
37 37
38 38 @pytest.mark.usefixtures('autologin_user', 'app')
39 39 class TestAdminSettingsController(object):
40 40
41 41 @pytest.mark.parametrize('urlname', [
42 42 'admin_settings_vcs',
43 43 'admin_settings_mapping',
44 44 'admin_settings_global',
45 45 'admin_settings_visual',
46 46 'admin_settings_email',
47 47 'admin_settings_hooks',
48 48 'admin_settings_search',
49 49 ])
50 50 def test_simple_get(self, urlname, app):
51 51 app.get(url(urlname))
52 52
53 53 def test_create_custom_hook(self, csrf_token):
54 54 response = self.app.post(
55 55 url('admin_settings_hooks'),
56 56 params={
57 57 'new_hook_ui_key': 'test_hooks_1',
58 58 'new_hook_ui_value': 'cd /tmp',
59 59 'csrf_token': csrf_token})
60 60
61 61 response = response.follow()
62 62 response.mustcontain('test_hooks_1')
63 63 response.mustcontain('cd /tmp')
64 64
65 65 def test_create_custom_hook_delete(self, csrf_token):
66 66 response = self.app.post(
67 67 url('admin_settings_hooks'),
68 68 params={
69 69 'new_hook_ui_key': 'test_hooks_2',
70 70 'new_hook_ui_value': 'cd /tmp2',
71 71 'csrf_token': csrf_token})
72 72
73 73 response = response.follow()
74 74 response.mustcontain('test_hooks_2')
75 75 response.mustcontain('cd /tmp2')
76 76
77 77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
78 78
79 79 # delete
80 80 self.app.post(
81 81 url('admin_settings_hooks'),
82 82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
83 83 response = self.app.get(url('admin_settings_hooks'))
84 84 response.mustcontain(no=['test_hooks_2'])
85 85 response.mustcontain(no=['cd /tmp2'])
86 86
87 87
88 88 @pytest.mark.usefixtures('autologin_user', 'app')
89 89 class TestAdminSettingsGlobal(object):
90 90
91 91 def test_pre_post_code_code_active(self, csrf_token):
92 92 pre_code = 'rc-pre-code-187652122'
93 93 post_code = 'rc-postcode-98165231'
94 94
95 95 response = self.post_and_verify_settings({
96 96 'rhodecode_pre_code': pre_code,
97 97 'rhodecode_post_code': post_code,
98 98 'csrf_token': csrf_token,
99 99 })
100 100
101 101 response = response.follow()
102 102 response.mustcontain(pre_code, post_code)
103 103
104 104 def test_pre_post_code_code_inactive(self, csrf_token):
105 105 pre_code = 'rc-pre-code-187652122'
106 106 post_code = 'rc-postcode-98165231'
107 107 response = self.post_and_verify_settings({
108 108 'rhodecode_pre_code': '',
109 109 'rhodecode_post_code': '',
110 110 'csrf_token': csrf_token,
111 111 })
112 112
113 113 response = response.follow()
114 114 response.mustcontain(no=[pre_code, post_code])
115 115
116 116 def test_captcha_activate(self, csrf_token):
117 117 self.post_and_verify_settings({
118 118 'rhodecode_captcha_private_key': '1234567890',
119 119 'rhodecode_captcha_public_key': '1234567890',
120 120 'csrf_token': csrf_token,
121 121 })
122 122
123 123 response = self.app.get(ADMIN_PREFIX + '/register')
124 124 response.mustcontain('captcha')
125 125
126 126 def test_captcha_deactivate(self, csrf_token):
127 127 self.post_and_verify_settings({
128 128 'rhodecode_captcha_private_key': '',
129 129 'rhodecode_captcha_public_key': '1234567890',
130 130 'csrf_token': csrf_token,
131 131 })
132 132
133 133 response = self.app.get(ADMIN_PREFIX + '/register')
134 134 response.mustcontain(no=['captcha'])
135 135
136 136 def test_title_change(self, csrf_token):
137 137 old_title = 'RhodeCode'
138 138 new_title = old_title + '_changed'
139 139
140 140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
141 141 response = self.post_and_verify_settings({
142 142 'rhodecode_title': new_title,
143 143 'csrf_token': csrf_token,
144 144 })
145 145
146 146 response = response.follow()
147 147 response.mustcontain(
148 148 """<div class="branding">- %s</div>""" % new_title)
149 149
150 150 def post_and_verify_settings(self, settings):
151 151 old_title = 'RhodeCode'
152 152 old_realm = 'RhodeCode authentication'
153 153 params = {
154 154 'rhodecode_title': old_title,
155 155 'rhodecode_realm': old_realm,
156 156 'rhodecode_pre_code': '',
157 157 'rhodecode_post_code': '',
158 158 'rhodecode_captcha_private_key': '',
159 159 'rhodecode_captcha_public_key': '',
160 160 'rhodecode_create_personal_repo_group': False,
161 161 'rhodecode_personal_repo_group_pattern': '${username}',
162 162 }
163 163 params.update(settings)
164 164 response = self.app.post(url('admin_settings_global'), params=params)
165 165
166 166 assert_session_flash(response, 'Updated application settings')
167 167 app_settings = SettingsModel().get_all_settings()
168 168 del settings['csrf_token']
169 169 for key, value in settings.iteritems():
170 170 assert app_settings[key] == value.decode('utf-8')
171 171
172 172 return response
173 173
174 174
175 175 @pytest.mark.usefixtures('autologin_user', 'app')
176 176 class TestAdminSettingsVcs(object):
177 177
178 178 def test_contains_svn_default_patterns(self, app):
179 179 response = app.get(url('admin_settings_vcs'))
180 180 expected_patterns = [
181 181 '/trunk',
182 182 '/branches/*',
183 183 '/tags/*',
184 184 ]
185 185 for pattern in expected_patterns:
186 186 response.mustcontain(pattern)
187 187
188 188 def test_add_new_svn_branch_and_tag_pattern(
189 189 self, app, backend_svn, form_defaults, disable_sql_cache,
190 190 csrf_token):
191 191 form_defaults.update({
192 192 'new_svn_branch': '/exp/branches/*',
193 193 'new_svn_tag': '/important_tags/*',
194 194 'csrf_token': csrf_token,
195 195 })
196 196
197 197 response = app.post(
198 198 url('admin_settings_vcs'), params=form_defaults, status=302)
199 199 response = response.follow()
200 200
201 201 # Expect to find the new values on the page
202 202 response.mustcontain('/exp/branches/*')
203 203 response.mustcontain('/important_tags/*')
204 204
205 205 # Expect that those patterns are used to match branches and tags now
206 206 repo = backend_svn['svn-simple-layout'].scm_instance()
207 207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
208 208 assert 'important_tags/v0.5' in repo.tags
209 209
210 210 def test_add_same_svn_value_twice_shows_an_error_message(
211 211 self, app, form_defaults, csrf_token, settings_util):
212 212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
213 213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
214 214
215 215 response = app.post(
216 216 url('admin_settings_vcs'),
217 217 params={
218 218 'paths_root_path': form_defaults['paths_root_path'],
219 219 'new_svn_branch': '/test',
220 220 'new_svn_tag': '/test',
221 221 'csrf_token': csrf_token,
222 222 },
223 223 status=200)
224 224
225 225 response.mustcontain("Pattern already exists")
226 226 response.mustcontain("Some form inputs contain invalid data.")
227 227
228 228 @pytest.mark.parametrize('section', [
229 229 'vcs_svn_branch',
230 230 'vcs_svn_tag',
231 231 ])
232 232 def test_delete_svn_patterns(
233 233 self, section, app, csrf_token, settings_util):
234 234 setting = settings_util.create_rhodecode_ui(
235 235 section, '/test_delete', cleanup=False)
236 236
237 237 app.post(
238 238 url('admin_settings_vcs'),
239 239 params={
240 240 '_method': 'delete',
241 241 'delete_svn_pattern': setting.ui_id,
242 242 'csrf_token': csrf_token},
243 243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
244 244
245 245 @pytest.mark.parametrize('section', [
246 246 'vcs_svn_branch',
247 247 'vcs_svn_tag',
248 248 ])
249 249 def test_delete_svn_patterns_raises_400_when_no_xhr(
250 250 self, section, app, csrf_token, settings_util):
251 251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
252 252
253 253 app.post(
254 254 url('admin_settings_vcs'),
255 255 params={
256 256 '_method': 'delete',
257 257 'delete_svn_pattern': setting.ui_id,
258 258 'csrf_token': csrf_token},
259 259 status=400)
260 260
261 261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
262 262 form_defaults.update({
263 263 'csrf_token': csrf_token,
264 264 'extensions_hgsubversion': 'True',
265 265 })
266 266 response = app.post(
267 267 url('admin_settings_vcs'),
268 268 params=form_defaults,
269 269 status=302)
270 270
271 271 response = response.follow()
272 272 extensions_input = (
273 273 '<input id="extensions_hgsubversion" '
274 274 'name="extensions_hgsubversion" type="checkbox" '
275 275 'value="True" checked="checked" />')
276 276 response.mustcontain(extensions_input)
277 277
278 278 def test_extensions_hgevolve(self, app, form_defaults, csrf_token):
279 279 form_defaults.update({
280 280 'csrf_token': csrf_token,
281 281 'extensions_evolve': 'True',
282 282 })
283 283 response = app.post(
284 284 url('admin_settings_vcs'),
285 285 params=form_defaults,
286 286 status=302)
287 287
288 288 response = response.follow()
289 289 extensions_input = (
290 290 '<input id="extensions_evolve" '
291 291 'name="extensions_evolve" type="checkbox" '
292 292 'value="True" checked="checked" />')
293 293 response.mustcontain(extensions_input)
294 294
295 295 def test_has_a_section_for_pull_request_settings(self, app):
296 296 response = app.get(url('admin_settings_vcs'))
297 297 response.mustcontain('Pull Request Settings')
298 298
299 299 def test_has_an_input_for_invalidation_of_inline_comments(
300 300 self, app):
301 301 response = app.get(url('admin_settings_vcs'))
302 302 assert_response = AssertResponse(response)
303 303 assert_response.one_element_exists(
304 304 '[name=rhodecode_use_outdated_comments]')
305 305
306 306 @pytest.mark.parametrize('new_value', [True, False])
307 307 def test_allows_to_change_invalidation_of_inline_comments(
308 308 self, app, form_defaults, csrf_token, new_value):
309 309 setting_key = 'use_outdated_comments'
310 310 setting = SettingsModel().create_or_update_setting(
311 311 setting_key, not new_value, 'bool')
312 312 Session().add(setting)
313 313 Session().commit()
314 314
315 315 form_defaults.update({
316 316 'csrf_token': csrf_token,
317 317 'rhodecode_use_outdated_comments': str(new_value),
318 318 })
319 319 response = app.post(
320 320 url('admin_settings_vcs'),
321 321 params=form_defaults,
322 322 status=302)
323 323 response = response.follow()
324 324 setting = SettingsModel().get_setting_by_name(setting_key)
325 325 assert setting.app_settings_value is new_value
326 326
327 327 @pytest.mark.parametrize('new_value', [True, False])
328 328 def test_allows_to_change_hg_rebase_merge_strategy(
329 329 self, app, form_defaults, csrf_token, new_value):
330 330 setting_key = 'hg_use_rebase_for_merging'
331 331
332 332 form_defaults.update({
333 333 'csrf_token': csrf_token,
334 334 'rhodecode_' + setting_key: str(new_value),
335 335 })
336 336
337 337 with mock.patch.dict(
338 338 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
339 339 app.post(
340 340 url('admin_settings_vcs'),
341 341 params=form_defaults,
342 342 status=302)
343 343
344 344 setting = SettingsModel().get_setting_by_name(setting_key)
345 345 assert setting.app_settings_value is new_value
346 346
347 347 @pytest.fixture
348 348 def disable_sql_cache(self, request):
349 349 patcher = mock.patch(
350 350 'rhodecode.lib.caching_query.FromCache.process_query')
351 351 request.addfinalizer(patcher.stop)
352 352 patcher.start()
353 353
354 354 @pytest.fixture
355 355 def form_defaults(self):
356 356 from rhodecode.controllers.admin.settings import SettingsController
357 357 controller = SettingsController()
358 358 return controller._form_defaults()
359 359
360 360 # TODO: johbo: What we really want is to checkpoint before a test run and
361 361 # reset the session afterwards.
362 362 @pytest.fixture(scope='class', autouse=True)
363 363 def cleanup_settings(self, request, pylonsapp):
364 364 ui_id = RhodeCodeUi.ui_id
365 365 original_ids = list(
366 366 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
367 367
368 368 @request.addfinalizer
369 369 def cleanup():
370 370 RhodeCodeUi.query().filter(
371 371 ui_id.notin_(original_ids)).delete(False)
372 372
373 373
374 374 @pytest.mark.usefixtures('autologin_user', 'app')
375 375 class TestLabsSettings(object):
376 376 def test_get_settings_page_disabled(self):
377 377 with mock.patch.dict(rhodecode.CONFIG,
378 378 {'labs_settings_active': 'false'}):
379 379 response = self.app.get(url('admin_settings_labs'), status=302)
380 380
381 381 assert response.location.endswith(url('admin_settings'))
382 382
383 383 def test_get_settings_page_enabled(self):
384 384 from rhodecode.controllers.admin import settings
385 385 lab_settings = [
386 386 settings.LabSetting(
387 387 key='rhodecode_bool',
388 388 type='bool',
389 389 group='bool group',
390 390 label='bool label',
391 391 help='bool help'
392 392 ),
393 393 settings.LabSetting(
394 394 key='rhodecode_text',
395 395 type='unicode',
396 396 group='text group',
397 397 label='text label',
398 398 help='text help'
399 399 ),
400 400 ]
401 401 with mock.patch.dict(rhodecode.CONFIG,
402 402 {'labs_settings_active': 'true'}):
403 403 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
404 404 response = self.app.get(url('admin_settings_labs'))
405 405
406 406 assert '<label>bool group:</label>' in response
407 407 assert '<label for="rhodecode_bool">bool label</label>' in response
408 408 assert '<p class="help-block">bool help</p>' in response
409 409 assert 'name="rhodecode_bool" type="checkbox"' in response
410 410
411 411 assert '<label>text group:</label>' in response
412 412 assert '<label for="rhodecode_text">text label</label>' in response
413 413 assert '<p class="help-block">text help</p>' in response
414 414 assert 'name="rhodecode_text" size="60" type="text"' in response
415 415
416 416
417 417 @pytest.mark.usefixtures('app')
418 418 class TestOpenSourceLicenses(object):
419 419
420 420 def _get_url(self):
421 421 return ADMIN_PREFIX + '/settings/open_source'
422 422
423 423 def test_records_are_displayed(self, autologin_user):
424 424 sample_licenses = {
425 425 "python2.7-pytest-2.7.1": {
426 426 "UNKNOWN": None
427 427 },
428 428 "python2.7-Markdown-2.6.2": {
429 429 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
430 430 }
431 431 }
432 432 read_licenses_patch = mock.patch(
433 433 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
434 434 return_value=sample_licenses)
435 435 with read_licenses_patch:
436 436 response = self.app.get(self._get_url(), status=200)
437 437
438 438 assert_response = AssertResponse(response)
439 439 assert_response.element_contains(
440 440 '.panel-heading', 'Licenses of Third Party Packages')
441 441 for name in sample_licenses:
442 442 response.mustcontain(name)
443 443 for license in sample_licenses[name]:
444 444 assert_response.element_contains('.panel-body', license)
445 445
446 446 def test_records_can_be_read(self, autologin_user):
447 447 response = self.app.get(self._get_url(), status=200)
448 448 assert_response = AssertResponse(response)
449 449 assert_response.element_contains(
450 450 '.panel-heading', 'Licenses of Third Party Packages')
451 451
452 452 def test_forbidden_when_normal_user(self, autologin_regular_user):
453 453 self.app.get(self._get_url(), status=404)
454 454
455 455
456 456 @pytest.mark.usefixtures('app')
457 457 class TestUserSessions(object):
458 458
459 459 def _get_url(self, name='admin_settings_sessions'):
460 460 return {
461 461 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
462 462 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
463 463 }[name]
464 464
465 465 def test_forbidden_when_normal_user(self, autologin_regular_user):
466 466 self.app.get(self._get_url(), status=404)
467 467
468 468 def test_show_sessions_page(self, autologin_user):
469 469 response = self.app.get(self._get_url(), status=200)
470 470 response.mustcontain('file')
471 471
472 472 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
473 473
474 474 post_data = {
475 475 'csrf_token': csrf_token,
476 476 'expire_days': '60'
477 477 }
478 478 response = self.app.post(
479 479 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
480 480 status=302)
481 481 assert_session_flash(response, 'Cleaned up old sessions')
482 482
483 483
484 484 @pytest.mark.usefixtures('app')
485 485 class TestAdminSystemInfo(object):
486 486 def _get_url(self, name='admin_settings_system'):
487 487 return {
488 488 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
489 489 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
490 490 }[name]
491 491
492 492 def test_forbidden_when_normal_user(self, autologin_regular_user):
493 493 self.app.get(self._get_url(), status=404)
494 494
495 495 def test_system_info_page(self, autologin_user):
496 496 response = self.app.get(self._get_url())
497 497 response.mustcontain('RhodeCode Community Edition, version {}'.format(
498 498 rhodecode.__version__))
499 499
500 500 def test_system_update_new_version(self, autologin_user):
501 501 update_data = {
502 502 'versions': [
503 503 {
504 504 'version': '100.3.1415926535',
505 505 'general': 'The latest version we are ever going to ship'
506 506 },
507 507 {
508 508 'version': '0.0.0',
509 509 'general': 'The first version we ever shipped'
510 510 }
511 511 ]
512 512 }
513 513 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
514 514 response = self.app.get(self._get_url('admin_settings_system_update'))
515 515 response.mustcontain('A <b>new version</b> is available')
516 516
517 517 def test_system_update_nothing_new(self, autologin_user):
518 518 update_data = {
519 519 'versions': [
520 520 {
521 521 'version': '0.0.0',
522 522 'general': 'The first version we ever shipped'
523 523 }
524 524 ]
525 525 }
526 526 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
527 527 response = self.app.get(self._get_url('admin_settings_system_update'))
528 528 response.mustcontain(
529 529 'You already have the <b>latest</b> stable version.')
530 530
531 531 def test_system_update_bad_response(self, autologin_user):
532 532 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
533 533 response = self.app.get(self._get_url('admin_settings_system_update'))
534 534 response.mustcontain(
535 535 'Bad data sent from update server')
536 536
537 537
538 538 @pytest.mark.usefixtures("app")
539 539 class TestAdminSettingsIssueTracker(object):
540 540 RC_PREFIX = 'rhodecode_'
541 541 SHORT_PATTERN_KEY = 'issuetracker_pat_'
542 542 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
543 543
544 544 def test_issuetracker_index(self, autologin_user):
545 545 response = self.app.get(url('admin_settings_issuetracker'))
546 546 assert response.status_code == 200
547 547
548 548 def test_add_empty_issuetracker_pattern(
549 549 self, request, autologin_user, csrf_token):
550 550 post_url = url('admin_settings_issuetracker_save')
551 551 post_data = {
552 552 'csrf_token': csrf_token
553 553 }
554 554 self.app.post(post_url, post_data, status=302)
555 555
556 556 def test_add_issuetracker_pattern(
557 557 self, request, autologin_user, csrf_token):
558 558 pattern = 'issuetracker_pat'
559 559 another_pattern = pattern+'1'
560 560 post_url = url('admin_settings_issuetracker_save')
561 561 post_data = {
562 562 'new_pattern_pattern_0': pattern,
563 563 'new_pattern_url_0': 'url',
564 564 'new_pattern_prefix_0': 'prefix',
565 565 'new_pattern_description_0': 'description',
566 566 'new_pattern_pattern_1': another_pattern,
567 567 'new_pattern_url_1': 'url1',
568 568 'new_pattern_prefix_1': 'prefix1',
569 569 'new_pattern_description_1': 'description1',
570 570 'csrf_token': csrf_token
571 571 }
572 572 self.app.post(post_url, post_data, status=302)
573 573 settings = SettingsModel().get_all_settings()
574 574 self.uid = md5(pattern)
575 575 assert settings[self.PATTERN_KEY+self.uid] == pattern
576 576 self.another_uid = md5(another_pattern)
577 577 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
578 578
579 579 @request.addfinalizer
580 580 def cleanup():
581 581 defaults = SettingsModel().get_all_settings()
582 582
583 583 entries = [name for name in defaults if (
584 584 (self.uid in name) or (self.another_uid) in name)]
585 585 start = len(self.RC_PREFIX)
586 586 for del_key in entries:
587 587 # TODO: anderson: get_by_name needs name without prefix
588 588 entry = SettingsModel().get_setting_by_name(del_key[start:])
589 589 Session().delete(entry)
590 590
591 591 Session().commit()
592 592
593 593 def test_edit_issuetracker_pattern(
594 594 self, autologin_user, backend, csrf_token, request):
595 595 old_pattern = 'issuetracker_pat'
596 596 old_uid = md5(old_pattern)
597 597 pattern = 'issuetracker_pat_new'
598 598 self.new_uid = md5(pattern)
599 599
600 600 SettingsModel().create_or_update_setting(
601 601 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
602 602
603 603 post_url = url('admin_settings_issuetracker_save')
604 604 post_data = {
605 605 'new_pattern_pattern_0': pattern,
606 606 'new_pattern_url_0': 'url',
607 607 'new_pattern_prefix_0': 'prefix',
608 608 'new_pattern_description_0': 'description',
609 609 'uid': old_uid,
610 610 'csrf_token': csrf_token
611 611 }
612 612 self.app.post(post_url, post_data, status=302)
613 613 settings = SettingsModel().get_all_settings()
614 614 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
615 615 assert self.PATTERN_KEY+old_uid not in settings
616 616
617 617 @request.addfinalizer
618 618 def cleanup():
619 619 IssueTrackerSettingsModel().delete_entries(self.new_uid)
620 620
621 621 def test_replace_issuetracker_pattern_description(
622 622 self, autologin_user, csrf_token, request, settings_util):
623 623 prefix = 'issuetracker'
624 624 pattern = 'issuetracker_pat'
625 625 self.uid = md5(pattern)
626 626 pattern_key = '_'.join([prefix, 'pat', self.uid])
627 627 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
628 628 desc_key = '_'.join([prefix, 'desc', self.uid])
629 629 rc_desc_key = '_'.join(['rhodecode', desc_key])
630 630 new_description = 'new_description'
631 631
632 632 settings_util.create_rhodecode_setting(
633 633 pattern_key, pattern, 'unicode', cleanup=False)
634 634 settings_util.create_rhodecode_setting(
635 635 desc_key, 'old description', 'unicode', cleanup=False)
636 636
637 637 post_url = url('admin_settings_issuetracker_save')
638 638 post_data = {
639 639 'new_pattern_pattern_0': pattern,
640 640 'new_pattern_url_0': 'url',
641 641 'new_pattern_prefix_0': 'prefix',
642 642 'new_pattern_description_0': new_description,
643 643 'uid': self.uid,
644 644 'csrf_token': csrf_token
645 645 }
646 646 self.app.post(post_url, post_data, status=302)
647 647 settings = SettingsModel().get_all_settings()
648 648 assert settings[rc_pattern_key] == pattern
649 649 assert settings[rc_desc_key] == new_description
650 650
651 651 @request.addfinalizer
652 652 def cleanup():
653 653 IssueTrackerSettingsModel().delete_entries(self.uid)
654 654
655 655 def test_delete_issuetracker_pattern(
656 656 self, autologin_user, backend, csrf_token, settings_util):
657 657 pattern = 'issuetracker_pat'
658 658 uid = md5(pattern)
659 659 settings_util.create_rhodecode_setting(
660 660 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
661 661
662 662 post_url = url('admin_issuetracker_delete')
663 663 post_data = {
664 664 '_method': 'delete',
665 665 'uid': uid,
666 666 'csrf_token': csrf_token
667 667 }
668 668 self.app.post(post_url, post_data, status=302)
669 669 settings = SettingsModel().get_all_settings()
670 670 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
General Comments 0
You need to be logged in to leave comments. Login now