##// END OF EJS Templates
global-permissions: ported controller to pyramid view....
marcink -
r1941:be441b36 default
parent child Browse files
Show More
@@ -0,0 +1,310 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import formencode
23
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.renderers import render
27 from pyramid.response import Response
28
29 from rhodecode.apps._base import BaseAppView
30
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import User, UserIpMap
35 from rhodecode.model.forms import (
36 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
37 from rhodecode.model.meta import Session
38 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.settings import SettingsModel
40
41
42 log = logging.getLogger(__name__)
43
44
45 class AdminPermissionsView(BaseAppView):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48
49 self._register_global_c(c)
50 PermissionModel().set_global_permission_choices(
51 c, gettext_translator=self.request.translate)
52 return c
53
54 @LoginRequired()
55 @HasPermissionAllDecorator('hg.admin')
56 @view_config(
57 route_name='admin_permissions_application', request_method='GET',
58 renderer='rhodecode:templates/admin/permissions/permissions.mako')
59 def permissions_application(self):
60 c = self.load_default_context()
61 c.active = 'application'
62
63 c.user = User.get_default_user(refresh=True)
64
65 app_settings = SettingsModel().get_all_settings()
66 defaults = {
67 'anonymous': c.user.active,
68 'default_register_message': app_settings.get(
69 'rhodecode_register_message')
70 }
71 defaults.update(c.user.get_default_perms())
72
73 data = render('rhodecode:templates/admin/permissions/permissions.mako',
74 self._get_template_context(c), self.request)
75 html = formencode.htmlfill.render(
76 data,
77 defaults=defaults,
78 encoding="UTF-8",
79 force_defaults=False
80 )
81 return Response(html)
82
83 @LoginRequired()
84 @HasPermissionAllDecorator('hg.admin')
85 @CSRFRequired()
86 @view_config(
87 route_name='admin_permissions_application_update', request_method='POST',
88 renderer='rhodecode:templates/admin/permissions/permissions.mako')
89 def permissions_application_update(self):
90 _ = self.request.translate
91 c = self.load_default_context()
92 c.active = 'application'
93
94 _form = ApplicationPermissionsForm(
95 [x[0] for x in c.register_choices],
96 [x[0] for x in c.password_reset_choices],
97 [x[0] for x in c.extern_activate_choices])()
98
99 try:
100 form_result = _form.to_python(dict(self.request.POST))
101 form_result.update({'perm_user_name': User.DEFAULT_USER})
102 PermissionModel().update_application_permissions(form_result)
103
104 settings = [
105 ('register_message', 'default_register_message'),
106 ]
107 for setting, form_key in settings:
108 sett = SettingsModel().create_or_update_setting(
109 setting, form_result[form_key])
110 Session().add(sett)
111
112 Session().commit()
113 h.flash(_('Application permissions updated successfully'),
114 category='success')
115
116 except formencode.Invalid as errors:
117 defaults = errors.value
118
119 data = render(
120 'rhodecode:templates/admin/permissions/permissions.mako',
121 self._get_template_context(c), self.request)
122 html = formencode.htmlfill.render(
123 data,
124 defaults=defaults,
125 errors=errors.error_dict or {},
126 prefix_error=False,
127 encoding="UTF-8",
128 force_defaults=False
129 )
130 return Response(html)
131
132 except Exception:
133 log.exception("Exception during update of permissions")
134 h.flash(_('Error occurred during update of permissions'),
135 category='error')
136
137 raise HTTPFound(h.route_path('admin_permissions_application'))
138
139 @LoginRequired()
140 @HasPermissionAllDecorator('hg.admin')
141 @view_config(
142 route_name='admin_permissions_object', request_method='GET',
143 renderer='rhodecode:templates/admin/permissions/permissions.mako')
144 def permissions_objects(self):
145 c = self.load_default_context()
146 c.active = 'objects'
147
148 c.user = User.get_default_user(refresh=True)
149 defaults = {}
150 defaults.update(c.user.get_default_perms())
151
152 data = render(
153 'rhodecode:templates/admin/permissions/permissions.mako',
154 self._get_template_context(c), self.request)
155 html = formencode.htmlfill.render(
156 data,
157 defaults=defaults,
158 encoding="UTF-8",
159 force_defaults=False
160 )
161 return Response(html)
162
163 @LoginRequired()
164 @HasPermissionAllDecorator('hg.admin')
165 @CSRFRequired()
166 @view_config(
167 route_name='admin_permissions_object_update', request_method='POST',
168 renderer='rhodecode:templates/admin/permissions/permissions.mako')
169 def permissions_objects_update(self):
170 _ = self.request.translate
171 c = self.load_default_context()
172 c.active = 'objects'
173
174 _form = ObjectPermissionsForm(
175 [x[0] for x in c.repo_perms_choices],
176 [x[0] for x in c.group_perms_choices],
177 [x[0] for x in c.user_group_perms_choices])()
178
179 try:
180 form_result = _form.to_python(dict(self.request.POST))
181 form_result.update({'perm_user_name': User.DEFAULT_USER})
182 PermissionModel().update_object_permissions(form_result)
183
184 Session().commit()
185 h.flash(_('Object permissions updated successfully'),
186 category='success')
187
188 except formencode.Invalid as errors:
189 defaults = errors.value
190
191 data = render(
192 'rhodecode:templates/admin/permissions/permissions.mako',
193 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
195 data,
196 defaults=defaults,
197 errors=errors.error_dict or {},
198 prefix_error=False,
199 encoding="UTF-8",
200 force_defaults=False
201 )
202 return Response(html)
203 except Exception:
204 log.exception("Exception during update of permissions")
205 h.flash(_('Error occurred during update of permissions'),
206 category='error')
207
208 raise HTTPFound(h.route_path('admin_permissions_object'))
209
210 @LoginRequired()
211 @HasPermissionAllDecorator('hg.admin')
212 @view_config(
213 route_name='admin_permissions_global', request_method='GET',
214 renderer='rhodecode:templates/admin/permissions/permissions.mako')
215 def permissions_global(self):
216 c = self.load_default_context()
217 c.active = 'global'
218
219 c.user = User.get_default_user(refresh=True)
220 defaults = {}
221 defaults.update(c.user.get_default_perms())
222
223 data = render(
224 'rhodecode:templates/admin/permissions/permissions.mako',
225 self._get_template_context(c), self.request)
226 html = formencode.htmlfill.render(
227 data,
228 defaults=defaults,
229 encoding="UTF-8",
230 force_defaults=False
231 )
232 return Response(html)
233
234 @LoginRequired()
235 @HasPermissionAllDecorator('hg.admin')
236 @CSRFRequired()
237 @view_config(
238 route_name='admin_permissions_global_update', request_method='POST',
239 renderer='rhodecode:templates/admin/permissions/permissions.mako')
240 def permissions_global_update(self):
241 _ = self.request.translate
242 c = self.load_default_context()
243 c.active = 'global'
244
245 _form = UserPermissionsForm(
246 [x[0] for x in c.repo_create_choices],
247 [x[0] for x in c.repo_create_on_write_choices],
248 [x[0] for x in c.repo_group_create_choices],
249 [x[0] for x in c.user_group_create_choices],
250 [x[0] for x in c.fork_choices],
251 [x[0] for x in c.inherit_default_permission_choices])()
252
253 try:
254 form_result = _form.to_python(dict(self.request.POST))
255 form_result.update({'perm_user_name': User.DEFAULT_USER})
256 PermissionModel().update_user_permissions(form_result)
257
258 Session().commit()
259 h.flash(_('Global permissions updated successfully'),
260 category='success')
261
262 except formencode.Invalid as errors:
263 defaults = errors.value
264
265 data = render(
266 'rhodecode:templates/admin/permissions/permissions.mako',
267 self._get_template_context(c), self.request)
268 html = formencode.htmlfill.render(
269 data,
270 defaults=defaults,
271 errors=errors.error_dict or {},
272 prefix_error=False,
273 encoding="UTF-8",
274 force_defaults=False
275 )
276 return Response(html)
277 except Exception:
278 log.exception("Exception during update of permissions")
279 h.flash(_('Error occurred during update of permissions'),
280 category='error')
281
282 raise HTTPFound(h.route_path('admin_permissions_global'))
283
284 @LoginRequired()
285 @HasPermissionAllDecorator('hg.admin')
286 @view_config(
287 route_name='admin_permissions_ips', request_method='GET',
288 renderer='rhodecode:templates/admin/permissions/permissions.mako')
289 def permissions_ips(self):
290 c = self.load_default_context()
291 c.active = 'ips'
292
293 c.user = User.get_default_user(refresh=True)
294 c.user_ip_map = (
295 UserIpMap.query().filter(UserIpMap.user == c.user).all())
296
297 return self._get_template_context(c)
298
299 @LoginRequired()
300 @HasPermissionAllDecorator('hg.admin')
301 @view_config(
302 route_name='admin_permissions_overview', request_method='GET',
303 renderer='rhodecode:templates/admin/permissions/permissions.mako')
304 def permissions_overview(self):
305 c = self.load_default_context()
306 c.active = 'perms'
307
308 c.user = User.get_default_user(refresh=True)
309 c.perm_user = c.user.AuthUser
310 return self._get_template_context(c)
@@ -1,149 +1,175 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 23 from rhodecode.config.routing 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='pull_requests_global_0', # backward compat
38 38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 39 config.add_route(
40 40 name='pull_requests_global_1', # backward compat
41 41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 42 config.add_route(
43 43 name='pull_requests_global',
44 44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45 45
46 46 config.add_route(
47 47 name='admin_settings_open_source',
48 48 pattern='/settings/open_source')
49 49 config.add_route(
50 50 name='admin_settings_vcs_svn_generate_cfg',
51 51 pattern='/settings/vcs/svn_generate_cfg')
52 52
53 53 config.add_route(
54 54 name='admin_settings_system',
55 55 pattern='/settings/system')
56 56 config.add_route(
57 57 name='admin_settings_system_update',
58 58 pattern='/settings/system/updates')
59 59
60 60 config.add_route(
61 61 name='admin_settings_sessions',
62 62 pattern='/settings/sessions')
63 63 config.add_route(
64 64 name='admin_settings_sessions_cleanup',
65 65 pattern='/settings/sessions/cleanup')
66 66
67 67 config.add_route(
68 68 name='admin_settings_process_management',
69 69 pattern='/settings/process_management')
70 70 config.add_route(
71 71 name='admin_settings_process_management_signal',
72 72 pattern='/settings/process_management/signal')
73 73
74 74 # global permissions
75
76 config.add_route(
77 name='admin_permissions_application',
78 pattern='/permissions/application')
79 config.add_route(
80 name='admin_permissions_application_update',
81 pattern='/permissions/application/update')
82
83 config.add_route(
84 name='admin_permissions_global',
85 pattern='/permissions/global')
86 config.add_route(
87 name='admin_permissions_global_update',
88 pattern='/permissions/global/update')
89
90 config.add_route(
91 name='admin_permissions_object',
92 pattern='/permissions/object')
93 config.add_route(
94 name='admin_permissions_object_update',
95 pattern='/permissions/object/update')
96
75 97 config.add_route(
76 98 name='admin_permissions_ips',
77 99 pattern='/permissions/ips')
78 100
101 config.add_route(
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
104
79 105 # users admin
80 106 config.add_route(
81 107 name='users',
82 108 pattern='/users')
83 109
84 110 config.add_route(
85 111 name='users_data',
86 112 pattern='/users_data')
87 113
88 114 # user auth tokens
89 115 config.add_route(
90 116 name='edit_user_auth_tokens',
91 117 pattern='/users/{user_id:\d+}/edit/auth_tokens')
92 118 config.add_route(
93 119 name='edit_user_auth_tokens_add',
94 120 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
95 121 config.add_route(
96 122 name='edit_user_auth_tokens_delete',
97 123 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
98 124
99 125 # user emails
100 126 config.add_route(
101 127 name='edit_user_emails',
102 128 pattern='/users/{user_id:\d+}/edit/emails')
103 129 config.add_route(
104 130 name='edit_user_emails_add',
105 131 pattern='/users/{user_id:\d+}/edit/emails/new')
106 132 config.add_route(
107 133 name='edit_user_emails_delete',
108 134 pattern='/users/{user_id:\d+}/edit/emails/delete')
109 135
110 136 # user IPs
111 137 config.add_route(
112 138 name='edit_user_ips',
113 139 pattern='/users/{user_id:\d+}/edit/ips')
114 140 config.add_route(
115 141 name='edit_user_ips_add',
116 142 pattern='/users/{user_id:\d+}/edit/ips/new')
117 143 config.add_route(
118 144 name='edit_user_ips_delete',
119 145 pattern='/users/{user_id:\d+}/edit/ips/delete')
120 146
121 147 # user groups management
122 148 config.add_route(
123 149 name='edit_user_groups_management',
124 150 pattern='/users/{user_id:\d+}/edit/groups_management')
125 151
126 152 config.add_route(
127 153 name='edit_user_groups_management_updates',
128 154 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
129 155
130 156 # user audit logs
131 157 config.add_route(
132 158 name='edit_user_audit_logs',
133 159 pattern='/users/{user_id:\d+}/edit/audit')
134 160
135 161
136 162 def includeme(config):
137 163 settings = config.get_settings()
138 164
139 165 # Create admin navigation registry and add it to the pyramid registry.
140 166 labs_active = str2bool(settings.get('labs_settings_active', False))
141 167 navigation_registry = NavigationRegistry(labs_active=labs_active)
142 168 config.registry.registerUtility(navigation_registry)
143 169
144 170 # main admin routes
145 171 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
146 172 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
147 173
148 174 # Scan module for configuration decorators.
149 175 config.scan()
@@ -1,229 +1,250 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 pytest
22 22 from rhodecode.model.db import User, UserIpMap
23 23 from rhodecode.model.permission import PermissionModel
24 24 from rhodecode.tests import (
25 TestController, url, clear_all_caches, assert_session_flash)
25 TestController, clear_all_caches, assert_session_flash)
26 26
27 27
28 28 def route_path(name, params=None, **kwargs):
29 29 import urllib
30 30 from rhodecode.apps._base import ADMIN_PREFIX
31 31
32 32 base_url = {
33 33 'edit_user_ips':
34 34 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
35 35 'edit_user_ips_add':
36 36 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
37 37 'edit_user_ips_delete':
38 38 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
39
40 'admin_permissions_application':
41 ADMIN_PREFIX + '/permissions/application',
42 'admin_permissions_application_update':
43 ADMIN_PREFIX + '/permissions/application/update',
44
45 'admin_permissions_global':
46 ADMIN_PREFIX + '/permissions/global',
47 'admin_permissions_global_update':
48 ADMIN_PREFIX + '/permissions/global/update',
49
50 'admin_permissions_object':
51 ADMIN_PREFIX + '/permissions/object',
52 'admin_permissions_object_update':
53 ADMIN_PREFIX + '/permissions/object/update',
54
55 'admin_permissions_ips':
56 ADMIN_PREFIX + '/permissions/ips',
57 'admin_permissions_overview':
58 ADMIN_PREFIX + '/permissions/overview'
59
39 60 }[name].format(**kwargs)
40 61
41 62 if params:
42 63 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 64 return base_url
44 65
45 66
46 67 class TestAdminPermissionsController(TestController):
47 68
48 69 @pytest.fixture(scope='class', autouse=True)
49 70 def prepare(self, request):
50 71 # cleanup and reset to default permissions after
51 72 @request.addfinalizer
52 73 def cleanup():
53 74 PermissionModel().create_default_user_permissions(
54 75 User.get_default_user(), force=True)
55 76
56 77 def test_index_application(self):
57 78 self.log_user()
58 self.app.get(url('admin_permissions_application'))
79 self.app.get(route_path('admin_permissions_application'))
59 80
60 81 @pytest.mark.parametrize(
61 82 'anonymous, default_register, default_register_message, default_password_reset,'
62 83 'default_extern_activate, expect_error, expect_form_error', [
63 84 (True, 'hg.register.none', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
64 85 False, False),
65 86 (True, 'hg.register.manual_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.auto',
66 87 False, False),
67 88 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
68 89 False, False),
69 90 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
70 91 False, False),
71 92 (True, 'hg.register.XXX', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
72 93 False, True),
73 94 (True, '', '', 'hg.password_reset.enabled', '', True, False),
74 95 ])
75 96 def test_update_application_permissions(
76 97 self, anonymous, default_register, default_register_message, default_password_reset,
77 98 default_extern_activate, expect_error, expect_form_error):
78 99
79 100 self.log_user()
80 101
81 102 # TODO: anonymous access set here to False, breaks some other tests
82 103 params = {
83 104 'csrf_token': self.csrf_token,
84 105 'anonymous': anonymous,
85 106 'default_register': default_register,
86 107 'default_register_message': default_register_message,
87 108 'default_password_reset': default_password_reset,
88 109 'default_extern_activate': default_extern_activate,
89 110 }
90 response = self.app.post(url('admin_permissions_application'),
111 response = self.app.post(route_path('admin_permissions_application_update'),
91 112 params=params)
92 113 if expect_form_error:
93 114 assert response.status_int == 200
94 115 response.mustcontain('Value must be one of')
95 116 else:
96 117 if expect_error:
97 118 msg = 'Error occurred during update of permissions'
98 119 else:
99 120 msg = 'Application permissions updated successfully'
100 121 assert_session_flash(response, msg)
101 122
102 123 def test_index_object(self):
103 124 self.log_user()
104 self.app.get(url('admin_permissions_object'))
125 self.app.get(route_path('admin_permissions_object'))
105 126
106 127 @pytest.mark.parametrize(
107 128 'repo, repo_group, user_group, expect_error, expect_form_error', [
108 129 ('repository.none', 'group.none', 'usergroup.none', False, False),
109 130 ('repository.read', 'group.read', 'usergroup.read', False, False),
110 131 ('repository.write', 'group.write', 'usergroup.write',
111 132 False, False),
112 133 ('repository.admin', 'group.admin', 'usergroup.admin',
113 134 False, False),
114 135 ('repository.XXX', 'group.admin', 'usergroup.admin', False, True),
115 136 ('', '', '', True, False),
116 137 ])
117 138 def test_update_object_permissions(self, repo, repo_group, user_group,
118 139 expect_error, expect_form_error):
119 140 self.log_user()
120 141
121 142 params = {
122 143 'csrf_token': self.csrf_token,
123 144 'default_repo_perm': repo,
124 145 'overwrite_default_repo': False,
125 146 'default_group_perm': repo_group,
126 147 'overwrite_default_group': False,
127 148 'default_user_group_perm': user_group,
128 149 'overwrite_default_user_group': False,
129 150 }
130 response = self.app.post(url('admin_permissions_object'),
151 response = self.app.post(route_path('admin_permissions_object_update'),
131 152 params=params)
132 153 if expect_form_error:
133 154 assert response.status_int == 200
134 155 response.mustcontain('Value must be one of')
135 156 else:
136 157 if expect_error:
137 158 msg = 'Error occurred during update of permissions'
138 159 else:
139 160 msg = 'Object permissions updated successfully'
140 161 assert_session_flash(response, msg)
141 162
142 163 def test_index_global(self):
143 164 self.log_user()
144 self.app.get(url('admin_permissions_global'))
165 self.app.get(route_path('admin_permissions_global'))
145 166
146 167 @pytest.mark.parametrize(
147 168 'repo_create, repo_create_write, user_group_create, repo_group_create,'
148 169 'fork_create, inherit_default_permissions, expect_error,'
149 170 'expect_form_error', [
150 171 ('hg.create.none', 'hg.create.write_on_repogroup.false',
151 172 'hg.usergroup.create.false', 'hg.repogroup.create.false',
152 173 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
153 174 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
154 175 'hg.usergroup.create.true', 'hg.repogroup.create.true',
155 176 'hg.fork.repository', 'hg.inherit_default_perms.false',
156 177 False, False),
157 178 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
158 179 'hg.usergroup.create.true', 'hg.repogroup.create.true',
159 180 'hg.fork.repository', 'hg.inherit_default_perms.false',
160 181 False, True),
161 182 ('', '', '', '', '', '', True, False),
162 183 ])
163 184 def test_update_global_permissions(
164 185 self, repo_create, repo_create_write, user_group_create,
165 186 repo_group_create, fork_create, inherit_default_permissions,
166 187 expect_error, expect_form_error):
167 188 self.log_user()
168 189
169 190 params = {
170 191 'csrf_token': self.csrf_token,
171 192 'default_repo_create': repo_create,
172 193 'default_repo_create_on_write': repo_create_write,
173 194 'default_user_group_create': user_group_create,
174 195 'default_repo_group_create': repo_group_create,
175 196 'default_fork_create': fork_create,
176 197 'default_inherit_default_permissions': inherit_default_permissions
177 198 }
178 response = self.app.post(url('admin_permissions_global'),
199 response = self.app.post(route_path('admin_permissions_global_update'),
179 200 params=params)
180 201 if expect_form_error:
181 202 assert response.status_int == 200
182 203 response.mustcontain('Value must be one of')
183 204 else:
184 205 if expect_error:
185 206 msg = 'Error occurred during update of permissions'
186 207 else:
187 208 msg = 'Global permissions updated successfully'
188 209 assert_session_flash(response, msg)
189 210
190 211 def test_index_ips(self):
191 212 self.log_user()
192 response = self.app.get(url('admin_permissions_ips'))
213 response = self.app.get(route_path('admin_permissions_ips'))
193 214 # TODO: Test response...
194 215 response.mustcontain('All IP addresses are allowed')
195 216
196 217 def test_add_delete_ips(self):
197 218 self.log_user()
198 219 clear_all_caches()
199 220
200 221 # ADD
201 222 default_user_id = User.get_default_user().user_id
202 223 self.app.post(
203 224 route_path('edit_user_ips_add', user_id=default_user_id),
204 225 params={'new_ip': '127.0.0.0/24', 'csrf_token': self.csrf_token})
205 226
206 response = self.app.get(url('admin_permissions_ips'))
227 response = self.app.get(route_path('admin_permissions_ips'))
207 228 response.mustcontain('127.0.0.0/24')
208 229 response.mustcontain('127.0.0.0 - 127.0.0.255')
209 230
210 231 # DELETE
211 232 default_user_id = User.get_default_user().user_id
212 233 del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
213 234 default_user_id).first().ip_id
214 235
215 236 response = self.app.post(
216 237 route_path('edit_user_ips_delete', user_id=default_user_id),
217 238 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
218 239
219 240 assert_session_flash(response, 'Removed ip address from user whitelist')
220 241
221 242 clear_all_caches()
222 response = self.app.get(url('admin_permissions_ips'))
243 response = self.app.get(route_path('admin_permissions_ips'))
223 244 response.mustcontain('All IP addresses are allowed')
224 245 response.mustcontain(no=['127.0.0.0/24'])
225 246 response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
226 247
227 248 def test_index_overview(self):
228 249 self.log_user()
229 self.app.get(url('admin_permissions_overview'))
250 self.app.get(route_path('admin_permissions_overview'))
@@ -1,513 +1,530 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 urlparse
22 22
23 23 import mock
24 24 import pytest
25 25
26 from rhodecode.config.routing import ADMIN_PREFIX
27 26 from rhodecode.tests import (
28 assert_session_flash, url, HG_REPO, TEST_USER_ADMIN_LOGIN,
27 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
29 28 no_newline_id_generator)
30 29 from rhodecode.tests.fixture import Fixture
31 30 from rhodecode.lib.auth import check_password
32 31 from rhodecode.lib import helpers as h
33 32 from rhodecode.model.auth_token import AuthTokenModel
34 33 from rhodecode.model import validators
35 34 from rhodecode.model.db import User, Notification, UserApiKeys
36 35 from rhodecode.model.meta import Session
37 36
38 37 fixture = Fixture()
39 38
40 # Hardcode URLs because we don't have a request object to use
41 # pyramids URL generation methods.
42 index_url = '/'
43 login_url = ADMIN_PREFIX + '/login'
44 logut_url = ADMIN_PREFIX + '/logout'
45 register_url = ADMIN_PREFIX + '/register'
46 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
47 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
39
40 def route_path(name, params=None, **kwargs):
41 import urllib
42 from rhodecode.apps._base import ADMIN_PREFIX
43
44 base_url = {
45 'login': ADMIN_PREFIX + '/login',
46 'logout': ADMIN_PREFIX + '/logout',
47 'register': ADMIN_PREFIX + '/register',
48 'reset_password':
49 ADMIN_PREFIX + '/password_reset',
50 'reset_password_confirmation':
51 ADMIN_PREFIX + '/password_reset_confirmation',
52
53 'admin_permissions_application':
54 ADMIN_PREFIX + '/permissions/application',
55 'admin_permissions_application_update':
56 ADMIN_PREFIX + '/permissions/application/update',
57
58 'repo_commit_raw': '/{repo_name}/raw-changeset/{commit_id}'
59
60 }[name].format(**kwargs)
61
62 if params:
63 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
64 return base_url
48 65
49 66
50 67 @pytest.mark.usefixtures('app')
51 68 class TestLoginController(object):
52 69 destroy_users = set()
53 70
54 71 @classmethod
55 72 def teardown_class(cls):
56 73 fixture.destroy_users(cls.destroy_users)
57 74
58 75 def teardown_method(self, method):
59 76 for n in Notification.query().all():
60 77 Session().delete(n)
61 78
62 79 Session().commit()
63 80 assert Notification.query().all() == []
64 81
65 82 def test_index(self):
66 response = self.app.get(login_url)
83 response = self.app.get(route_path('login'))
67 84 assert response.status == '200 OK'
68 85 # Test response...
69 86
70 87 def test_login_admin_ok(self):
71 response = self.app.post(login_url,
88 response = self.app.post(route_path('login'),
72 89 {'username': 'test_admin',
73 90 'password': 'test12'})
74 91 assert response.status == '302 Found'
75 92 session = response.get_session_from_response()
76 93 username = session['rhodecode_user'].get('username')
77 94 assert username == 'test_admin'
78 95 response = response.follow()
79 96 response.mustcontain('/%s' % HG_REPO)
80 97
81 98 def test_login_regular_ok(self):
82 response = self.app.post(login_url,
99 response = self.app.post(route_path('login'),
83 100 {'username': 'test_regular',
84 101 'password': 'test12'})
85 102
86 103 assert response.status == '302 Found'
87 104 session = response.get_session_from_response()
88 105 username = session['rhodecode_user'].get('username')
89 106 assert username == 'test_regular'
90 107 response = response.follow()
91 108 response.mustcontain('/%s' % HG_REPO)
92 109
93 110 def test_login_ok_came_from(self):
94 111 test_came_from = '/_admin/users?branch=stable'
95 _url = '{}?came_from={}'.format(login_url, test_came_from)
112 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
96 113 response = self.app.post(
97 114 _url, {'username': 'test_admin', 'password': 'test12'})
98 115 assert response.status == '302 Found'
99 116 assert 'branch=stable' in response.location
100 117 response = response.follow()
101 118
102 119 assert response.status == '200 OK'
103 120 response.mustcontain('Users administration')
104 121
105 122 def test_redirect_to_login_with_get_args(self):
106 123 with fixture.anon_access(False):
107 124 kwargs = {'branch': 'stable'}
108 125 response = self.app.get(
109 126 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
110 127 assert response.status == '302 Found'
111 128
112 129 response_query = urlparse.parse_qsl(response.location)
113 130 assert 'branch=stable' in response_query[0][1]
114 131
115 132 def test_login_form_with_get_args(self):
116 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
133 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
117 134 response = self.app.get(_url)
118 135 assert 'branch%3Dstable' in response.form.action
119 136
120 137 @pytest.mark.parametrize("url_came_from", [
121 138 'data:text/html,<script>window.alert("xss")</script>',
122 139 'mailto:test@rhodecode.org',
123 140 'file:///etc/passwd',
124 141 'ftp://some.ftp.server',
125 142 'http://other.domain',
126 143 '/\r\nX-Forwarded-Host: http://example.org',
127 144 ], ids=no_newline_id_generator)
128 145 def test_login_bad_came_froms(self, url_came_from):
129 _url = '{}?came_from={}'.format(login_url, url_came_from)
146 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
130 147 response = self.app.post(
131 148 _url,
132 149 {'username': 'test_admin', 'password': 'test12'})
133 150 assert response.status == '302 Found'
134 151 response = response.follow()
135 152 assert response.status == '200 OK'
136 153 assert response.request.path == '/'
137 154
138 155 def test_login_short_password(self):
139 response = self.app.post(login_url,
156 response = self.app.post(route_path('login'),
140 157 {'username': 'test_admin',
141 158 'password': 'as'})
142 159 assert response.status == '200 OK'
143 160
144 161 response.mustcontain('Enter 3 characters or more')
145 162
146 163 def test_login_wrong_non_ascii_password(self, user_regular):
147 164 response = self.app.post(
148 login_url,
165 route_path('login'),
149 166 {'username': user_regular.username,
150 167 'password': u'invalid-non-asci\xe4'.encode('utf8')})
151 168
152 169 response.mustcontain('invalid user name')
153 170 response.mustcontain('invalid password')
154 171
155 172 def test_login_with_non_ascii_password(self, user_util):
156 173 password = u'valid-non-ascii\xe4'
157 174 user = user_util.create_user(password=password)
158 175 response = self.app.post(
159 login_url,
176 route_path('login'),
160 177 {'username': user.username,
161 178 'password': password.encode('utf-8')})
162 179 assert response.status_code == 302
163 180
164 181 def test_login_wrong_username_password(self):
165 response = self.app.post(login_url,
182 response = self.app.post(route_path('login'),
166 183 {'username': 'error',
167 184 'password': 'test12'})
168 185
169 186 response.mustcontain('invalid user name')
170 187 response.mustcontain('invalid password')
171 188
172 189 def test_login_admin_ok_password_migration(self, real_crypto_backend):
173 190 from rhodecode.lib import auth
174 191
175 192 # create new user, with sha256 password
176 193 temp_user = 'test_admin_sha256'
177 194 user = fixture.create_user(temp_user)
178 195 user.password = auth._RhodeCodeCryptoSha256().hash_create(
179 196 b'test123')
180 197 Session().add(user)
181 198 Session().commit()
182 199 self.destroy_users.add(temp_user)
183 response = self.app.post(login_url,
200 response = self.app.post(route_path('login'),
184 201 {'username': temp_user,
185 202 'password': 'test123'})
186 203
187 204 assert response.status == '302 Found'
188 205 session = response.get_session_from_response()
189 206 username = session['rhodecode_user'].get('username')
190 207 assert username == temp_user
191 208 response = response.follow()
192 209 response.mustcontain('/%s' % HG_REPO)
193 210
194 211 # new password should be bcrypted, after log-in and transfer
195 212 user = User.get_by_username(temp_user)
196 213 assert user.password.startswith('$')
197 214
198 215 # REGISTRATIONS
199 216 def test_register(self):
200 response = self.app.get(register_url)
217 response = self.app.get(route_path('register'))
201 218 response.mustcontain('Create an Account')
202 219
203 220 def test_register_err_same_username(self):
204 221 uname = 'test_admin'
205 222 response = self.app.post(
206 register_url,
223 route_path('register'),
207 224 {
208 225 'username': uname,
209 226 'password': 'test12',
210 227 'password_confirmation': 'test12',
211 228 'email': 'goodmail@domain.com',
212 229 'firstname': 'test',
213 230 'lastname': 'test'
214 231 }
215 232 )
216 233
217 234 assertr = response.assert_response()
218 235 msg = validators.ValidUsername()._messages['username_exists']
219 236 msg = msg % {'username': uname}
220 237 assertr.element_contains('#username+.error-message', msg)
221 238
222 239 def test_register_err_same_email(self):
223 240 response = self.app.post(
224 register_url,
241 route_path('register'),
225 242 {
226 243 'username': 'test_admin_0',
227 244 'password': 'test12',
228 245 'password_confirmation': 'test12',
229 246 'email': 'test_admin@mail.com',
230 247 'firstname': 'test',
231 248 'lastname': 'test'
232 249 }
233 250 )
234 251
235 252 assertr = response.assert_response()
236 253 msg = validators.UniqSystemEmail()()._messages['email_taken']
237 254 assertr.element_contains('#email+.error-message', msg)
238 255
239 256 def test_register_err_same_email_case_sensitive(self):
240 257 response = self.app.post(
241 register_url,
258 route_path('register'),
242 259 {
243 260 'username': 'test_admin_1',
244 261 'password': 'test12',
245 262 'password_confirmation': 'test12',
246 263 'email': 'TesT_Admin@mail.COM',
247 264 'firstname': 'test',
248 265 'lastname': 'test'
249 266 }
250 267 )
251 268 assertr = response.assert_response()
252 269 msg = validators.UniqSystemEmail()()._messages['email_taken']
253 270 assertr.element_contains('#email+.error-message', msg)
254 271
255 272 def test_register_err_wrong_data(self):
256 273 response = self.app.post(
257 register_url,
274 route_path('register'),
258 275 {
259 276 'username': 'xs',
260 277 'password': 'test',
261 278 'password_confirmation': 'test',
262 279 'email': 'goodmailm',
263 280 'firstname': 'test',
264 281 'lastname': 'test'
265 282 }
266 283 )
267 284 assert response.status == '200 OK'
268 285 response.mustcontain('An email address must contain a single @')
269 286 response.mustcontain('Enter a value 6 characters long or more')
270 287
271 288 def test_register_err_username(self):
272 289 response = self.app.post(
273 register_url,
290 route_path('register'),
274 291 {
275 292 'username': 'error user',
276 293 'password': 'test12',
277 294 'password_confirmation': 'test12',
278 295 'email': 'goodmailm',
279 296 'firstname': 'test',
280 297 'lastname': 'test'
281 298 }
282 299 )
283 300
284 301 response.mustcontain('An email address must contain a single @')
285 302 response.mustcontain(
286 303 'Username may only contain '
287 304 'alphanumeric characters underscores, '
288 305 'periods or dashes and must begin with '
289 306 'alphanumeric character')
290 307
291 308 def test_register_err_case_sensitive(self):
292 309 usr = 'Test_Admin'
293 310 response = self.app.post(
294 register_url,
311 route_path('register'),
295 312 {
296 313 'username': usr,
297 314 'password': 'test12',
298 315 'password_confirmation': 'test12',
299 316 'email': 'goodmailm',
300 317 'firstname': 'test',
301 318 'lastname': 'test'
302 319 }
303 320 )
304 321
305 322 assertr = response.assert_response()
306 323 msg = validators.ValidUsername()._messages['username_exists']
307 324 msg = msg % {'username': usr}
308 325 assertr.element_contains('#username+.error-message', msg)
309 326
310 327 def test_register_special_chars(self):
311 328 response = self.app.post(
312 register_url,
329 route_path('register'),
313 330 {
314 331 'username': 'xxxaxn',
315 332 'password': 'ąćźżąśśśś',
316 333 'password_confirmation': 'ąćźżąśśśś',
317 334 'email': 'goodmailm@test.plx',
318 335 'firstname': 'test',
319 336 'lastname': 'test'
320 337 }
321 338 )
322 339
323 340 msg = validators.ValidPassword()._messages['invalid_password']
324 341 response.mustcontain(msg)
325 342
326 343 def test_register_password_mismatch(self):
327 344 response = self.app.post(
328 register_url,
345 route_path('register'),
329 346 {
330 347 'username': 'xs',
331 348 'password': '123qwe',
332 349 'password_confirmation': 'qwe123',
333 350 'email': 'goodmailm@test.plxa',
334 351 'firstname': 'test',
335 352 'lastname': 'test'
336 353 }
337 354 )
338 355 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
339 356 response.mustcontain(msg)
340 357
341 358 def test_register_ok(self):
342 359 username = 'test_regular4'
343 360 password = 'qweqwe'
344 361 email = 'marcin@test.com'
345 362 name = 'testname'
346 363 lastname = 'testlastname'
347 364
348 365 response = self.app.post(
349 register_url,
366 route_path('register'),
350 367 {
351 368 'username': username,
352 369 'password': password,
353 370 'password_confirmation': password,
354 371 'email': email,
355 372 'firstname': name,
356 373 'lastname': lastname,
357 374 'admin': True
358 375 }
359 376 ) # This should be overriden
360 377 assert response.status == '302 Found'
361 378 assert_session_flash(
362 379 response, 'You have successfully registered with RhodeCode')
363 380
364 381 ret = Session().query(User).filter(
365 382 User.username == 'test_regular4').one()
366 383 assert ret.username == username
367 384 assert check_password(password, ret.password)
368 385 assert ret.email == email
369 386 assert ret.name == name
370 387 assert ret.lastname == lastname
371 388 assert ret.auth_tokens is not None
372 389 assert not ret.admin
373 390
374 391 def test_forgot_password_wrong_mail(self):
375 392 bad_email = 'marcin@wrongmail.org'
376 393 response = self.app.post(
377 pwd_reset_url, {'email': bad_email, }
394 route_path('reset_password'), {'email': bad_email, }
378 395 )
379 396 assert_session_flash(response,
380 397 'If such email exists, a password reset link was sent to it.')
381 398
382 399 def test_forgot_password(self, user_util):
383 response = self.app.get(pwd_reset_url)
400 response = self.app.get(route_path('reset_password'))
384 401 assert response.status == '200 OK'
385 402
386 403 user = user_util.create_user()
387 404 user_id = user.user_id
388 405 email = user.email
389 406
390 response = self.app.post(pwd_reset_url, {'email': email, })
407 response = self.app.post(route_path('reset_password'), {'email': email, })
391 408
392 409 assert_session_flash(response,
393 410 'If such email exists, a password reset link was sent to it.')
394 411
395 412 # BAD KEY
396 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, 'badkey')
413 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
397 414 response = self.app.get(confirm_url)
398 415 assert response.status == '302 Found'
399 assert response.location.endswith(pwd_reset_url)
416 assert response.location.endswith(route_path('reset_password'))
400 417 assert_session_flash(response, 'Given reset token is invalid')
401 418
402 419 response.follow() # cleanup flash
403 420
404 421 # GOOD KEY
405 422 key = UserApiKeys.query()\
406 423 .filter(UserApiKeys.user_id == user_id)\
407 424 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
408 425 .first()
409 426
410 427 assert key
411 428
412 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key.api_key)
429 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
413 430 response = self.app.get(confirm_url)
414 431 assert response.status == '302 Found'
415 assert response.location.endswith(login_url)
432 assert response.location.endswith(route_path('login'))
416 433
417 434 assert_session_flash(
418 435 response,
419 436 'Your password reset was successful, '
420 437 'a new password has been sent to your email')
421 438
422 439 response.follow()
423 440
424 441 def _get_api_whitelist(self, values=None):
425 442 config = {'api_access_controllers_whitelist': values or []}
426 443 return config
427 444
428 445 @pytest.mark.parametrize("test_name, auth_token", [
429 446 ('none', None),
430 447 ('empty_string', ''),
431 448 ('fake_number', '123456'),
432 449 ('proper_auth_token', None)
433 450 ])
434 451 def test_access_not_whitelisted_page_via_auth_token(
435 452 self, test_name, auth_token, user_admin):
436 453
437 454 whitelist = self._get_api_whitelist([])
438 455 with mock.patch.dict('rhodecode.CONFIG', whitelist):
439 456 assert [] == whitelist['api_access_controllers_whitelist']
440 457 if test_name == 'proper_auth_token':
441 458 # use builtin if api_key is None
442 459 auth_token = user_admin.api_key
443 460
444 461 with fixture.anon_access(False):
445 self.app.get(url(controller='changeset',
446 action='changeset_raw',
447 repo_name=HG_REPO, revision='tip',
448 api_key=auth_token),
449 status=302)
462 self.app.get(
463 route_path('repo_commit_raw',
464 repo_name=HG_REPO, commit_id='tip',
465 params=dict(api_key=auth_token)),
466 status=302)
450 467
451 468 @pytest.mark.parametrize("test_name, auth_token, code", [
452 469 ('none', None, 302),
453 470 ('empty_string', '', 302),
454 471 ('fake_number', '123456', 302),
455 472 ('proper_auth_token', None, 200)
456 473 ])
457 474 def test_access_whitelisted_page_via_auth_token(
458 475 self, test_name, auth_token, code, user_admin):
459 476
460 477 whitelist_entry = ['ChangesetController:changeset_raw']
461 478 whitelist = self._get_api_whitelist(whitelist_entry)
462 479
463 480 with mock.patch.dict('rhodecode.CONFIG', whitelist):
464 481 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
465 482
466 483 if test_name == 'proper_auth_token':
467 484 auth_token = user_admin.api_key
468 485 assert auth_token
469 486
470 487 with fixture.anon_access(False):
471 self.app.get(url(controller='changeset',
472 action='changeset_raw',
473 repo_name=HG_REPO, revision='tip',
474 api_key=auth_token),
475 status=code)
488 self.app.get(
489 route_path('repo_commit_raw',
490 repo_name=HG_REPO, commit_id='tip',
491 params=dict(api_key=auth_token)),
492 status=code)
476 493
477 494 def test_access_page_via_extra_auth_token(self):
478 495 whitelist = self._get_api_whitelist(
479 496 ['ChangesetController:changeset_raw'])
480 497 with mock.patch.dict('rhodecode.CONFIG', whitelist):
481 498 assert ['ChangesetController:changeset_raw'] == \
482 499 whitelist['api_access_controllers_whitelist']
483 500
484 501 new_auth_token = AuthTokenModel().create(
485 502 TEST_USER_ADMIN_LOGIN, 'test')
486 503 Session().commit()
487 504 with fixture.anon_access(False):
488 self.app.get(url(controller='changeset',
489 action='changeset_raw',
490 repo_name=HG_REPO, revision='tip',
491 api_key=new_auth_token.api_key),
492 status=200)
505 self.app.get(
506 route_path('repo_commit_raw',
507 repo_name=HG_REPO, commit_id='tip',
508 params=dict(api_key=new_auth_token.api_key)),
509 status=200)
493 510
494 511 def test_access_page_via_expired_auth_token(self):
495 512 whitelist = self._get_api_whitelist(
496 513 ['ChangesetController:changeset_raw'])
497 514 with mock.patch.dict('rhodecode.CONFIG', whitelist):
498 515 assert ['ChangesetController:changeset_raw'] == \
499 516 whitelist['api_access_controllers_whitelist']
500 517
501 518 new_auth_token = AuthTokenModel().create(
502 519 TEST_USER_ADMIN_LOGIN, 'test')
503 520 Session().commit()
504 521 # patch the api key and make it expired
505 522 new_auth_token.expires = 0
506 523 Session().add(new_auth_token)
507 524 Session().commit()
508 525 with fixture.anon_access(False):
509 self.app.get(url(controller='changeset',
510 action='changeset_raw',
511 repo_name=HG_REPO, revision='tip',
512 api_key=new_auth_token.api_key),
513 status=302)
526 self.app.get(
527 route_path('repo_commit_raw',
528 repo_name=HG_REPO, commit_id='tip',
529 params=dict(api_key=new_auth_token.api_key)),
530 status=302)
@@ -1,106 +1,121 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 pytest
22 22
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.lib import helpers as h
24 24 from rhodecode.tests import (
25 TestController, clear_all_caches, url,
25 TestController, clear_all_caches,
26 26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
27 27 from rhodecode.tests.fixture import Fixture
28 28 from rhodecode.tests.utils import AssertResponse
29 29
30 30 fixture = Fixture()
31 31
32 # Hardcode URLs because we don't have a request object to use
33 # pyramids URL generation methods.
34 index_url = '/'
35 login_url = ADMIN_PREFIX + '/login'
36 logut_url = ADMIN_PREFIX + '/logout'
37 register_url = ADMIN_PREFIX + '/register'
38 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
39 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
32
33 def route_path(name, params=None, **kwargs):
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
36
37 base_url = {
38 'login': ADMIN_PREFIX + '/login',
39 'logout': ADMIN_PREFIX + '/logout',
40 'register': ADMIN_PREFIX + '/register',
41 'reset_password':
42 ADMIN_PREFIX + '/password_reset',
43 'reset_password_confirmation':
44 ADMIN_PREFIX + '/password_reset_confirmation',
45
46 'admin_permissions_application':
47 ADMIN_PREFIX + '/permissions/application',
48 'admin_permissions_application_update':
49 ADMIN_PREFIX + '/permissions/application/update',
50 }[name].format(**kwargs)
51
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
40 55
41 56
42 57 class TestPasswordReset(TestController):
43 58
44 59 @pytest.mark.parametrize(
45 60 'pwd_reset_setting, show_link, show_reset', [
46 61 ('hg.password_reset.enabled', True, True),
47 62 ('hg.password_reset.hidden', False, True),
48 63 ('hg.password_reset.disabled', False, False),
49 64 ])
50 65 def test_password_reset_settings(
51 66 self, pwd_reset_setting, show_link, show_reset):
52 67 clear_all_caches()
53 68 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
54 69 params = {
55 70 'csrf_token': self.csrf_token,
56 71 'anonymous': 'True',
57 72 'default_register': 'hg.register.auto_activate',
58 73 'default_register_message': '',
59 74 'default_password_reset': pwd_reset_setting,
60 75 'default_extern_activate': 'hg.extern_activate.auto',
61 76 }
62 resp = self.app.post(url('admin_permissions_application'), params=params)
77 resp = self.app.post(route_path('admin_permissions_application_update'), params=params)
63 78 self.logout_user()
64 79
65 login_page = self.app.get(login_url)
80 login_page = self.app.get(route_path('login'))
66 81 asr_login = AssertResponse(login_page)
67 index_page = self.app.get(index_url)
82 index_page = self.app.get(h.route_path('home'))
68 83 asr_index = AssertResponse(index_page)
69 84
70 85 if show_link:
71 86 asr_login.one_element_exists('a.pwd_reset')
72 87 asr_index.one_element_exists('a.pwd_reset')
73 88 else:
74 89 asr_login.no_element_exists('a.pwd_reset')
75 90 asr_index.no_element_exists('a.pwd_reset')
76 91
77 response = self.app.get(pwd_reset_url)
92 response = self.app.get(route_path('reset_password'))
78 93
79 94 assert_response = AssertResponse(response)
80 95 if show_reset:
81 96 response.mustcontain('Send password reset email')
82 97 assert_response.one_element_exists('#email')
83 98 assert_response.one_element_exists('#send')
84 99 else:
85 100 response.mustcontain('Password reset is disabled.')
86 101 assert_response.no_element_exists('#email')
87 102 assert_response.no_element_exists('#send')
88 103
89 104 def test_password_form_disabled(self):
90 105 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
91 106 params = {
92 107 'csrf_token': self.csrf_token,
93 108 'anonymous': 'True',
94 109 'default_register': 'hg.register.auto_activate',
95 110 'default_register_message': '',
96 111 'default_password_reset': 'hg.password_reset.disabled',
97 112 'default_extern_activate': 'hg.extern_activate.auto',
98 113 }
99 self.app.post(url('admin_permissions_application'), params=params)
114 self.app.post(route_path('admin_permissions_application_update'), params=params)
100 115 self.logout_user()
101 116
102 117 response = self.app.post(
103 pwd_reset_url, {'email': 'lisa@rhodecode.com',}
118 route_path('reset_password'), {'email': 'lisa@rhodecode.com',}
104 119 )
105 120 response = response.follow()
106 121 response.mustcontain('Password reset is disabled.')
@@ -1,686 +1,660 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 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('show_user', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(
108 108 directory=config['pylons.paths']['controllers'],
109 109 always_scan=config['debug'])
110 110 rmap.minimization = False
111 111 rmap.explicit = False
112 112
113 113 from rhodecode.lib.utils2 import str2bool
114 114 from rhodecode.model import repo, repo_group
115 115
116 116 def check_repo(environ, match_dict):
117 117 """
118 118 check for valid repository for proper 404 handling
119 119
120 120 :param environ:
121 121 :param match_dict:
122 122 """
123 123 repo_name = match_dict.get('repo_name')
124 124
125 125 if match_dict.get('f_path'):
126 126 # fix for multiple initial slashes that causes errors
127 127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 128 repo_model = repo.RepoModel()
129 129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 130 # if we match quickly from database, short circuit the operation,
131 131 # and validate repo based on the type.
132 132 if by_name_match:
133 133 return True
134 134
135 135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 136 if by_id_match:
137 137 repo_name = by_id_match.repo_name
138 138 match_dict['repo_name'] = repo_name
139 139 return True
140 140
141 141 return False
142 142
143 143 def check_group(environ, match_dict):
144 144 """
145 145 check for valid repository group path for proper 404 handling
146 146
147 147 :param environ:
148 148 :param match_dict:
149 149 """
150 150 repo_group_name = match_dict.get('group_name')
151 151 repo_group_model = repo_group.RepoGroupModel()
152 152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 153 if by_name_match:
154 154 return True
155 155
156 156 return False
157 157
158 158 def check_user_group(environ, match_dict):
159 159 """
160 160 check for valid user group for proper 404 handling
161 161
162 162 :param environ:
163 163 :param match_dict:
164 164 """
165 165 return True
166 166
167 167 def check_int(environ, match_dict):
168 168 return match_dict.get('id').isdigit()
169 169
170 170
171 171 #==========================================================================
172 172 # CUSTOM ROUTES HERE
173 173 #==========================================================================
174 174
175 175 # ping and pylons error test
176 176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178 178
179 179 # ADMIN REPOSITORY ROUTES
180 180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 181 controller='admin/repos') as m:
182 182 m.connect('repos', '/repos',
183 183 action='create', conditions={'method': ['POST']})
184 184 m.connect('repos', '/repos',
185 185 action='index', conditions={'method': ['GET']})
186 186 m.connect('new_repo', '/create_repository', jsroute=True,
187 187 action='create_repository', conditions={'method': ['GET']})
188 188 m.connect('delete_repo', '/repos/{repo_name}',
189 189 action='delete', conditions={'method': ['DELETE']},
190 190 requirements=URL_NAME_REQUIREMENTS)
191 191 m.connect('repo', '/repos/{repo_name}',
192 192 action='show', conditions={'method': ['GET'],
193 193 'function': check_repo},
194 194 requirements=URL_NAME_REQUIREMENTS)
195 195
196 196 # ADMIN REPOSITORY GROUPS ROUTES
197 197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 198 controller='admin/repo_groups') as m:
199 199 m.connect('repo_groups', '/repo_groups',
200 200 action='create', conditions={'method': ['POST']})
201 201 m.connect('repo_groups', '/repo_groups',
202 202 action='index', conditions={'method': ['GET']})
203 203 m.connect('new_repo_group', '/repo_groups/new',
204 204 action='new', conditions={'method': ['GET']})
205 205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 206 action='update', conditions={'method': ['PUT'],
207 207 'function': check_group},
208 208 requirements=URL_NAME_REQUIREMENTS)
209 209
210 210 # EXTRAS REPO GROUP ROUTES
211 211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 212 action='edit',
213 213 conditions={'method': ['GET'], 'function': check_group},
214 214 requirements=URL_NAME_REQUIREMENTS)
215 215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 216 action='edit',
217 217 conditions={'method': ['PUT'], 'function': check_group},
218 218 requirements=URL_NAME_REQUIREMENTS)
219 219
220 220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 221 action='edit_repo_group_advanced',
222 222 conditions={'method': ['GET'], 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 225 action='edit_repo_group_advanced',
226 226 conditions={'method': ['PUT'], 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228
229 229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 230 action='edit_repo_group_perms',
231 231 conditions={'method': ['GET'], 'function': check_group},
232 232 requirements=URL_NAME_REQUIREMENTS)
233 233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 234 action='update_perms',
235 235 conditions={'method': ['PUT'], 'function': check_group},
236 236 requirements=URL_NAME_REQUIREMENTS)
237 237
238 238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 239 action='delete', conditions={'method': ['DELETE'],
240 240 'function': check_group},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242
243 243 # ADMIN USER ROUTES
244 244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 245 controller='admin/users') as m:
246 246 m.connect('users', '/users',
247 247 action='create', conditions={'method': ['POST']})
248 248 m.connect('new_user', '/users/new',
249 249 action='new', conditions={'method': ['GET']})
250 250 m.connect('update_user', '/users/{user_id}',
251 251 action='update', conditions={'method': ['PUT']})
252 252 m.connect('delete_user', '/users/{user_id}',
253 253 action='delete', conditions={'method': ['DELETE']})
254 254 m.connect('edit_user', '/users/{user_id}/edit',
255 255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 256 m.connect('user', '/users/{user_id}',
257 257 action='show', conditions={'method': ['GET']})
258 258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 259 action='reset_password', conditions={'method': ['POST']})
260 260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 261 action='create_personal_repo_group', conditions={'method': ['POST']})
262 262
263 263 # EXTRAS USER ROUTES
264 264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 265 action='edit_advanced', conditions={'method': ['GET']})
266 266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 267 action='update_advanced', conditions={'method': ['PUT']})
268 268
269 269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 270 action='edit_global_perms', conditions={'method': ['GET']})
271 271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 272 action='update_global_perms', conditions={'method': ['PUT']})
273 273
274 274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 275 action='edit_perms_summary', conditions={'method': ['GET']})
276 276
277 277 # ADMIN USER GROUPS REST ROUTES
278 278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 279 controller='admin/user_groups') as m:
280 280 m.connect('users_groups', '/user_groups',
281 281 action='create', conditions={'method': ['POST']})
282 282 m.connect('users_groups', '/user_groups',
283 283 action='index', conditions={'method': ['GET']})
284 284 m.connect('new_users_group', '/user_groups/new',
285 285 action='new', conditions={'method': ['GET']})
286 286 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 287 action='update', conditions={'method': ['PUT']})
288 288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 289 action='delete', conditions={'method': ['DELETE']})
290 290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 291 action='edit', conditions={'method': ['GET']},
292 292 function=check_user_group)
293 293
294 294 # EXTRAS USER GROUP ROUTES
295 295 m.connect('edit_user_group_global_perms',
296 296 '/user_groups/{user_group_id}/edit/global_permissions',
297 297 action='edit_global_perms', conditions={'method': ['GET']})
298 298 m.connect('edit_user_group_global_perms',
299 299 '/user_groups/{user_group_id}/edit/global_permissions',
300 300 action='update_global_perms', conditions={'method': ['PUT']})
301 301 m.connect('edit_user_group_perms_summary',
302 302 '/user_groups/{user_group_id}/edit/permissions_summary',
303 303 action='edit_perms_summary', conditions={'method': ['GET']})
304 304
305 305 m.connect('edit_user_group_perms',
306 306 '/user_groups/{user_group_id}/edit/permissions',
307 307 action='edit_perms', conditions={'method': ['GET']})
308 308 m.connect('edit_user_group_perms',
309 309 '/user_groups/{user_group_id}/edit/permissions',
310 310 action='update_perms', conditions={'method': ['PUT']})
311 311
312 312 m.connect('edit_user_group_advanced',
313 313 '/user_groups/{user_group_id}/edit/advanced',
314 314 action='edit_advanced', conditions={'method': ['GET']})
315 315
316 316 m.connect('edit_user_group_advanced_sync',
317 317 '/user_groups/{user_group_id}/edit/advanced/sync',
318 318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319 319
320 320 m.connect('edit_user_group_members',
321 321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 322 action='user_group_members', conditions={'method': ['GET']})
323 323
324 # ADMIN PERMISSIONS ROUTES
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 controller='admin/permissions') as m:
327 m.connect('admin_permissions_application', '/permissions/application',
328 action='permission_application_update', conditions={'method': ['POST']})
329 m.connect('admin_permissions_application', '/permissions/application',
330 action='permission_application', conditions={'method': ['GET']})
331
332 m.connect('admin_permissions_global', '/permissions/global',
333 action='permission_global_update', conditions={'method': ['POST']})
334 m.connect('admin_permissions_global', '/permissions/global',
335 action='permission_global', conditions={'method': ['GET']})
336
337 m.connect('admin_permissions_object', '/permissions/object',
338 action='permission_objects_update', conditions={'method': ['POST']})
339 m.connect('admin_permissions_object', '/permissions/object',
340 action='permission_objects', conditions={'method': ['GET']})
341
342 m.connect('admin_permissions_ips', '/permissions/ips',
343 action='permission_ips', conditions={'method': ['POST']})
344 m.connect('admin_permissions_ips', '/permissions/ips',
345 action='permission_ips', conditions={'method': ['GET']})
346
347 m.connect('admin_permissions_overview', '/permissions/overview',
348 action='permission_perms', conditions={'method': ['GET']})
349
350 324 # ADMIN DEFAULTS REST ROUTES
351 325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 326 controller='admin/defaults') as m:
353 327 m.connect('admin_defaults_repositories', '/defaults/repositories',
354 328 action='update_repository_defaults', conditions={'method': ['POST']})
355 329 m.connect('admin_defaults_repositories', '/defaults/repositories',
356 330 action='index', conditions={'method': ['GET']})
357 331
358 332 # ADMIN SETTINGS ROUTES
359 333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 334 controller='admin/settings') as m:
361 335
362 336 # default
363 337 m.connect('admin_settings', '/settings',
364 338 action='settings_global_update',
365 339 conditions={'method': ['POST']})
366 340 m.connect('admin_settings', '/settings',
367 341 action='settings_global', conditions={'method': ['GET']})
368 342
369 343 m.connect('admin_settings_vcs', '/settings/vcs',
370 344 action='settings_vcs_update',
371 345 conditions={'method': ['POST']})
372 346 m.connect('admin_settings_vcs', '/settings/vcs',
373 347 action='settings_vcs',
374 348 conditions={'method': ['GET']})
375 349 m.connect('admin_settings_vcs', '/settings/vcs',
376 350 action='delete_svn_pattern',
377 351 conditions={'method': ['DELETE']})
378 352
379 353 m.connect('admin_settings_mapping', '/settings/mapping',
380 354 action='settings_mapping_update',
381 355 conditions={'method': ['POST']})
382 356 m.connect('admin_settings_mapping', '/settings/mapping',
383 357 action='settings_mapping', conditions={'method': ['GET']})
384 358
385 359 m.connect('admin_settings_global', '/settings/global',
386 360 action='settings_global_update',
387 361 conditions={'method': ['POST']})
388 362 m.connect('admin_settings_global', '/settings/global',
389 363 action='settings_global', conditions={'method': ['GET']})
390 364
391 365 m.connect('admin_settings_visual', '/settings/visual',
392 366 action='settings_visual_update',
393 367 conditions={'method': ['POST']})
394 368 m.connect('admin_settings_visual', '/settings/visual',
395 369 action='settings_visual', conditions={'method': ['GET']})
396 370
397 371 m.connect('admin_settings_issuetracker',
398 372 '/settings/issue-tracker', action='settings_issuetracker',
399 373 conditions={'method': ['GET']})
400 374 m.connect('admin_settings_issuetracker_save',
401 375 '/settings/issue-tracker/save',
402 376 action='settings_issuetracker_save',
403 377 conditions={'method': ['POST']})
404 378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
405 379 action='settings_issuetracker_test',
406 380 conditions={'method': ['POST']})
407 381 m.connect('admin_issuetracker_delete',
408 382 '/settings/issue-tracker/delete',
409 383 action='settings_issuetracker_delete',
410 384 conditions={'method': ['DELETE']})
411 385
412 386 m.connect('admin_settings_email', '/settings/email',
413 387 action='settings_email_update',
414 388 conditions={'method': ['POST']})
415 389 m.connect('admin_settings_email', '/settings/email',
416 390 action='settings_email', conditions={'method': ['GET']})
417 391
418 392 m.connect('admin_settings_hooks', '/settings/hooks',
419 393 action='settings_hooks_update',
420 394 conditions={'method': ['POST', 'DELETE']})
421 395 m.connect('admin_settings_hooks', '/settings/hooks',
422 396 action='settings_hooks', conditions={'method': ['GET']})
423 397
424 398 m.connect('admin_settings_search', '/settings/search',
425 399 action='settings_search', conditions={'method': ['GET']})
426 400
427 401 m.connect('admin_settings_supervisor', '/settings/supervisor',
428 402 action='settings_supervisor', conditions={'method': ['GET']})
429 403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
430 404 action='settings_supervisor_log', conditions={'method': ['GET']})
431 405
432 406 m.connect('admin_settings_labs', '/settings/labs',
433 407 action='settings_labs_update',
434 408 conditions={'method': ['POST']})
435 409 m.connect('admin_settings_labs', '/settings/labs',
436 410 action='settings_labs', conditions={'method': ['GET']})
437 411
438 412 # ADMIN MY ACCOUNT
439 413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
440 414 controller='admin/my_account') as m:
441 415
442 416 # NOTE(marcink): this needs to be kept for password force flag to be
443 417 # handled in pylons controllers, remove after full migration to pyramid
444 418 m.connect('my_account_password', '/my_account/password',
445 419 action='my_account_password', conditions={'method': ['GET']})
446 420
447 421 #==========================================================================
448 422 # REPOSITORY ROUTES
449 423 #==========================================================================
450 424
451 425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
452 426 controller='admin/repos', action='repo_creating',
453 427 requirements=URL_NAME_REQUIREMENTS)
454 428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
455 429 controller='admin/repos', action='repo_check',
456 430 requirements=URL_NAME_REQUIREMENTS)
457 431
458 432 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
459 433 controller='changeset', revision='tip',
460 434 conditions={'function': check_repo},
461 435 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
462 436 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
463 437 controller='changeset', revision='tip', action='changeset_children',
464 438 conditions={'function': check_repo},
465 439 requirements=URL_NAME_REQUIREMENTS)
466 440 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
467 441 controller='changeset', revision='tip', action='changeset_parents',
468 442 conditions={'function': check_repo},
469 443 requirements=URL_NAME_REQUIREMENTS)
470 444
471 445 # repo edit options
472 446 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
473 447 controller='admin/repos', action='edit_fields',
474 448 conditions={'method': ['GET'], 'function': check_repo},
475 449 requirements=URL_NAME_REQUIREMENTS)
476 450 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
477 451 controller='admin/repos', action='create_repo_field',
478 452 conditions={'method': ['PUT'], 'function': check_repo},
479 453 requirements=URL_NAME_REQUIREMENTS)
480 454 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
481 455 controller='admin/repos', action='delete_repo_field',
482 456 conditions={'method': ['DELETE'], 'function': check_repo},
483 457 requirements=URL_NAME_REQUIREMENTS)
484 458
485 459 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
486 460 controller='admin/repos', action='toggle_locking',
487 461 conditions={'method': ['GET'], 'function': check_repo},
488 462 requirements=URL_NAME_REQUIREMENTS)
489 463
490 464 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
491 465 controller='admin/repos', action='edit_remote_form',
492 466 conditions={'method': ['GET'], 'function': check_repo},
493 467 requirements=URL_NAME_REQUIREMENTS)
494 468 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
495 469 controller='admin/repos', action='edit_remote',
496 470 conditions={'method': ['PUT'], 'function': check_repo},
497 471 requirements=URL_NAME_REQUIREMENTS)
498 472
499 473 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
500 474 controller='admin/repos', action='edit_statistics_form',
501 475 conditions={'method': ['GET'], 'function': check_repo},
502 476 requirements=URL_NAME_REQUIREMENTS)
503 477 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
504 478 controller='admin/repos', action='edit_statistics',
505 479 conditions={'method': ['PUT'], 'function': check_repo},
506 480 requirements=URL_NAME_REQUIREMENTS)
507 481 rmap.connect('repo_settings_issuetracker',
508 482 '/{repo_name}/settings/issue-tracker',
509 483 controller='admin/repos', action='repo_issuetracker',
510 484 conditions={'method': ['GET'], 'function': check_repo},
511 485 requirements=URL_NAME_REQUIREMENTS)
512 486 rmap.connect('repo_issuetracker_test',
513 487 '/{repo_name}/settings/issue-tracker/test',
514 488 controller='admin/repos', action='repo_issuetracker_test',
515 489 conditions={'method': ['POST'], 'function': check_repo},
516 490 requirements=URL_NAME_REQUIREMENTS)
517 491 rmap.connect('repo_issuetracker_delete',
518 492 '/{repo_name}/settings/issue-tracker/delete',
519 493 controller='admin/repos', action='repo_issuetracker_delete',
520 494 conditions={'method': ['DELETE'], 'function': check_repo},
521 495 requirements=URL_NAME_REQUIREMENTS)
522 496 rmap.connect('repo_issuetracker_save',
523 497 '/{repo_name}/settings/issue-tracker/save',
524 498 controller='admin/repos', action='repo_issuetracker_save',
525 499 conditions={'method': ['POST'], 'function': check_repo},
526 500 requirements=URL_NAME_REQUIREMENTS)
527 501 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
528 502 controller='admin/repos', action='repo_settings_vcs_update',
529 503 conditions={'method': ['POST'], 'function': check_repo},
530 504 requirements=URL_NAME_REQUIREMENTS)
531 505 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
532 506 controller='admin/repos', action='repo_settings_vcs',
533 507 conditions={'method': ['GET'], 'function': check_repo},
534 508 requirements=URL_NAME_REQUIREMENTS)
535 509 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
536 510 controller='admin/repos', action='repo_delete_svn_pattern',
537 511 conditions={'method': ['DELETE'], 'function': check_repo},
538 512 requirements=URL_NAME_REQUIREMENTS)
539 513 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
540 514 controller='admin/repos', action='repo_settings_pullrequest',
541 515 conditions={'method': ['GET', 'POST'], 'function': check_repo},
542 516 requirements=URL_NAME_REQUIREMENTS)
543 517
544 518 # still working url for backward compat.
545 519 rmap.connect('raw_changeset_home_depraced',
546 520 '/{repo_name}/raw-changeset/{revision}',
547 521 controller='changeset', action='changeset_raw',
548 522 revision='tip', conditions={'function': check_repo},
549 523 requirements=URL_NAME_REQUIREMENTS)
550 524
551 525 # new URLs
552 526 rmap.connect('changeset_raw_home',
553 527 '/{repo_name}/changeset-diff/{revision}',
554 528 controller='changeset', action='changeset_raw',
555 529 revision='tip', conditions={'function': check_repo},
556 530 requirements=URL_NAME_REQUIREMENTS)
557 531
558 532 rmap.connect('changeset_patch_home',
559 533 '/{repo_name}/changeset-patch/{revision}',
560 534 controller='changeset', action='changeset_patch',
561 535 revision='tip', conditions={'function': check_repo},
562 536 requirements=URL_NAME_REQUIREMENTS)
563 537
564 538 rmap.connect('changeset_download_home',
565 539 '/{repo_name}/changeset-download/{revision}',
566 540 controller='changeset', action='changeset_download',
567 541 revision='tip', conditions={'function': check_repo},
568 542 requirements=URL_NAME_REQUIREMENTS)
569 543
570 544 rmap.connect('changeset_comment',
571 545 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
572 546 controller='changeset', revision='tip', action='comment',
573 547 conditions={'function': check_repo},
574 548 requirements=URL_NAME_REQUIREMENTS)
575 549
576 550 rmap.connect('changeset_comment_preview',
577 551 '/{repo_name}/changeset/comment/preview', jsroute=True,
578 552 controller='changeset', action='preview_comment',
579 553 conditions={'function': check_repo, 'method': ['POST']},
580 554 requirements=URL_NAME_REQUIREMENTS)
581 555
582 556 rmap.connect('changeset_comment_delete',
583 557 '/{repo_name}/changeset/comment/{comment_id}/delete',
584 558 controller='changeset', action='delete_comment',
585 559 conditions={'function': check_repo, 'method': ['DELETE']},
586 560 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
587 561
588 562 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
589 563 controller='changeset', action='changeset_info',
590 564 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
591 565
592 566 rmap.connect('compare_home',
593 567 '/{repo_name}/compare',
594 568 controller='compare', action='index',
595 569 conditions={'function': check_repo},
596 570 requirements=URL_NAME_REQUIREMENTS)
597 571
598 572 rmap.connect('compare_url',
599 573 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
600 574 controller='compare', action='compare',
601 575 conditions={'function': check_repo},
602 576 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
603 577
604 578 rmap.connect('pullrequest_home',
605 579 '/{repo_name}/pull-request/new', controller='pullrequests',
606 580 action='index', conditions={'function': check_repo,
607 581 'method': ['GET']},
608 582 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
609 583
610 584 rmap.connect('pullrequest',
611 585 '/{repo_name}/pull-request/new', controller='pullrequests',
612 586 action='create', conditions={'function': check_repo,
613 587 'method': ['POST']},
614 588 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
615 589
616 590 rmap.connect('pullrequest_repo_refs',
617 591 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
618 592 controller='pullrequests',
619 593 action='get_repo_refs',
620 594 conditions={'function': check_repo, 'method': ['GET']},
621 595 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
622 596
623 597 rmap.connect('pullrequest_repo_destinations',
624 598 '/{repo_name}/pull-request/repo-destinations',
625 599 controller='pullrequests',
626 600 action='get_repo_destinations',
627 601 conditions={'function': check_repo, 'method': ['GET']},
628 602 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
629 603
630 604 rmap.connect('pullrequest_show',
631 605 '/{repo_name}/pull-request/{pull_request_id}',
632 606 controller='pullrequests',
633 607 action='show', conditions={'function': check_repo,
634 608 'method': ['GET']},
635 609 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
636 610
637 611 rmap.connect('pullrequest_update',
638 612 '/{repo_name}/pull-request/{pull_request_id}',
639 613 controller='pullrequests',
640 614 action='update', conditions={'function': check_repo,
641 615 'method': ['PUT']},
642 616 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 617
644 618 rmap.connect('pullrequest_merge',
645 619 '/{repo_name}/pull-request/{pull_request_id}',
646 620 controller='pullrequests',
647 621 action='merge', conditions={'function': check_repo,
648 622 'method': ['POST']},
649 623 requirements=URL_NAME_REQUIREMENTS)
650 624
651 625 rmap.connect('pullrequest_delete',
652 626 '/{repo_name}/pull-request/{pull_request_id}',
653 627 controller='pullrequests',
654 628 action='delete', conditions={'function': check_repo,
655 629 'method': ['DELETE']},
656 630 requirements=URL_NAME_REQUIREMENTS)
657 631
658 632 rmap.connect('pullrequest_comment',
659 633 '/{repo_name}/pull-request-comment/{pull_request_id}',
660 634 controller='pullrequests',
661 635 action='comment', conditions={'function': check_repo,
662 636 'method': ['POST']},
663 637 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
664 638
665 639 rmap.connect('pullrequest_comment_delete',
666 640 '/{repo_name}/pull-request-comment/{comment_id}/delete',
667 641 controller='pullrequests', action='delete_comment',
668 642 conditions={'function': check_repo, 'method': ['DELETE']},
669 643 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
670 644
671 645 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
672 646 controller='forks', action='fork_create',
673 647 conditions={'function': check_repo, 'method': ['POST']},
674 648 requirements=URL_NAME_REQUIREMENTS)
675 649
676 650 rmap.connect('repo_fork_home', '/{repo_name}/fork',
677 651 controller='forks', action='fork',
678 652 conditions={'function': check_repo},
679 653 requirements=URL_NAME_REQUIREMENTS)
680 654
681 655 rmap.connect('repo_forks_home', '/{repo_name}/forks',
682 656 controller='forks', action='forks',
683 657 conditions={'function': check_repo},
684 658 requirements=URL_NAME_REQUIREMENTS)
685 659
686 660 return rmap
@@ -1,483 +1,484 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 permissions model for RhodeCode
23 23 """
24 24
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from sqlalchemy.exc import DatabaseError
30 30
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import (
33 33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
35 35 from rhodecode.lib.utils2 import str2bool, safe_int
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class PermissionModel(BaseModel):
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 44
45 45 cls = Permission
46 46 global_perms = {
47 47 'default_repo_create': None,
48 48 # special case for create repos on write access to group
49 49 'default_repo_create_on_write': None,
50 50 'default_repo_group_create': None,
51 51 'default_user_group_create': None,
52 52 'default_fork_create': None,
53 53 'default_inherit_default_permissions': None,
54 54 'default_register': None,
55 55 'default_password_reset': None,
56 56 'default_extern_activate': None,
57 57
58 58 # object permissions below
59 59 'default_repo_perm': None,
60 60 'default_group_perm': None,
61 61 'default_user_group_perm': None,
62 62 }
63 63
64 64 def set_global_permission_choices(self, c_obj, gettext_translator):
65 _ = gettext_translator
65 66
66 67 c_obj.repo_perms_choices = [
67 ('repository.none', gettext_translator('None'),),
68 ('repository.read', gettext_translator('Read'),),
69 ('repository.write', gettext_translator('Write'),),
70 ('repository.admin', gettext_translator('Admin'),)]
68 ('repository.none', _('None'),),
69 ('repository.read', _('Read'),),
70 ('repository.write', _('Write'),),
71 ('repository.admin', _('Admin'),)]
71 72
72 73 c_obj.group_perms_choices = [
73 ('group.none', gettext_translator('None'),),
74 ('group.read', gettext_translator('Read'),),
75 ('group.write', gettext_translator('Write'),),
76 ('group.admin', gettext_translator('Admin'),)]
74 ('group.none', _('None'),),
75 ('group.read', _('Read'),),
76 ('group.write', _('Write'),),
77 ('group.admin', _('Admin'),)]
77 78
78 79 c_obj.user_group_perms_choices = [
79 ('usergroup.none', gettext_translator('None'),),
80 ('usergroup.read', gettext_translator('Read'),),
81 ('usergroup.write', gettext_translator('Write'),),
82 ('usergroup.admin', gettext_translator('Admin'),)]
80 ('usergroup.none', _('None'),),
81 ('usergroup.read', _('Read'),),
82 ('usergroup.write', _('Write'),),
83 ('usergroup.admin', _('Admin'),)]
83 84
84 85 c_obj.register_choices = [
85 ('hg.register.none', gettext_translator('Disabled')),
86 ('hg.register.manual_activate', gettext_translator('Allowed with manual account activation')),
87 ('hg.register.auto_activate', gettext_translator('Allowed with automatic account activation')),]
86 ('hg.register.none', _('Disabled')),
87 ('hg.register.manual_activate', _('Allowed with manual account activation')),
88 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
88 89
89 90 c_obj.password_reset_choices = [
90 ('hg.password_reset.enabled', gettext_translator('Allow password recovery')),
91 ('hg.password_reset.hidden', gettext_translator('Hide password recovery link')),
92 ('hg.password_reset.disabled', gettext_translator('Disable password recovery')),]
91 ('hg.password_reset.enabled', _('Allow password recovery')),
92 ('hg.password_reset.hidden', _('Hide password recovery link')),
93 ('hg.password_reset.disabled', _('Disable password recovery')),]
93 94
94 95 c_obj.extern_activate_choices = [
95 ('hg.extern_activate.manual', gettext_translator('Manual activation of external account')),
96 ('hg.extern_activate.auto', gettext_translator('Automatic activation of external account')),]
96 ('hg.extern_activate.manual', _('Manual activation of external account')),
97 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
97 98
98 99 c_obj.repo_create_choices = [
99 ('hg.create.none', gettext_translator('Disabled')),
100 ('hg.create.repository', gettext_translator('Enabled'))]
100 ('hg.create.none', _('Disabled')),
101 ('hg.create.repository', _('Enabled'))]
101 102
102 103 c_obj.repo_create_on_write_choices = [
103 ('hg.create.write_on_repogroup.false', gettext_translator('Disabled')),
104 ('hg.create.write_on_repogroup.true', gettext_translator('Enabled'))]
104 ('hg.create.write_on_repogroup.false', _('Disabled')),
105 ('hg.create.write_on_repogroup.true', _('Enabled'))]
105 106
106 107 c_obj.user_group_create_choices = [
107 ('hg.usergroup.create.false', gettext_translator('Disabled')),
108 ('hg.usergroup.create.true', gettext_translator('Enabled'))]
108 ('hg.usergroup.create.false', _('Disabled')),
109 ('hg.usergroup.create.true', _('Enabled'))]
109 110
110 111 c_obj.repo_group_create_choices = [
111 ('hg.repogroup.create.false', gettext_translator('Disabled')),
112 ('hg.repogroup.create.true', gettext_translator('Enabled'))]
112 ('hg.repogroup.create.false', _('Disabled')),
113 ('hg.repogroup.create.true', _('Enabled'))]
113 114
114 115 c_obj.fork_choices = [
115 ('hg.fork.none', gettext_translator('Disabled')),
116 ('hg.fork.repository', gettext_translator('Enabled'))]
116 ('hg.fork.none', _('Disabled')),
117 ('hg.fork.repository', _('Enabled'))]
117 118
118 119 c_obj.inherit_default_permission_choices = [
119 ('hg.inherit_default_perms.false', gettext_translator('Disabled')),
120 ('hg.inherit_default_perms.true', gettext_translator('Enabled'))]
120 ('hg.inherit_default_perms.false', _('Disabled')),
121 ('hg.inherit_default_perms.true', _('Enabled'))]
121 122
122 123 def get_default_perms(self, object_perms, suffix):
123 124 defaults = {}
124 125 for perm in object_perms:
125 126 # perms
126 127 if perm.permission.permission_name.startswith('repository.'):
127 128 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
128 129
129 130 if perm.permission.permission_name.startswith('group.'):
130 131 defaults['default_group_perm' + suffix] = perm.permission.permission_name
131 132
132 133 if perm.permission.permission_name.startswith('usergroup.'):
133 134 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
134 135
135 136 # creation of objects
136 137 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
137 138 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
138 139
139 140 elif perm.permission.permission_name.startswith('hg.create.'):
140 141 defaults['default_repo_create' + suffix] = perm.permission.permission_name
141 142
142 143 if perm.permission.permission_name.startswith('hg.fork.'):
143 144 defaults['default_fork_create' + suffix] = perm.permission.permission_name
144 145
145 146 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
146 147 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
147 148
148 149 if perm.permission.permission_name.startswith('hg.repogroup.'):
149 150 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
150 151
151 152 if perm.permission.permission_name.startswith('hg.usergroup.'):
152 153 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
153 154
154 155 # registration and external account activation
155 156 if perm.permission.permission_name.startswith('hg.register.'):
156 157 defaults['default_register' + suffix] = perm.permission.permission_name
157 158
158 159 if perm.permission.permission_name.startswith('hg.password_reset.'):
159 160 defaults['default_password_reset' + suffix] = perm.permission.permission_name
160 161
161 162 if perm.permission.permission_name.startswith('hg.extern_activate.'):
162 163 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
163 164
164 165 return defaults
165 166
166 167 def _make_new_user_perm(self, user, perm_name):
167 168 log.debug('Creating new user permission:%s', perm_name)
168 169 new = UserToPerm()
169 170 new.user = user
170 171 new.permission = Permission.get_by_key(perm_name)
171 172 return new
172 173
173 174 def _make_new_user_group_perm(self, user_group, perm_name):
174 175 log.debug('Creating new user group permission:%s', perm_name)
175 176 new = UserGroupToPerm()
176 177 new.users_group = user_group
177 178 new.permission = Permission.get_by_key(perm_name)
178 179 return new
179 180
180 181 def _keep_perm(self, perm_name, keep_fields):
181 182 def get_pat(field_name):
182 183 return {
183 184 # global perms
184 185 'default_repo_create': 'hg.create.',
185 186 # special case for create repos on write access to group
186 187 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
187 188 'default_repo_group_create': 'hg.repogroup.create.',
188 189 'default_user_group_create': 'hg.usergroup.create.',
189 190 'default_fork_create': 'hg.fork.',
190 191 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
191 192
192 193 # application perms
193 194 'default_register': 'hg.register.',
194 195 'default_password_reset': 'hg.password_reset.',
195 196 'default_extern_activate': 'hg.extern_activate.',
196 197
197 198 # object permissions below
198 199 'default_repo_perm': 'repository.',
199 200 'default_group_perm': 'group.',
200 201 'default_user_group_perm': 'usergroup.',
201 202 }[field_name]
202 203 for field in keep_fields:
203 204 pat = get_pat(field)
204 205 if perm_name.startswith(pat):
205 206 return True
206 207 return False
207 208
208 209 def _clear_object_perm(self, object_perms, preserve=None):
209 210 preserve = preserve or []
210 211 _deleted = []
211 212 for perm in object_perms:
212 213 perm_name = perm.permission.permission_name
213 214 if not self._keep_perm(perm_name, keep_fields=preserve):
214 215 _deleted.append(perm_name)
215 216 self.sa.delete(perm)
216 217 return _deleted
217 218
218 219 def _clear_user_perms(self, user_id, preserve=None):
219 220 perms = self.sa.query(UserToPerm)\
220 221 .filter(UserToPerm.user_id == user_id)\
221 222 .all()
222 223 return self._clear_object_perm(perms, preserve=preserve)
223 224
224 225 def _clear_user_group_perms(self, user_group_id, preserve=None):
225 226 perms = self.sa.query(UserGroupToPerm)\
226 227 .filter(UserGroupToPerm.users_group_id == user_group_id)\
227 228 .all()
228 229 return self._clear_object_perm(perms, preserve=preserve)
229 230
230 231 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
231 232 # clear current entries, to make this function idempotent
232 233 # it will fix even if we define more permissions or permissions
233 234 # are somehow missing
234 235 preserve = preserve or []
235 236 _global_perms = self.global_perms.copy()
236 237 if obj_type not in ['user', 'user_group']:
237 238 raise ValueError("obj_type must be on of 'user' or 'user_group'")
238 239 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
239 240 raise Exception('Inconsistent permissions definition')
240 241
241 242 if obj_type == 'user':
242 243 self._clear_user_perms(object.user_id, preserve)
243 244 if obj_type == 'user_group':
244 245 self._clear_user_group_perms(object.users_group_id, preserve)
245 246
246 247 # now kill the keys that we want to preserve from the form.
247 248 for key in preserve:
248 249 del _global_perms[key]
249 250
250 251 for k in _global_perms.copy():
251 252 _global_perms[k] = form_result[k]
252 253
253 254 # at that stage we validate all are passed inside form_result
254 255 for _perm_key, perm_value in _global_perms.items():
255 256 if perm_value is None:
256 257 raise ValueError('Missing permission for %s' % (_perm_key,))
257 258
258 259 if obj_type == 'user':
259 260 p = self._make_new_user_perm(object, perm_value)
260 261 self.sa.add(p)
261 262 if obj_type == 'user_group':
262 263 p = self._make_new_user_group_perm(object, perm_value)
263 264 self.sa.add(p)
264 265
265 266 def _set_new_user_perms(self, user, form_result, preserve=None):
266 267 return self._set_new_object_perms(
267 268 'user', user, form_result, preserve)
268 269
269 270 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
270 271 return self._set_new_object_perms(
271 272 'user_group', user_group, form_result, preserve)
272 273
273 274 def set_new_user_perms(self, user, form_result):
274 275 # calculate what to preserve from what is given in form_result
275 276 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
276 277 return self._set_new_user_perms(user, form_result, preserve)
277 278
278 279 def set_new_user_group_perms(self, user_group, form_result):
279 280 # calculate what to preserve from what is given in form_result
280 281 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
281 282 return self._set_new_user_group_perms(user_group, form_result, preserve)
282 283
283 284 def create_permissions(self):
284 285 """
285 286 Create permissions for whole system
286 287 """
287 288 for p in Permission.PERMS:
288 289 if not Permission.get_by_key(p[0]):
289 290 new_perm = Permission()
290 291 new_perm.permission_name = p[0]
291 292 new_perm.permission_longname = p[0] # translation err with p[1]
292 293 self.sa.add(new_perm)
293 294
294 295 def _create_default_object_permission(self, obj_type, obj, obj_perms,
295 296 force=False):
296 297 if obj_type not in ['user', 'user_group']:
297 298 raise ValueError("obj_type must be on of 'user' or 'user_group'")
298 299
299 300 def _get_group(perm_name):
300 301 return '.'.join(perm_name.split('.')[:1])
301 302
302 303 defined_perms_groups = map(
303 304 _get_group, (x.permission.permission_name for x in obj_perms))
304 305 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
305 306
306 307 if force:
307 308 self._clear_object_perm(obj_perms)
308 309 self.sa.commit()
309 310 defined_perms_groups = []
310 311 # for every default permission that needs to be created, we check if
311 312 # it's group is already defined, if it's not we create default perm
312 313 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
313 314 gr = _get_group(perm_name)
314 315 if gr not in defined_perms_groups:
315 316 log.debug('GR:%s not found, creating permission %s',
316 317 gr, perm_name)
317 318 if obj_type == 'user':
318 319 new_perm = self._make_new_user_perm(obj, perm_name)
319 320 self.sa.add(new_perm)
320 321 if obj_type == 'user_group':
321 322 new_perm = self._make_new_user_group_perm(obj, perm_name)
322 323 self.sa.add(new_perm)
323 324
324 325 def create_default_user_permissions(self, user, force=False):
325 326 """
326 327 Creates only missing default permissions for user, if force is set it
327 328 resets the default permissions for that user
328 329
329 330 :param user:
330 331 :param force:
331 332 """
332 333 user = self._get_user(user)
333 334 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
334 335 return self._create_default_object_permission(
335 336 'user', user, obj_perms, force)
336 337
337 338 def create_default_user_group_permissions(self, user_group, force=False):
338 339 """
339 340 Creates only missing default permissions for user group, if force is set it
340 341 resets the default permissions for that user group
341 342
342 343 :param user_group:
343 344 :param force:
344 345 """
345 346 user_group = self._get_user_group(user_group)
346 347 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
347 348 return self._create_default_object_permission(
348 349 'user_group', user_group, obj_perms, force)
349 350
350 351 def update_application_permissions(self, form_result):
351 352 if 'perm_user_id' in form_result:
352 353 perm_user = User.get(safe_int(form_result['perm_user_id']))
353 354 else:
354 355 # used mostly to do lookup for default user
355 356 perm_user = User.get_by_username(form_result['perm_user_name'])
356 357
357 358 try:
358 359 # stage 1 set anonymous access
359 360 if perm_user.username == User.DEFAULT_USER:
360 361 perm_user.active = str2bool(form_result['anonymous'])
361 362 self.sa.add(perm_user)
362 363
363 364 # stage 2 reset defaults and set them from form data
364 365 self._set_new_user_perms(perm_user, form_result, preserve=[
365 366 'default_repo_perm',
366 367 'default_group_perm',
367 368 'default_user_group_perm',
368 369
369 370 'default_repo_group_create',
370 371 'default_user_group_create',
371 372 'default_repo_create_on_write',
372 373 'default_repo_create',
373 374 'default_fork_create',
374 375 'default_inherit_default_permissions',])
375 376
376 377 self.sa.commit()
377 378 except (DatabaseError,):
378 379 log.error(traceback.format_exc())
379 380 self.sa.rollback()
380 381 raise
381 382
382 383 def update_user_permissions(self, form_result):
383 384 if 'perm_user_id' in form_result:
384 385 perm_user = User.get(safe_int(form_result['perm_user_id']))
385 386 else:
386 387 # used mostly to do lookup for default user
387 388 perm_user = User.get_by_username(form_result['perm_user_name'])
388 389 try:
389 390 # stage 2 reset defaults and set them from form data
390 391 self._set_new_user_perms(perm_user, form_result, preserve=[
391 392 'default_repo_perm',
392 393 'default_group_perm',
393 394 'default_user_group_perm',
394 395
395 396 'default_register',
396 397 'default_password_reset',
397 398 'default_extern_activate'])
398 399 self.sa.commit()
399 400 except (DatabaseError,):
400 401 log.error(traceback.format_exc())
401 402 self.sa.rollback()
402 403 raise
403 404
404 405 def update_user_group_permissions(self, form_result):
405 406 if 'perm_user_group_id' in form_result:
406 407 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
407 408 else:
408 409 # used mostly to do lookup for default user
409 410 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
410 411 try:
411 412 # stage 2 reset defaults and set them from form data
412 413 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
413 414 'default_repo_perm',
414 415 'default_group_perm',
415 416 'default_user_group_perm',
416 417
417 418 'default_register',
418 419 'default_password_reset',
419 420 'default_extern_activate'])
420 421 self.sa.commit()
421 422 except (DatabaseError,):
422 423 log.error(traceback.format_exc())
423 424 self.sa.rollback()
424 425 raise
425 426
426 427 def update_object_permissions(self, form_result):
427 428 if 'perm_user_id' in form_result:
428 429 perm_user = User.get(safe_int(form_result['perm_user_id']))
429 430 else:
430 431 # used mostly to do lookup for default user
431 432 perm_user = User.get_by_username(form_result['perm_user_name'])
432 433 try:
433 434
434 435 # stage 2 reset defaults and set them from form data
435 436 self._set_new_user_perms(perm_user, form_result, preserve=[
436 437 'default_repo_group_create',
437 438 'default_user_group_create',
438 439 'default_repo_create_on_write',
439 440 'default_repo_create',
440 441 'default_fork_create',
441 442 'default_inherit_default_permissions',
442 443
443 444 'default_register',
444 445 'default_password_reset',
445 446 'default_extern_activate'])
446 447
447 448 # overwrite default repo permissions
448 449 if form_result['overwrite_default_repo']:
449 450 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
450 451 _def = Permission.get_by_key('repository.' + _def_name)
451 452 for r2p in self.sa.query(UserRepoToPerm)\
452 453 .filter(UserRepoToPerm.user == perm_user)\
453 454 .all():
454 455 # don't reset PRIVATE repositories
455 456 if not r2p.repository.private:
456 457 r2p.permission = _def
457 458 self.sa.add(r2p)
458 459
459 460 # overwrite default repo group permissions
460 461 if form_result['overwrite_default_group']:
461 462 _def_name = form_result['default_group_perm'].split('group.')[-1]
462 463 _def = Permission.get_by_key('group.' + _def_name)
463 464 for g2p in self.sa.query(UserRepoGroupToPerm)\
464 465 .filter(UserRepoGroupToPerm.user == perm_user)\
465 466 .all():
466 467 g2p.permission = _def
467 468 self.sa.add(g2p)
468 469
469 470 # overwrite default user group permissions
470 471 if form_result['overwrite_default_user_group']:
471 472 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
472 473 # user groups
473 474 _def = Permission.get_by_key('usergroup.' + _def_name)
474 475 for g2p in self.sa.query(UserUserGroupToPerm)\
475 476 .filter(UserUserGroupToPerm.user == perm_user)\
476 477 .all():
477 478 g2p.permission = _def
478 479 self.sa.add(g2p)
479 480 self.sa.commit()
480 481 except (DatabaseError,):
481 482 log.exception('Failed to set default object permissions')
482 483 self.sa.rollback()
483 484 raise
@@ -1,206 +1,213 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 18 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
19 19 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
20 20 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
21 21 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
22 22 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
23 23 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
24 24 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
25 25 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
26 26 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
27 27 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
28 28 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
29 29 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 30 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 31 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 32 pyroutes.register('favicon', '/favicon.ico', []);
33 33 pyroutes.register('robots', '/robots.txt', []);
34 34 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
35 35 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
36 36 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
37 37 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
38 38 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
39 39 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
40 40 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
41 41 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
42 42 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
43 43 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
44 44 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
45 45 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
46 46 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
47 47 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
48 48 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
49 49 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
50 50 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
51 51 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
52 52 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
53 53 pyroutes.register('admin_home', '/_admin', []);
54 54 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
55 55 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
56 56 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
57 57 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
58 58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
59 59 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
60 60 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
61 61 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
62 62 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
63 63 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
64 64 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
65 65 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
66 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
67 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
68 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
69 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
70 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
71 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
66 72 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
73 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
67 74 pyroutes.register('users', '/_admin/users', []);
68 75 pyroutes.register('users_data', '/_admin/users_data', []);
69 76 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
70 77 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
71 78 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
72 79 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
73 80 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
74 81 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
75 82 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
76 83 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
77 84 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
78 85 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
79 86 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
80 87 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
81 88 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
82 89 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
83 90 pyroutes.register('channelstream_proxy', '/_channelstream', []);
84 91 pyroutes.register('login', '/_admin/login', []);
85 92 pyroutes.register('logout', '/_admin/logout', []);
86 93 pyroutes.register('register', '/_admin/register', []);
87 94 pyroutes.register('reset_password', '/_admin/password_reset', []);
88 95 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
89 96 pyroutes.register('home', '/', []);
90 97 pyroutes.register('user_autocomplete_data', '/_users', []);
91 98 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
92 99 pyroutes.register('repo_list_data', '/_repos', []);
93 100 pyroutes.register('goto_switcher_data', '/_goto_data', []);
94 101 pyroutes.register('journal', '/_admin/journal', []);
95 102 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
96 103 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
97 104 pyroutes.register('journal_public', '/_admin/public_journal', []);
98 105 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
99 106 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
100 107 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
101 108 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
102 109 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
103 110 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
104 111 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
105 112 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
106 113 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
107 114 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
108 115 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
109 116 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 117 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
111 118 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
112 119 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 120 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 121 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 122 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 123 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
117 124 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 125 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 126 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 127 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 128 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 129 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 130 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 131 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 132 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 133 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 134 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 135 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 136 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
130 137 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
131 138 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
132 139 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
133 140 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 141 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
135 142 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
136 143 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
137 144 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
138 145 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
139 146 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
140 147 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
141 148 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
142 149 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
143 150 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
144 151 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
145 152 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
146 153 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
147 154 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
148 155 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
149 156 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
150 157 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
151 158 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
152 159 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
153 160 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
154 161 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
155 162 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
156 163 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
157 164 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
158 165 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
159 166 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
160 167 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
161 168 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
162 169 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
163 170 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
164 171 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
165 172 pyroutes.register('search', '/_admin/search', []);
166 173 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
167 174 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
168 175 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
169 176 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
170 177 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
171 178 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
172 179 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
173 180 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
174 181 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
175 182 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
176 183 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
177 184 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
178 185 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
179 186 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
180 187 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
181 188 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
182 189 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
183 190 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
184 191 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
185 192 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
186 193 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
187 194 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
188 195 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
189 196 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
190 197 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
191 198 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
192 199 pyroutes.register('gists_show', '/_admin/gists', []);
193 200 pyroutes.register('gists_new', '/_admin/gists/new', []);
194 201 pyroutes.register('gists_create', '/_admin/gists/create', []);
195 202 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
196 203 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
197 204 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
198 205 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
199 206 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
200 207 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
201 208 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
202 209 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
203 210 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
204 211 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
205 212 pyroutes.register('apiv2', '/_admin/api', []);
206 213 }
@@ -1,56 +1,56 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Permissions Administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Permissions')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class="sidebar-col-wrapper scw-small">
29 29 ##main
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 32 <li class="${'active' if c.active=='application' else ''}">
33 <a href="${h.url('admin_permissions_application')}">${_('Application')}</a>
33 <a href="${h.route_path('admin_permissions_application')}">${_('Application')}</a>
34 34 </li>
35 35 <li class="${'active' if c.active=='global' else ''}">
36 <a href="${h.url('admin_permissions_global')}">${_('Global')}</a>
36 <a href="${h.route_path('admin_permissions_global')}">${_('Global')}</a>
37 37 </li>
38 38 <li class="${'active' if c.active=='objects' else ''}">
39 <a href="${h.url('admin_permissions_object')}">${_('Object')}</a>
39 <a href="${h.route_path('admin_permissions_object')}">${_('Object')}</a>
40 40 </li>
41 41 <li class="${'active' if c.active=='ips' else ''}">
42 <a href="${h.url('admin_permissions_ips')}">${_('IP Whitelist')}</a>
42 <a href="${h.route_path('admin_permissions_ips')}">${_('IP Whitelist')}</a>
43 43 </li>
44 44 <li class="${'active' if c.active=='perms' else ''}">
45 <a href="${h.url('admin_permissions_overview')}">${_('Overview')}</a>
45 <a href="${h.route_path('admin_permissions_overview')}">${_('Overview')}</a>
46 46 </li>
47 47 </ul>
48 48 </div>
49 49
50 50 <div class="main-content-full-width">
51 51 <%include file="/admin/permissions/permissions_${c.active}.mako"/>
52 52 </div>
53 53 </div>
54 54 </div>
55 55
56 56 </%def>
@@ -1,81 +1,81 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 ${h.secure_form(h.url('admin_permissions_application'), method='post')}
6 ${h.secure_form(h.route_path('admin_permissions_application_update'), method='POST', request=request)}
7 7 <div class="form">
8 8 <!-- fields -->
9 9 <div class="fields">
10 10 <div class="field">
11 11 <div class="label label-checkbox">
12 12 <label for="anonymous">${_('Anonymous Access')}:</label>
13 13 </div>
14 14 <div class="checkboxes">
15 15 <div class="checkbox">
16 16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
17 17 </div>
18 <span class="help-block">${h.literal(_('Allow access to RhodeCode Enterprise without requiring users to login. Anonymous users get the %s permission settings.' % (h.link_to('"default user"',h.url('admin_permissions_object')))))}</span>
18 <span class="help-block">${h.literal(_('Allow access to RhodeCode Enterprise without requiring users to login. Anonymous users get the %s permission settings.' % (h.link_to('"default user"',h.route_path('admin_permissions_object')))))}</span>
19 19 </div>
20 20 </div>
21 21
22 22 <div class="field">
23 23 <div class="label label-select">
24 24 <label for="default_register">${_('Registration')}:</label>
25 25 </div>
26 26 <div class="select">
27 27 ${h.select('default_register','',c.register_choices)}
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 32 <div class="label label-select">
33 33 <label for="default_password_reset">${_('Password Reset')}:</label>
34 34 </div>
35 35 <div class="select">
36 36 ${h.select('default_password_reset','',c.password_reset_choices)}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-textarea">
42 42 <label for="default_register_message">${_('Registration Page Message')}:</label>
43 43 </div>
44 44 <div class="textarea text-area editor" >
45 45 ${h.textarea('default_register_message', class_="medium", )}
46 46 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
47 47 </div>
48 48 </div>
49 49
50 50 <div class="field">
51 51 <div class="label">
52 52 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
53 53 </div>
54 54 <div class="select">
55 55 ${h.select('default_extern_activate','',c.extern_activate_choices)}
56 56 </div>
57 57 </div>
58 58 <div class="buttons">
59 59 ${h.submit('save',_('Save'),class_="btn")}
60 60 ${h.reset('reset',_('Reset'),class_="btn")}
61 61 </div>
62 62 </div>
63 63 </div>
64 64 ${h.end_form()}
65 65 </div>
66 66 </div>
67 67
68 68 <script>
69 69 $(document).ready(function(){
70 70 var select2Options = {
71 71 containerCssClass: 'drop-menu',
72 72 dropdownCssClass: 'drop-menu-dropdown',
73 73 dropdownAutoWidth: true,
74 74 minimumResultsForSearch: -1
75 75 };
76 76
77 77 $("#default_register").select2(select2Options);
78 78 $("#default_password_reset").select2(select2Options);
79 79 $("#default_extern_activate").select2(select2Options);
80 80 });
81 81 </script>
@@ -1,10 +1,10 b''
1 1
2 ${h.secure_form(h.url('admin_permissions_global'), method='post')}
2 ${h.secure_form(h.route_path('admin_permissions_global_update'), method='POST', request=request)}
3 3 <div class="form permissions-global">
4 4 <!-- fields -->
5 5 <div class="fields">
6 6 <%namespace name="dpb" file="/base/default_perms_box.mako"/>
7 7 ${dpb.default_perms_radios(global_permissions_template = True)}
8 8 </div>
9 9 </div>
10 10 ${h.end_form()}
@@ -1,67 +1,70 b''
1 1
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Default IP Whitelist For All Users')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="ips_wrap">
9 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
10
11
9 12 <table class="rctable ip-whitelist">
10 13 <tr>
11 14 <th>IP Address</th>
12 15 <th>IP Range</th>
13 16 <th>Description</th>
14 17 <th></th>
15 18 </tr>
16 19 %if c.user_ip_map:
17 20 %for ip in c.user_ip_map:
18 21 <tr>
19 22 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
20 23 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 24 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 25 <td class="td-action">
23 26 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
24 27 ${h.hidden('del_ip_id',ip.ip_id)}
25 28 ${h.hidden('default_user', 'True')}
26 29 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
27 30 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
28 31 ${h.end_form()}
29 32 </td>
30 33 </tr>
31 34 %endfor
32 35 %else:
33 36 <tr>
34 37 <td class="ip">${_('All IP addresses are allowed')}</td>
35 38 <td></td>
36 39 <td></td>
37 40 <td></td>
38 41 </tr>
39 42 %endif
40 43 </table>
41 44 </div>
42 45
43 46 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
44 47 <div class="form">
45 48 <!-- fields -->
46 49 <div class="fields">
47 50 <div class="field">
48 51 <div class="label">
49 52 <label for="new_ip">${_('New IP Address')}:</label>
50 53 </div>
51 54 <div class="input">
52 55 ${h.hidden('default_user', 'True')}
53 56 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
54 57 <span class="help-block">${_('Enter a comma separated list of IP Addresses like 127.0.0.1,\n'
55 58 'or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n'
56 59 'To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax')}</span>
57 60 </div>
58 61 </div>
59 62 <div class="buttons">
60 63 ${h.submit('save',_('Add'),class_="btn")}
61 64 ${h.reset('reset',_('Reset'),class_="btn")}
62 65 </div>
63 66 </div>
64 67 </div>
65 68 ${h.end_form()}
66 69 </div>
67 70 </div>
@@ -1,77 +1,77 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Default Permissions for Repositories, User Groups and Repository Groups.')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <p>${_('Default system permissions. Each permissions management entity will be created with the following default settings. Check the overwrite checkbox to force any permission changes on already existing settings.')}
7 7 </p>
8 ${h.secure_form(h.url('admin_permissions_object'), method='post')}
8 ${h.secure_form(h.route_path('admin_permissions_object_update'), method='POST', request=request)}
9 9 <div class="form">
10 10 <div class="fields">
11 11 <div class="field">
12 12 <div class="label">
13 13 <label for="default_repo_perm">${_('Repository')}:</label>
14 14 </div>
15 15 <div class="select">
16 16 ${h.select('default_repo_perm','',c.repo_perms_choices)}
17 17
18 18 ${h.checkbox('overwrite_default_repo','true')}
19 19 <label for="overwrite_default_repo">
20 20 <span class="tooltip" title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
21 21 ${_('Overwrite Existing Settings')}
22 22 </span>
23 23 </label>
24 24 </div>
25 25 </div>
26 26 <div class="field">
27 27 <div class="label">
28 28 <label for="default_group_perm">${_('Repository Groups')}:</label>
29 29 </div>
30 30 <div class="select">
31 31 ${h.select('default_group_perm','',c.group_perms_choices)}
32 32 ${h.checkbox('overwrite_default_group','true')}
33 33 <label for="overwrite_default_group">
34 34 <span class="tooltip" title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
35 35 ${_('Overwrite Existing Settings')}
36 36 </span>
37 37 </label>
38 38 </div>
39 39 </div>
40 40 <div class="field">
41 41 <div class="label">
42 42 <label for="default_group_perm">${_('User Groups')}:</label>
43 43 </div>
44 44 <div class="select">
45 45 ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
46 46 ${h.checkbox('overwrite_default_user_group','true')}
47 47 <label for="overwrite_default_user_group">
48 48 <span class="tooltip" title="${h.tooltip(_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
49 49 ${_('Overwrite Existing Settings')}
50 50 </span>
51 51 </label>
52 52 </div>
53 53 </div>
54 54
55 55 <div class="buttons">
56 56 ${h.submit('save',_('Save'),class_="btn")}
57 57 ${h.reset('reset',_('Reset'),class_="btn")}
58 58 </div>
59 59 </div>
60 60 </div>
61 61 ${h.end_form()}
62 62 </div>
63 63 </div>
64 64
65 65 <script>
66 66 $(document).ready(function(){
67 67 var select2Options = {
68 68 containerCssClass: 'drop-menu',
69 69 dropdownCssClass: 'drop-menu-dropdown',
70 70 dropdownAutoWidth: true,
71 71 minimumResultsForSearch: -1
72 72 };
73 73 $("#default_repo_perm").select2(select2Options);
74 74 $("#default_group_perm").select2(select2Options);
75 75 $("#default_user_group_perm").select2(select2Options);
76 76 });
77 77 </script>
@@ -1,78 +1,78 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Custom IP Whitelist')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="ips_wrap">
7 7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
8 8 <table class="rctable ip-whitelist">
9 9 <tr>
10 10 <th>${_('IP Address')}</th>
11 11 <th>${_('IP Range')}</th>
12 12 <th>${_('Description')}</th>
13 13 <th></th>
14 14 </tr>
15 15 %if c.default_user_ip_map and c.inherit_default_ips:
16 16 %for ip in c.default_user_ip_map:
17 17 <tr>
18 18 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
19 19 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
20 <td class="td-description">${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}</td>
20 <td class="td-description">${h.literal(_('Inherited from %s') % h.link_to('*default*',h.route_path('admin_permissions_ips')))}</td>
21 21 <td></td>
22 22 </tr>
23 23 %endfor
24 24 %endif
25 25
26 26 %if c.user_ip_map:
27 27 %for ip in c.user_ip_map:
28 28 <tr>
29 29 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
30 30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
31 31 <td class="td-description"><div class="ip">${ip.description}</div></td>
32 32 <td class="td-action">
33 33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
34 34 ${h.hidden('del_ip_id', ip.ip_id)}
35 35 ${h.submit('remove_', _('Delete'),id="remove_ip_%s" % ip.ip_id,
36 36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
37 37 ${h.end_form()}
38 38 </td>
39 39 </tr>
40 40 %endfor
41 41 %endif
42 42 %if not c.default_user_ip_map and not c.user_ip_map:
43 43 <tr>
44 44 <td><h2 class="ip">${_('All IP addresses are allowed')}</h2></td>
45 45 <td></td>
46 46 <td></td>
47 47 <td></td>
48 48 </tr>
49 49 %endif
50 50 </table>
51 51 </div>
52 52
53 53 <div>
54 54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
55 55 <div class="form">
56 56 <!-- fields -->
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="new_ip">${_('New IP Address')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
64 64 <span class="help-block">${_('Enter comma separated list of ip addresses like 127.0.0.1,\n'
65 65 'or use a ip address with a mask 127.0.0.1/24, to create a network range.\n'
66 66 'To specify multiple address range use 127.0.0.1-127.0.0.10 syntax')}</span>
67 67 </div>
68 68 </div>
69 69 <div class="buttons">
70 70 ${h.submit('save',_('Add'),class_="btn btn-small")}
71 71 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76 </div>
77 77 </div>
78 78 </div>
@@ -1,600 +1,600 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink dropdown">
247 247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 248 </a>
249 249 <ul class="submenu">
250 250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 252 %endif
253 253 %if c.rhodecode_db_repo.fork:
254 254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
255 255 ${_('Compare fork')}</a></li>
256 256 %endif
257 257
258 258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259 259
260 260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 261 %if c.rhodecode_db_repo.locked[0]:
262 262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 263 %else:
264 264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 265 %endif
266 266 %endif
267 267 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 271 %endif
272 272 %endif
273 273 </ul>
274 274 </li>
275 275 </ul>
276 276 </div>
277 277 <div class="clear"></div>
278 278 </div>
279 279 <!--- END CONTEXT BAR -->
280 280
281 281 </%def>
282 282
283 283 <%def name="usermenu(active=False)">
284 284 ## USER MENU
285 285 <li id="quick_login_li" class="${'active' if active else ''}">
286 286 <a id="quick_login_link" class="menulink childs">
287 287 ${gravatar(c.rhodecode_user.email, 20)}
288 288 <span class="user">
289 289 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 291 %else:
292 292 <span>${_('Sign in')}</span>
293 293 %endif
294 294 </span>
295 295 </a>
296 296
297 297 <div class="user-menu submenu">
298 298 <div id="quick_login">
299 299 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 300 <h4>${_('Sign in to your account')}</h4>
301 301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 302 <div class="form form-vertical">
303 303 <div class="fields">
304 304 <div class="field">
305 305 <div class="label">
306 306 <label for="username">${_('Username')}:</label>
307 307 </div>
308 308 <div class="input">
309 309 ${h.text('username',class_='focus',tabindex=1)}
310 310 </div>
311 311
312 312 </div>
313 313 <div class="field">
314 314 <div class="label">
315 315 <label for="password">${_('Password')}:</label>
316 316 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 318 %endif
319 319 </div>
320 320 <div class="input">
321 321 ${h.password('password',class_='focus',tabindex=2)}
322 322 </div>
323 323 </div>
324 324 <div class="buttons">
325 325 <div class="register">
326 326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 328 %endif
329 329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 330 </div>
331 331 <div class="submit">
332 332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 333 </div>
334 334 </div>
335 335 </div>
336 336 </div>
337 337 ${h.end_form()}
338 338 %else:
339 339 <div class="">
340 340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 342 <div class="email">${c.rhodecode_user.email}</div>
343 343 </div>
344 344 <div class="">
345 345 <ol class="links">
346 346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 347 % if c.rhodecode_user.personal_repo_group:
348 348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
349 349 % endif
350 350 <li class="logout">
351 351 ${h.secure_form(h.route_path('logout'), request=request)}
352 352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 353 ${h.end_form()}
354 354 </li>
355 355 </ol>
356 356 </div>
357 357 %endif
358 358 </div>
359 359 </div>
360 360 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 361 <div class="pill_container">
362 362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
363 363 </div>
364 364 % endif
365 365 </li>
366 366 </%def>
367 367
368 368 <%def name="menu_items(active=None)">
369 369 <%
370 370 def is_active(selected):
371 371 if selected == active:
372 372 return "active"
373 373 return ""
374 374 %>
375 375 <ul id="quick" class="main_nav navigation horizontal-list">
376 376 <!-- repo switcher -->
377 377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 378 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 379 </li>
380 380
381 381 ## ROOT MENU
382 382 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 383 <li class="${is_active('journal')}">
384 384 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
385 385 <div class="menulabel">${_('Journal')}</div>
386 386 </a>
387 387 </li>
388 388 %else:
389 389 <li class="${is_active('journal')}">
390 390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
391 391 <div class="menulabel">${_('Public journal')}</div>
392 392 </a>
393 393 </li>
394 394 %endif
395 395 <li class="${is_active('gists')}">
396 396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
397 397 <div class="menulabel">${_('Gists')}</div>
398 398 </a>
399 399 </li>
400 400 <li class="${is_active('search')}">
401 401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 402 <div class="menulabel">${_('Search')}</div>
403 403 </a>
404 404 </li>
405 405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 406 <li class="${is_active('admin')}">
407 407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 409 </a>
410 410 ${admin_menu()}
411 411 </li>
412 412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 413 <li class="${is_active('admin')}">
414 414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 416 </a>
417 417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 418 c.rhodecode_user.repository_groups_admin,
419 419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 420 </li>
421 421 % endif
422 422 % if c.debug_style:
423 423 <li class="${is_active('debug_style')}">
424 424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
425 425 <div class="menulabel">${_('Style')}</div>
426 426 </a>
427 427 </li>
428 428 % endif
429 429 ## render extra user menu
430 430 ${usermenu(active=(active=='my_account'))}
431 431 </ul>
432 432
433 433 <script type="text/javascript">
434 434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435 435
436 436 /*format the look of items in the list*/
437 437 var format = function(state, escapeMarkup){
438 438 if (!state.id){
439 439 return state.text; // optgroup
440 440 }
441 441 var obj_dict = state.obj;
442 442 var tmpl = '';
443 443
444 444 if(obj_dict && state.type == 'repo'){
445 445 if(obj_dict['repo_type'] === 'hg'){
446 446 tmpl += '<i class="icon-hg"></i> ';
447 447 }
448 448 else if(obj_dict['repo_type'] === 'git'){
449 449 tmpl += '<i class="icon-git"></i> ';
450 450 }
451 451 else if(obj_dict['repo_type'] === 'svn'){
452 452 tmpl += '<i class="icon-svn"></i> ';
453 453 }
454 454 if(obj_dict['private']){
455 455 tmpl += '<i class="icon-lock" ></i> ';
456 456 }
457 457 else if(visual_show_public_icon){
458 458 tmpl += '<i class="icon-unlock-alt"></i> ';
459 459 }
460 460 }
461 461 if(obj_dict && state.type == 'commit') {
462 462 tmpl += '<i class="icon-tag"></i>';
463 463 }
464 464 if(obj_dict && state.type == 'group'){
465 465 tmpl += '<i class="icon-folder-close"></i> ';
466 466 }
467 467 tmpl += escapeMarkup(state.text);
468 468 return tmpl;
469 469 };
470 470
471 471 var formatResult = function(result, container, query, escapeMarkup) {
472 472 return format(result, escapeMarkup);
473 473 };
474 474
475 475 var formatSelection = function(data, container, escapeMarkup) {
476 476 return format(data, escapeMarkup);
477 477 };
478 478
479 479 $("#repo_switcher").select2({
480 480 cachedDataSource: {},
481 481 minimumInputLength: 2,
482 482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 483 dropdownAutoWidth: true,
484 484 formatResult: formatResult,
485 485 formatSelection: formatSelection,
486 486 containerCssClass: "repo-switcher",
487 487 dropdownCssClass: "repo-switcher-dropdown",
488 488 escapeMarkup: function(m){
489 489 // don't escape our custom placeholder
490 490 if(m.substr(0,23) == '<div class="menulabel">'){
491 491 return m;
492 492 }
493 493
494 494 return Select2.util.escapeMarkup(m);
495 495 },
496 496 query: $.debounce(250, function(query){
497 497 self = this;
498 498 var cacheKey = query.term;
499 499 var cachedData = self.cachedDataSource[cacheKey];
500 500
501 501 if (cachedData) {
502 502 query.callback({results: cachedData.results});
503 503 } else {
504 504 $.ajax({
505 505 url: pyroutes.url('goto_switcher_data'),
506 506 data: {'query': query.term},
507 507 dataType: 'json',
508 508 type: 'GET',
509 509 success: function(data) {
510 510 self.cachedDataSource[cacheKey] = data;
511 511 query.callback({results: data.results});
512 512 },
513 513 error: function(data, textStatus, errorThrown) {
514 514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 515 }
516 516 })
517 517 }
518 518 })
519 519 });
520 520
521 521 $("#repo_switcher").on('select2-selecting', function(e){
522 522 e.preventDefault();
523 523 window.location = e.choice.url;
524 524 });
525 525
526 526 </script>
527 527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 528 </%def>
529 529
530 530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 531 <div class="modal-dialog">
532 532 <div class="modal-content">
533 533 <div class="modal-header">
534 534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 536 </div>
537 537 <div class="modal-body">
538 538 <div class="block-left">
539 539 <table class="keyboard-mappings">
540 540 <tbody>
541 541 <tr>
542 542 <th></th>
543 543 <th>${_('Site-wide shortcuts')}</th>
544 544 </tr>
545 545 <%
546 546 elems = [
547 547 ('/', 'Open quick search box'),
548 548 ('g h', 'Goto home page'),
549 549 ('g g', 'Goto my private gists page'),
550 550 ('g G', 'Goto my public gists page'),
551 551 ('n r', 'New repository page'),
552 552 ('n g', 'New gist page'),
553 553 ]
554 554 %>
555 555 %for key, desc in elems:
556 556 <tr>
557 557 <td class="keys">
558 558 <span class="key tag">${key}</span>
559 559 </td>
560 560 <td>${desc}</td>
561 561 </tr>
562 562 %endfor
563 563 </tbody>
564 564 </table>
565 565 </div>
566 566 <div class="block-left">
567 567 <table class="keyboard-mappings">
568 568 <tbody>
569 569 <tr>
570 570 <th></th>
571 571 <th>${_('Repositories')}</th>
572 572 </tr>
573 573 <%
574 574 elems = [
575 575 ('g s', 'Goto summary page'),
576 576 ('g c', 'Goto changelog page'),
577 577 ('g f', 'Goto files page'),
578 578 ('g F', 'Goto files page with file search activated'),
579 579 ('g p', 'Goto pull requests page'),
580 580 ('g o', 'Goto repository settings'),
581 581 ('g O', 'Goto repository permissions settings'),
582 582 ]
583 583 %>
584 584 %for key, desc in elems:
585 585 <tr>
586 586 <td class="keys">
587 587 <span class="key tag">${key}</span>
588 588 </td>
589 589 <td>${desc}</td>
590 590 </tr>
591 591 %endfor
592 592 </tbody>
593 593 </table>
594 594 </div>
595 595 </div>
596 596 <div class="modal-footer">
597 597 </div>
598 598 </div><!-- /.modal-content -->
599 599 </div><!-- /.modal-dialog -->
600 600 </div><!-- /.modal -->
@@ -1,156 +1,156 b''
1 1 ## snippet for displaying default permission box
2 2 ## usage:
3 3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
4 4 ## ${dpb.default_perms_box(<url_to_form>)}
5 5 ## ${dpb.default_perms_radios()}
6 6
7 7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
8 8 <div class="main-content-full-width">
9 9 <div class="panel panel-default">
10 10
11 11 ## displayed according to checkbox selection
12 12 <div class="panel-heading">
13 13 %if not global_permissions_template:
14 14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
15 15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
16 16 %else:
17 17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
18 18 %endif
19 19 </div>
20 20
21 21 <div class="panel-body">
22 22 %if global_permissions_template:
23 23 <p>${_('The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings.')}</p>
24 24 %endif
25 25 <div class="field">
26 26 <div class="label">
27 27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
28 28 </div>
29 29 <div class="radios">
30 30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
31 31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
32 32 <span class="help-block">${_('Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups.')}</span>
33 33 </div>
34 34 </div>
35 35 <div class="field">
36 36 <div class="label">
37 37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
38 38 </div>
39 39 <div class="radios">
40 40 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[1][0], label=c.repo_create_on_write_choices[1][1], **kwargs)}
41 41 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[0][0], label=c.repo_create_on_write_choices[0][1], **kwargs)}
42 42 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
43 43 </div>
44 44 </div>
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
48 48 </div>
49 49 <div class="radios">
50 50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
51 51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
52 52 <span class="help-block">${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')}</span>
53 53 </div>
54 54 </div>
55 55 <div class="field">
56 56 <div class="label">
57 57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
58 58 </div>
59 59 <div class="radios">
60 60 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[1][0], label=c.repo_group_create_choices[1][1], **kwargs)}
61 61 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[0][0], label=c.repo_group_create_choices[0][1], **kwargs)}
62 62 <span class="help-block">${_('Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups.')}</span>
63 63 </div>
64 64 </div>
65 65 <div class="field">
66 66 <div class="label">
67 67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
68 68 </div>
69 69 <div class="radios">
70 70 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[1][0], label=c.user_group_create_choices[1][1], **kwargs)}
71 71 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[0][0], label=c.user_group_create_choices[0][1], **kwargs)}
72 72 <span class="help-block">${_('Permission to allow user group creation.')}</span>
73 73 </div>
74 74 </div>
75 75
76 76 <div class="field">
77 77 <div class="label">
78 78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
79 79 </div>
80 80 <div class="radios">
81 81 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[1][0], label=c.inherit_default_permission_choices[1][1], **kwargs)}
82 82 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[0][0], label=c.inherit_default_permission_choices[0][1], **kwargs)}
83 83 <span class="help-block">${_('Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions.')}</span>
84 84 </div>
85 85 </div>
86 86
87 87 <div class="buttons">
88 88 ${h.submit('save',_('Save'),class_="btn")}
89 89 ${h.reset('reset',_('Reset'),class_="btn")}
90 90 </div>
91 91 </div>
92 92 </div>
93 93 </div>
94 94 </%def>
95 95
96 96 <%def name="default_perms_box(form_url)">
97 97 ${h.secure_form(form_url, method='put')}
98 98 <div class="form">
99 99 <div class="fields">
100 100 <div class="field panel panel-default panel-body">
101 101 <div class="label label-checkbox">
102 102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
103 103 </div>
104 104 <div class="checkboxes">
105 105 ${h.checkbox('inherit_default_permissions',value=True)}
106 106 <span class="help-block">
107 107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
108 108 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
109 % h.link_to('default user', h.url('admin_permissions_global')))}
109 % h.link_to('default user', h.route_path('admin_permissions_global')))}
110 110 </span>
111 111 </div>
112 112 </div>
113 113
114 114 ## INHERITED permissions == the user permissions in admin
115 115 ## if inherit checkbox is set this is displayed in non-edit mode
116 116 <div class="inherit_overlay_default">
117 117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
118 118 </div>
119 119
120 120 ## CUSTOM permissions
121 121 <div class="inherit_overlay">
122 122 ${default_perms_radios(global_permissions_template = False)}
123 123 </div>
124 124 </div>
125 125 </div>
126 126 ${h.end_form()}
127 127
128 128
129 129 ## JS
130 130 <script>
131 131 var show_custom_perms = function(inherit_default){
132 132 if(inherit_default) {
133 133 $('.inherit_overlay_default').show();
134 134 $('.inherit_overlay').hide();
135 135 }
136 136 else {
137 137 $('.inherit_overlay').show();
138 138 $('.inherit_overlay_default').hide();
139 139 }
140 140 };
141 141 $(document).ready(function(e){
142 142 var inherit_checkbox = $('#inherit_default_permissions');
143 143 var defaults = inherit_checkbox.prop('checked');
144 144 show_custom_perms(defaults);
145 145 inherit_checkbox.on('change', function(){
146 146 if($(this).prop('checked')){
147 147 show_custom_perms(true);
148 148 }
149 149 else{
150 150 show_custom_perms(false);
151 151 }
152 152 })
153 153 })
154 154 </script>
155 155
156 156 </%def>
@@ -1,207 +1,207 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
7 7 <div id="perms" class="table fields">
8 8 %for section in sorted(permissions.keys()):
9 9 <div class="panel panel-default">
10 10 <div class="panel-heading">
11 11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 12 </div>
13 13 <div class="panel-body">
14 14 <div class="perms_section_head field">
15 15 <div class="radios">
16 16 %if section != 'global':
17 17 <span class="permissions_boxes">
18 18 <span class="desc">${_('show')}: </span>
19 19 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
20 20 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
21 21 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
22 22 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
23 23 </span>
24 24 %endif
25 25 </div>
26 26 </div>
27 27 <div class="field">
28 28 %if not permissions[section]:
29 29 <p class="empty_data help-block">${_('No permissions defined')}</p>
30 30 %else:
31 31 <div id='tbl_list_wrap_${section}'>
32 32 <table id="tbl_list_${section}" class="rctable">
33 33 ## global permission box
34 34 %if section == 'global':
35 35 <thead>
36 36 <tr>
37 37 <th colspan="2" class="left">${_('Permission')}</th>
38 38 %if actions:
39 39 <th>${_('Edit Permission')}</th>
40 40 %endif
41 41 </thead>
42 42 <tbody>
43 43
44 44 <%
45 45 def get_section_perms(prefix, opts):
46 46 _selected = []
47 47 for op in opts:
48 48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
49 49 _selected.append(op)
50 50 admin = 'hg.admin' in opts
51 51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
52 52 return admin, _selected_vals, _selected
53 53 %>
54 54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
55 55 <tr>
56 56 <td class="td-tags">
57 57 ${lbl}
58 58 </td>
59 59 <td class="td-tags">
60 60 %if val[0]:
61 61 %if not val_lbl:
62 62 ${h.bool2icon(True)}
63 63 %else:
64 64 <span class="perm_tag admin">${val_lbl}.admin</span>
65 65 %endif
66 66 %else:
67 67 %if not val_lbl:
68 68 ${h.bool2icon({'false': False,
69 69 'true': True,
70 70 'none': False,
71 71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
72 72 %else:
73 73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
74 74 %endif
75 75 %endif
76 76 </td>
77 77 %if actions:
78 78 <td class="td-action">
79 <a href="${custom_url or h.url('admin_permissions_global')}">${_('edit')}</a>
79 <a href="${custom_url or h.route_path('admin_permissions_global')}">${_('edit')}</a>
80 80 </td>
81 81 %endif
82 82 </tr>
83 83 </%def>
84 84
85 85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
86 86
87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.url('admin_permissions_object'))}
88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.url('admin_permissions_object'))}
89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.url('admin_permissions_object'))}
87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.route_path('admin_permissions_object'))}
88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.route_path('admin_permissions_object'))}
89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.route_path('admin_permissions_object'))}
90 90
91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.route_path('admin_permissions_global'))}
95 95
96 96
97 97 </tbody>
98 98 %else:
99 99 ## none/read/write/admin permissions on groups/repos etc
100 100 <thead>
101 101 <tr>
102 102 <th>${_('Name')}</th>
103 103 <th>${_('Permission')}</th>
104 104 %if actions:
105 105 <th>${_('Edit Permission')}</th>
106 106 %endif
107 107 </thead>
108 108 <tbody class="section_${section}">
109 109 <%
110 110 def sorter(permissions):
111 111 def custom_sorter(item):
112 112 ## read/write/admin
113 113 section = item[1].split('.')[-1]
114 114 section_importance = {'none': u'0',
115 115 'read': u'1',
116 116 'write':u'2',
117 117 'admin':u'3'}.get(section)
118 118 ## sort by group importance+name
119 119 return section_importance+item[0]
120 120 return sorted(permissions, key=custom_sorter)
121 121 %>
122 122 %for k, section_perm in sorter(permissions[section].items()):
123 123 %if section_perm.split('.')[-1] != 'none' or show_all:
124 124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 125 <td class="td-componentname">
126 126 %if section == 'repositories':
127 127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
128 128 %elif section == 'repositories_groups':
129 129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
130 130 %elif section == 'user_groups':
131 131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 132 ${k}
133 133 %endif
134 134 </td>
135 135 <td class="td-tags">
136 136 %if hasattr(permissions[section], 'perm_origin_stack'):
137 137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
138 138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
139 139 ${perm} (${origin})
140 140 </span>
141 141 %endfor
142 142 %else:
143 143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
144 144 %endif
145 145 </td>
146 146 %if actions:
147 147 <td class="td-action">
148 148 %if section == 'repositories':
149 149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 150 %elif section == 'repositories_groups':
151 151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 152 %elif section == 'user_groups':
153 153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
154 154 %endif
155 155 </td>
156 156 %endif
157 157 </tr>
158 158 %endif
159 159 %endfor
160 160
161 161 <tr id="empty_${section}" class="noborder" style="display:none;">
162 162 <td colspan="6">${_('No permission defined')}</td>
163 163 </tr>
164 164
165 165 </tbody>
166 166 %endif
167 167 </table>
168 168 </div>
169 169 %endif
170 170 </div>
171 171 </div>
172 172 </div>
173 173 %endfor
174 174 </div>
175 175
176 176 <script>
177 177 $(document).ready(function(){
178 178 var show_empty = function(section){
179 179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
180 180 if(visible == 0){
181 181 $('#empty_{0}'.format(section)).show();
182 182 }
183 183 else{
184 184 $('#empty_{0}'.format(section)).hide();
185 185 }
186 186 };
187 187 $('.perm_filter').on('change', function(e){
188 188 var self = this;
189 189 var section = $(this).attr('section');
190 190
191 191 var opts = {};
192 192 var elems = $('.filter_' + section).each(function(el){
193 193 var perm_type = $(this).attr('perm_type');
194 194 var checked = this.checked;
195 195 opts[perm_type] = checked;
196 196 if(checked){
197 197 $('.'+section+'_'+perm_type).show();
198 198 }
199 199 else{
200 200 $('.'+section+'_'+perm_type).hide();
201 201 }
202 202 });
203 203 show_empty(section);
204 204 })
205 205 })
206 206 </script>
207 207 </%def>
@@ -1,265 +1,265 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 import time
23 23 import logging
24 24 import datetime
25 25 import hashlib
26 26 import tempfile
27 27 from os.path import join as jn
28 28
29 29 from tempfile import _RandomNameSequence
30 30
31 31 from paste.deploy import loadapp
32 32 from paste.script.appinstall import SetupCommand
33 33
34 34 import pylons
35 35 import pylons.test
36 36 from pylons import config, url
37 37 from pylons.i18n.translation import _get_translator
38 38 from pylons.util import ContextObj
39 39
40 40 from routes.util import URLGenerator
41 41 from nose.plugins.skip import SkipTest
42 42 import pytest
43 43
44 44 from rhodecode import is_windows
45 45 from rhodecode.config.routing import ADMIN_PREFIX
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.db import User
48 48 from rhodecode.lib import auth
49 from rhodecode.lib import helpers as h
49 50 from rhodecode.lib.helpers import flash, link_to
50 51 from rhodecode.lib.utils2 import safe_unicode, safe_str
51 52
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56 __all__ = [
56 57 'get_new_dir', 'TestController', 'SkipTest',
57 58 'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
58 59 'assert_session_flash', 'login_user', 'no_newline_id_generator',
59 60 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
60 61 'NEW_HG_REPO', 'NEW_GIT_REPO',
61 62 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
62 63 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
63 64 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
64 65 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
65 66 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
66 67 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
67 68 ]
68 69
69 70 # Invoke websetup with the current config file
70 71 # SetupCommand('setup-app').run([config_file])
71 72
72 73 # SOME GLOBALS FOR TESTS
73 74 TEST_DIR = tempfile.gettempdir()
74 75
75 76 TESTS_TMP_PATH = jn(TEST_DIR, 'rc_test_%s' % _RandomNameSequence().next())
76 77 TEST_USER_ADMIN_LOGIN = 'test_admin'
77 78 TEST_USER_ADMIN_PASS = 'test12'
78 79 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
79 80
80 81 TEST_USER_REGULAR_LOGIN = 'test_regular'
81 82 TEST_USER_REGULAR_PASS = 'test12'
82 83 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
83 84
84 85 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
85 86 TEST_USER_REGULAR2_PASS = 'test12'
86 87 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
87 88
88 89 HG_REPO = 'vcs_test_hg'
89 90 GIT_REPO = 'vcs_test_git'
90 91 SVN_REPO = 'vcs_test_svn'
91 92
92 93 NEW_HG_REPO = 'vcs_test_hg_new'
93 94 NEW_GIT_REPO = 'vcs_test_git_new'
94 95
95 96 HG_FORK = 'vcs_test_hg_fork'
96 97 GIT_FORK = 'vcs_test_git_fork'
97 98
98 99 ## VCS
99 100 SCM_TESTS = ['hg', 'git']
100 101 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
101 102
102 103 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
103 104 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
104 105 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
105 106
106 107 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
107 108 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
108 109 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
109 110
110 111 TEST_REPO_PREFIX = 'vcs-test'
111 112
112 113
113 114 # skip ldap tests if LDAP lib is not installed
114 115 ldap_lib_installed = False
115 116 try:
116 117 import ldap
117 118 ldap_lib_installed = True
118 119 except ImportError:
119 120 # means that python-ldap is not installed
120 121 pass
121 122
122 123
123 124 def clear_all_caches():
124 125 from beaker.cache import cache_managers
125 126 for _cache in cache_managers.values():
126 127 _cache.clear()
127 128
128 129
129 130 def get_new_dir(title):
130 131 """
131 132 Returns always new directory path.
132 133 """
133 134 from rhodecode.tests.vcs.utils import get_normalized_path
134 135 name_parts = [TEST_REPO_PREFIX]
135 136 if title:
136 137 name_parts.append(title)
137 138 hex_str = hashlib.sha1('%s %s' % (os.getpid(), time.time())).hexdigest()
138 139 name_parts.append(hex_str)
139 140 name = '-'.join(name_parts)
140 141 path = os.path.join(TEST_DIR, name)
141 142 return get_normalized_path(path)
142 143
143 144
144 145 @pytest.mark.usefixtures('app', 'index_location')
145 146 class TestController(object):
146 147
147 148 maxDiff = None
148 149
149 150 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
150 151 password=TEST_USER_ADMIN_PASS):
151 152 self._logged_username = username
152 153 self._session = login_user_session(self.app, username, password)
153 154 self.csrf_token = auth.get_csrf_token(self._session)
154 155
155 156 return self._session['rhodecode_user']
156 157
157 158 def logout_user(self):
158 159 logout_user_session(self.app, auth.get_csrf_token(self._session))
159 160 self.csrf_token = None
160 161 self._logged_username = None
161 162 self._session = None
162 163
163 164 def _get_logged_user(self):
164 165 return User.get_by_username(self._logged_username)
165 166
166 167
167 168 def login_user_session(
168 169 app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
169 from rhodecode.tests.functional.test_login import login_url
170
170 171 response = app.post(
171 login_url,
172 h.route_path('login'),
172 173 {'username': username, 'password': password})
173 174 if 'invalid user name' in response.body:
174 175 pytest.fail('could not login using %s %s' % (username, password))
175 176
176 177 assert response.status == '302 Found'
177 178 response = response.follow()
178 179 assert response.status == '200 OK'
179 180
180 181 session = response.get_session_from_response()
181 182 assert 'rhodecode_user' in session
182 183 rc_user = session['rhodecode_user']
183 184 assert rc_user.get('username') == username
184 185 assert rc_user.get('is_authenticated')
185 186
186 187 return session
187 188
188 189
189 190 def logout_user_session(app, csrf_token):
190 from rhodecode.tests.functional.test_login import logut_url
191 app.post(logut_url, {'csrf_token': csrf_token}, status=302)
191 app.post(h.route_path('logout'), {'csrf_token': csrf_token}, status=302)
192 192
193 193
194 194 def login_user(app, username=TEST_USER_ADMIN_LOGIN,
195 195 password=TEST_USER_ADMIN_PASS):
196 196 return login_user_session(app, username, password)['rhodecode_user']
197 197
198 198
199 199 def assert_session_flash(response=None, msg=None, category=None, no_=None):
200 200 """
201 201 Assert on a flash message in the current session.
202 202
203 203 :param msg: Required. The expected message. Will be evaluated if a
204 204 :class:`LazyString` is passed in.
205 205 :param response: Optional. For functional testing, pass in the response
206 206 object. Otherwise don't pass in any value.
207 207 :param category: Optional. If passed, the message category will be
208 208 checked as well.
209 209 :param no_: Optional. If passed, the message will be checked to NOT be in the
210 210 flash session
211 211 """
212 212 if msg is None and no_ is None:
213 213 raise ValueError("Parameter msg or no_ is required.")
214 214
215 215 if msg and no_:
216 216 raise ValueError("Please specify either msg or no_, but not both")
217 217
218 218 messages = flash.pop_messages()
219 219 msg = _eval_if_lazy(msg)
220 220
221 221 assert messages, 'unable to find message `%s` in empty flash list' % msg
222 222 message = messages[0]
223 223
224 224 message_text = _eval_if_lazy(message.message) or ''
225 225
226 226 if no_:
227 227 if no_ in message_text:
228 228 msg = u'msg `%s` found in session flash.' % (no_,)
229 229 pytest.fail(safe_str(msg))
230 230 else:
231 231 if msg not in message_text:
232 232 fail_msg = u'msg `%s` not found in session ' \
233 233 u'flash: got `%s` (type:%s) instead' % (
234 234 msg, message_text, type(message_text))
235 235
236 236 pytest.fail(safe_str(fail_msg))
237 237 if category:
238 238 assert category == message.category
239 239
240 240
241 241 def _eval_if_lazy(value):
242 242 return value.eval() if hasattr(value, 'eval') else value
243 243
244 244
245 245 def assert_session_flash_is_empty(response):
246 246 assert 'flash' in response.session, 'Response session has no flash key'
247 247
248 248 msg = 'flash messages are present in session:%s' % \
249 249 response.session['flash'][0]
250 250 pytest.fail(safe_str(msg))
251 251
252 252
253 253 def no_newline_id_generator(test_name):
254 254 """
255 255 Generates a test name without spaces or newlines characters. Used for
256 256 nicer output of progress of test
257 257 """
258 258 org_name = test_name
259 259 test_name = test_name\
260 260 .replace('\n', '_N') \
261 261 .replace('\r', '_N') \
262 262 .replace('\t', '_T') \
263 263 .replace(' ', '_S')
264 264
265 265 return test_name or 'test-with-empty-name'
@@ -1,1107 +1,1106 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 from webob.exc import HTTPNotFound
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.vcs.nodes import FileNode
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.model.changeset_status import ChangesetStatusModel
29 29 from rhodecode.model.db import (
30 30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
31 31 from rhodecode.model.meta import Session
32 32 from rhodecode.model.pull_request import PullRequestModel
33 33 from rhodecode.model.user import UserModel
34 34 from rhodecode.tests import (
35 35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 36 from rhodecode.tests.utils import AssertResponse
37 37
38 38
39 39 def route_path(name, params=None, **kwargs):
40 40 import urllib
41 41
42 42 base_url = {
43 43 'repo_changelog':'/{repo_name}/changelog',
44 44 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}',
45 45 }[name].format(**kwargs)
46 46
47 47 if params:
48 48 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
49 49 return base_url
50 50
51 51
52 52 @pytest.mark.usefixtures('app', 'autologin_user')
53 53 @pytest.mark.backends("git", "hg")
54 54 class TestPullrequestsController(object):
55 55
56 56 def test_index(self, backend):
57 57 self.app.get(url(
58 58 controller='pullrequests', action='index',
59 59 repo_name=backend.repo_name))
60 60
61 61 def test_option_menu_create_pull_request_exists(self, backend):
62 62 repo_name = backend.repo_name
63 63 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
64 64
65 65 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
66 66 'pullrequest', repo_name=repo_name)
67 67 response.mustcontain(create_pr_link)
68 68
69 69 def test_create_pr_form_with_raw_commit_id(self, backend):
70 70 repo = backend.repo
71 71
72 72 self.app.get(
73 73 url(controller='pullrequests', action='index',
74 74 repo_name=repo.repo_name,
75 75 commit=repo.get_commit().raw_id),
76 76 status=200)
77 77
78 78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
79 79 def test_show(self, pr_util, pr_merge_enabled):
80 80 pull_request = pr_util.create_pull_request(
81 81 mergeable=pr_merge_enabled, enable_notifications=False)
82 82
83 83 response = self.app.get(url(
84 84 controller='pullrequests', action='show',
85 85 repo_name=pull_request.target_repo.scm_instance().name,
86 86 pull_request_id=str(pull_request.pull_request_id)))
87 87
88 88 for commit_id in pull_request.revisions:
89 89 response.mustcontain(commit_id)
90 90
91 91 assert pull_request.target_ref_parts.type in response
92 92 assert pull_request.target_ref_parts.name in response
93 93 target_clone_url = pull_request.target_repo.clone_url()
94 94 assert target_clone_url in response
95 95
96 96 assert 'class="pull-request-merge"' in response
97 97 assert (
98 98 'Server-side pull request merging is disabled.'
99 99 in response) != pr_merge_enabled
100 100
101 101 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
102 from rhodecode.tests.functional.test_login import login_url, logut_url
103 102 # Logout
104 103 response = self.app.post(
105 logut_url,
104 h.route_path('logout'),
106 105 params={'csrf_token': csrf_token})
107 106 # Login as regular user
108 response = self.app.post(login_url,
107 response = self.app.post(h.route_path('login'),
109 108 {'username': TEST_USER_REGULAR_LOGIN,
110 109 'password': 'test12'})
111 110
112 111 pull_request = pr_util.create_pull_request(
113 112 author=TEST_USER_REGULAR_LOGIN)
114 113
115 114 response = self.app.get(url(
116 115 controller='pullrequests', action='show',
117 116 repo_name=pull_request.target_repo.scm_instance().name,
118 117 pull_request_id=str(pull_request.pull_request_id)))
119 118
120 119 response.mustcontain('Server-side pull request merging is disabled.')
121 120
122 121 assert_response = response.assert_response()
123 122 # for regular user without a merge permissions, we don't see it
124 123 assert_response.no_element_exists('#close-pull-request-action')
125 124
126 125 user_util.grant_user_permission_to_repo(
127 126 pull_request.target_repo,
128 127 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
129 128 'repository.write')
130 129 response = self.app.get(url(
131 130 controller='pullrequests', action='show',
132 131 repo_name=pull_request.target_repo.scm_instance().name,
133 132 pull_request_id=str(pull_request.pull_request_id)))
134 133
135 134 response.mustcontain('Server-side pull request merging is disabled.')
136 135
137 136 assert_response = response.assert_response()
138 137 # now regular user has a merge permissions, we have CLOSE button
139 138 assert_response.one_element_exists('#close-pull-request-action')
140 139
141 140 def test_show_invalid_commit_id(self, pr_util):
142 141 # Simulating invalid revisions which will cause a lookup error
143 142 pull_request = pr_util.create_pull_request()
144 143 pull_request.revisions = ['invalid']
145 144 Session().add(pull_request)
146 145 Session().commit()
147 146
148 147 response = self.app.get(url(
149 148 controller='pullrequests', action='show',
150 149 repo_name=pull_request.target_repo.scm_instance().name,
151 150 pull_request_id=str(pull_request.pull_request_id)))
152 151
153 152 for commit_id in pull_request.revisions:
154 153 response.mustcontain(commit_id)
155 154
156 155 def test_show_invalid_source_reference(self, pr_util):
157 156 pull_request = pr_util.create_pull_request()
158 157 pull_request.source_ref = 'branch:b:invalid'
159 158 Session().add(pull_request)
160 159 Session().commit()
161 160
162 161 self.app.get(url(
163 162 controller='pullrequests', action='show',
164 163 repo_name=pull_request.target_repo.scm_instance().name,
165 164 pull_request_id=str(pull_request.pull_request_id)))
166 165
167 166 def test_edit_title_description(self, pr_util, csrf_token):
168 167 pull_request = pr_util.create_pull_request()
169 168 pull_request_id = pull_request.pull_request_id
170 169
171 170 response = self.app.post(
172 171 url(controller='pullrequests', action='update',
173 172 repo_name=pull_request.target_repo.repo_name,
174 173 pull_request_id=str(pull_request_id)),
175 174 params={
176 175 'edit_pull_request': 'true',
177 176 '_method': 'put',
178 177 'title': 'New title',
179 178 'description': 'New description',
180 179 'csrf_token': csrf_token})
181 180
182 181 assert_session_flash(
183 182 response, u'Pull request title & description updated.',
184 183 category='success')
185 184
186 185 pull_request = PullRequest.get(pull_request_id)
187 186 assert pull_request.title == 'New title'
188 187 assert pull_request.description == 'New description'
189 188
190 189 def test_edit_title_description_closed(self, pr_util, csrf_token):
191 190 pull_request = pr_util.create_pull_request()
192 191 pull_request_id = pull_request.pull_request_id
193 192 pr_util.close()
194 193
195 194 response = self.app.post(
196 195 url(controller='pullrequests', action='update',
197 196 repo_name=pull_request.target_repo.repo_name,
198 197 pull_request_id=str(pull_request_id)),
199 198 params={
200 199 'edit_pull_request': 'true',
201 200 '_method': 'put',
202 201 'title': 'New title',
203 202 'description': 'New description',
204 203 'csrf_token': csrf_token})
205 204
206 205 assert_session_flash(
207 206 response, u'Cannot update closed pull requests.',
208 207 category='error')
209 208
210 209 def test_update_invalid_source_reference(self, pr_util, csrf_token):
211 210 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
212 211
213 212 pull_request = pr_util.create_pull_request()
214 213 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
215 214 Session().add(pull_request)
216 215 Session().commit()
217 216
218 217 pull_request_id = pull_request.pull_request_id
219 218
220 219 response = self.app.post(
221 220 url(controller='pullrequests', action='update',
222 221 repo_name=pull_request.target_repo.repo_name,
223 222 pull_request_id=str(pull_request_id)),
224 223 params={'update_commits': 'true', '_method': 'put',
225 224 'csrf_token': csrf_token})
226 225
227 226 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
228 227 UpdateFailureReason.MISSING_SOURCE_REF]
229 228 assert_session_flash(response, expected_msg, category='error')
230 229
231 230 def test_missing_target_reference(self, pr_util, csrf_token):
232 231 from rhodecode.lib.vcs.backends.base import MergeFailureReason
233 232 pull_request = pr_util.create_pull_request(
234 233 approved=True, mergeable=True)
235 234 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
236 235 Session().add(pull_request)
237 236 Session().commit()
238 237
239 238 pull_request_id = pull_request.pull_request_id
240 239 pull_request_url = url(
241 240 controller='pullrequests', action='show',
242 241 repo_name=pull_request.target_repo.repo_name,
243 242 pull_request_id=str(pull_request_id))
244 243
245 244 response = self.app.get(pull_request_url)
246 245
247 246 assertr = AssertResponse(response)
248 247 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
249 248 MergeFailureReason.MISSING_TARGET_REF]
250 249 assertr.element_contains(
251 250 'span[data-role="merge-message"]', str(expected_msg))
252 251
253 252 def test_comment_and_close_pull_request_custom_message_approved(
254 253 self, pr_util, csrf_token, xhr_header):
255 254
256 255 pull_request = pr_util.create_pull_request(approved=True)
257 256 pull_request_id = pull_request.pull_request_id
258 257 author = pull_request.user_id
259 258 repo = pull_request.target_repo.repo_id
260 259
261 260 self.app.post(
262 261 url(controller='pullrequests',
263 262 action='comment',
264 263 repo_name=pull_request.target_repo.scm_instance().name,
265 264 pull_request_id=str(pull_request_id)),
266 265 params={
267 266 'close_pull_request': '1',
268 267 'text': 'Closing a PR',
269 268 'csrf_token': csrf_token},
270 269 extra_environ=xhr_header,)
271 270
272 271 journal = UserLog.query()\
273 272 .filter(UserLog.user_id == author)\
274 273 .filter(UserLog.repository_id == repo) \
275 274 .order_by('user_log_id') \
276 275 .all()
277 276 assert journal[-1].action == 'repo.pull_request.close'
278 277
279 278 pull_request = PullRequest.get(pull_request_id)
280 279 assert pull_request.is_closed()
281 280
282 281 status = ChangesetStatusModel().get_status(
283 282 pull_request.source_repo, pull_request=pull_request)
284 283 assert status == ChangesetStatus.STATUS_APPROVED
285 284 comments = ChangesetComment().query() \
286 285 .filter(ChangesetComment.pull_request == pull_request) \
287 286 .order_by(ChangesetComment.comment_id.asc())\
288 287 .all()
289 288 assert comments[-1].text == 'Closing a PR'
290 289
291 290 def test_comment_force_close_pull_request_rejected(
292 291 self, pr_util, csrf_token, xhr_header):
293 292 pull_request = pr_util.create_pull_request()
294 293 pull_request_id = pull_request.pull_request_id
295 294 PullRequestModel().update_reviewers(
296 295 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
297 296 pull_request.author)
298 297 author = pull_request.user_id
299 298 repo = pull_request.target_repo.repo_id
300 299
301 300 self.app.post(
302 301 url(controller='pullrequests',
303 302 action='comment',
304 303 repo_name=pull_request.target_repo.scm_instance().name,
305 304 pull_request_id=str(pull_request_id)),
306 305 params={
307 306 'close_pull_request': '1',
308 307 'csrf_token': csrf_token},
309 308 extra_environ=xhr_header)
310 309
311 310 pull_request = PullRequest.get(pull_request_id)
312 311
313 312 journal = UserLog.query()\
314 313 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
315 314 .order_by('user_log_id') \
316 315 .all()
317 316 assert journal[-1].action == 'repo.pull_request.close'
318 317
319 318 # check only the latest status, not the review status
320 319 status = ChangesetStatusModel().get_status(
321 320 pull_request.source_repo, pull_request=pull_request)
322 321 assert status == ChangesetStatus.STATUS_REJECTED
323 322
324 323 def test_comment_and_close_pull_request(
325 324 self, pr_util, csrf_token, xhr_header):
326 325 pull_request = pr_util.create_pull_request()
327 326 pull_request_id = pull_request.pull_request_id
328 327
329 328 response = self.app.post(
330 329 url(controller='pullrequests',
331 330 action='comment',
332 331 repo_name=pull_request.target_repo.scm_instance().name,
333 332 pull_request_id=str(pull_request.pull_request_id)),
334 333 params={
335 334 'close_pull_request': 'true',
336 335 'csrf_token': csrf_token},
337 336 extra_environ=xhr_header)
338 337
339 338 assert response.json
340 339
341 340 pull_request = PullRequest.get(pull_request_id)
342 341 assert pull_request.is_closed()
343 342
344 343 # check only the latest status, not the review status
345 344 status = ChangesetStatusModel().get_status(
346 345 pull_request.source_repo, pull_request=pull_request)
347 346 assert status == ChangesetStatus.STATUS_REJECTED
348 347
349 348 def test_create_pull_request(self, backend, csrf_token):
350 349 commits = [
351 350 {'message': 'ancestor'},
352 351 {'message': 'change'},
353 352 {'message': 'change2'},
354 353 ]
355 354 commit_ids = backend.create_master_repo(commits)
356 355 target = backend.create_repo(heads=['ancestor'])
357 356 source = backend.create_repo(heads=['change2'])
358 357
359 358 response = self.app.post(
360 359 url(
361 360 controller='pullrequests',
362 361 action='create',
363 362 repo_name=source.repo_name
364 363 ),
365 364 [
366 365 ('source_repo', source.repo_name),
367 366 ('source_ref', 'branch:default:' + commit_ids['change2']),
368 367 ('target_repo', target.repo_name),
369 368 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
370 369 ('common_ancestor', commit_ids['ancestor']),
371 370 ('pullrequest_desc', 'Description'),
372 371 ('pullrequest_title', 'Title'),
373 372 ('__start__', 'review_members:sequence'),
374 373 ('__start__', 'reviewer:mapping'),
375 374 ('user_id', '1'),
376 375 ('__start__', 'reasons:sequence'),
377 376 ('reason', 'Some reason'),
378 377 ('__end__', 'reasons:sequence'),
379 378 ('mandatory', 'False'),
380 379 ('__end__', 'reviewer:mapping'),
381 380 ('__end__', 'review_members:sequence'),
382 381 ('__start__', 'revisions:sequence'),
383 382 ('revisions', commit_ids['change']),
384 383 ('revisions', commit_ids['change2']),
385 384 ('__end__', 'revisions:sequence'),
386 385 ('user', ''),
387 386 ('csrf_token', csrf_token),
388 387 ],
389 388 status=302)
390 389
391 390 location = response.headers['Location']
392 391 pull_request_id = location.rsplit('/', 1)[1]
393 392 assert pull_request_id != 'new'
394 393 pull_request = PullRequest.get(int(pull_request_id))
395 394
396 395 # check that we have now both revisions
397 396 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
398 397 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
399 398 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
400 399 assert pull_request.target_ref == expected_target_ref
401 400
402 401 def test_reviewer_notifications(self, backend, csrf_token):
403 402 # We have to use the app.post for this test so it will create the
404 403 # notifications properly with the new PR
405 404 commits = [
406 405 {'message': 'ancestor',
407 406 'added': [FileNode('file_A', content='content_of_ancestor')]},
408 407 {'message': 'change',
409 408 'added': [FileNode('file_a', content='content_of_change')]},
410 409 {'message': 'change-child'},
411 410 {'message': 'ancestor-child', 'parents': ['ancestor'],
412 411 'added': [
413 412 FileNode('file_B', content='content_of_ancestor_child')]},
414 413 {'message': 'ancestor-child-2'},
415 414 ]
416 415 commit_ids = backend.create_master_repo(commits)
417 416 target = backend.create_repo(heads=['ancestor-child'])
418 417 source = backend.create_repo(heads=['change'])
419 418
420 419 response = self.app.post(
421 420 url(
422 421 controller='pullrequests',
423 422 action='create',
424 423 repo_name=source.repo_name
425 424 ),
426 425 [
427 426 ('source_repo', source.repo_name),
428 427 ('source_ref', 'branch:default:' + commit_ids['change']),
429 428 ('target_repo', target.repo_name),
430 429 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
431 430 ('common_ancestor', commit_ids['ancestor']),
432 431 ('pullrequest_desc', 'Description'),
433 432 ('pullrequest_title', 'Title'),
434 433 ('__start__', 'review_members:sequence'),
435 434 ('__start__', 'reviewer:mapping'),
436 435 ('user_id', '2'),
437 436 ('__start__', 'reasons:sequence'),
438 437 ('reason', 'Some reason'),
439 438 ('__end__', 'reasons:sequence'),
440 439 ('mandatory', 'False'),
441 440 ('__end__', 'reviewer:mapping'),
442 441 ('__end__', 'review_members:sequence'),
443 442 ('__start__', 'revisions:sequence'),
444 443 ('revisions', commit_ids['change']),
445 444 ('__end__', 'revisions:sequence'),
446 445 ('user', ''),
447 446 ('csrf_token', csrf_token),
448 447 ],
449 448 status=302)
450 449
451 450 location = response.headers['Location']
452 451
453 452 pull_request_id = location.rsplit('/', 1)[1]
454 453 assert pull_request_id != 'new'
455 454 pull_request = PullRequest.get(int(pull_request_id))
456 455
457 456 # Check that a notification was made
458 457 notifications = Notification.query()\
459 458 .filter(Notification.created_by == pull_request.author.user_id,
460 459 Notification.type_ == Notification.TYPE_PULL_REQUEST,
461 460 Notification.subject.contains(
462 461 "wants you to review pull request #%s" % pull_request_id))
463 462 assert len(notifications.all()) == 1
464 463
465 464 # Change reviewers and check that a notification was made
466 465 PullRequestModel().update_reviewers(
467 466 pull_request.pull_request_id, [(1, [], False)],
468 467 pull_request.author)
469 468 assert len(notifications.all()) == 2
470 469
471 470 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
472 471 csrf_token):
473 472 commits = [
474 473 {'message': 'ancestor',
475 474 'added': [FileNode('file_A', content='content_of_ancestor')]},
476 475 {'message': 'change',
477 476 'added': [FileNode('file_a', content='content_of_change')]},
478 477 {'message': 'change-child'},
479 478 {'message': 'ancestor-child', 'parents': ['ancestor'],
480 479 'added': [
481 480 FileNode('file_B', content='content_of_ancestor_child')]},
482 481 {'message': 'ancestor-child-2'},
483 482 ]
484 483 commit_ids = backend.create_master_repo(commits)
485 484 target = backend.create_repo(heads=['ancestor-child'])
486 485 source = backend.create_repo(heads=['change'])
487 486
488 487 response = self.app.post(
489 488 url(
490 489 controller='pullrequests',
491 490 action='create',
492 491 repo_name=source.repo_name
493 492 ),
494 493 [
495 494 ('source_repo', source.repo_name),
496 495 ('source_ref', 'branch:default:' + commit_ids['change']),
497 496 ('target_repo', target.repo_name),
498 497 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
499 498 ('common_ancestor', commit_ids['ancestor']),
500 499 ('pullrequest_desc', 'Description'),
501 500 ('pullrequest_title', 'Title'),
502 501 ('__start__', 'review_members:sequence'),
503 502 ('__start__', 'reviewer:mapping'),
504 503 ('user_id', '1'),
505 504 ('__start__', 'reasons:sequence'),
506 505 ('reason', 'Some reason'),
507 506 ('__end__', 'reasons:sequence'),
508 507 ('mandatory', 'False'),
509 508 ('__end__', 'reviewer:mapping'),
510 509 ('__end__', 'review_members:sequence'),
511 510 ('__start__', 'revisions:sequence'),
512 511 ('revisions', commit_ids['change']),
513 512 ('__end__', 'revisions:sequence'),
514 513 ('user', ''),
515 514 ('csrf_token', csrf_token),
516 515 ],
517 516 status=302)
518 517
519 518 location = response.headers['Location']
520 519
521 520 pull_request_id = location.rsplit('/', 1)[1]
522 521 assert pull_request_id != 'new'
523 522 pull_request = PullRequest.get(int(pull_request_id))
524 523
525 524 # target_ref has to point to the ancestor's commit_id in order to
526 525 # show the correct diff
527 526 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
528 527 assert pull_request.target_ref == expected_target_ref
529 528
530 529 # Check generated diff contents
531 530 response = response.follow()
532 531 assert 'content_of_ancestor' not in response.body
533 532 assert 'content_of_ancestor-child' not in response.body
534 533 assert 'content_of_change' in response.body
535 534
536 535 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
537 536 # Clear any previous calls to rcextensions
538 537 rhodecode.EXTENSIONS.calls.clear()
539 538
540 539 pull_request = pr_util.create_pull_request(
541 540 approved=True, mergeable=True)
542 541 pull_request_id = pull_request.pull_request_id
543 542 repo_name = pull_request.target_repo.scm_instance().name,
544 543
545 544 response = self.app.post(
546 545 url(controller='pullrequests',
547 546 action='merge',
548 547 repo_name=str(repo_name[0]),
549 548 pull_request_id=str(pull_request_id)),
550 549 params={'csrf_token': csrf_token}).follow()
551 550
552 551 pull_request = PullRequest.get(pull_request_id)
553 552
554 553 assert response.status_int == 200
555 554 assert pull_request.is_closed()
556 555 assert_pull_request_status(
557 556 pull_request, ChangesetStatus.STATUS_APPROVED)
558 557
559 558 # Check the relevant log entries were added
560 559 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
561 560 actions = [log.action for log in user_logs]
562 561 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
563 562 expected_actions = [
564 563 u'repo.pull_request.close',
565 564 u'repo.pull_request.merge',
566 565 u'repo.pull_request.comment.create'
567 566 ]
568 567 assert actions == expected_actions
569 568
570 569 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
571 570 actions = [log for log in user_logs]
572 571 assert actions[-1].action == 'user.push'
573 572 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
574 573
575 574 # Check post_push rcextension was really executed
576 575 push_calls = rhodecode.EXTENSIONS.calls['post_push']
577 576 assert len(push_calls) == 1
578 577 unused_last_call_args, last_call_kwargs = push_calls[0]
579 578 assert last_call_kwargs['action'] == 'push'
580 579 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
581 580
582 581 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
583 582 pull_request = pr_util.create_pull_request(mergeable=False)
584 583 pull_request_id = pull_request.pull_request_id
585 584 pull_request = PullRequest.get(pull_request_id)
586 585
587 586 response = self.app.post(
588 587 url(controller='pullrequests',
589 588 action='merge',
590 589 repo_name=pull_request.target_repo.scm_instance().name,
591 590 pull_request_id=str(pull_request.pull_request_id)),
592 591 params={'csrf_token': csrf_token}).follow()
593 592
594 593 assert response.status_int == 200
595 594 response.mustcontain(
596 595 'Merge is not currently possible because of below failed checks.')
597 596 response.mustcontain('Server-side pull request merging is disabled.')
598 597
599 598 @pytest.mark.skip_backends('svn')
600 599 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
601 600 pull_request = pr_util.create_pull_request(mergeable=True)
602 601 pull_request_id = pull_request.pull_request_id
603 602 repo_name = pull_request.target_repo.scm_instance().name,
604 603
605 604 response = self.app.post(
606 605 url(controller='pullrequests',
607 606 action='merge',
608 607 repo_name=str(repo_name[0]),
609 608 pull_request_id=str(pull_request_id)),
610 609 params={'csrf_token': csrf_token}).follow()
611 610
612 611 assert response.status_int == 200
613 612
614 613 response.mustcontain(
615 614 'Merge is not currently possible because of below failed checks.')
616 615 response.mustcontain('Pull request reviewer approval is pending.')
617 616
618 617 def test_update_source_revision(self, backend, csrf_token):
619 618 commits = [
620 619 {'message': 'ancestor'},
621 620 {'message': 'change'},
622 621 {'message': 'change-2'},
623 622 ]
624 623 commit_ids = backend.create_master_repo(commits)
625 624 target = backend.create_repo(heads=['ancestor'])
626 625 source = backend.create_repo(heads=['change'])
627 626
628 627 # create pr from a in source to A in target
629 628 pull_request = PullRequest()
630 629 pull_request.source_repo = source
631 630 # TODO: johbo: Make sure that we write the source ref this way!
632 631 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
633 632 branch=backend.default_branch_name, commit_id=commit_ids['change'])
634 633 pull_request.target_repo = target
635 634
636 635 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
637 636 branch=backend.default_branch_name,
638 637 commit_id=commit_ids['ancestor'])
639 638 pull_request.revisions = [commit_ids['change']]
640 639 pull_request.title = u"Test"
641 640 pull_request.description = u"Description"
642 641 pull_request.author = UserModel().get_by_username(
643 642 TEST_USER_ADMIN_LOGIN)
644 643 Session().add(pull_request)
645 644 Session().commit()
646 645 pull_request_id = pull_request.pull_request_id
647 646
648 647 # source has ancestor - change - change-2
649 648 backend.pull_heads(source, heads=['change-2'])
650 649
651 650 # update PR
652 651 self.app.post(
653 652 url(controller='pullrequests', action='update',
654 653 repo_name=target.repo_name,
655 654 pull_request_id=str(pull_request_id)),
656 655 params={'update_commits': 'true', '_method': 'put',
657 656 'csrf_token': csrf_token})
658 657
659 658 # check that we have now both revisions
660 659 pull_request = PullRequest.get(pull_request_id)
661 660 assert pull_request.revisions == [
662 661 commit_ids['change-2'], commit_ids['change']]
663 662
664 663 # TODO: johbo: this should be a test on its own
665 664 response = self.app.get(url(
666 665 controller='pullrequests', action='index',
667 666 repo_name=target.repo_name))
668 667 assert response.status_int == 200
669 668 assert 'Pull request updated to' in response.body
670 669 assert 'with 1 added, 0 removed commits.' in response.body
671 670
672 671 def test_update_target_revision(self, backend, csrf_token):
673 672 commits = [
674 673 {'message': 'ancestor'},
675 674 {'message': 'change'},
676 675 {'message': 'ancestor-new', 'parents': ['ancestor']},
677 676 {'message': 'change-rebased'},
678 677 ]
679 678 commit_ids = backend.create_master_repo(commits)
680 679 target = backend.create_repo(heads=['ancestor'])
681 680 source = backend.create_repo(heads=['change'])
682 681
683 682 # create pr from a in source to A in target
684 683 pull_request = PullRequest()
685 684 pull_request.source_repo = source
686 685 # TODO: johbo: Make sure that we write the source ref this way!
687 686 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
688 687 branch=backend.default_branch_name, commit_id=commit_ids['change'])
689 688 pull_request.target_repo = target
690 689 # TODO: johbo: Target ref should be branch based, since tip can jump
691 690 # from branch to branch
692 691 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
693 692 branch=backend.default_branch_name,
694 693 commit_id=commit_ids['ancestor'])
695 694 pull_request.revisions = [commit_ids['change']]
696 695 pull_request.title = u"Test"
697 696 pull_request.description = u"Description"
698 697 pull_request.author = UserModel().get_by_username(
699 698 TEST_USER_ADMIN_LOGIN)
700 699 Session().add(pull_request)
701 700 Session().commit()
702 701 pull_request_id = pull_request.pull_request_id
703 702
704 703 # target has ancestor - ancestor-new
705 704 # source has ancestor - ancestor-new - change-rebased
706 705 backend.pull_heads(target, heads=['ancestor-new'])
707 706 backend.pull_heads(source, heads=['change-rebased'])
708 707
709 708 # update PR
710 709 self.app.post(
711 710 url(controller='pullrequests', action='update',
712 711 repo_name=target.repo_name,
713 712 pull_request_id=str(pull_request_id)),
714 713 params={'update_commits': 'true', '_method': 'put',
715 714 'csrf_token': csrf_token},
716 715 status=200)
717 716
718 717 # check that we have now both revisions
719 718 pull_request = PullRequest.get(pull_request_id)
720 719 assert pull_request.revisions == [commit_ids['change-rebased']]
721 720 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
722 721 branch=backend.default_branch_name,
723 722 commit_id=commit_ids['ancestor-new'])
724 723
725 724 # TODO: johbo: This should be a test on its own
726 725 response = self.app.get(url(
727 726 controller='pullrequests', action='index',
728 727 repo_name=target.repo_name))
729 728 assert response.status_int == 200
730 729 assert 'Pull request updated to' in response.body
731 730 assert 'with 1 added, 1 removed commits.' in response.body
732 731
733 732 def test_update_of_ancestor_reference(self, backend, csrf_token):
734 733 commits = [
735 734 {'message': 'ancestor'},
736 735 {'message': 'change'},
737 736 {'message': 'change-2'},
738 737 {'message': 'ancestor-new', 'parents': ['ancestor']},
739 738 {'message': 'change-rebased'},
740 739 ]
741 740 commit_ids = backend.create_master_repo(commits)
742 741 target = backend.create_repo(heads=['ancestor'])
743 742 source = backend.create_repo(heads=['change'])
744 743
745 744 # create pr from a in source to A in target
746 745 pull_request = PullRequest()
747 746 pull_request.source_repo = source
748 747 # TODO: johbo: Make sure that we write the source ref this way!
749 748 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
750 749 branch=backend.default_branch_name,
751 750 commit_id=commit_ids['change'])
752 751 pull_request.target_repo = target
753 752 # TODO: johbo: Target ref should be branch based, since tip can jump
754 753 # from branch to branch
755 754 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
756 755 branch=backend.default_branch_name,
757 756 commit_id=commit_ids['ancestor'])
758 757 pull_request.revisions = [commit_ids['change']]
759 758 pull_request.title = u"Test"
760 759 pull_request.description = u"Description"
761 760 pull_request.author = UserModel().get_by_username(
762 761 TEST_USER_ADMIN_LOGIN)
763 762 Session().add(pull_request)
764 763 Session().commit()
765 764 pull_request_id = pull_request.pull_request_id
766 765
767 766 # target has ancestor - ancestor-new
768 767 # source has ancestor - ancestor-new - change-rebased
769 768 backend.pull_heads(target, heads=['ancestor-new'])
770 769 backend.pull_heads(source, heads=['change-rebased'])
771 770
772 771 # update PR
773 772 self.app.post(
774 773 url(controller='pullrequests', action='update',
775 774 repo_name=target.repo_name,
776 775 pull_request_id=str(pull_request_id)),
777 776 params={'update_commits': 'true', '_method': 'put',
778 777 'csrf_token': csrf_token},
779 778 status=200)
780 779
781 780 # Expect the target reference to be updated correctly
782 781 pull_request = PullRequest.get(pull_request_id)
783 782 assert pull_request.revisions == [commit_ids['change-rebased']]
784 783 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
785 784 branch=backend.default_branch_name,
786 785 commit_id=commit_ids['ancestor-new'])
787 786 assert pull_request.target_ref == expected_target_ref
788 787
789 788 def test_remove_pull_request_branch(self, backend_git, csrf_token):
790 789 branch_name = 'development'
791 790 commits = [
792 791 {'message': 'initial-commit'},
793 792 {'message': 'old-feature'},
794 793 {'message': 'new-feature', 'branch': branch_name},
795 794 ]
796 795 repo = backend_git.create_repo(commits)
797 796 commit_ids = backend_git.commit_ids
798 797
799 798 pull_request = PullRequest()
800 799 pull_request.source_repo = repo
801 800 pull_request.target_repo = repo
802 801 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
803 802 branch=branch_name, commit_id=commit_ids['new-feature'])
804 803 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
805 804 branch=backend_git.default_branch_name,
806 805 commit_id=commit_ids['old-feature'])
807 806 pull_request.revisions = [commit_ids['new-feature']]
808 807 pull_request.title = u"Test"
809 808 pull_request.description = u"Description"
810 809 pull_request.author = UserModel().get_by_username(
811 810 TEST_USER_ADMIN_LOGIN)
812 811 Session().add(pull_request)
813 812 Session().commit()
814 813
815 814 vcs = repo.scm_instance()
816 815 vcs.remove_ref('refs/heads/{}'.format(branch_name))
817 816
818 817 response = self.app.get(url(
819 818 controller='pullrequests', action='show',
820 819 repo_name=repo.repo_name,
821 820 pull_request_id=str(pull_request.pull_request_id)))
822 821
823 822 assert response.status_int == 200
824 823 assert_response = AssertResponse(response)
825 824 assert_response.element_contains(
826 825 '#changeset_compare_view_content .alert strong',
827 826 'Missing commits')
828 827 assert_response.element_contains(
829 828 '#changeset_compare_view_content .alert',
830 829 'This pull request cannot be displayed, because one or more'
831 830 ' commits no longer exist in the source repository.')
832 831
833 832 def test_strip_commits_from_pull_request(
834 833 self, backend, pr_util, csrf_token):
835 834 commits = [
836 835 {'message': 'initial-commit'},
837 836 {'message': 'old-feature'},
838 837 {'message': 'new-feature', 'parents': ['initial-commit']},
839 838 ]
840 839 pull_request = pr_util.create_pull_request(
841 840 commits, target_head='initial-commit', source_head='new-feature',
842 841 revisions=['new-feature'])
843 842
844 843 vcs = pr_util.source_repository.scm_instance()
845 844 if backend.alias == 'git':
846 845 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
847 846 else:
848 847 vcs.strip(pr_util.commit_ids['new-feature'])
849 848
850 849 response = self.app.get(url(
851 850 controller='pullrequests', action='show',
852 851 repo_name=pr_util.target_repository.repo_name,
853 852 pull_request_id=str(pull_request.pull_request_id)))
854 853
855 854 assert response.status_int == 200
856 855 assert_response = AssertResponse(response)
857 856 assert_response.element_contains(
858 857 '#changeset_compare_view_content .alert strong',
859 858 'Missing commits')
860 859 assert_response.element_contains(
861 860 '#changeset_compare_view_content .alert',
862 861 'This pull request cannot be displayed, because one or more'
863 862 ' commits no longer exist in the source repository.')
864 863 assert_response.element_contains(
865 864 '#update_commits',
866 865 'Update commits')
867 866
868 867 def test_strip_commits_and_update(
869 868 self, backend, pr_util, csrf_token):
870 869 commits = [
871 870 {'message': 'initial-commit'},
872 871 {'message': 'old-feature'},
873 872 {'message': 'new-feature', 'parents': ['old-feature']},
874 873 ]
875 874 pull_request = pr_util.create_pull_request(
876 875 commits, target_head='old-feature', source_head='new-feature',
877 876 revisions=['new-feature'], mergeable=True)
878 877
879 878 vcs = pr_util.source_repository.scm_instance()
880 879 if backend.alias == 'git':
881 880 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
882 881 else:
883 882 vcs.strip(pr_util.commit_ids['new-feature'])
884 883
885 884 response = self.app.post(
886 885 url(controller='pullrequests', action='update',
887 886 repo_name=pull_request.target_repo.repo_name,
888 887 pull_request_id=str(pull_request.pull_request_id)),
889 888 params={'update_commits': 'true', '_method': 'put',
890 889 'csrf_token': csrf_token})
891 890
892 891 assert response.status_int == 200
893 892 assert response.body == 'true'
894 893
895 894 # Make sure that after update, it won't raise 500 errors
896 895 response = self.app.get(url(
897 896 controller='pullrequests', action='show',
898 897 repo_name=pr_util.target_repository.repo_name,
899 898 pull_request_id=str(pull_request.pull_request_id)))
900 899
901 900 assert response.status_int == 200
902 901 assert_response = AssertResponse(response)
903 902 assert_response.element_contains(
904 903 '#changeset_compare_view_content .alert strong',
905 904 'Missing commits')
906 905
907 906 def test_branch_is_a_link(self, pr_util):
908 907 pull_request = pr_util.create_pull_request()
909 908 pull_request.source_ref = 'branch:origin:1234567890abcdef'
910 909 pull_request.target_ref = 'branch:target:abcdef1234567890'
911 910 Session().add(pull_request)
912 911 Session().commit()
913 912
914 913 response = self.app.get(url(
915 914 controller='pullrequests', action='show',
916 915 repo_name=pull_request.target_repo.scm_instance().name,
917 916 pull_request_id=str(pull_request.pull_request_id)))
918 917 assert response.status_int == 200
919 918 assert_response = AssertResponse(response)
920 919
921 920 origin = assert_response.get_element('.pr-origininfo .tag')
922 921 origin_children = origin.getchildren()
923 922 assert len(origin_children) == 1
924 923 target = assert_response.get_element('.pr-targetinfo .tag')
925 924 target_children = target.getchildren()
926 925 assert len(target_children) == 1
927 926
928 927 expected_origin_link = route_path(
929 928 'repo_changelog',
930 929 repo_name=pull_request.source_repo.scm_instance().name,
931 930 params=dict(branch='origin'))
932 931 expected_target_link = route_path(
933 932 'repo_changelog',
934 933 repo_name=pull_request.target_repo.scm_instance().name,
935 934 params=dict(branch='target'))
936 935 assert origin_children[0].attrib['href'] == expected_origin_link
937 936 assert origin_children[0].text == 'branch: origin'
938 937 assert target_children[0].attrib['href'] == expected_target_link
939 938 assert target_children[0].text == 'branch: target'
940 939
941 940 def test_bookmark_is_not_a_link(self, pr_util):
942 941 pull_request = pr_util.create_pull_request()
943 942 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
944 943 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
945 944 Session().add(pull_request)
946 945 Session().commit()
947 946
948 947 response = self.app.get(url(
949 948 controller='pullrequests', action='show',
950 949 repo_name=pull_request.target_repo.scm_instance().name,
951 950 pull_request_id=str(pull_request.pull_request_id)))
952 951 assert response.status_int == 200
953 952 assert_response = AssertResponse(response)
954 953
955 954 origin = assert_response.get_element('.pr-origininfo .tag')
956 955 assert origin.text.strip() == 'bookmark: origin'
957 956 assert origin.getchildren() == []
958 957
959 958 target = assert_response.get_element('.pr-targetinfo .tag')
960 959 assert target.text.strip() == 'bookmark: target'
961 960 assert target.getchildren() == []
962 961
963 962 def test_tag_is_not_a_link(self, pr_util):
964 963 pull_request = pr_util.create_pull_request()
965 964 pull_request.source_ref = 'tag:origin:1234567890abcdef'
966 965 pull_request.target_ref = 'tag:target:abcdef1234567890'
967 966 Session().add(pull_request)
968 967 Session().commit()
969 968
970 969 response = self.app.get(url(
971 970 controller='pullrequests', action='show',
972 971 repo_name=pull_request.target_repo.scm_instance().name,
973 972 pull_request_id=str(pull_request.pull_request_id)))
974 973 assert response.status_int == 200
975 974 assert_response = AssertResponse(response)
976 975
977 976 origin = assert_response.get_element('.pr-origininfo .tag')
978 977 assert origin.text.strip() == 'tag: origin'
979 978 assert origin.getchildren() == []
980 979
981 980 target = assert_response.get_element('.pr-targetinfo .tag')
982 981 assert target.text.strip() == 'tag: target'
983 982 assert target.getchildren() == []
984 983
985 984 @pytest.mark.parametrize('mergeable', [True, False])
986 985 def test_shadow_repository_link(
987 986 self, mergeable, pr_util, http_host_only_stub):
988 987 """
989 988 Check that the pull request summary page displays a link to the shadow
990 989 repository if the pull request is mergeable. If it is not mergeable
991 990 the link should not be displayed.
992 991 """
993 992 pull_request = pr_util.create_pull_request(
994 993 mergeable=mergeable, enable_notifications=False)
995 994 target_repo = pull_request.target_repo.scm_instance()
996 995 pr_id = pull_request.pull_request_id
997 996 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
998 997 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
999 998
1000 999 response = self.app.get(url(
1001 1000 controller='pullrequests', action='show',
1002 1001 repo_name=target_repo.name,
1003 1002 pull_request_id=str(pr_id)))
1004 1003
1005 1004 assertr = AssertResponse(response)
1006 1005 if mergeable:
1007 1006 assertr.element_value_contains(
1008 1007 'div.pr-mergeinfo input', shadow_url)
1009 1008 assertr.element_value_contains(
1010 1009 'div.pr-mergeinfo input', 'pr-merge')
1011 1010 else:
1012 1011 assertr.no_element_exists('div.pr-mergeinfo')
1013 1012
1014 1013
1015 1014 @pytest.mark.usefixtures('app')
1016 1015 @pytest.mark.backends("git", "hg")
1017 1016 class TestPullrequestsControllerDelete(object):
1018 1017 def test_pull_request_delete_button_permissions_admin(
1019 1018 self, autologin_user, user_admin, pr_util):
1020 1019 pull_request = pr_util.create_pull_request(
1021 1020 author=user_admin.username, enable_notifications=False)
1022 1021
1023 1022 response = self.app.get(url(
1024 1023 controller='pullrequests', action='show',
1025 1024 repo_name=pull_request.target_repo.scm_instance().name,
1026 1025 pull_request_id=str(pull_request.pull_request_id)))
1027 1026
1028 1027 response.mustcontain('id="delete_pullrequest"')
1029 1028 response.mustcontain('Confirm to delete this pull request')
1030 1029
1031 1030 def test_pull_request_delete_button_permissions_owner(
1032 1031 self, autologin_regular_user, user_regular, pr_util):
1033 1032 pull_request = pr_util.create_pull_request(
1034 1033 author=user_regular.username, enable_notifications=False)
1035 1034
1036 1035 response = self.app.get(url(
1037 1036 controller='pullrequests', action='show',
1038 1037 repo_name=pull_request.target_repo.scm_instance().name,
1039 1038 pull_request_id=str(pull_request.pull_request_id)))
1040 1039
1041 1040 response.mustcontain('id="delete_pullrequest"')
1042 1041 response.mustcontain('Confirm to delete this pull request')
1043 1042
1044 1043 def test_pull_request_delete_button_permissions_forbidden(
1045 1044 self, autologin_regular_user, user_regular, user_admin, pr_util):
1046 1045 pull_request = pr_util.create_pull_request(
1047 1046 author=user_admin.username, enable_notifications=False)
1048 1047
1049 1048 response = self.app.get(url(
1050 1049 controller='pullrequests', action='show',
1051 1050 repo_name=pull_request.target_repo.scm_instance().name,
1052 1051 pull_request_id=str(pull_request.pull_request_id)))
1053 1052 response.mustcontain(no=['id="delete_pullrequest"'])
1054 1053 response.mustcontain(no=['Confirm to delete this pull request'])
1055 1054
1056 1055 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1057 1056 self, autologin_regular_user, user_regular, user_admin, pr_util,
1058 1057 user_util):
1059 1058
1060 1059 pull_request = pr_util.create_pull_request(
1061 1060 author=user_admin.username, enable_notifications=False)
1062 1061
1063 1062 user_util.grant_user_permission_to_repo(
1064 1063 pull_request.target_repo, user_regular,
1065 1064 'repository.write')
1066 1065
1067 1066 response = self.app.get(url(
1068 1067 controller='pullrequests', action='show',
1069 1068 repo_name=pull_request.target_repo.scm_instance().name,
1070 1069 pull_request_id=str(pull_request.pull_request_id)))
1071 1070
1072 1071 response.mustcontain('id="open_edit_pullrequest"')
1073 1072 response.mustcontain('id="delete_pullrequest"')
1074 1073 response.mustcontain(no=['Confirm to delete this pull request'])
1075 1074
1076 1075 def test_delete_comment_returns_404_if_comment_does_not_exist(
1077 1076 self, autologin_user, pr_util, user_admin):
1078 1077
1079 1078 pull_request = pr_util.create_pull_request(
1080 1079 author=user_admin.username, enable_notifications=False)
1081 1080
1082 1081 self.app.get(url(
1083 1082 controller='pullrequests', action='delete_comment',
1084 1083 repo_name=pull_request.target_repo.scm_instance().name,
1085 1084 comment_id=1024404), status=404)
1086 1085
1087 1086
1088 1087 def assert_pull_request_status(pull_request, expected_status):
1089 1088 status = ChangesetStatusModel().calculated_review_status(
1090 1089 pull_request=pull_request)
1091 1090 assert status == expected_status
1092 1091
1093 1092
1094 1093 @pytest.mark.parametrize('action', ['index', 'create'])
1095 1094 @pytest.mark.usefixtures("autologin_user")
1096 1095 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1097 1096 response = app.get(url(
1098 1097 controller='pullrequests', action=action,
1099 1098 repo_name=backend_svn.repo_name))
1100 1099 assert response.status_int == 302
1101 1100
1102 1101 # Not allowed, redirect to the summary
1103 1102 redirected = response.follow()
1104 1103 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1105 1104
1106 1105 # URL adds leading slash and path doesn't have it
1107 1106 assert redirected.request.path == summary_url
@@ -1,426 +1,431 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 threading
22 22 import time
23 23 import logging
24 24 import os.path
25 25 import subprocess32
26 26 import tempfile
27 27 import urllib2
28 28 from lxml.html import fromstring, tostring
29 29 from lxml.cssselect import CSSSelector
30 30 from urlparse import urlparse, parse_qsl
31 31 from urllib import unquote_plus
32 32 import webob
33 33
34 34 from webtest.app import TestResponse, TestApp, string_types
35 35 from webtest.compat import print_stderr
36 36
37 37 import pytest
38 38 import rc_testdata
39 39
40 40 from rhodecode.model.db import User, Repository
41 41 from rhodecode.model.meta import Session
42 42 from rhodecode.model.scm import ScmModel
43 43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
44 44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class CustomTestResponse(TestResponse):
51 51 def _save_output(self, out):
52 52 f = tempfile.NamedTemporaryFile(
53 53 delete=False, prefix='rc-test-', suffix='.html')
54 54 f.write(out)
55 55 return f.name
56 56
57 57 def mustcontain(self, *strings, **kw):
58 58 """
59 59 Assert that the response contains all of the strings passed
60 60 in as arguments.
61 61
62 62 Equivalent to::
63 63
64 64 assert string in res
65 65 """
66 66 if 'no' in kw:
67 67 no = kw['no']
68 68 del kw['no']
69 69 if isinstance(no, string_types):
70 70 no = [no]
71 71 else:
72 72 no = []
73 73 if kw:
74 74 raise TypeError(
75 75 "The only keyword argument allowed is 'no'")
76 76
77 77 f = self._save_output(str(self))
78 78
79 79 for s in strings:
80 80 if not s in self:
81 81 print_stderr("Actual response (no %r):" % s)
82 82 print_stderr(str(self))
83 83 raise IndexError(
84 84 "Body does not contain string %r, output saved as %s" % (
85 85 s, f))
86 86
87 87 for no_s in no:
88 88 if no_s in self:
89 89 print_stderr("Actual response (has %r)" % no_s)
90 90 print_stderr(str(self))
91 91 raise IndexError(
92 92 "Body contains bad string %r, output saved as %s" % (
93 93 no_s, f))
94 94
95 95 def assert_response(self):
96 96 return AssertResponse(self)
97 97
98 98 def get_session_from_response(self):
99 99 """
100 100 This returns the session from a response object. Pylons has some magic
101 101 to make the session available as `response.session`. But pyramid
102 102 doesn't expose it.
103 103 """
104 104 return self.request.environ['beaker.session']
105 105
106 106
107 107 class TestRequest(webob.BaseRequest):
108 108
109 109 # for py.test
110 110 disabled = True
111 111 ResponseClass = CustomTestResponse
112 112
113 113
114 114 class CustomTestApp(TestApp):
115 115 """
116 116 Custom app to make mustcontain more usefull
117 117 """
118 118 RequestClass = TestRequest
119 119
120 120
121 121 def set_anonymous_access(enabled):
122 122 """(Dis)allows anonymous access depending on parameter `enabled`"""
123 123 user = User.get_default_user()
124 124 user.active = enabled
125 125 Session().add(user)
126 126 Session().commit()
127 127 time.sleep(1.5) # must sleep for cache (1s to expire)
128 128 log.info('anonymous access is now: %s', enabled)
129 129 assert enabled == User.get_default_user().active, (
130 130 'Cannot set anonymous access')
131 131
132 132
133 133 def check_xfail_backends(node, backend_alias):
134 134 # Using "xfail_backends" here intentionally, since this marks work
135 135 # which is "to be done" soon.
136 136 skip_marker = node.get_marker('xfail_backends')
137 137 if skip_marker and backend_alias in skip_marker.args:
138 138 msg = "Support for backend %s to be developed." % (backend_alias, )
139 139 msg = skip_marker.kwargs.get('reason', msg)
140 140 pytest.xfail(msg)
141 141
142 142
143 143 def check_skip_backends(node, backend_alias):
144 144 # Using "skip_backends" here intentionally, since this marks work which is
145 145 # not supported.
146 146 skip_marker = node.get_marker('skip_backends')
147 147 if skip_marker and backend_alias in skip_marker.args:
148 148 msg = "Feature not supported for backend %s." % (backend_alias, )
149 149 msg = skip_marker.kwargs.get('reason', msg)
150 150 pytest.skip(msg)
151 151
152 152
153 153 def extract_git_repo_from_dump(dump_name, repo_name):
154 154 """Create git repo `repo_name` from dump `dump_name`."""
155 155 repos_path = ScmModel().repos_path
156 156 target_path = os.path.join(repos_path, repo_name)
157 157 rc_testdata.extract_git_dump(dump_name, target_path)
158 158 return target_path
159 159
160 160
161 161 def extract_hg_repo_from_dump(dump_name, repo_name):
162 162 """Create hg repo `repo_name` from dump `dump_name`."""
163 163 repos_path = ScmModel().repos_path
164 164 target_path = os.path.join(repos_path, repo_name)
165 165 rc_testdata.extract_hg_dump(dump_name, target_path)
166 166 return target_path
167 167
168 168
169 169 def extract_svn_repo_from_dump(dump_name, repo_name):
170 170 """Create a svn repo `repo_name` from dump `dump_name`."""
171 171 repos_path = ScmModel().repos_path
172 172 target_path = os.path.join(repos_path, repo_name)
173 173 SubversionRepository(target_path, create=True)
174 174 _load_svn_dump_into_repo(dump_name, target_path)
175 175 return target_path
176 176
177 177
178 178 def assert_message_in_log(log_records, message, levelno, module):
179 179 messages = [
180 180 r.message for r in log_records
181 181 if r.module == module and r.levelno == levelno
182 182 ]
183 183 assert message in messages
184 184
185 185
186 186 def _load_svn_dump_into_repo(dump_name, repo_path):
187 187 """
188 188 Utility to populate a svn repository with a named dump
189 189
190 190 Currently the dumps are in rc_testdata. They might later on be
191 191 integrated with the main repository once they stabilize more.
192 192 """
193 193 dump = rc_testdata.load_svn_dump(dump_name)
194 194 load_dump = subprocess32.Popen(
195 195 ['svnadmin', 'load', repo_path],
196 196 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
197 197 stderr=subprocess32.PIPE)
198 198 out, err = load_dump.communicate(dump)
199 199 if load_dump.returncode != 0:
200 200 log.error("Output of load_dump command: %s", out)
201 201 log.error("Error output of load_dump command: %s", err)
202 202 raise Exception(
203 203 'Failed to load dump "%s" into repository at path "%s".'
204 204 % (dump_name, repo_path))
205 205
206 206
207 207 class AssertResponse(object):
208 208 """
209 209 Utility that helps to assert things about a given HTML response.
210 210 """
211 211
212 212 def __init__(self, response):
213 213 self.response = response
214 214
215 215 def get_imports(self):
216 216 return fromstring, tostring, CSSSelector
217 217
218 218 def one_element_exists(self, css_selector):
219 219 self.get_element(css_selector)
220 220
221 221 def no_element_exists(self, css_selector):
222 222 assert not self._get_elements(css_selector)
223 223
224 224 def element_equals_to(self, css_selector, expected_content):
225 225 element = self.get_element(css_selector)
226 226 element_text = self._element_to_string(element)
227 227 assert expected_content in element_text
228 228
229 229 def element_contains(self, css_selector, expected_content):
230 230 element = self.get_element(css_selector)
231 231 assert expected_content in element.text_content()
232 232
233 233 def element_value_contains(self, css_selector, expected_content):
234 234 element = self.get_element(css_selector)
235 235 assert expected_content in element.value
236 236
237 237 def contains_one_link(self, link_text, href):
238 238 fromstring, tostring, CSSSelector = self.get_imports()
239 239 doc = fromstring(self.response.body)
240 240 sel = CSSSelector('a[href]')
241 241 elements = [
242 242 e for e in sel(doc) if e.text_content().strip() == link_text]
243 243 assert len(elements) == 1, "Did not find link or found multiple links"
244 244 self._ensure_url_equal(elements[0].attrib.get('href'), href)
245 245
246 246 def contains_one_anchor(self, anchor_id):
247 247 fromstring, tostring, CSSSelector = self.get_imports()
248 248 doc = fromstring(self.response.body)
249 249 sel = CSSSelector('#' + anchor_id)
250 250 elements = sel(doc)
251 251 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
252 252
253 253 def _ensure_url_equal(self, found, expected):
254 254 assert _Url(found) == _Url(expected)
255 255
256 256 def get_element(self, css_selector):
257 257 elements = self._get_elements(css_selector)
258 258 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
259 259 return elements[0]
260 260
261 261 def get_elements(self, css_selector):
262 262 return self._get_elements(css_selector)
263 263
264 264 def _get_elements(self, css_selector):
265 265 fromstring, tostring, CSSSelector = self.get_imports()
266 266 doc = fromstring(self.response.body)
267 267 sel = CSSSelector(css_selector)
268 268 elements = sel(doc)
269 269 return elements
270 270
271 271 def _element_to_string(self, element):
272 272 fromstring, tostring, CSSSelector = self.get_imports()
273 273 return tostring(element)
274 274
275 275
276 276 class _Url(object):
277 277 """
278 278 A url object that can be compared with other url orbjects
279 279 without regard to the vagaries of encoding, escaping, and ordering
280 280 of parameters in query strings.
281 281
282 282 Inspired by
283 283 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
284 284 """
285 285
286 286 def __init__(self, url):
287 287 parts = urlparse(url)
288 288 _query = frozenset(parse_qsl(parts.query))
289 289 _path = unquote_plus(parts.path)
290 290 parts = parts._replace(query=_query, path=_path)
291 291 self.parts = parts
292 292
293 293 def __eq__(self, other):
294 294 return self.parts == other.parts
295 295
296 296 def __hash__(self):
297 297 return hash(self.parts)
298 298
299 299
300 300 def run_test_concurrently(times, raise_catched_exc=True):
301 301 """
302 302 Add this decorator to small pieces of code that you want to test
303 303 concurrently
304 304
305 305 ex:
306 306
307 307 @test_concurrently(25)
308 308 def my_test_function():
309 309 ...
310 310 """
311 311 def test_concurrently_decorator(test_func):
312 312 def wrapper(*args, **kwargs):
313 313 exceptions = []
314 314
315 315 def call_test_func():
316 316 try:
317 317 test_func(*args, **kwargs)
318 318 except Exception as e:
319 319 exceptions.append(e)
320 320 if raise_catched_exc:
321 321 raise
322 322 threads = []
323 323 for i in range(times):
324 324 threads.append(threading.Thread(target=call_test_func))
325 325 for t in threads:
326 326 t.start()
327 327 for t in threads:
328 328 t.join()
329 329 if exceptions:
330 330 raise Exception(
331 331 'test_concurrently intercepted %s exceptions: %s' % (
332 332 len(exceptions), exceptions))
333 333 return wrapper
334 334 return test_concurrently_decorator
335 335
336 336
337 337 def wait_for_url(url, timeout=10):
338 338 """
339 339 Wait until URL becomes reachable.
340 340
341 341 It polls the URL until the timeout is reached or it became reachable.
342 342 If will call to `py.test.fail` in case the URL is not reachable.
343 343 """
344 344 timeout = time.time() + timeout
345 345 last = 0
346 346 wait = 0.1
347 347
348 348 while timeout > last:
349 349 last = time.time()
350 350 if is_url_reachable(url):
351 351 break
352 352 elif (last + wait) > time.time():
353 353 # Go to sleep because not enough time has passed since last check.
354 354 time.sleep(wait)
355 355 else:
356 356 pytest.fail("Timeout while waiting for URL {}".format(url))
357 357
358 358
359 359 def is_url_reachable(url):
360 360 try:
361 361 urllib2.urlopen(url)
362 362 except urllib2.URLError:
363 363 return False
364 364 return True
365 365
366 366
367 367 def repo_on_filesystem(repo_name):
368 368 from rhodecode.lib import vcs
369 369 from rhodecode.tests import TESTS_TMP_PATH
370 370 repo = vcs.get_vcs_instance(
371 371 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
372 372 return repo is not None
373 373
374 374
375 375 def commit_change(
376 376 repo, filename, content, message, vcs_type, parent=None, newfile=False):
377 377 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
378 378
379 379 repo = Repository.get_by_repo_name(repo)
380 380 _commit = parent
381 381 if not parent:
382 382 _commit = EmptyCommit(alias=vcs_type)
383 383
384 384 if newfile:
385 385 nodes = {
386 386 filename: {
387 387 'content': content
388 388 }
389 389 }
390 390 commit = ScmModel().create_nodes(
391 391 user=TEST_USER_ADMIN_LOGIN, repo=repo,
392 392 message=message,
393 393 nodes=nodes,
394 394 parent_commit=_commit,
395 395 author=TEST_USER_ADMIN_LOGIN,
396 396 )
397 397 else:
398 398 commit = ScmModel().commit_change(
399 399 repo=repo.scm_instance(), repo_name=repo.repo_name,
400 400 commit=parent, user=TEST_USER_ADMIN_LOGIN,
401 401 author=TEST_USER_ADMIN_LOGIN,
402 402 message=message,
403 403 content=content,
404 404 f_path=filename
405 405 )
406 406 return commit
407 407
408 408
409 409 def add_test_routes(config):
410 410 """
411 411 Adds test routing that can be used in different functional tests
412 412 """
413 from rhodecode.apps._base import ADMIN_PREFIX
414
413 415 config.add_route(name='home', pattern='/')
416
417 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
418 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
414 419 config.add_route(name='repo_summary', pattern='/{repo_name}')
415 420 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
416 421 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
417 422
418 423 config.add_route(name='pullrequest_show',
419 424 pattern='/{repo_name}/pull-request/{pull_request_id}')
420 425 config.add_route(name='pull_requests_global',
421 426 pattern='/pull-request/{pull_request_id}')
422 427 config.add_route(name='repo_commit',
423 428 pattern='/{repo_name}/changeset/{commit_id}')
424 429
425 430 config.add_route(name='repo_files',
426 431 pattern='/{repo_name}/files/{commit_id}/{f_path}')
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now