##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
27 def admin_routes(config):
28 """
28 """
29 Admin prefixed routes
29 Admin prefixed routes
30 """
30 """
31
31
32 config.add_route(
32 config.add_route(
33 name='admin_audit_logs',
33 name='admin_audit_logs',
34 pattern='/audit_logs')
34 pattern='/audit_logs')
35
35
36 config.add_route(
36 config.add_route(
37 name='pull_requests_global_0', # backward compat
37 name='pull_requests_global_0', # backward compat
38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
38 pattern='/pull_requests/{pull_request_id:[0-9]+}')
39 config.add_route(
39 config.add_route(
40 name='pull_requests_global_1', # backward compat
40 name='pull_requests_global_1', # backward compat
41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
41 pattern='/pull-requests/{pull_request_id:[0-9]+}')
42 config.add_route(
42 config.add_route(
43 name='pull_requests_global',
43 name='pull_requests_global',
44 pattern='/pull-request/{pull_request_id:[0-9]+}')
44 pattern='/pull-request/{pull_request_id:[0-9]+}')
45
45
46 config.add_route(
46 config.add_route(
47 name='admin_settings_open_source',
47 name='admin_settings_open_source',
48 pattern='/settings/open_source')
48 pattern='/settings/open_source')
49 config.add_route(
49 config.add_route(
50 name='admin_settings_vcs_svn_generate_cfg',
50 name='admin_settings_vcs_svn_generate_cfg',
51 pattern='/settings/vcs/svn_generate_cfg')
51 pattern='/settings/vcs/svn_generate_cfg')
52
52
53 config.add_route(
53 config.add_route(
54 name='admin_settings_system',
54 name='admin_settings_system',
55 pattern='/settings/system')
55 pattern='/settings/system')
56 config.add_route(
56 config.add_route(
57 name='admin_settings_system_update',
57 name='admin_settings_system_update',
58 pattern='/settings/system/updates')
58 pattern='/settings/system/updates')
59
59
60 config.add_route(
60 config.add_route(
61 name='admin_settings_sessions',
61 name='admin_settings_sessions',
62 pattern='/settings/sessions')
62 pattern='/settings/sessions')
63 config.add_route(
63 config.add_route(
64 name='admin_settings_sessions_cleanup',
64 name='admin_settings_sessions_cleanup',
65 pattern='/settings/sessions/cleanup')
65 pattern='/settings/sessions/cleanup')
66
66
67 config.add_route(
67 config.add_route(
68 name='admin_settings_process_management',
68 name='admin_settings_process_management',
69 pattern='/settings/process_management')
69 pattern='/settings/process_management')
70 config.add_route(
70 config.add_route(
71 name='admin_settings_process_management_signal',
71 name='admin_settings_process_management_signal',
72 pattern='/settings/process_management/signal')
72 pattern='/settings/process_management/signal')
73
73
74 # global permissions
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 config.add_route(
97 config.add_route(
76 name='admin_permissions_ips',
98 name='admin_permissions_ips',
77 pattern='/permissions/ips')
99 pattern='/permissions/ips')
78
100
101 config.add_route(
102 name='admin_permissions_overview',
103 pattern='/permissions/overview')
104
79 # users admin
105 # users admin
80 config.add_route(
106 config.add_route(
81 name='users',
107 name='users',
82 pattern='/users')
108 pattern='/users')
83
109
84 config.add_route(
110 config.add_route(
85 name='users_data',
111 name='users_data',
86 pattern='/users_data')
112 pattern='/users_data')
87
113
88 # user auth tokens
114 # user auth tokens
89 config.add_route(
115 config.add_route(
90 name='edit_user_auth_tokens',
116 name='edit_user_auth_tokens',
91 pattern='/users/{user_id:\d+}/edit/auth_tokens')
117 pattern='/users/{user_id:\d+}/edit/auth_tokens')
92 config.add_route(
118 config.add_route(
93 name='edit_user_auth_tokens_add',
119 name='edit_user_auth_tokens_add',
94 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
120 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
95 config.add_route(
121 config.add_route(
96 name='edit_user_auth_tokens_delete',
122 name='edit_user_auth_tokens_delete',
97 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
123 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
98
124
99 # user emails
125 # user emails
100 config.add_route(
126 config.add_route(
101 name='edit_user_emails',
127 name='edit_user_emails',
102 pattern='/users/{user_id:\d+}/edit/emails')
128 pattern='/users/{user_id:\d+}/edit/emails')
103 config.add_route(
129 config.add_route(
104 name='edit_user_emails_add',
130 name='edit_user_emails_add',
105 pattern='/users/{user_id:\d+}/edit/emails/new')
131 pattern='/users/{user_id:\d+}/edit/emails/new')
106 config.add_route(
132 config.add_route(
107 name='edit_user_emails_delete',
133 name='edit_user_emails_delete',
108 pattern='/users/{user_id:\d+}/edit/emails/delete')
134 pattern='/users/{user_id:\d+}/edit/emails/delete')
109
135
110 # user IPs
136 # user IPs
111 config.add_route(
137 config.add_route(
112 name='edit_user_ips',
138 name='edit_user_ips',
113 pattern='/users/{user_id:\d+}/edit/ips')
139 pattern='/users/{user_id:\d+}/edit/ips')
114 config.add_route(
140 config.add_route(
115 name='edit_user_ips_add',
141 name='edit_user_ips_add',
116 pattern='/users/{user_id:\d+}/edit/ips/new')
142 pattern='/users/{user_id:\d+}/edit/ips/new')
117 config.add_route(
143 config.add_route(
118 name='edit_user_ips_delete',
144 name='edit_user_ips_delete',
119 pattern='/users/{user_id:\d+}/edit/ips/delete')
145 pattern='/users/{user_id:\d+}/edit/ips/delete')
120
146
121 # user groups management
147 # user groups management
122 config.add_route(
148 config.add_route(
123 name='edit_user_groups_management',
149 name='edit_user_groups_management',
124 pattern='/users/{user_id:\d+}/edit/groups_management')
150 pattern='/users/{user_id:\d+}/edit/groups_management')
125
151
126 config.add_route(
152 config.add_route(
127 name='edit_user_groups_management_updates',
153 name='edit_user_groups_management_updates',
128 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
154 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
129
155
130 # user audit logs
156 # user audit logs
131 config.add_route(
157 config.add_route(
132 name='edit_user_audit_logs',
158 name='edit_user_audit_logs',
133 pattern='/users/{user_id:\d+}/edit/audit')
159 pattern='/users/{user_id:\d+}/edit/audit')
134
160
135
161
136 def includeme(config):
162 def includeme(config):
137 settings = config.get_settings()
163 settings = config.get_settings()
138
164
139 # Create admin navigation registry and add it to the pyramid registry.
165 # Create admin navigation registry and add it to the pyramid registry.
140 labs_active = str2bool(settings.get('labs_settings_active', False))
166 labs_active = str2bool(settings.get('labs_settings_active', False))
141 navigation_registry = NavigationRegistry(labs_active=labs_active)
167 navigation_registry = NavigationRegistry(labs_active=labs_active)
142 config.registry.registerUtility(navigation_registry)
168 config.registry.registerUtility(navigation_registry)
143
169
144 # main admin routes
170 # main admin routes
145 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
171 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
146 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
172 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
147
173
148 # Scan module for configuration decorators.
174 # Scan module for configuration decorators.
149 config.scan()
175 config.scan()
@@ -1,229 +1,250 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from rhodecode.model.db import User, UserIpMap
22 from rhodecode.model.db import User, UserIpMap
23 from rhodecode.model.permission import PermissionModel
23 from rhodecode.model.permission import PermissionModel
24 from rhodecode.tests import (
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 def route_path(name, params=None, **kwargs):
28 def route_path(name, params=None, **kwargs):
29 import urllib
29 import urllib
30 from rhodecode.apps._base import ADMIN_PREFIX
30 from rhodecode.apps._base import ADMIN_PREFIX
31
31
32 base_url = {
32 base_url = {
33 'edit_user_ips':
33 'edit_user_ips':
34 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
34 ADMIN_PREFIX + '/users/{user_id}/edit/ips',
35 'edit_user_ips_add':
35 'edit_user_ips_add':
36 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
36 ADMIN_PREFIX + '/users/{user_id}/edit/ips/new',
37 'edit_user_ips_delete':
37 'edit_user_ips_delete':
38 ADMIN_PREFIX + '/users/{user_id}/edit/ips/delete',
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 }[name].format(**kwargs)
60 }[name].format(**kwargs)
40
61
41 if params:
62 if params:
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
63 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 return base_url
64 return base_url
44
65
45
66
46 class TestAdminPermissionsController(TestController):
67 class TestAdminPermissionsController(TestController):
47
68
48 @pytest.fixture(scope='class', autouse=True)
69 @pytest.fixture(scope='class', autouse=True)
49 def prepare(self, request):
70 def prepare(self, request):
50 # cleanup and reset to default permissions after
71 # cleanup and reset to default permissions after
51 @request.addfinalizer
72 @request.addfinalizer
52 def cleanup():
73 def cleanup():
53 PermissionModel().create_default_user_permissions(
74 PermissionModel().create_default_user_permissions(
54 User.get_default_user(), force=True)
75 User.get_default_user(), force=True)
55
76
56 def test_index_application(self):
77 def test_index_application(self):
57 self.log_user()
78 self.log_user()
58 self.app.get(url('admin_permissions_application'))
79 self.app.get(route_path('admin_permissions_application'))
59
80
60 @pytest.mark.parametrize(
81 @pytest.mark.parametrize(
61 'anonymous, default_register, default_register_message, default_password_reset,'
82 'anonymous, default_register, default_register_message, default_password_reset,'
62 'default_extern_activate, expect_error, expect_form_error', [
83 'default_extern_activate, expect_error, expect_form_error', [
63 (True, 'hg.register.none', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
84 (True, 'hg.register.none', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
64 False, False),
85 False, False),
65 (True, 'hg.register.manual_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.auto',
86 (True, 'hg.register.manual_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.auto',
66 False, False),
87 False, False),
67 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
88 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
68 False, False),
89 False, False),
69 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
90 (True, 'hg.register.auto_activate', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
70 False, False),
91 False, False),
71 (True, 'hg.register.XXX', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
92 (True, 'hg.register.XXX', '', 'hg.password_reset.enabled', 'hg.extern_activate.manual',
72 False, True),
93 False, True),
73 (True, '', '', 'hg.password_reset.enabled', '', True, False),
94 (True, '', '', 'hg.password_reset.enabled', '', True, False),
74 ])
95 ])
75 def test_update_application_permissions(
96 def test_update_application_permissions(
76 self, anonymous, default_register, default_register_message, default_password_reset,
97 self, anonymous, default_register, default_register_message, default_password_reset,
77 default_extern_activate, expect_error, expect_form_error):
98 default_extern_activate, expect_error, expect_form_error):
78
99
79 self.log_user()
100 self.log_user()
80
101
81 # TODO: anonymous access set here to False, breaks some other tests
102 # TODO: anonymous access set here to False, breaks some other tests
82 params = {
103 params = {
83 'csrf_token': self.csrf_token,
104 'csrf_token': self.csrf_token,
84 'anonymous': anonymous,
105 'anonymous': anonymous,
85 'default_register': default_register,
106 'default_register': default_register,
86 'default_register_message': default_register_message,
107 'default_register_message': default_register_message,
87 'default_password_reset': default_password_reset,
108 'default_password_reset': default_password_reset,
88 'default_extern_activate': default_extern_activate,
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 params=params)
112 params=params)
92 if expect_form_error:
113 if expect_form_error:
93 assert response.status_int == 200
114 assert response.status_int == 200
94 response.mustcontain('Value must be one of')
115 response.mustcontain('Value must be one of')
95 else:
116 else:
96 if expect_error:
117 if expect_error:
97 msg = 'Error occurred during update of permissions'
118 msg = 'Error occurred during update of permissions'
98 else:
119 else:
99 msg = 'Application permissions updated successfully'
120 msg = 'Application permissions updated successfully'
100 assert_session_flash(response, msg)
121 assert_session_flash(response, msg)
101
122
102 def test_index_object(self):
123 def test_index_object(self):
103 self.log_user()
124 self.log_user()
104 self.app.get(url('admin_permissions_object'))
125 self.app.get(route_path('admin_permissions_object'))
105
126
106 @pytest.mark.parametrize(
127 @pytest.mark.parametrize(
107 'repo, repo_group, user_group, expect_error, expect_form_error', [
128 'repo, repo_group, user_group, expect_error, expect_form_error', [
108 ('repository.none', 'group.none', 'usergroup.none', False, False),
129 ('repository.none', 'group.none', 'usergroup.none', False, False),
109 ('repository.read', 'group.read', 'usergroup.read', False, False),
130 ('repository.read', 'group.read', 'usergroup.read', False, False),
110 ('repository.write', 'group.write', 'usergroup.write',
131 ('repository.write', 'group.write', 'usergroup.write',
111 False, False),
132 False, False),
112 ('repository.admin', 'group.admin', 'usergroup.admin',
133 ('repository.admin', 'group.admin', 'usergroup.admin',
113 False, False),
134 False, False),
114 ('repository.XXX', 'group.admin', 'usergroup.admin', False, True),
135 ('repository.XXX', 'group.admin', 'usergroup.admin', False, True),
115 ('', '', '', True, False),
136 ('', '', '', True, False),
116 ])
137 ])
117 def test_update_object_permissions(self, repo, repo_group, user_group,
138 def test_update_object_permissions(self, repo, repo_group, user_group,
118 expect_error, expect_form_error):
139 expect_error, expect_form_error):
119 self.log_user()
140 self.log_user()
120
141
121 params = {
142 params = {
122 'csrf_token': self.csrf_token,
143 'csrf_token': self.csrf_token,
123 'default_repo_perm': repo,
144 'default_repo_perm': repo,
124 'overwrite_default_repo': False,
145 'overwrite_default_repo': False,
125 'default_group_perm': repo_group,
146 'default_group_perm': repo_group,
126 'overwrite_default_group': False,
147 'overwrite_default_group': False,
127 'default_user_group_perm': user_group,
148 'default_user_group_perm': user_group,
128 'overwrite_default_user_group': False,
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 params=params)
152 params=params)
132 if expect_form_error:
153 if expect_form_error:
133 assert response.status_int == 200
154 assert response.status_int == 200
134 response.mustcontain('Value must be one of')
155 response.mustcontain('Value must be one of')
135 else:
156 else:
136 if expect_error:
157 if expect_error:
137 msg = 'Error occurred during update of permissions'
158 msg = 'Error occurred during update of permissions'
138 else:
159 else:
139 msg = 'Object permissions updated successfully'
160 msg = 'Object permissions updated successfully'
140 assert_session_flash(response, msg)
161 assert_session_flash(response, msg)
141
162
142 def test_index_global(self):
163 def test_index_global(self):
143 self.log_user()
164 self.log_user()
144 self.app.get(url('admin_permissions_global'))
165 self.app.get(route_path('admin_permissions_global'))
145
166
146 @pytest.mark.parametrize(
167 @pytest.mark.parametrize(
147 'repo_create, repo_create_write, user_group_create, repo_group_create,'
168 'repo_create, repo_create_write, user_group_create, repo_group_create,'
148 'fork_create, inherit_default_permissions, expect_error,'
169 'fork_create, inherit_default_permissions, expect_error,'
149 'expect_form_error', [
170 'expect_form_error', [
150 ('hg.create.none', 'hg.create.write_on_repogroup.false',
171 ('hg.create.none', 'hg.create.write_on_repogroup.false',
151 'hg.usergroup.create.false', 'hg.repogroup.create.false',
172 'hg.usergroup.create.false', 'hg.repogroup.create.false',
152 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
173 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
153 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
174 ('hg.create.repository', 'hg.create.write_on_repogroup.true',
154 'hg.usergroup.create.true', 'hg.repogroup.create.true',
175 'hg.usergroup.create.true', 'hg.repogroup.create.true',
155 'hg.fork.repository', 'hg.inherit_default_perms.false',
176 'hg.fork.repository', 'hg.inherit_default_perms.false',
156 False, False),
177 False, False),
157 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
178 ('hg.create.XXX', 'hg.create.write_on_repogroup.true',
158 'hg.usergroup.create.true', 'hg.repogroup.create.true',
179 'hg.usergroup.create.true', 'hg.repogroup.create.true',
159 'hg.fork.repository', 'hg.inherit_default_perms.false',
180 'hg.fork.repository', 'hg.inherit_default_perms.false',
160 False, True),
181 False, True),
161 ('', '', '', '', '', '', True, False),
182 ('', '', '', '', '', '', True, False),
162 ])
183 ])
163 def test_update_global_permissions(
184 def test_update_global_permissions(
164 self, repo_create, repo_create_write, user_group_create,
185 self, repo_create, repo_create_write, user_group_create,
165 repo_group_create, fork_create, inherit_default_permissions,
186 repo_group_create, fork_create, inherit_default_permissions,
166 expect_error, expect_form_error):
187 expect_error, expect_form_error):
167 self.log_user()
188 self.log_user()
168
189
169 params = {
190 params = {
170 'csrf_token': self.csrf_token,
191 'csrf_token': self.csrf_token,
171 'default_repo_create': repo_create,
192 'default_repo_create': repo_create,
172 'default_repo_create_on_write': repo_create_write,
193 'default_repo_create_on_write': repo_create_write,
173 'default_user_group_create': user_group_create,
194 'default_user_group_create': user_group_create,
174 'default_repo_group_create': repo_group_create,
195 'default_repo_group_create': repo_group_create,
175 'default_fork_create': fork_create,
196 'default_fork_create': fork_create,
176 'default_inherit_default_permissions': inherit_default_permissions
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 params=params)
200 params=params)
180 if expect_form_error:
201 if expect_form_error:
181 assert response.status_int == 200
202 assert response.status_int == 200
182 response.mustcontain('Value must be one of')
203 response.mustcontain('Value must be one of')
183 else:
204 else:
184 if expect_error:
205 if expect_error:
185 msg = 'Error occurred during update of permissions'
206 msg = 'Error occurred during update of permissions'
186 else:
207 else:
187 msg = 'Global permissions updated successfully'
208 msg = 'Global permissions updated successfully'
188 assert_session_flash(response, msg)
209 assert_session_flash(response, msg)
189
210
190 def test_index_ips(self):
211 def test_index_ips(self):
191 self.log_user()
212 self.log_user()
192 response = self.app.get(url('admin_permissions_ips'))
213 response = self.app.get(route_path('admin_permissions_ips'))
193 # TODO: Test response...
214 # TODO: Test response...
194 response.mustcontain('All IP addresses are allowed')
215 response.mustcontain('All IP addresses are allowed')
195
216
196 def test_add_delete_ips(self):
217 def test_add_delete_ips(self):
197 self.log_user()
218 self.log_user()
198 clear_all_caches()
219 clear_all_caches()
199
220
200 # ADD
221 # ADD
201 default_user_id = User.get_default_user().user_id
222 default_user_id = User.get_default_user().user_id
202 self.app.post(
223 self.app.post(
203 route_path('edit_user_ips_add', user_id=default_user_id),
224 route_path('edit_user_ips_add', user_id=default_user_id),
204 params={'new_ip': '127.0.0.0/24', 'csrf_token': self.csrf_token})
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 response.mustcontain('127.0.0.0/24')
228 response.mustcontain('127.0.0.0/24')
208 response.mustcontain('127.0.0.0 - 127.0.0.255')
229 response.mustcontain('127.0.0.0 - 127.0.0.255')
209
230
210 # DELETE
231 # DELETE
211 default_user_id = User.get_default_user().user_id
232 default_user_id = User.get_default_user().user_id
212 del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
233 del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
213 default_user_id).first().ip_id
234 default_user_id).first().ip_id
214
235
215 response = self.app.post(
236 response = self.app.post(
216 route_path('edit_user_ips_delete', user_id=default_user_id),
237 route_path('edit_user_ips_delete', user_id=default_user_id),
217 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
238 params={'del_ip_id': del_ip_id, 'csrf_token': self.csrf_token})
218
239
219 assert_session_flash(response, 'Removed ip address from user whitelist')
240 assert_session_flash(response, 'Removed ip address from user whitelist')
220
241
221 clear_all_caches()
242 clear_all_caches()
222 response = self.app.get(url('admin_permissions_ips'))
243 response = self.app.get(route_path('admin_permissions_ips'))
223 response.mustcontain('All IP addresses are allowed')
244 response.mustcontain('All IP addresses are allowed')
224 response.mustcontain(no=['127.0.0.0/24'])
245 response.mustcontain(no=['127.0.0.0/24'])
225 response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
246 response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
226
247
227 def test_index_overview(self):
248 def test_index_overview(self):
228 self.log_user()
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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
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 no_newline_id_generator)
28 no_newline_id_generator)
30 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
31 from rhodecode.lib.auth import check_password
30 from rhodecode.lib.auth import check_password
32 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
33 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model import validators
33 from rhodecode.model import validators
35 from rhodecode.model.db import User, Notification, UserApiKeys
34 from rhodecode.model.db import User, Notification, UserApiKeys
36 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
37
36
38 fixture = Fixture()
37 fixture = Fixture()
39
38
40 # Hardcode URLs because we don't have a request object to use
39
41 # pyramids URL generation methods.
40 def route_path(name, params=None, **kwargs):
42 index_url = '/'
41 import urllib
43 login_url = ADMIN_PREFIX + '/login'
42 from rhodecode.apps._base import ADMIN_PREFIX
44 logut_url = ADMIN_PREFIX + '/logout'
43
45 register_url = ADMIN_PREFIX + '/register'
44 base_url = {
46 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
45 'login': ADMIN_PREFIX + '/login',
47 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
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 @pytest.mark.usefixtures('app')
67 @pytest.mark.usefixtures('app')
51 class TestLoginController(object):
68 class TestLoginController(object):
52 destroy_users = set()
69 destroy_users = set()
53
70
54 @classmethod
71 @classmethod
55 def teardown_class(cls):
72 def teardown_class(cls):
56 fixture.destroy_users(cls.destroy_users)
73 fixture.destroy_users(cls.destroy_users)
57
74
58 def teardown_method(self, method):
75 def teardown_method(self, method):
59 for n in Notification.query().all():
76 for n in Notification.query().all():
60 Session().delete(n)
77 Session().delete(n)
61
78
62 Session().commit()
79 Session().commit()
63 assert Notification.query().all() == []
80 assert Notification.query().all() == []
64
81
65 def test_index(self):
82 def test_index(self):
66 response = self.app.get(login_url)
83 response = self.app.get(route_path('login'))
67 assert response.status == '200 OK'
84 assert response.status == '200 OK'
68 # Test response...
85 # Test response...
69
86
70 def test_login_admin_ok(self):
87 def test_login_admin_ok(self):
71 response = self.app.post(login_url,
88 response = self.app.post(route_path('login'),
72 {'username': 'test_admin',
89 {'username': 'test_admin',
73 'password': 'test12'})
90 'password': 'test12'})
74 assert response.status == '302 Found'
91 assert response.status == '302 Found'
75 session = response.get_session_from_response()
92 session = response.get_session_from_response()
76 username = session['rhodecode_user'].get('username')
93 username = session['rhodecode_user'].get('username')
77 assert username == 'test_admin'
94 assert username == 'test_admin'
78 response = response.follow()
95 response = response.follow()
79 response.mustcontain('/%s' % HG_REPO)
96 response.mustcontain('/%s' % HG_REPO)
80
97
81 def test_login_regular_ok(self):
98 def test_login_regular_ok(self):
82 response = self.app.post(login_url,
99 response = self.app.post(route_path('login'),
83 {'username': 'test_regular',
100 {'username': 'test_regular',
84 'password': 'test12'})
101 'password': 'test12'})
85
102
86 assert response.status == '302 Found'
103 assert response.status == '302 Found'
87 session = response.get_session_from_response()
104 session = response.get_session_from_response()
88 username = session['rhodecode_user'].get('username')
105 username = session['rhodecode_user'].get('username')
89 assert username == 'test_regular'
106 assert username == 'test_regular'
90 response = response.follow()
107 response = response.follow()
91 response.mustcontain('/%s' % HG_REPO)
108 response.mustcontain('/%s' % HG_REPO)
92
109
93 def test_login_ok_came_from(self):
110 def test_login_ok_came_from(self):
94 test_came_from = '/_admin/users?branch=stable'
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 response = self.app.post(
113 response = self.app.post(
97 _url, {'username': 'test_admin', 'password': 'test12'})
114 _url, {'username': 'test_admin', 'password': 'test12'})
98 assert response.status == '302 Found'
115 assert response.status == '302 Found'
99 assert 'branch=stable' in response.location
116 assert 'branch=stable' in response.location
100 response = response.follow()
117 response = response.follow()
101
118
102 assert response.status == '200 OK'
119 assert response.status == '200 OK'
103 response.mustcontain('Users administration')
120 response.mustcontain('Users administration')
104
121
105 def test_redirect_to_login_with_get_args(self):
122 def test_redirect_to_login_with_get_args(self):
106 with fixture.anon_access(False):
123 with fixture.anon_access(False):
107 kwargs = {'branch': 'stable'}
124 kwargs = {'branch': 'stable'}
108 response = self.app.get(
125 response = self.app.get(
109 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
126 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs))
110 assert response.status == '302 Found'
127 assert response.status == '302 Found'
111
128
112 response_query = urlparse.parse_qsl(response.location)
129 response_query = urlparse.parse_qsl(response.location)
113 assert 'branch=stable' in response_query[0][1]
130 assert 'branch=stable' in response_query[0][1]
114
131
115 def test_login_form_with_get_args(self):
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 response = self.app.get(_url)
134 response = self.app.get(_url)
118 assert 'branch%3Dstable' in response.form.action
135 assert 'branch%3Dstable' in response.form.action
119
136
120 @pytest.mark.parametrize("url_came_from", [
137 @pytest.mark.parametrize("url_came_from", [
121 'data:text/html,<script>window.alert("xss")</script>',
138 'data:text/html,<script>window.alert("xss")</script>',
122 'mailto:test@rhodecode.org',
139 'mailto:test@rhodecode.org',
123 'file:///etc/passwd',
140 'file:///etc/passwd',
124 'ftp://some.ftp.server',
141 'ftp://some.ftp.server',
125 'http://other.domain',
142 'http://other.domain',
126 '/\r\nX-Forwarded-Host: http://example.org',
143 '/\r\nX-Forwarded-Host: http://example.org',
127 ], ids=no_newline_id_generator)
144 ], ids=no_newline_id_generator)
128 def test_login_bad_came_froms(self, url_came_from):
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 response = self.app.post(
147 response = self.app.post(
131 _url,
148 _url,
132 {'username': 'test_admin', 'password': 'test12'})
149 {'username': 'test_admin', 'password': 'test12'})
133 assert response.status == '302 Found'
150 assert response.status == '302 Found'
134 response = response.follow()
151 response = response.follow()
135 assert response.status == '200 OK'
152 assert response.status == '200 OK'
136 assert response.request.path == '/'
153 assert response.request.path == '/'
137
154
138 def test_login_short_password(self):
155 def test_login_short_password(self):
139 response = self.app.post(login_url,
156 response = self.app.post(route_path('login'),
140 {'username': 'test_admin',
157 {'username': 'test_admin',
141 'password': 'as'})
158 'password': 'as'})
142 assert response.status == '200 OK'
159 assert response.status == '200 OK'
143
160
144 response.mustcontain('Enter 3 characters or more')
161 response.mustcontain('Enter 3 characters or more')
145
162
146 def test_login_wrong_non_ascii_password(self, user_regular):
163 def test_login_wrong_non_ascii_password(self, user_regular):
147 response = self.app.post(
164 response = self.app.post(
148 login_url,
165 route_path('login'),
149 {'username': user_regular.username,
166 {'username': user_regular.username,
150 'password': u'invalid-non-asci\xe4'.encode('utf8')})
167 'password': u'invalid-non-asci\xe4'.encode('utf8')})
151
168
152 response.mustcontain('invalid user name')
169 response.mustcontain('invalid user name')
153 response.mustcontain('invalid password')
170 response.mustcontain('invalid password')
154
171
155 def test_login_with_non_ascii_password(self, user_util):
172 def test_login_with_non_ascii_password(self, user_util):
156 password = u'valid-non-ascii\xe4'
173 password = u'valid-non-ascii\xe4'
157 user = user_util.create_user(password=password)
174 user = user_util.create_user(password=password)
158 response = self.app.post(
175 response = self.app.post(
159 login_url,
176 route_path('login'),
160 {'username': user.username,
177 {'username': user.username,
161 'password': password.encode('utf-8')})
178 'password': password.encode('utf-8')})
162 assert response.status_code == 302
179 assert response.status_code == 302
163
180
164 def test_login_wrong_username_password(self):
181 def test_login_wrong_username_password(self):
165 response = self.app.post(login_url,
182 response = self.app.post(route_path('login'),
166 {'username': 'error',
183 {'username': 'error',
167 'password': 'test12'})
184 'password': 'test12'})
168
185
169 response.mustcontain('invalid user name')
186 response.mustcontain('invalid user name')
170 response.mustcontain('invalid password')
187 response.mustcontain('invalid password')
171
188
172 def test_login_admin_ok_password_migration(self, real_crypto_backend):
189 def test_login_admin_ok_password_migration(self, real_crypto_backend):
173 from rhodecode.lib import auth
190 from rhodecode.lib import auth
174
191
175 # create new user, with sha256 password
192 # create new user, with sha256 password
176 temp_user = 'test_admin_sha256'
193 temp_user = 'test_admin_sha256'
177 user = fixture.create_user(temp_user)
194 user = fixture.create_user(temp_user)
178 user.password = auth._RhodeCodeCryptoSha256().hash_create(
195 user.password = auth._RhodeCodeCryptoSha256().hash_create(
179 b'test123')
196 b'test123')
180 Session().add(user)
197 Session().add(user)
181 Session().commit()
198 Session().commit()
182 self.destroy_users.add(temp_user)
199 self.destroy_users.add(temp_user)
183 response = self.app.post(login_url,
200 response = self.app.post(route_path('login'),
184 {'username': temp_user,
201 {'username': temp_user,
185 'password': 'test123'})
202 'password': 'test123'})
186
203
187 assert response.status == '302 Found'
204 assert response.status == '302 Found'
188 session = response.get_session_from_response()
205 session = response.get_session_from_response()
189 username = session['rhodecode_user'].get('username')
206 username = session['rhodecode_user'].get('username')
190 assert username == temp_user
207 assert username == temp_user
191 response = response.follow()
208 response = response.follow()
192 response.mustcontain('/%s' % HG_REPO)
209 response.mustcontain('/%s' % HG_REPO)
193
210
194 # new password should be bcrypted, after log-in and transfer
211 # new password should be bcrypted, after log-in and transfer
195 user = User.get_by_username(temp_user)
212 user = User.get_by_username(temp_user)
196 assert user.password.startswith('$')
213 assert user.password.startswith('$')
197
214
198 # REGISTRATIONS
215 # REGISTRATIONS
199 def test_register(self):
216 def test_register(self):
200 response = self.app.get(register_url)
217 response = self.app.get(route_path('register'))
201 response.mustcontain('Create an Account')
218 response.mustcontain('Create an Account')
202
219
203 def test_register_err_same_username(self):
220 def test_register_err_same_username(self):
204 uname = 'test_admin'
221 uname = 'test_admin'
205 response = self.app.post(
222 response = self.app.post(
206 register_url,
223 route_path('register'),
207 {
224 {
208 'username': uname,
225 'username': uname,
209 'password': 'test12',
226 'password': 'test12',
210 'password_confirmation': 'test12',
227 'password_confirmation': 'test12',
211 'email': 'goodmail@domain.com',
228 'email': 'goodmail@domain.com',
212 'firstname': 'test',
229 'firstname': 'test',
213 'lastname': 'test'
230 'lastname': 'test'
214 }
231 }
215 )
232 )
216
233
217 assertr = response.assert_response()
234 assertr = response.assert_response()
218 msg = validators.ValidUsername()._messages['username_exists']
235 msg = validators.ValidUsername()._messages['username_exists']
219 msg = msg % {'username': uname}
236 msg = msg % {'username': uname}
220 assertr.element_contains('#username+.error-message', msg)
237 assertr.element_contains('#username+.error-message', msg)
221
238
222 def test_register_err_same_email(self):
239 def test_register_err_same_email(self):
223 response = self.app.post(
240 response = self.app.post(
224 register_url,
241 route_path('register'),
225 {
242 {
226 'username': 'test_admin_0',
243 'username': 'test_admin_0',
227 'password': 'test12',
244 'password': 'test12',
228 'password_confirmation': 'test12',
245 'password_confirmation': 'test12',
229 'email': 'test_admin@mail.com',
246 'email': 'test_admin@mail.com',
230 'firstname': 'test',
247 'firstname': 'test',
231 'lastname': 'test'
248 'lastname': 'test'
232 }
249 }
233 )
250 )
234
251
235 assertr = response.assert_response()
252 assertr = response.assert_response()
236 msg = validators.UniqSystemEmail()()._messages['email_taken']
253 msg = validators.UniqSystemEmail()()._messages['email_taken']
237 assertr.element_contains('#email+.error-message', msg)
254 assertr.element_contains('#email+.error-message', msg)
238
255
239 def test_register_err_same_email_case_sensitive(self):
256 def test_register_err_same_email_case_sensitive(self):
240 response = self.app.post(
257 response = self.app.post(
241 register_url,
258 route_path('register'),
242 {
259 {
243 'username': 'test_admin_1',
260 'username': 'test_admin_1',
244 'password': 'test12',
261 'password': 'test12',
245 'password_confirmation': 'test12',
262 'password_confirmation': 'test12',
246 'email': 'TesT_Admin@mail.COM',
263 'email': 'TesT_Admin@mail.COM',
247 'firstname': 'test',
264 'firstname': 'test',
248 'lastname': 'test'
265 'lastname': 'test'
249 }
266 }
250 )
267 )
251 assertr = response.assert_response()
268 assertr = response.assert_response()
252 msg = validators.UniqSystemEmail()()._messages['email_taken']
269 msg = validators.UniqSystemEmail()()._messages['email_taken']
253 assertr.element_contains('#email+.error-message', msg)
270 assertr.element_contains('#email+.error-message', msg)
254
271
255 def test_register_err_wrong_data(self):
272 def test_register_err_wrong_data(self):
256 response = self.app.post(
273 response = self.app.post(
257 register_url,
274 route_path('register'),
258 {
275 {
259 'username': 'xs',
276 'username': 'xs',
260 'password': 'test',
277 'password': 'test',
261 'password_confirmation': 'test',
278 'password_confirmation': 'test',
262 'email': 'goodmailm',
279 'email': 'goodmailm',
263 'firstname': 'test',
280 'firstname': 'test',
264 'lastname': 'test'
281 'lastname': 'test'
265 }
282 }
266 )
283 )
267 assert response.status == '200 OK'
284 assert response.status == '200 OK'
268 response.mustcontain('An email address must contain a single @')
285 response.mustcontain('An email address must contain a single @')
269 response.mustcontain('Enter a value 6 characters long or more')
286 response.mustcontain('Enter a value 6 characters long or more')
270
287
271 def test_register_err_username(self):
288 def test_register_err_username(self):
272 response = self.app.post(
289 response = self.app.post(
273 register_url,
290 route_path('register'),
274 {
291 {
275 'username': 'error user',
292 'username': 'error user',
276 'password': 'test12',
293 'password': 'test12',
277 'password_confirmation': 'test12',
294 'password_confirmation': 'test12',
278 'email': 'goodmailm',
295 'email': 'goodmailm',
279 'firstname': 'test',
296 'firstname': 'test',
280 'lastname': 'test'
297 'lastname': 'test'
281 }
298 }
282 )
299 )
283
300
284 response.mustcontain('An email address must contain a single @')
301 response.mustcontain('An email address must contain a single @')
285 response.mustcontain(
302 response.mustcontain(
286 'Username may only contain '
303 'Username may only contain '
287 'alphanumeric characters underscores, '
304 'alphanumeric characters underscores, '
288 'periods or dashes and must begin with '
305 'periods or dashes and must begin with '
289 'alphanumeric character')
306 'alphanumeric character')
290
307
291 def test_register_err_case_sensitive(self):
308 def test_register_err_case_sensitive(self):
292 usr = 'Test_Admin'
309 usr = 'Test_Admin'
293 response = self.app.post(
310 response = self.app.post(
294 register_url,
311 route_path('register'),
295 {
312 {
296 'username': usr,
313 'username': usr,
297 'password': 'test12',
314 'password': 'test12',
298 'password_confirmation': 'test12',
315 'password_confirmation': 'test12',
299 'email': 'goodmailm',
316 'email': 'goodmailm',
300 'firstname': 'test',
317 'firstname': 'test',
301 'lastname': 'test'
318 'lastname': 'test'
302 }
319 }
303 )
320 )
304
321
305 assertr = response.assert_response()
322 assertr = response.assert_response()
306 msg = validators.ValidUsername()._messages['username_exists']
323 msg = validators.ValidUsername()._messages['username_exists']
307 msg = msg % {'username': usr}
324 msg = msg % {'username': usr}
308 assertr.element_contains('#username+.error-message', msg)
325 assertr.element_contains('#username+.error-message', msg)
309
326
310 def test_register_special_chars(self):
327 def test_register_special_chars(self):
311 response = self.app.post(
328 response = self.app.post(
312 register_url,
329 route_path('register'),
313 {
330 {
314 'username': 'xxxaxn',
331 'username': 'xxxaxn',
315 'password': 'ąćźżąśśśś',
332 'password': 'ąćźżąśśśś',
316 'password_confirmation': 'ąćźżąśśśś',
333 'password_confirmation': 'ąćźżąśśśś',
317 'email': 'goodmailm@test.plx',
334 'email': 'goodmailm@test.plx',
318 'firstname': 'test',
335 'firstname': 'test',
319 'lastname': 'test'
336 'lastname': 'test'
320 }
337 }
321 )
338 )
322
339
323 msg = validators.ValidPassword()._messages['invalid_password']
340 msg = validators.ValidPassword()._messages['invalid_password']
324 response.mustcontain(msg)
341 response.mustcontain(msg)
325
342
326 def test_register_password_mismatch(self):
343 def test_register_password_mismatch(self):
327 response = self.app.post(
344 response = self.app.post(
328 register_url,
345 route_path('register'),
329 {
346 {
330 'username': 'xs',
347 'username': 'xs',
331 'password': '123qwe',
348 'password': '123qwe',
332 'password_confirmation': 'qwe123',
349 'password_confirmation': 'qwe123',
333 'email': 'goodmailm@test.plxa',
350 'email': 'goodmailm@test.plxa',
334 'firstname': 'test',
351 'firstname': 'test',
335 'lastname': 'test'
352 'lastname': 'test'
336 }
353 }
337 )
354 )
338 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
355 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
339 response.mustcontain(msg)
356 response.mustcontain(msg)
340
357
341 def test_register_ok(self):
358 def test_register_ok(self):
342 username = 'test_regular4'
359 username = 'test_regular4'
343 password = 'qweqwe'
360 password = 'qweqwe'
344 email = 'marcin@test.com'
361 email = 'marcin@test.com'
345 name = 'testname'
362 name = 'testname'
346 lastname = 'testlastname'
363 lastname = 'testlastname'
347
364
348 response = self.app.post(
365 response = self.app.post(
349 register_url,
366 route_path('register'),
350 {
367 {
351 'username': username,
368 'username': username,
352 'password': password,
369 'password': password,
353 'password_confirmation': password,
370 'password_confirmation': password,
354 'email': email,
371 'email': email,
355 'firstname': name,
372 'firstname': name,
356 'lastname': lastname,
373 'lastname': lastname,
357 'admin': True
374 'admin': True
358 }
375 }
359 ) # This should be overriden
376 ) # This should be overriden
360 assert response.status == '302 Found'
377 assert response.status == '302 Found'
361 assert_session_flash(
378 assert_session_flash(
362 response, 'You have successfully registered with RhodeCode')
379 response, 'You have successfully registered with RhodeCode')
363
380
364 ret = Session().query(User).filter(
381 ret = Session().query(User).filter(
365 User.username == 'test_regular4').one()
382 User.username == 'test_regular4').one()
366 assert ret.username == username
383 assert ret.username == username
367 assert check_password(password, ret.password)
384 assert check_password(password, ret.password)
368 assert ret.email == email
385 assert ret.email == email
369 assert ret.name == name
386 assert ret.name == name
370 assert ret.lastname == lastname
387 assert ret.lastname == lastname
371 assert ret.auth_tokens is not None
388 assert ret.auth_tokens is not None
372 assert not ret.admin
389 assert not ret.admin
373
390
374 def test_forgot_password_wrong_mail(self):
391 def test_forgot_password_wrong_mail(self):
375 bad_email = 'marcin@wrongmail.org'
392 bad_email = 'marcin@wrongmail.org'
376 response = self.app.post(
393 response = self.app.post(
377 pwd_reset_url, {'email': bad_email, }
394 route_path('reset_password'), {'email': bad_email, }
378 )
395 )
379 assert_session_flash(response,
396 assert_session_flash(response,
380 'If such email exists, a password reset link was sent to it.')
397 'If such email exists, a password reset link was sent to it.')
381
398
382 def test_forgot_password(self, user_util):
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 assert response.status == '200 OK'
401 assert response.status == '200 OK'
385
402
386 user = user_util.create_user()
403 user = user_util.create_user()
387 user_id = user.user_id
404 user_id = user.user_id
388 email = user.email
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 assert_session_flash(response,
409 assert_session_flash(response,
393 'If such email exists, a password reset link was sent to it.')
410 'If such email exists, a password reset link was sent to it.')
394
411
395 # BAD KEY
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 response = self.app.get(confirm_url)
414 response = self.app.get(confirm_url)
398 assert response.status == '302 Found'
415 assert response.status == '302 Found'
399 assert response.location.endswith(pwd_reset_url)
416 assert response.location.endswith(route_path('reset_password'))
400 assert_session_flash(response, 'Given reset token is invalid')
417 assert_session_flash(response, 'Given reset token is invalid')
401
418
402 response.follow() # cleanup flash
419 response.follow() # cleanup flash
403
420
404 # GOOD KEY
421 # GOOD KEY
405 key = UserApiKeys.query()\
422 key = UserApiKeys.query()\
406 .filter(UserApiKeys.user_id == user_id)\
423 .filter(UserApiKeys.user_id == user_id)\
407 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
424 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
408 .first()
425 .first()
409
426
410 assert key
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 response = self.app.get(confirm_url)
430 response = self.app.get(confirm_url)
414 assert response.status == '302 Found'
431 assert response.status == '302 Found'
415 assert response.location.endswith(login_url)
432 assert response.location.endswith(route_path('login'))
416
433
417 assert_session_flash(
434 assert_session_flash(
418 response,
435 response,
419 'Your password reset was successful, '
436 'Your password reset was successful, '
420 'a new password has been sent to your email')
437 'a new password has been sent to your email')
421
438
422 response.follow()
439 response.follow()
423
440
424 def _get_api_whitelist(self, values=None):
441 def _get_api_whitelist(self, values=None):
425 config = {'api_access_controllers_whitelist': values or []}
442 config = {'api_access_controllers_whitelist': values or []}
426 return config
443 return config
427
444
428 @pytest.mark.parametrize("test_name, auth_token", [
445 @pytest.mark.parametrize("test_name, auth_token", [
429 ('none', None),
446 ('none', None),
430 ('empty_string', ''),
447 ('empty_string', ''),
431 ('fake_number', '123456'),
448 ('fake_number', '123456'),
432 ('proper_auth_token', None)
449 ('proper_auth_token', None)
433 ])
450 ])
434 def test_access_not_whitelisted_page_via_auth_token(
451 def test_access_not_whitelisted_page_via_auth_token(
435 self, test_name, auth_token, user_admin):
452 self, test_name, auth_token, user_admin):
436
453
437 whitelist = self._get_api_whitelist([])
454 whitelist = self._get_api_whitelist([])
438 with mock.patch.dict('rhodecode.CONFIG', whitelist):
455 with mock.patch.dict('rhodecode.CONFIG', whitelist):
439 assert [] == whitelist['api_access_controllers_whitelist']
456 assert [] == whitelist['api_access_controllers_whitelist']
440 if test_name == 'proper_auth_token':
457 if test_name == 'proper_auth_token':
441 # use builtin if api_key is None
458 # use builtin if api_key is None
442 auth_token = user_admin.api_key
459 auth_token = user_admin.api_key
443
460
444 with fixture.anon_access(False):
461 with fixture.anon_access(False):
445 self.app.get(url(controller='changeset',
462 self.app.get(
446 action='changeset_raw',
463 route_path('repo_commit_raw',
447 repo_name=HG_REPO, revision='tip',
464 repo_name=HG_REPO, commit_id='tip',
448 api_key=auth_token),
465 params=dict(api_key=auth_token)),
449 status=302)
466 status=302)
450
467
451 @pytest.mark.parametrize("test_name, auth_token, code", [
468 @pytest.mark.parametrize("test_name, auth_token, code", [
452 ('none', None, 302),
469 ('none', None, 302),
453 ('empty_string', '', 302),
470 ('empty_string', '', 302),
454 ('fake_number', '123456', 302),
471 ('fake_number', '123456', 302),
455 ('proper_auth_token', None, 200)
472 ('proper_auth_token', None, 200)
456 ])
473 ])
457 def test_access_whitelisted_page_via_auth_token(
474 def test_access_whitelisted_page_via_auth_token(
458 self, test_name, auth_token, code, user_admin):
475 self, test_name, auth_token, code, user_admin):
459
476
460 whitelist_entry = ['ChangesetController:changeset_raw']
477 whitelist_entry = ['ChangesetController:changeset_raw']
461 whitelist = self._get_api_whitelist(whitelist_entry)
478 whitelist = self._get_api_whitelist(whitelist_entry)
462
479
463 with mock.patch.dict('rhodecode.CONFIG', whitelist):
480 with mock.patch.dict('rhodecode.CONFIG', whitelist):
464 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
481 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
465
482
466 if test_name == 'proper_auth_token':
483 if test_name == 'proper_auth_token':
467 auth_token = user_admin.api_key
484 auth_token = user_admin.api_key
468 assert auth_token
485 assert auth_token
469
486
470 with fixture.anon_access(False):
487 with fixture.anon_access(False):
471 self.app.get(url(controller='changeset',
488 self.app.get(
472 action='changeset_raw',
489 route_path('repo_commit_raw',
473 repo_name=HG_REPO, revision='tip',
490 repo_name=HG_REPO, commit_id='tip',
474 api_key=auth_token),
491 params=dict(api_key=auth_token)),
475 status=code)
492 status=code)
476
493
477 def test_access_page_via_extra_auth_token(self):
494 def test_access_page_via_extra_auth_token(self):
478 whitelist = self._get_api_whitelist(
495 whitelist = self._get_api_whitelist(
479 ['ChangesetController:changeset_raw'])
496 ['ChangesetController:changeset_raw'])
480 with mock.patch.dict('rhodecode.CONFIG', whitelist):
497 with mock.patch.dict('rhodecode.CONFIG', whitelist):
481 assert ['ChangesetController:changeset_raw'] == \
498 assert ['ChangesetController:changeset_raw'] == \
482 whitelist['api_access_controllers_whitelist']
499 whitelist['api_access_controllers_whitelist']
483
500
484 new_auth_token = AuthTokenModel().create(
501 new_auth_token = AuthTokenModel().create(
485 TEST_USER_ADMIN_LOGIN, 'test')
502 TEST_USER_ADMIN_LOGIN, 'test')
486 Session().commit()
503 Session().commit()
487 with fixture.anon_access(False):
504 with fixture.anon_access(False):
488 self.app.get(url(controller='changeset',
505 self.app.get(
489 action='changeset_raw',
506 route_path('repo_commit_raw',
490 repo_name=HG_REPO, revision='tip',
507 repo_name=HG_REPO, commit_id='tip',
491 api_key=new_auth_token.api_key),
508 params=dict(api_key=new_auth_token.api_key)),
492 status=200)
509 status=200)
493
510
494 def test_access_page_via_expired_auth_token(self):
511 def test_access_page_via_expired_auth_token(self):
495 whitelist = self._get_api_whitelist(
512 whitelist = self._get_api_whitelist(
496 ['ChangesetController:changeset_raw'])
513 ['ChangesetController:changeset_raw'])
497 with mock.patch.dict('rhodecode.CONFIG', whitelist):
514 with mock.patch.dict('rhodecode.CONFIG', whitelist):
498 assert ['ChangesetController:changeset_raw'] == \
515 assert ['ChangesetController:changeset_raw'] == \
499 whitelist['api_access_controllers_whitelist']
516 whitelist['api_access_controllers_whitelist']
500
517
501 new_auth_token = AuthTokenModel().create(
518 new_auth_token = AuthTokenModel().create(
502 TEST_USER_ADMIN_LOGIN, 'test')
519 TEST_USER_ADMIN_LOGIN, 'test')
503 Session().commit()
520 Session().commit()
504 # patch the api key and make it expired
521 # patch the api key and make it expired
505 new_auth_token.expires = 0
522 new_auth_token.expires = 0
506 Session().add(new_auth_token)
523 Session().add(new_auth_token)
507 Session().commit()
524 Session().commit()
508 with fixture.anon_access(False):
525 with fixture.anon_access(False):
509 self.app.get(url(controller='changeset',
526 self.app.get(
510 action='changeset_raw',
527 route_path('repo_commit_raw',
511 repo_name=HG_REPO, revision='tip',
528 repo_name=HG_REPO, commit_id='tip',
512 api_key=new_auth_token.api_key),
529 params=dict(api_key=new_auth_token.api_key)),
513 status=302)
530 status=302)
@@ -1,106 +1,121 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.lib import helpers as h
24 from rhodecode.tests import (
24 from rhodecode.tests import (
25 TestController, clear_all_caches, url,
25 TestController, clear_all_caches,
26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.utils import AssertResponse
28 from rhodecode.tests.utils import AssertResponse
29
29
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32 # Hardcode URLs because we don't have a request object to use
32
33 # pyramids URL generation methods.
33 def route_path(name, params=None, **kwargs):
34 index_url = '/'
34 import urllib
35 login_url = ADMIN_PREFIX + '/login'
35 from rhodecode.apps._base import ADMIN_PREFIX
36 logut_url = ADMIN_PREFIX + '/logout'
36
37 register_url = ADMIN_PREFIX + '/register'
37 base_url = {
38 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
38 'login': ADMIN_PREFIX + '/login',
39 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
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 class TestPasswordReset(TestController):
57 class TestPasswordReset(TestController):
43
58
44 @pytest.mark.parametrize(
59 @pytest.mark.parametrize(
45 'pwd_reset_setting, show_link, show_reset', [
60 'pwd_reset_setting, show_link, show_reset', [
46 ('hg.password_reset.enabled', True, True),
61 ('hg.password_reset.enabled', True, True),
47 ('hg.password_reset.hidden', False, True),
62 ('hg.password_reset.hidden', False, True),
48 ('hg.password_reset.disabled', False, False),
63 ('hg.password_reset.disabled', False, False),
49 ])
64 ])
50 def test_password_reset_settings(
65 def test_password_reset_settings(
51 self, pwd_reset_setting, show_link, show_reset):
66 self, pwd_reset_setting, show_link, show_reset):
52 clear_all_caches()
67 clear_all_caches()
53 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
68 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
54 params = {
69 params = {
55 'csrf_token': self.csrf_token,
70 'csrf_token': self.csrf_token,
56 'anonymous': 'True',
71 'anonymous': 'True',
57 'default_register': 'hg.register.auto_activate',
72 'default_register': 'hg.register.auto_activate',
58 'default_register_message': '',
73 'default_register_message': '',
59 'default_password_reset': pwd_reset_setting,
74 'default_password_reset': pwd_reset_setting,
60 'default_extern_activate': 'hg.extern_activate.auto',
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 self.logout_user()
78 self.logout_user()
64
79
65 login_page = self.app.get(login_url)
80 login_page = self.app.get(route_path('login'))
66 asr_login = AssertResponse(login_page)
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 asr_index = AssertResponse(index_page)
83 asr_index = AssertResponse(index_page)
69
84
70 if show_link:
85 if show_link:
71 asr_login.one_element_exists('a.pwd_reset')
86 asr_login.one_element_exists('a.pwd_reset')
72 asr_index.one_element_exists('a.pwd_reset')
87 asr_index.one_element_exists('a.pwd_reset')
73 else:
88 else:
74 asr_login.no_element_exists('a.pwd_reset')
89 asr_login.no_element_exists('a.pwd_reset')
75 asr_index.no_element_exists('a.pwd_reset')
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 assert_response = AssertResponse(response)
94 assert_response = AssertResponse(response)
80 if show_reset:
95 if show_reset:
81 response.mustcontain('Send password reset email')
96 response.mustcontain('Send password reset email')
82 assert_response.one_element_exists('#email')
97 assert_response.one_element_exists('#email')
83 assert_response.one_element_exists('#send')
98 assert_response.one_element_exists('#send')
84 else:
99 else:
85 response.mustcontain('Password reset is disabled.')
100 response.mustcontain('Password reset is disabled.')
86 assert_response.no_element_exists('#email')
101 assert_response.no_element_exists('#email')
87 assert_response.no_element_exists('#send')
102 assert_response.no_element_exists('#send')
88
103
89 def test_password_form_disabled(self):
104 def test_password_form_disabled(self):
90 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
105 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
91 params = {
106 params = {
92 'csrf_token': self.csrf_token,
107 'csrf_token': self.csrf_token,
93 'anonymous': 'True',
108 'anonymous': 'True',
94 'default_register': 'hg.register.auto_activate',
109 'default_register': 'hg.register.auto_activate',
95 'default_register_message': '',
110 'default_register_message': '',
96 'default_password_reset': 'hg.password_reset.disabled',
111 'default_password_reset': 'hg.password_reset.disabled',
97 'default_extern_activate': 'hg.extern_activate.auto',
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 self.logout_user()
115 self.logout_user()
101
116
102 response = self.app.post(
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 response = response.follow()
120 response = response.follow()
106 response.mustcontain('Password reset is disabled.')
121 response.mustcontain('Password reset is disabled.')
@@ -1,686 +1,660 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 class JSRoutesMapper(Mapper):
54 class JSRoutesMapper(Mapper):
55 """
55 """
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 """
57 """
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 def __init__(self, *args, **kw):
60 def __init__(self, *args, **kw):
61 super(JSRoutesMapper, self).__init__(*args, **kw)
61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 self._jsroutes = []
62 self._jsroutes = []
63
63
64 def connect(self, *args, **kw):
64 def connect(self, *args, **kw):
65 """
65 """
66 Wrapper for connect to take an extra argument jsroute=True
66 Wrapper for connect to take an extra argument jsroute=True
67
67
68 :param jsroute: boolean, if True will add the route to the pyroutes list
68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 """
69 """
70 if kw.pop('jsroute', False):
70 if kw.pop('jsroute', False):
71 if not self._named_route_regex.match(args[0]):
71 if not self._named_route_regex.match(args[0]):
72 raise Exception('only named routes can be added to pyroutes')
72 raise Exception('only named routes can be added to pyroutes')
73 self._jsroutes.append(args[0])
73 self._jsroutes.append(args[0])
74
74
75 super(JSRoutesMapper, self).connect(*args, **kw)
75 super(JSRoutesMapper, self).connect(*args, **kw)
76
76
77 def _extract_route_information(self, route):
77 def _extract_route_information(self, route):
78 """
78 """
79 Convert a route into tuple(name, path, args), eg:
79 Convert a route into tuple(name, path, args), eg:
80 ('show_user', '/profile/%(username)s', ['username'])
80 ('show_user', '/profile/%(username)s', ['username'])
81 """
81 """
82 routepath = route.routepath
82 routepath = route.routepath
83 def replace(matchobj):
83 def replace(matchobj):
84 if matchobj.group(1):
84 if matchobj.group(1):
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 else:
86 else:
87 return "%%(%s)s" % matchobj.group(2)
87 return "%%(%s)s" % matchobj.group(2)
88
88
89 routepath = self._argument_prog.sub(replace, routepath)
89 routepath = self._argument_prog.sub(replace, routepath)
90 return (
90 return (
91 route.name,
91 route.name,
92 routepath,
92 routepath,
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 for arg in self._argument_prog.findall(route.routepath)]
94 for arg in self._argument_prog.findall(route.routepath)]
95 )
95 )
96
96
97 def jsroutes(self):
97 def jsroutes(self):
98 """
98 """
99 Return a list of pyroutes.js compatible routes
99 Return a list of pyroutes.js compatible routes
100 """
100 """
101 for route_name in self._jsroutes:
101 for route_name in self._jsroutes:
102 yield self._extract_route_information(self._routenames[route_name])
102 yield self._extract_route_information(self._routenames[route_name])
103
103
104
104
105 def make_map(config):
105 def make_map(config):
106 """Create, configure and return the routes Mapper"""
106 """Create, configure and return the routes Mapper"""
107 rmap = JSRoutesMapper(
107 rmap = JSRoutesMapper(
108 directory=config['pylons.paths']['controllers'],
108 directory=config['pylons.paths']['controllers'],
109 always_scan=config['debug'])
109 always_scan=config['debug'])
110 rmap.minimization = False
110 rmap.minimization = False
111 rmap.explicit = False
111 rmap.explicit = False
112
112
113 from rhodecode.lib.utils2 import str2bool
113 from rhodecode.lib.utils2 import str2bool
114 from rhodecode.model import repo, repo_group
114 from rhodecode.model import repo, repo_group
115
115
116 def check_repo(environ, match_dict):
116 def check_repo(environ, match_dict):
117 """
117 """
118 check for valid repository for proper 404 handling
118 check for valid repository for proper 404 handling
119
119
120 :param environ:
120 :param environ:
121 :param match_dict:
121 :param match_dict:
122 """
122 """
123 repo_name = match_dict.get('repo_name')
123 repo_name = match_dict.get('repo_name')
124
124
125 if match_dict.get('f_path'):
125 if match_dict.get('f_path'):
126 # fix for multiple initial slashes that causes errors
126 # fix for multiple initial slashes that causes errors
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 repo_model = repo.RepoModel()
128 repo_model = repo.RepoModel()
129 by_name_match = repo_model.get_by_repo_name(repo_name)
129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 # if we match quickly from database, short circuit the operation,
130 # if we match quickly from database, short circuit the operation,
131 # and validate repo based on the type.
131 # and validate repo based on the type.
132 if by_name_match:
132 if by_name_match:
133 return True
133 return True
134
134
135 by_id_match = repo_model.get_repo_by_id(repo_name)
135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 if by_id_match:
136 if by_id_match:
137 repo_name = by_id_match.repo_name
137 repo_name = by_id_match.repo_name
138 match_dict['repo_name'] = repo_name
138 match_dict['repo_name'] = repo_name
139 return True
139 return True
140
140
141 return False
141 return False
142
142
143 def check_group(environ, match_dict):
143 def check_group(environ, match_dict):
144 """
144 """
145 check for valid repository group path for proper 404 handling
145 check for valid repository group path for proper 404 handling
146
146
147 :param environ:
147 :param environ:
148 :param match_dict:
148 :param match_dict:
149 """
149 """
150 repo_group_name = match_dict.get('group_name')
150 repo_group_name = match_dict.get('group_name')
151 repo_group_model = repo_group.RepoGroupModel()
151 repo_group_model = repo_group.RepoGroupModel()
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 if by_name_match:
153 if by_name_match:
154 return True
154 return True
155
155
156 return False
156 return False
157
157
158 def check_user_group(environ, match_dict):
158 def check_user_group(environ, match_dict):
159 """
159 """
160 check for valid user group for proper 404 handling
160 check for valid user group for proper 404 handling
161
161
162 :param environ:
162 :param environ:
163 :param match_dict:
163 :param match_dict:
164 """
164 """
165 return True
165 return True
166
166
167 def check_int(environ, match_dict):
167 def check_int(environ, match_dict):
168 return match_dict.get('id').isdigit()
168 return match_dict.get('id').isdigit()
169
169
170
170
171 #==========================================================================
171 #==========================================================================
172 # CUSTOM ROUTES HERE
172 # CUSTOM ROUTES HERE
173 #==========================================================================
173 #==========================================================================
174
174
175 # ping and pylons error test
175 # ping and pylons error test
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178
178
179 # ADMIN REPOSITORY ROUTES
179 # ADMIN REPOSITORY ROUTES
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 controller='admin/repos') as m:
181 controller='admin/repos') as m:
182 m.connect('repos', '/repos',
182 m.connect('repos', '/repos',
183 action='create', conditions={'method': ['POST']})
183 action='create', conditions={'method': ['POST']})
184 m.connect('repos', '/repos',
184 m.connect('repos', '/repos',
185 action='index', conditions={'method': ['GET']})
185 action='index', conditions={'method': ['GET']})
186 m.connect('new_repo', '/create_repository', jsroute=True,
186 m.connect('new_repo', '/create_repository', jsroute=True,
187 action='create_repository', conditions={'method': ['GET']})
187 action='create_repository', conditions={'method': ['GET']})
188 m.connect('delete_repo', '/repos/{repo_name}',
188 m.connect('delete_repo', '/repos/{repo_name}',
189 action='delete', conditions={'method': ['DELETE']},
189 action='delete', conditions={'method': ['DELETE']},
190 requirements=URL_NAME_REQUIREMENTS)
190 requirements=URL_NAME_REQUIREMENTS)
191 m.connect('repo', '/repos/{repo_name}',
191 m.connect('repo', '/repos/{repo_name}',
192 action='show', conditions={'method': ['GET'],
192 action='show', conditions={'method': ['GET'],
193 'function': check_repo},
193 'function': check_repo},
194 requirements=URL_NAME_REQUIREMENTS)
194 requirements=URL_NAME_REQUIREMENTS)
195
195
196 # ADMIN REPOSITORY GROUPS ROUTES
196 # ADMIN REPOSITORY GROUPS ROUTES
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 controller='admin/repo_groups') as m:
198 controller='admin/repo_groups') as m:
199 m.connect('repo_groups', '/repo_groups',
199 m.connect('repo_groups', '/repo_groups',
200 action='create', conditions={'method': ['POST']})
200 action='create', conditions={'method': ['POST']})
201 m.connect('repo_groups', '/repo_groups',
201 m.connect('repo_groups', '/repo_groups',
202 action='index', conditions={'method': ['GET']})
202 action='index', conditions={'method': ['GET']})
203 m.connect('new_repo_group', '/repo_groups/new',
203 m.connect('new_repo_group', '/repo_groups/new',
204 action='new', conditions={'method': ['GET']})
204 action='new', conditions={'method': ['GET']})
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 action='update', conditions={'method': ['PUT'],
206 action='update', conditions={'method': ['PUT'],
207 'function': check_group},
207 'function': check_group},
208 requirements=URL_NAME_REQUIREMENTS)
208 requirements=URL_NAME_REQUIREMENTS)
209
209
210 # EXTRAS REPO GROUP ROUTES
210 # EXTRAS REPO GROUP ROUTES
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 action='edit',
212 action='edit',
213 conditions={'method': ['GET'], 'function': check_group},
213 conditions={'method': ['GET'], 'function': check_group},
214 requirements=URL_NAME_REQUIREMENTS)
214 requirements=URL_NAME_REQUIREMENTS)
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 action='edit',
216 action='edit',
217 conditions={'method': ['PUT'], 'function': check_group},
217 conditions={'method': ['PUT'], 'function': check_group},
218 requirements=URL_NAME_REQUIREMENTS)
218 requirements=URL_NAME_REQUIREMENTS)
219
219
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 action='edit_repo_group_advanced',
221 action='edit_repo_group_advanced',
222 conditions={'method': ['GET'], 'function': check_group},
222 conditions={'method': ['GET'], 'function': check_group},
223 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 action='edit_repo_group_advanced',
225 action='edit_repo_group_advanced',
226 conditions={'method': ['PUT'], 'function': check_group},
226 conditions={'method': ['PUT'], 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
227 requirements=URL_NAME_REQUIREMENTS)
228
228
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 action='edit_repo_group_perms',
230 action='edit_repo_group_perms',
231 conditions={'method': ['GET'], 'function': check_group},
231 conditions={'method': ['GET'], 'function': check_group},
232 requirements=URL_NAME_REQUIREMENTS)
232 requirements=URL_NAME_REQUIREMENTS)
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 action='update_perms',
234 action='update_perms',
235 conditions={'method': ['PUT'], 'function': check_group},
235 conditions={'method': ['PUT'], 'function': check_group},
236 requirements=URL_NAME_REQUIREMENTS)
236 requirements=URL_NAME_REQUIREMENTS)
237
237
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 action='delete', conditions={'method': ['DELETE'],
239 action='delete', conditions={'method': ['DELETE'],
240 'function': check_group},
240 'function': check_group},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242
242
243 # ADMIN USER ROUTES
243 # ADMIN USER ROUTES
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 controller='admin/users') as m:
245 controller='admin/users') as m:
246 m.connect('users', '/users',
246 m.connect('users', '/users',
247 action='create', conditions={'method': ['POST']})
247 action='create', conditions={'method': ['POST']})
248 m.connect('new_user', '/users/new',
248 m.connect('new_user', '/users/new',
249 action='new', conditions={'method': ['GET']})
249 action='new', conditions={'method': ['GET']})
250 m.connect('update_user', '/users/{user_id}',
250 m.connect('update_user', '/users/{user_id}',
251 action='update', conditions={'method': ['PUT']})
251 action='update', conditions={'method': ['PUT']})
252 m.connect('delete_user', '/users/{user_id}',
252 m.connect('delete_user', '/users/{user_id}',
253 action='delete', conditions={'method': ['DELETE']})
253 action='delete', conditions={'method': ['DELETE']})
254 m.connect('edit_user', '/users/{user_id}/edit',
254 m.connect('edit_user', '/users/{user_id}/edit',
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 m.connect('user', '/users/{user_id}',
256 m.connect('user', '/users/{user_id}',
257 action='show', conditions={'method': ['GET']})
257 action='show', conditions={'method': ['GET']})
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 action='reset_password', conditions={'method': ['POST']})
259 action='reset_password', conditions={'method': ['POST']})
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 action='create_personal_repo_group', conditions={'method': ['POST']})
261 action='create_personal_repo_group', conditions={'method': ['POST']})
262
262
263 # EXTRAS USER ROUTES
263 # EXTRAS USER ROUTES
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 action='edit_advanced', conditions={'method': ['GET']})
265 action='edit_advanced', conditions={'method': ['GET']})
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 action='update_advanced', conditions={'method': ['PUT']})
267 action='update_advanced', conditions={'method': ['PUT']})
268
268
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 action='edit_global_perms', conditions={'method': ['GET']})
270 action='edit_global_perms', conditions={'method': ['GET']})
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 action='update_global_perms', conditions={'method': ['PUT']})
272 action='update_global_perms', conditions={'method': ['PUT']})
273
273
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 action='edit_perms_summary', conditions={'method': ['GET']})
275 action='edit_perms_summary', conditions={'method': ['GET']})
276
276
277 # ADMIN USER GROUPS REST ROUTES
277 # ADMIN USER GROUPS REST ROUTES
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 controller='admin/user_groups') as m:
279 controller='admin/user_groups') as m:
280 m.connect('users_groups', '/user_groups',
280 m.connect('users_groups', '/user_groups',
281 action='create', conditions={'method': ['POST']})
281 action='create', conditions={'method': ['POST']})
282 m.connect('users_groups', '/user_groups',
282 m.connect('users_groups', '/user_groups',
283 action='index', conditions={'method': ['GET']})
283 action='index', conditions={'method': ['GET']})
284 m.connect('new_users_group', '/user_groups/new',
284 m.connect('new_users_group', '/user_groups/new',
285 action='new', conditions={'method': ['GET']})
285 action='new', conditions={'method': ['GET']})
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
286 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 action='update', conditions={'method': ['PUT']})
287 action='update', conditions={'method': ['PUT']})
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 action='delete', conditions={'method': ['DELETE']})
289 action='delete', conditions={'method': ['DELETE']})
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 action='edit', conditions={'method': ['GET']},
291 action='edit', conditions={'method': ['GET']},
292 function=check_user_group)
292 function=check_user_group)
293
293
294 # EXTRAS USER GROUP ROUTES
294 # EXTRAS USER GROUP ROUTES
295 m.connect('edit_user_group_global_perms',
295 m.connect('edit_user_group_global_perms',
296 '/user_groups/{user_group_id}/edit/global_permissions',
296 '/user_groups/{user_group_id}/edit/global_permissions',
297 action='edit_global_perms', conditions={'method': ['GET']})
297 action='edit_global_perms', conditions={'method': ['GET']})
298 m.connect('edit_user_group_global_perms',
298 m.connect('edit_user_group_global_perms',
299 '/user_groups/{user_group_id}/edit/global_permissions',
299 '/user_groups/{user_group_id}/edit/global_permissions',
300 action='update_global_perms', conditions={'method': ['PUT']})
300 action='update_global_perms', conditions={'method': ['PUT']})
301 m.connect('edit_user_group_perms_summary',
301 m.connect('edit_user_group_perms_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
302 '/user_groups/{user_group_id}/edit/permissions_summary',
303 action='edit_perms_summary', conditions={'method': ['GET']})
303 action='edit_perms_summary', conditions={'method': ['GET']})
304
304
305 m.connect('edit_user_group_perms',
305 m.connect('edit_user_group_perms',
306 '/user_groups/{user_group_id}/edit/permissions',
306 '/user_groups/{user_group_id}/edit/permissions',
307 action='edit_perms', conditions={'method': ['GET']})
307 action='edit_perms', conditions={'method': ['GET']})
308 m.connect('edit_user_group_perms',
308 m.connect('edit_user_group_perms',
309 '/user_groups/{user_group_id}/edit/permissions',
309 '/user_groups/{user_group_id}/edit/permissions',
310 action='update_perms', conditions={'method': ['PUT']})
310 action='update_perms', conditions={'method': ['PUT']})
311
311
312 m.connect('edit_user_group_advanced',
312 m.connect('edit_user_group_advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
313 '/user_groups/{user_group_id}/edit/advanced',
314 action='edit_advanced', conditions={'method': ['GET']})
314 action='edit_advanced', conditions={'method': ['GET']})
315
315
316 m.connect('edit_user_group_advanced_sync',
316 m.connect('edit_user_group_advanced_sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
317 '/user_groups/{user_group_id}/edit/advanced/sync',
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319
319
320 m.connect('edit_user_group_members',
320 m.connect('edit_user_group_members',
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 action='user_group_members', conditions={'method': ['GET']})
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 # ADMIN DEFAULTS REST ROUTES
324 # ADMIN DEFAULTS REST ROUTES
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 controller='admin/defaults') as m:
326 controller='admin/defaults') as m:
353 m.connect('admin_defaults_repositories', '/defaults/repositories',
327 m.connect('admin_defaults_repositories', '/defaults/repositories',
354 action='update_repository_defaults', conditions={'method': ['POST']})
328 action='update_repository_defaults', conditions={'method': ['POST']})
355 m.connect('admin_defaults_repositories', '/defaults/repositories',
329 m.connect('admin_defaults_repositories', '/defaults/repositories',
356 action='index', conditions={'method': ['GET']})
330 action='index', conditions={'method': ['GET']})
357
331
358 # ADMIN SETTINGS ROUTES
332 # ADMIN SETTINGS ROUTES
359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 controller='admin/settings') as m:
334 controller='admin/settings') as m:
361
335
362 # default
336 # default
363 m.connect('admin_settings', '/settings',
337 m.connect('admin_settings', '/settings',
364 action='settings_global_update',
338 action='settings_global_update',
365 conditions={'method': ['POST']})
339 conditions={'method': ['POST']})
366 m.connect('admin_settings', '/settings',
340 m.connect('admin_settings', '/settings',
367 action='settings_global', conditions={'method': ['GET']})
341 action='settings_global', conditions={'method': ['GET']})
368
342
369 m.connect('admin_settings_vcs', '/settings/vcs',
343 m.connect('admin_settings_vcs', '/settings/vcs',
370 action='settings_vcs_update',
344 action='settings_vcs_update',
371 conditions={'method': ['POST']})
345 conditions={'method': ['POST']})
372 m.connect('admin_settings_vcs', '/settings/vcs',
346 m.connect('admin_settings_vcs', '/settings/vcs',
373 action='settings_vcs',
347 action='settings_vcs',
374 conditions={'method': ['GET']})
348 conditions={'method': ['GET']})
375 m.connect('admin_settings_vcs', '/settings/vcs',
349 m.connect('admin_settings_vcs', '/settings/vcs',
376 action='delete_svn_pattern',
350 action='delete_svn_pattern',
377 conditions={'method': ['DELETE']})
351 conditions={'method': ['DELETE']})
378
352
379 m.connect('admin_settings_mapping', '/settings/mapping',
353 m.connect('admin_settings_mapping', '/settings/mapping',
380 action='settings_mapping_update',
354 action='settings_mapping_update',
381 conditions={'method': ['POST']})
355 conditions={'method': ['POST']})
382 m.connect('admin_settings_mapping', '/settings/mapping',
356 m.connect('admin_settings_mapping', '/settings/mapping',
383 action='settings_mapping', conditions={'method': ['GET']})
357 action='settings_mapping', conditions={'method': ['GET']})
384
358
385 m.connect('admin_settings_global', '/settings/global',
359 m.connect('admin_settings_global', '/settings/global',
386 action='settings_global_update',
360 action='settings_global_update',
387 conditions={'method': ['POST']})
361 conditions={'method': ['POST']})
388 m.connect('admin_settings_global', '/settings/global',
362 m.connect('admin_settings_global', '/settings/global',
389 action='settings_global', conditions={'method': ['GET']})
363 action='settings_global', conditions={'method': ['GET']})
390
364
391 m.connect('admin_settings_visual', '/settings/visual',
365 m.connect('admin_settings_visual', '/settings/visual',
392 action='settings_visual_update',
366 action='settings_visual_update',
393 conditions={'method': ['POST']})
367 conditions={'method': ['POST']})
394 m.connect('admin_settings_visual', '/settings/visual',
368 m.connect('admin_settings_visual', '/settings/visual',
395 action='settings_visual', conditions={'method': ['GET']})
369 action='settings_visual', conditions={'method': ['GET']})
396
370
397 m.connect('admin_settings_issuetracker',
371 m.connect('admin_settings_issuetracker',
398 '/settings/issue-tracker', action='settings_issuetracker',
372 '/settings/issue-tracker', action='settings_issuetracker',
399 conditions={'method': ['GET']})
373 conditions={'method': ['GET']})
400 m.connect('admin_settings_issuetracker_save',
374 m.connect('admin_settings_issuetracker_save',
401 '/settings/issue-tracker/save',
375 '/settings/issue-tracker/save',
402 action='settings_issuetracker_save',
376 action='settings_issuetracker_save',
403 conditions={'method': ['POST']})
377 conditions={'method': ['POST']})
404 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
378 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
405 action='settings_issuetracker_test',
379 action='settings_issuetracker_test',
406 conditions={'method': ['POST']})
380 conditions={'method': ['POST']})
407 m.connect('admin_issuetracker_delete',
381 m.connect('admin_issuetracker_delete',
408 '/settings/issue-tracker/delete',
382 '/settings/issue-tracker/delete',
409 action='settings_issuetracker_delete',
383 action='settings_issuetracker_delete',
410 conditions={'method': ['DELETE']})
384 conditions={'method': ['DELETE']})
411
385
412 m.connect('admin_settings_email', '/settings/email',
386 m.connect('admin_settings_email', '/settings/email',
413 action='settings_email_update',
387 action='settings_email_update',
414 conditions={'method': ['POST']})
388 conditions={'method': ['POST']})
415 m.connect('admin_settings_email', '/settings/email',
389 m.connect('admin_settings_email', '/settings/email',
416 action='settings_email', conditions={'method': ['GET']})
390 action='settings_email', conditions={'method': ['GET']})
417
391
418 m.connect('admin_settings_hooks', '/settings/hooks',
392 m.connect('admin_settings_hooks', '/settings/hooks',
419 action='settings_hooks_update',
393 action='settings_hooks_update',
420 conditions={'method': ['POST', 'DELETE']})
394 conditions={'method': ['POST', 'DELETE']})
421 m.connect('admin_settings_hooks', '/settings/hooks',
395 m.connect('admin_settings_hooks', '/settings/hooks',
422 action='settings_hooks', conditions={'method': ['GET']})
396 action='settings_hooks', conditions={'method': ['GET']})
423
397
424 m.connect('admin_settings_search', '/settings/search',
398 m.connect('admin_settings_search', '/settings/search',
425 action='settings_search', conditions={'method': ['GET']})
399 action='settings_search', conditions={'method': ['GET']})
426
400
427 m.connect('admin_settings_supervisor', '/settings/supervisor',
401 m.connect('admin_settings_supervisor', '/settings/supervisor',
428 action='settings_supervisor', conditions={'method': ['GET']})
402 action='settings_supervisor', conditions={'method': ['GET']})
429 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
403 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
430 action='settings_supervisor_log', conditions={'method': ['GET']})
404 action='settings_supervisor_log', conditions={'method': ['GET']})
431
405
432 m.connect('admin_settings_labs', '/settings/labs',
406 m.connect('admin_settings_labs', '/settings/labs',
433 action='settings_labs_update',
407 action='settings_labs_update',
434 conditions={'method': ['POST']})
408 conditions={'method': ['POST']})
435 m.connect('admin_settings_labs', '/settings/labs',
409 m.connect('admin_settings_labs', '/settings/labs',
436 action='settings_labs', conditions={'method': ['GET']})
410 action='settings_labs', conditions={'method': ['GET']})
437
411
438 # ADMIN MY ACCOUNT
412 # ADMIN MY ACCOUNT
439 with rmap.submapper(path_prefix=ADMIN_PREFIX,
413 with rmap.submapper(path_prefix=ADMIN_PREFIX,
440 controller='admin/my_account') as m:
414 controller='admin/my_account') as m:
441
415
442 # NOTE(marcink): this needs to be kept for password force flag to be
416 # NOTE(marcink): this needs to be kept for password force flag to be
443 # handled in pylons controllers, remove after full migration to pyramid
417 # handled in pylons controllers, remove after full migration to pyramid
444 m.connect('my_account_password', '/my_account/password',
418 m.connect('my_account_password', '/my_account/password',
445 action='my_account_password', conditions={'method': ['GET']})
419 action='my_account_password', conditions={'method': ['GET']})
446
420
447 #==========================================================================
421 #==========================================================================
448 # REPOSITORY ROUTES
422 # REPOSITORY ROUTES
449 #==========================================================================
423 #==========================================================================
450
424
451 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
425 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
452 controller='admin/repos', action='repo_creating',
426 controller='admin/repos', action='repo_creating',
453 requirements=URL_NAME_REQUIREMENTS)
427 requirements=URL_NAME_REQUIREMENTS)
454 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
428 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
455 controller='admin/repos', action='repo_check',
429 controller='admin/repos', action='repo_check',
456 requirements=URL_NAME_REQUIREMENTS)
430 requirements=URL_NAME_REQUIREMENTS)
457
431
458 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
432 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
459 controller='changeset', revision='tip',
433 controller='changeset', revision='tip',
460 conditions={'function': check_repo},
434 conditions={'function': check_repo},
461 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
435 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
462 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
436 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
463 controller='changeset', revision='tip', action='changeset_children',
437 controller='changeset', revision='tip', action='changeset_children',
464 conditions={'function': check_repo},
438 conditions={'function': check_repo},
465 requirements=URL_NAME_REQUIREMENTS)
439 requirements=URL_NAME_REQUIREMENTS)
466 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
440 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
467 controller='changeset', revision='tip', action='changeset_parents',
441 controller='changeset', revision='tip', action='changeset_parents',
468 conditions={'function': check_repo},
442 conditions={'function': check_repo},
469 requirements=URL_NAME_REQUIREMENTS)
443 requirements=URL_NAME_REQUIREMENTS)
470
444
471 # repo edit options
445 # repo edit options
472 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
446 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
473 controller='admin/repos', action='edit_fields',
447 controller='admin/repos', action='edit_fields',
474 conditions={'method': ['GET'], 'function': check_repo},
448 conditions={'method': ['GET'], 'function': check_repo},
475 requirements=URL_NAME_REQUIREMENTS)
449 requirements=URL_NAME_REQUIREMENTS)
476 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
450 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
477 controller='admin/repos', action='create_repo_field',
451 controller='admin/repos', action='create_repo_field',
478 conditions={'method': ['PUT'], 'function': check_repo},
452 conditions={'method': ['PUT'], 'function': check_repo},
479 requirements=URL_NAME_REQUIREMENTS)
453 requirements=URL_NAME_REQUIREMENTS)
480 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
454 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
481 controller='admin/repos', action='delete_repo_field',
455 controller='admin/repos', action='delete_repo_field',
482 conditions={'method': ['DELETE'], 'function': check_repo},
456 conditions={'method': ['DELETE'], 'function': check_repo},
483 requirements=URL_NAME_REQUIREMENTS)
457 requirements=URL_NAME_REQUIREMENTS)
484
458
485 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
459 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
486 controller='admin/repos', action='toggle_locking',
460 controller='admin/repos', action='toggle_locking',
487 conditions={'method': ['GET'], 'function': check_repo},
461 conditions={'method': ['GET'], 'function': check_repo},
488 requirements=URL_NAME_REQUIREMENTS)
462 requirements=URL_NAME_REQUIREMENTS)
489
463
490 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
464 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
491 controller='admin/repos', action='edit_remote_form',
465 controller='admin/repos', action='edit_remote_form',
492 conditions={'method': ['GET'], 'function': check_repo},
466 conditions={'method': ['GET'], 'function': check_repo},
493 requirements=URL_NAME_REQUIREMENTS)
467 requirements=URL_NAME_REQUIREMENTS)
494 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
468 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
495 controller='admin/repos', action='edit_remote',
469 controller='admin/repos', action='edit_remote',
496 conditions={'method': ['PUT'], 'function': check_repo},
470 conditions={'method': ['PUT'], 'function': check_repo},
497 requirements=URL_NAME_REQUIREMENTS)
471 requirements=URL_NAME_REQUIREMENTS)
498
472
499 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
473 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
500 controller='admin/repos', action='edit_statistics_form',
474 controller='admin/repos', action='edit_statistics_form',
501 conditions={'method': ['GET'], 'function': check_repo},
475 conditions={'method': ['GET'], 'function': check_repo},
502 requirements=URL_NAME_REQUIREMENTS)
476 requirements=URL_NAME_REQUIREMENTS)
503 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
477 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
504 controller='admin/repos', action='edit_statistics',
478 controller='admin/repos', action='edit_statistics',
505 conditions={'method': ['PUT'], 'function': check_repo},
479 conditions={'method': ['PUT'], 'function': check_repo},
506 requirements=URL_NAME_REQUIREMENTS)
480 requirements=URL_NAME_REQUIREMENTS)
507 rmap.connect('repo_settings_issuetracker',
481 rmap.connect('repo_settings_issuetracker',
508 '/{repo_name}/settings/issue-tracker',
482 '/{repo_name}/settings/issue-tracker',
509 controller='admin/repos', action='repo_issuetracker',
483 controller='admin/repos', action='repo_issuetracker',
510 conditions={'method': ['GET'], 'function': check_repo},
484 conditions={'method': ['GET'], 'function': check_repo},
511 requirements=URL_NAME_REQUIREMENTS)
485 requirements=URL_NAME_REQUIREMENTS)
512 rmap.connect('repo_issuetracker_test',
486 rmap.connect('repo_issuetracker_test',
513 '/{repo_name}/settings/issue-tracker/test',
487 '/{repo_name}/settings/issue-tracker/test',
514 controller='admin/repos', action='repo_issuetracker_test',
488 controller='admin/repos', action='repo_issuetracker_test',
515 conditions={'method': ['POST'], 'function': check_repo},
489 conditions={'method': ['POST'], 'function': check_repo},
516 requirements=URL_NAME_REQUIREMENTS)
490 requirements=URL_NAME_REQUIREMENTS)
517 rmap.connect('repo_issuetracker_delete',
491 rmap.connect('repo_issuetracker_delete',
518 '/{repo_name}/settings/issue-tracker/delete',
492 '/{repo_name}/settings/issue-tracker/delete',
519 controller='admin/repos', action='repo_issuetracker_delete',
493 controller='admin/repos', action='repo_issuetracker_delete',
520 conditions={'method': ['DELETE'], 'function': check_repo},
494 conditions={'method': ['DELETE'], 'function': check_repo},
521 requirements=URL_NAME_REQUIREMENTS)
495 requirements=URL_NAME_REQUIREMENTS)
522 rmap.connect('repo_issuetracker_save',
496 rmap.connect('repo_issuetracker_save',
523 '/{repo_name}/settings/issue-tracker/save',
497 '/{repo_name}/settings/issue-tracker/save',
524 controller='admin/repos', action='repo_issuetracker_save',
498 controller='admin/repos', action='repo_issuetracker_save',
525 conditions={'method': ['POST'], 'function': check_repo},
499 conditions={'method': ['POST'], 'function': check_repo},
526 requirements=URL_NAME_REQUIREMENTS)
500 requirements=URL_NAME_REQUIREMENTS)
527 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
501 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
528 controller='admin/repos', action='repo_settings_vcs_update',
502 controller='admin/repos', action='repo_settings_vcs_update',
529 conditions={'method': ['POST'], 'function': check_repo},
503 conditions={'method': ['POST'], 'function': check_repo},
530 requirements=URL_NAME_REQUIREMENTS)
504 requirements=URL_NAME_REQUIREMENTS)
531 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
505 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
532 controller='admin/repos', action='repo_settings_vcs',
506 controller='admin/repos', action='repo_settings_vcs',
533 conditions={'method': ['GET'], 'function': check_repo},
507 conditions={'method': ['GET'], 'function': check_repo},
534 requirements=URL_NAME_REQUIREMENTS)
508 requirements=URL_NAME_REQUIREMENTS)
535 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
509 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
536 controller='admin/repos', action='repo_delete_svn_pattern',
510 controller='admin/repos', action='repo_delete_svn_pattern',
537 conditions={'method': ['DELETE'], 'function': check_repo},
511 conditions={'method': ['DELETE'], 'function': check_repo},
538 requirements=URL_NAME_REQUIREMENTS)
512 requirements=URL_NAME_REQUIREMENTS)
539 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
513 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
540 controller='admin/repos', action='repo_settings_pullrequest',
514 controller='admin/repos', action='repo_settings_pullrequest',
541 conditions={'method': ['GET', 'POST'], 'function': check_repo},
515 conditions={'method': ['GET', 'POST'], 'function': check_repo},
542 requirements=URL_NAME_REQUIREMENTS)
516 requirements=URL_NAME_REQUIREMENTS)
543
517
544 # still working url for backward compat.
518 # still working url for backward compat.
545 rmap.connect('raw_changeset_home_depraced',
519 rmap.connect('raw_changeset_home_depraced',
546 '/{repo_name}/raw-changeset/{revision}',
520 '/{repo_name}/raw-changeset/{revision}',
547 controller='changeset', action='changeset_raw',
521 controller='changeset', action='changeset_raw',
548 revision='tip', conditions={'function': check_repo},
522 revision='tip', conditions={'function': check_repo},
549 requirements=URL_NAME_REQUIREMENTS)
523 requirements=URL_NAME_REQUIREMENTS)
550
524
551 # new URLs
525 # new URLs
552 rmap.connect('changeset_raw_home',
526 rmap.connect('changeset_raw_home',
553 '/{repo_name}/changeset-diff/{revision}',
527 '/{repo_name}/changeset-diff/{revision}',
554 controller='changeset', action='changeset_raw',
528 controller='changeset', action='changeset_raw',
555 revision='tip', conditions={'function': check_repo},
529 revision='tip', conditions={'function': check_repo},
556 requirements=URL_NAME_REQUIREMENTS)
530 requirements=URL_NAME_REQUIREMENTS)
557
531
558 rmap.connect('changeset_patch_home',
532 rmap.connect('changeset_patch_home',
559 '/{repo_name}/changeset-patch/{revision}',
533 '/{repo_name}/changeset-patch/{revision}',
560 controller='changeset', action='changeset_patch',
534 controller='changeset', action='changeset_patch',
561 revision='tip', conditions={'function': check_repo},
535 revision='tip', conditions={'function': check_repo},
562 requirements=URL_NAME_REQUIREMENTS)
536 requirements=URL_NAME_REQUIREMENTS)
563
537
564 rmap.connect('changeset_download_home',
538 rmap.connect('changeset_download_home',
565 '/{repo_name}/changeset-download/{revision}',
539 '/{repo_name}/changeset-download/{revision}',
566 controller='changeset', action='changeset_download',
540 controller='changeset', action='changeset_download',
567 revision='tip', conditions={'function': check_repo},
541 revision='tip', conditions={'function': check_repo},
568 requirements=URL_NAME_REQUIREMENTS)
542 requirements=URL_NAME_REQUIREMENTS)
569
543
570 rmap.connect('changeset_comment',
544 rmap.connect('changeset_comment',
571 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
545 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
572 controller='changeset', revision='tip', action='comment',
546 controller='changeset', revision='tip', action='comment',
573 conditions={'function': check_repo},
547 conditions={'function': check_repo},
574 requirements=URL_NAME_REQUIREMENTS)
548 requirements=URL_NAME_REQUIREMENTS)
575
549
576 rmap.connect('changeset_comment_preview',
550 rmap.connect('changeset_comment_preview',
577 '/{repo_name}/changeset/comment/preview', jsroute=True,
551 '/{repo_name}/changeset/comment/preview', jsroute=True,
578 controller='changeset', action='preview_comment',
552 controller='changeset', action='preview_comment',
579 conditions={'function': check_repo, 'method': ['POST']},
553 conditions={'function': check_repo, 'method': ['POST']},
580 requirements=URL_NAME_REQUIREMENTS)
554 requirements=URL_NAME_REQUIREMENTS)
581
555
582 rmap.connect('changeset_comment_delete',
556 rmap.connect('changeset_comment_delete',
583 '/{repo_name}/changeset/comment/{comment_id}/delete',
557 '/{repo_name}/changeset/comment/{comment_id}/delete',
584 controller='changeset', action='delete_comment',
558 controller='changeset', action='delete_comment',
585 conditions={'function': check_repo, 'method': ['DELETE']},
559 conditions={'function': check_repo, 'method': ['DELETE']},
586 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
560 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
587
561
588 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
562 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
589 controller='changeset', action='changeset_info',
563 controller='changeset', action='changeset_info',
590 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
564 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
591
565
592 rmap.connect('compare_home',
566 rmap.connect('compare_home',
593 '/{repo_name}/compare',
567 '/{repo_name}/compare',
594 controller='compare', action='index',
568 controller='compare', action='index',
595 conditions={'function': check_repo},
569 conditions={'function': check_repo},
596 requirements=URL_NAME_REQUIREMENTS)
570 requirements=URL_NAME_REQUIREMENTS)
597
571
598 rmap.connect('compare_url',
572 rmap.connect('compare_url',
599 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
573 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
600 controller='compare', action='compare',
574 controller='compare', action='compare',
601 conditions={'function': check_repo},
575 conditions={'function': check_repo},
602 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
576 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
603
577
604 rmap.connect('pullrequest_home',
578 rmap.connect('pullrequest_home',
605 '/{repo_name}/pull-request/new', controller='pullrequests',
579 '/{repo_name}/pull-request/new', controller='pullrequests',
606 action='index', conditions={'function': check_repo,
580 action='index', conditions={'function': check_repo,
607 'method': ['GET']},
581 'method': ['GET']},
608 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
582 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
609
583
610 rmap.connect('pullrequest',
584 rmap.connect('pullrequest',
611 '/{repo_name}/pull-request/new', controller='pullrequests',
585 '/{repo_name}/pull-request/new', controller='pullrequests',
612 action='create', conditions={'function': check_repo,
586 action='create', conditions={'function': check_repo,
613 'method': ['POST']},
587 'method': ['POST']},
614 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
588 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
615
589
616 rmap.connect('pullrequest_repo_refs',
590 rmap.connect('pullrequest_repo_refs',
617 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
591 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
618 controller='pullrequests',
592 controller='pullrequests',
619 action='get_repo_refs',
593 action='get_repo_refs',
620 conditions={'function': check_repo, 'method': ['GET']},
594 conditions={'function': check_repo, 'method': ['GET']},
621 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
595 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
622
596
623 rmap.connect('pullrequest_repo_destinations',
597 rmap.connect('pullrequest_repo_destinations',
624 '/{repo_name}/pull-request/repo-destinations',
598 '/{repo_name}/pull-request/repo-destinations',
625 controller='pullrequests',
599 controller='pullrequests',
626 action='get_repo_destinations',
600 action='get_repo_destinations',
627 conditions={'function': check_repo, 'method': ['GET']},
601 conditions={'function': check_repo, 'method': ['GET']},
628 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
602 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
629
603
630 rmap.connect('pullrequest_show',
604 rmap.connect('pullrequest_show',
631 '/{repo_name}/pull-request/{pull_request_id}',
605 '/{repo_name}/pull-request/{pull_request_id}',
632 controller='pullrequests',
606 controller='pullrequests',
633 action='show', conditions={'function': check_repo,
607 action='show', conditions={'function': check_repo,
634 'method': ['GET']},
608 'method': ['GET']},
635 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
609 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
636
610
637 rmap.connect('pullrequest_update',
611 rmap.connect('pullrequest_update',
638 '/{repo_name}/pull-request/{pull_request_id}',
612 '/{repo_name}/pull-request/{pull_request_id}',
639 controller='pullrequests',
613 controller='pullrequests',
640 action='update', conditions={'function': check_repo,
614 action='update', conditions={'function': check_repo,
641 'method': ['PUT']},
615 'method': ['PUT']},
642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
616 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643
617
644 rmap.connect('pullrequest_merge',
618 rmap.connect('pullrequest_merge',
645 '/{repo_name}/pull-request/{pull_request_id}',
619 '/{repo_name}/pull-request/{pull_request_id}',
646 controller='pullrequests',
620 controller='pullrequests',
647 action='merge', conditions={'function': check_repo,
621 action='merge', conditions={'function': check_repo,
648 'method': ['POST']},
622 'method': ['POST']},
649 requirements=URL_NAME_REQUIREMENTS)
623 requirements=URL_NAME_REQUIREMENTS)
650
624
651 rmap.connect('pullrequest_delete',
625 rmap.connect('pullrequest_delete',
652 '/{repo_name}/pull-request/{pull_request_id}',
626 '/{repo_name}/pull-request/{pull_request_id}',
653 controller='pullrequests',
627 controller='pullrequests',
654 action='delete', conditions={'function': check_repo,
628 action='delete', conditions={'function': check_repo,
655 'method': ['DELETE']},
629 'method': ['DELETE']},
656 requirements=URL_NAME_REQUIREMENTS)
630 requirements=URL_NAME_REQUIREMENTS)
657
631
658 rmap.connect('pullrequest_comment',
632 rmap.connect('pullrequest_comment',
659 '/{repo_name}/pull-request-comment/{pull_request_id}',
633 '/{repo_name}/pull-request-comment/{pull_request_id}',
660 controller='pullrequests',
634 controller='pullrequests',
661 action='comment', conditions={'function': check_repo,
635 action='comment', conditions={'function': check_repo,
662 'method': ['POST']},
636 'method': ['POST']},
663 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
637 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
664
638
665 rmap.connect('pullrequest_comment_delete',
639 rmap.connect('pullrequest_comment_delete',
666 '/{repo_name}/pull-request-comment/{comment_id}/delete',
640 '/{repo_name}/pull-request-comment/{comment_id}/delete',
667 controller='pullrequests', action='delete_comment',
641 controller='pullrequests', action='delete_comment',
668 conditions={'function': check_repo, 'method': ['DELETE']},
642 conditions={'function': check_repo, 'method': ['DELETE']},
669 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
670
644
671 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
645 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
672 controller='forks', action='fork_create',
646 controller='forks', action='fork_create',
673 conditions={'function': check_repo, 'method': ['POST']},
647 conditions={'function': check_repo, 'method': ['POST']},
674 requirements=URL_NAME_REQUIREMENTS)
648 requirements=URL_NAME_REQUIREMENTS)
675
649
676 rmap.connect('repo_fork_home', '/{repo_name}/fork',
650 rmap.connect('repo_fork_home', '/{repo_name}/fork',
677 controller='forks', action='fork',
651 controller='forks', action='fork',
678 conditions={'function': check_repo},
652 conditions={'function': check_repo},
679 requirements=URL_NAME_REQUIREMENTS)
653 requirements=URL_NAME_REQUIREMENTS)
680
654
681 rmap.connect('repo_forks_home', '/{repo_name}/forks',
655 rmap.connect('repo_forks_home', '/{repo_name}/forks',
682 controller='forks', action='forks',
656 controller='forks', action='forks',
683 conditions={'function': check_repo},
657 conditions={'function': check_repo},
684 requirements=URL_NAME_REQUIREMENTS)
658 requirements=URL_NAME_REQUIREMENTS)
685
659
686 return rmap
660 return rmap
@@ -1,483 +1,484 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 permissions model for RhodeCode
22 permissions model for RhodeCode
23 """
23 """
24
24
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import (
32 from rhodecode.model.db import (
33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
34 UserUserGroupToPerm, UserGroup, UserGroupToPerm)
35 from rhodecode.lib.utils2 import str2bool, safe_int
35 from rhodecode.lib.utils2 import str2bool, safe_int
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class PermissionModel(BaseModel):
40 class PermissionModel(BaseModel):
41 """
41 """
42 Permissions model for RhodeCode
42 Permissions model for RhodeCode
43 """
43 """
44
44
45 cls = Permission
45 cls = Permission
46 global_perms = {
46 global_perms = {
47 'default_repo_create': None,
47 'default_repo_create': None,
48 # special case for create repos on write access to group
48 # special case for create repos on write access to group
49 'default_repo_create_on_write': None,
49 'default_repo_create_on_write': None,
50 'default_repo_group_create': None,
50 'default_repo_group_create': None,
51 'default_user_group_create': None,
51 'default_user_group_create': None,
52 'default_fork_create': None,
52 'default_fork_create': None,
53 'default_inherit_default_permissions': None,
53 'default_inherit_default_permissions': None,
54 'default_register': None,
54 'default_register': None,
55 'default_password_reset': None,
55 'default_password_reset': None,
56 'default_extern_activate': None,
56 'default_extern_activate': None,
57
57
58 # object permissions below
58 # object permissions below
59 'default_repo_perm': None,
59 'default_repo_perm': None,
60 'default_group_perm': None,
60 'default_group_perm': None,
61 'default_user_group_perm': None,
61 'default_user_group_perm': None,
62 }
62 }
63
63
64 def set_global_permission_choices(self, c_obj, gettext_translator):
64 def set_global_permission_choices(self, c_obj, gettext_translator):
65 _ = gettext_translator
65
66
66 c_obj.repo_perms_choices = [
67 c_obj.repo_perms_choices = [
67 ('repository.none', gettext_translator('None'),),
68 ('repository.none', _('None'),),
68 ('repository.read', gettext_translator('Read'),),
69 ('repository.read', _('Read'),),
69 ('repository.write', gettext_translator('Write'),),
70 ('repository.write', _('Write'),),
70 ('repository.admin', gettext_translator('Admin'),)]
71 ('repository.admin', _('Admin'),)]
71
72
72 c_obj.group_perms_choices = [
73 c_obj.group_perms_choices = [
73 ('group.none', gettext_translator('None'),),
74 ('group.none', _('None'),),
74 ('group.read', gettext_translator('Read'),),
75 ('group.read', _('Read'),),
75 ('group.write', gettext_translator('Write'),),
76 ('group.write', _('Write'),),
76 ('group.admin', gettext_translator('Admin'),)]
77 ('group.admin', _('Admin'),)]
77
78
78 c_obj.user_group_perms_choices = [
79 c_obj.user_group_perms_choices = [
79 ('usergroup.none', gettext_translator('None'),),
80 ('usergroup.none', _('None'),),
80 ('usergroup.read', gettext_translator('Read'),),
81 ('usergroup.read', _('Read'),),
81 ('usergroup.write', gettext_translator('Write'),),
82 ('usergroup.write', _('Write'),),
82 ('usergroup.admin', gettext_translator('Admin'),)]
83 ('usergroup.admin', _('Admin'),)]
83
84
84 c_obj.register_choices = [
85 c_obj.register_choices = [
85 ('hg.register.none', gettext_translator('Disabled')),
86 ('hg.register.none', _('Disabled')),
86 ('hg.register.manual_activate', gettext_translator('Allowed with manual account activation')),
87 ('hg.register.manual_activate', _('Allowed with manual account activation')),
87 ('hg.register.auto_activate', gettext_translator('Allowed with automatic account activation')),]
88 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
88
89
89 c_obj.password_reset_choices = [
90 c_obj.password_reset_choices = [
90 ('hg.password_reset.enabled', gettext_translator('Allow password recovery')),
91 ('hg.password_reset.enabled', _('Allow password recovery')),
91 ('hg.password_reset.hidden', gettext_translator('Hide password recovery link')),
92 ('hg.password_reset.hidden', _('Hide password recovery link')),
92 ('hg.password_reset.disabled', gettext_translator('Disable password recovery')),]
93 ('hg.password_reset.disabled', _('Disable password recovery')),]
93
94
94 c_obj.extern_activate_choices = [
95 c_obj.extern_activate_choices = [
95 ('hg.extern_activate.manual', gettext_translator('Manual activation of external account')),
96 ('hg.extern_activate.manual', _('Manual activation of external account')),
96 ('hg.extern_activate.auto', gettext_translator('Automatic activation of external account')),]
97 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
97
98
98 c_obj.repo_create_choices = [
99 c_obj.repo_create_choices = [
99 ('hg.create.none', gettext_translator('Disabled')),
100 ('hg.create.none', _('Disabled')),
100 ('hg.create.repository', gettext_translator('Enabled'))]
101 ('hg.create.repository', _('Enabled'))]
101
102
102 c_obj.repo_create_on_write_choices = [
103 c_obj.repo_create_on_write_choices = [
103 ('hg.create.write_on_repogroup.false', gettext_translator('Disabled')),
104 ('hg.create.write_on_repogroup.false', _('Disabled')),
104 ('hg.create.write_on_repogroup.true', gettext_translator('Enabled'))]
105 ('hg.create.write_on_repogroup.true', _('Enabled'))]
105
106
106 c_obj.user_group_create_choices = [
107 c_obj.user_group_create_choices = [
107 ('hg.usergroup.create.false', gettext_translator('Disabled')),
108 ('hg.usergroup.create.false', _('Disabled')),
108 ('hg.usergroup.create.true', gettext_translator('Enabled'))]
109 ('hg.usergroup.create.true', _('Enabled'))]
109
110
110 c_obj.repo_group_create_choices = [
111 c_obj.repo_group_create_choices = [
111 ('hg.repogroup.create.false', gettext_translator('Disabled')),
112 ('hg.repogroup.create.false', _('Disabled')),
112 ('hg.repogroup.create.true', gettext_translator('Enabled'))]
113 ('hg.repogroup.create.true', _('Enabled'))]
113
114
114 c_obj.fork_choices = [
115 c_obj.fork_choices = [
115 ('hg.fork.none', gettext_translator('Disabled')),
116 ('hg.fork.none', _('Disabled')),
116 ('hg.fork.repository', gettext_translator('Enabled'))]
117 ('hg.fork.repository', _('Enabled'))]
117
118
118 c_obj.inherit_default_permission_choices = [
119 c_obj.inherit_default_permission_choices = [
119 ('hg.inherit_default_perms.false', gettext_translator('Disabled')),
120 ('hg.inherit_default_perms.false', _('Disabled')),
120 ('hg.inherit_default_perms.true', gettext_translator('Enabled'))]
121 ('hg.inherit_default_perms.true', _('Enabled'))]
121
122
122 def get_default_perms(self, object_perms, suffix):
123 def get_default_perms(self, object_perms, suffix):
123 defaults = {}
124 defaults = {}
124 for perm in object_perms:
125 for perm in object_perms:
125 # perms
126 # perms
126 if perm.permission.permission_name.startswith('repository.'):
127 if perm.permission.permission_name.startswith('repository.'):
127 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
128 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
128
129
129 if perm.permission.permission_name.startswith('group.'):
130 if perm.permission.permission_name.startswith('group.'):
130 defaults['default_group_perm' + suffix] = perm.permission.permission_name
131 defaults['default_group_perm' + suffix] = perm.permission.permission_name
131
132
132 if perm.permission.permission_name.startswith('usergroup.'):
133 if perm.permission.permission_name.startswith('usergroup.'):
133 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
134 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
134
135
135 # creation of objects
136 # creation of objects
136 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
137 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
137 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
138 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
138
139
139 elif perm.permission.permission_name.startswith('hg.create.'):
140 elif perm.permission.permission_name.startswith('hg.create.'):
140 defaults['default_repo_create' + suffix] = perm.permission.permission_name
141 defaults['default_repo_create' + suffix] = perm.permission.permission_name
141
142
142 if perm.permission.permission_name.startswith('hg.fork.'):
143 if perm.permission.permission_name.startswith('hg.fork.'):
143 defaults['default_fork_create' + suffix] = perm.permission.permission_name
144 defaults['default_fork_create' + suffix] = perm.permission.permission_name
144
145
145 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
146 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
146 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
147 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
147
148
148 if perm.permission.permission_name.startswith('hg.repogroup.'):
149 if perm.permission.permission_name.startswith('hg.repogroup.'):
149 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
150 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
150
151
151 if perm.permission.permission_name.startswith('hg.usergroup.'):
152 if perm.permission.permission_name.startswith('hg.usergroup.'):
152 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
153 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
153
154
154 # registration and external account activation
155 # registration and external account activation
155 if perm.permission.permission_name.startswith('hg.register.'):
156 if perm.permission.permission_name.startswith('hg.register.'):
156 defaults['default_register' + suffix] = perm.permission.permission_name
157 defaults['default_register' + suffix] = perm.permission.permission_name
157
158
158 if perm.permission.permission_name.startswith('hg.password_reset.'):
159 if perm.permission.permission_name.startswith('hg.password_reset.'):
159 defaults['default_password_reset' + suffix] = perm.permission.permission_name
160 defaults['default_password_reset' + suffix] = perm.permission.permission_name
160
161
161 if perm.permission.permission_name.startswith('hg.extern_activate.'):
162 if perm.permission.permission_name.startswith('hg.extern_activate.'):
162 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
163 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
163
164
164 return defaults
165 return defaults
165
166
166 def _make_new_user_perm(self, user, perm_name):
167 def _make_new_user_perm(self, user, perm_name):
167 log.debug('Creating new user permission:%s', perm_name)
168 log.debug('Creating new user permission:%s', perm_name)
168 new = UserToPerm()
169 new = UserToPerm()
169 new.user = user
170 new.user = user
170 new.permission = Permission.get_by_key(perm_name)
171 new.permission = Permission.get_by_key(perm_name)
171 return new
172 return new
172
173
173 def _make_new_user_group_perm(self, user_group, perm_name):
174 def _make_new_user_group_perm(self, user_group, perm_name):
174 log.debug('Creating new user group permission:%s', perm_name)
175 log.debug('Creating new user group permission:%s', perm_name)
175 new = UserGroupToPerm()
176 new = UserGroupToPerm()
176 new.users_group = user_group
177 new.users_group = user_group
177 new.permission = Permission.get_by_key(perm_name)
178 new.permission = Permission.get_by_key(perm_name)
178 return new
179 return new
179
180
180 def _keep_perm(self, perm_name, keep_fields):
181 def _keep_perm(self, perm_name, keep_fields):
181 def get_pat(field_name):
182 def get_pat(field_name):
182 return {
183 return {
183 # global perms
184 # global perms
184 'default_repo_create': 'hg.create.',
185 'default_repo_create': 'hg.create.',
185 # special case for create repos on write access to group
186 # special case for create repos on write access to group
186 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
187 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
187 'default_repo_group_create': 'hg.repogroup.create.',
188 'default_repo_group_create': 'hg.repogroup.create.',
188 'default_user_group_create': 'hg.usergroup.create.',
189 'default_user_group_create': 'hg.usergroup.create.',
189 'default_fork_create': 'hg.fork.',
190 'default_fork_create': 'hg.fork.',
190 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
191 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
191
192
192 # application perms
193 # application perms
193 'default_register': 'hg.register.',
194 'default_register': 'hg.register.',
194 'default_password_reset': 'hg.password_reset.',
195 'default_password_reset': 'hg.password_reset.',
195 'default_extern_activate': 'hg.extern_activate.',
196 'default_extern_activate': 'hg.extern_activate.',
196
197
197 # object permissions below
198 # object permissions below
198 'default_repo_perm': 'repository.',
199 'default_repo_perm': 'repository.',
199 'default_group_perm': 'group.',
200 'default_group_perm': 'group.',
200 'default_user_group_perm': 'usergroup.',
201 'default_user_group_perm': 'usergroup.',
201 }[field_name]
202 }[field_name]
202 for field in keep_fields:
203 for field in keep_fields:
203 pat = get_pat(field)
204 pat = get_pat(field)
204 if perm_name.startswith(pat):
205 if perm_name.startswith(pat):
205 return True
206 return True
206 return False
207 return False
207
208
208 def _clear_object_perm(self, object_perms, preserve=None):
209 def _clear_object_perm(self, object_perms, preserve=None):
209 preserve = preserve or []
210 preserve = preserve or []
210 _deleted = []
211 _deleted = []
211 for perm in object_perms:
212 for perm in object_perms:
212 perm_name = perm.permission.permission_name
213 perm_name = perm.permission.permission_name
213 if not self._keep_perm(perm_name, keep_fields=preserve):
214 if not self._keep_perm(perm_name, keep_fields=preserve):
214 _deleted.append(perm_name)
215 _deleted.append(perm_name)
215 self.sa.delete(perm)
216 self.sa.delete(perm)
216 return _deleted
217 return _deleted
217
218
218 def _clear_user_perms(self, user_id, preserve=None):
219 def _clear_user_perms(self, user_id, preserve=None):
219 perms = self.sa.query(UserToPerm)\
220 perms = self.sa.query(UserToPerm)\
220 .filter(UserToPerm.user_id == user_id)\
221 .filter(UserToPerm.user_id == user_id)\
221 .all()
222 .all()
222 return self._clear_object_perm(perms, preserve=preserve)
223 return self._clear_object_perm(perms, preserve=preserve)
223
224
224 def _clear_user_group_perms(self, user_group_id, preserve=None):
225 def _clear_user_group_perms(self, user_group_id, preserve=None):
225 perms = self.sa.query(UserGroupToPerm)\
226 perms = self.sa.query(UserGroupToPerm)\
226 .filter(UserGroupToPerm.users_group_id == user_group_id)\
227 .filter(UserGroupToPerm.users_group_id == user_group_id)\
227 .all()
228 .all()
228 return self._clear_object_perm(perms, preserve=preserve)
229 return self._clear_object_perm(perms, preserve=preserve)
229
230
230 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
231 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
231 # clear current entries, to make this function idempotent
232 # clear current entries, to make this function idempotent
232 # it will fix even if we define more permissions or permissions
233 # it will fix even if we define more permissions or permissions
233 # are somehow missing
234 # are somehow missing
234 preserve = preserve or []
235 preserve = preserve or []
235 _global_perms = self.global_perms.copy()
236 _global_perms = self.global_perms.copy()
236 if obj_type not in ['user', 'user_group']:
237 if obj_type not in ['user', 'user_group']:
237 raise ValueError("obj_type must be on of 'user' or 'user_group'")
238 raise ValueError("obj_type must be on of 'user' or 'user_group'")
238 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
239 if len(_global_perms) != len(Permission.DEFAULT_USER_PERMISSIONS):
239 raise Exception('Inconsistent permissions definition')
240 raise Exception('Inconsistent permissions definition')
240
241
241 if obj_type == 'user':
242 if obj_type == 'user':
242 self._clear_user_perms(object.user_id, preserve)
243 self._clear_user_perms(object.user_id, preserve)
243 if obj_type == 'user_group':
244 if obj_type == 'user_group':
244 self._clear_user_group_perms(object.users_group_id, preserve)
245 self._clear_user_group_perms(object.users_group_id, preserve)
245
246
246 # now kill the keys that we want to preserve from the form.
247 # now kill the keys that we want to preserve from the form.
247 for key in preserve:
248 for key in preserve:
248 del _global_perms[key]
249 del _global_perms[key]
249
250
250 for k in _global_perms.copy():
251 for k in _global_perms.copy():
251 _global_perms[k] = form_result[k]
252 _global_perms[k] = form_result[k]
252
253
253 # at that stage we validate all are passed inside form_result
254 # at that stage we validate all are passed inside form_result
254 for _perm_key, perm_value in _global_perms.items():
255 for _perm_key, perm_value in _global_perms.items():
255 if perm_value is None:
256 if perm_value is None:
256 raise ValueError('Missing permission for %s' % (_perm_key,))
257 raise ValueError('Missing permission for %s' % (_perm_key,))
257
258
258 if obj_type == 'user':
259 if obj_type == 'user':
259 p = self._make_new_user_perm(object, perm_value)
260 p = self._make_new_user_perm(object, perm_value)
260 self.sa.add(p)
261 self.sa.add(p)
261 if obj_type == 'user_group':
262 if obj_type == 'user_group':
262 p = self._make_new_user_group_perm(object, perm_value)
263 p = self._make_new_user_group_perm(object, perm_value)
263 self.sa.add(p)
264 self.sa.add(p)
264
265
265 def _set_new_user_perms(self, user, form_result, preserve=None):
266 def _set_new_user_perms(self, user, form_result, preserve=None):
266 return self._set_new_object_perms(
267 return self._set_new_object_perms(
267 'user', user, form_result, preserve)
268 'user', user, form_result, preserve)
268
269
269 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
270 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
270 return self._set_new_object_perms(
271 return self._set_new_object_perms(
271 'user_group', user_group, form_result, preserve)
272 'user_group', user_group, form_result, preserve)
272
273
273 def set_new_user_perms(self, user, form_result):
274 def set_new_user_perms(self, user, form_result):
274 # calculate what to preserve from what is given in form_result
275 # calculate what to preserve from what is given in form_result
275 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
276 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
276 return self._set_new_user_perms(user, form_result, preserve)
277 return self._set_new_user_perms(user, form_result, preserve)
277
278
278 def set_new_user_group_perms(self, user_group, form_result):
279 def set_new_user_group_perms(self, user_group, form_result):
279 # calculate what to preserve from what is given in form_result
280 # calculate what to preserve from what is given in form_result
280 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
281 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
281 return self._set_new_user_group_perms(user_group, form_result, preserve)
282 return self._set_new_user_group_perms(user_group, form_result, preserve)
282
283
283 def create_permissions(self):
284 def create_permissions(self):
284 """
285 """
285 Create permissions for whole system
286 Create permissions for whole system
286 """
287 """
287 for p in Permission.PERMS:
288 for p in Permission.PERMS:
288 if not Permission.get_by_key(p[0]):
289 if not Permission.get_by_key(p[0]):
289 new_perm = Permission()
290 new_perm = Permission()
290 new_perm.permission_name = p[0]
291 new_perm.permission_name = p[0]
291 new_perm.permission_longname = p[0] # translation err with p[1]
292 new_perm.permission_longname = p[0] # translation err with p[1]
292 self.sa.add(new_perm)
293 self.sa.add(new_perm)
293
294
294 def _create_default_object_permission(self, obj_type, obj, obj_perms,
295 def _create_default_object_permission(self, obj_type, obj, obj_perms,
295 force=False):
296 force=False):
296 if obj_type not in ['user', 'user_group']:
297 if obj_type not in ['user', 'user_group']:
297 raise ValueError("obj_type must be on of 'user' or 'user_group'")
298 raise ValueError("obj_type must be on of 'user' or 'user_group'")
298
299
299 def _get_group(perm_name):
300 def _get_group(perm_name):
300 return '.'.join(perm_name.split('.')[:1])
301 return '.'.join(perm_name.split('.')[:1])
301
302
302 defined_perms_groups = map(
303 defined_perms_groups = map(
303 _get_group, (x.permission.permission_name for x in obj_perms))
304 _get_group, (x.permission.permission_name for x in obj_perms))
304 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
305 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
305
306
306 if force:
307 if force:
307 self._clear_object_perm(obj_perms)
308 self._clear_object_perm(obj_perms)
308 self.sa.commit()
309 self.sa.commit()
309 defined_perms_groups = []
310 defined_perms_groups = []
310 # for every default permission that needs to be created, we check if
311 # for every default permission that needs to be created, we check if
311 # it's group is already defined, if it's not we create default perm
312 # it's group is already defined, if it's not we create default perm
312 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
313 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
313 gr = _get_group(perm_name)
314 gr = _get_group(perm_name)
314 if gr not in defined_perms_groups:
315 if gr not in defined_perms_groups:
315 log.debug('GR:%s not found, creating permission %s',
316 log.debug('GR:%s not found, creating permission %s',
316 gr, perm_name)
317 gr, perm_name)
317 if obj_type == 'user':
318 if obj_type == 'user':
318 new_perm = self._make_new_user_perm(obj, perm_name)
319 new_perm = self._make_new_user_perm(obj, perm_name)
319 self.sa.add(new_perm)
320 self.sa.add(new_perm)
320 if obj_type == 'user_group':
321 if obj_type == 'user_group':
321 new_perm = self._make_new_user_group_perm(obj, perm_name)
322 new_perm = self._make_new_user_group_perm(obj, perm_name)
322 self.sa.add(new_perm)
323 self.sa.add(new_perm)
323
324
324 def create_default_user_permissions(self, user, force=False):
325 def create_default_user_permissions(self, user, force=False):
325 """
326 """
326 Creates only missing default permissions for user, if force is set it
327 Creates only missing default permissions for user, if force is set it
327 resets the default permissions for that user
328 resets the default permissions for that user
328
329
329 :param user:
330 :param user:
330 :param force:
331 :param force:
331 """
332 """
332 user = self._get_user(user)
333 user = self._get_user(user)
333 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
334 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
334 return self._create_default_object_permission(
335 return self._create_default_object_permission(
335 'user', user, obj_perms, force)
336 'user', user, obj_perms, force)
336
337
337 def create_default_user_group_permissions(self, user_group, force=False):
338 def create_default_user_group_permissions(self, user_group, force=False):
338 """
339 """
339 Creates only missing default permissions for user group, if force is set it
340 Creates only missing default permissions for user group, if force is set it
340 resets the default permissions for that user group
341 resets the default permissions for that user group
341
342
342 :param user_group:
343 :param user_group:
343 :param force:
344 :param force:
344 """
345 """
345 user_group = self._get_user_group(user_group)
346 user_group = self._get_user_group(user_group)
346 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
347 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
347 return self._create_default_object_permission(
348 return self._create_default_object_permission(
348 'user_group', user_group, obj_perms, force)
349 'user_group', user_group, obj_perms, force)
349
350
350 def update_application_permissions(self, form_result):
351 def update_application_permissions(self, form_result):
351 if 'perm_user_id' in form_result:
352 if 'perm_user_id' in form_result:
352 perm_user = User.get(safe_int(form_result['perm_user_id']))
353 perm_user = User.get(safe_int(form_result['perm_user_id']))
353 else:
354 else:
354 # used mostly to do lookup for default user
355 # used mostly to do lookup for default user
355 perm_user = User.get_by_username(form_result['perm_user_name'])
356 perm_user = User.get_by_username(form_result['perm_user_name'])
356
357
357 try:
358 try:
358 # stage 1 set anonymous access
359 # stage 1 set anonymous access
359 if perm_user.username == User.DEFAULT_USER:
360 if perm_user.username == User.DEFAULT_USER:
360 perm_user.active = str2bool(form_result['anonymous'])
361 perm_user.active = str2bool(form_result['anonymous'])
361 self.sa.add(perm_user)
362 self.sa.add(perm_user)
362
363
363 # stage 2 reset defaults and set them from form data
364 # stage 2 reset defaults and set them from form data
364 self._set_new_user_perms(perm_user, form_result, preserve=[
365 self._set_new_user_perms(perm_user, form_result, preserve=[
365 'default_repo_perm',
366 'default_repo_perm',
366 'default_group_perm',
367 'default_group_perm',
367 'default_user_group_perm',
368 'default_user_group_perm',
368
369
369 'default_repo_group_create',
370 'default_repo_group_create',
370 'default_user_group_create',
371 'default_user_group_create',
371 'default_repo_create_on_write',
372 'default_repo_create_on_write',
372 'default_repo_create',
373 'default_repo_create',
373 'default_fork_create',
374 'default_fork_create',
374 'default_inherit_default_permissions',])
375 'default_inherit_default_permissions',])
375
376
376 self.sa.commit()
377 self.sa.commit()
377 except (DatabaseError,):
378 except (DatabaseError,):
378 log.error(traceback.format_exc())
379 log.error(traceback.format_exc())
379 self.sa.rollback()
380 self.sa.rollback()
380 raise
381 raise
381
382
382 def update_user_permissions(self, form_result):
383 def update_user_permissions(self, form_result):
383 if 'perm_user_id' in form_result:
384 if 'perm_user_id' in form_result:
384 perm_user = User.get(safe_int(form_result['perm_user_id']))
385 perm_user = User.get(safe_int(form_result['perm_user_id']))
385 else:
386 else:
386 # used mostly to do lookup for default user
387 # used mostly to do lookup for default user
387 perm_user = User.get_by_username(form_result['perm_user_name'])
388 perm_user = User.get_by_username(form_result['perm_user_name'])
388 try:
389 try:
389 # stage 2 reset defaults and set them from form data
390 # stage 2 reset defaults and set them from form data
390 self._set_new_user_perms(perm_user, form_result, preserve=[
391 self._set_new_user_perms(perm_user, form_result, preserve=[
391 'default_repo_perm',
392 'default_repo_perm',
392 'default_group_perm',
393 'default_group_perm',
393 'default_user_group_perm',
394 'default_user_group_perm',
394
395
395 'default_register',
396 'default_register',
396 'default_password_reset',
397 'default_password_reset',
397 'default_extern_activate'])
398 'default_extern_activate'])
398 self.sa.commit()
399 self.sa.commit()
399 except (DatabaseError,):
400 except (DatabaseError,):
400 log.error(traceback.format_exc())
401 log.error(traceback.format_exc())
401 self.sa.rollback()
402 self.sa.rollback()
402 raise
403 raise
403
404
404 def update_user_group_permissions(self, form_result):
405 def update_user_group_permissions(self, form_result):
405 if 'perm_user_group_id' in form_result:
406 if 'perm_user_group_id' in form_result:
406 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
407 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
407 else:
408 else:
408 # used mostly to do lookup for default user
409 # used mostly to do lookup for default user
409 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
410 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
410 try:
411 try:
411 # stage 2 reset defaults and set them from form data
412 # stage 2 reset defaults and set them from form data
412 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
413 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
413 'default_repo_perm',
414 'default_repo_perm',
414 'default_group_perm',
415 'default_group_perm',
415 'default_user_group_perm',
416 'default_user_group_perm',
416
417
417 'default_register',
418 'default_register',
418 'default_password_reset',
419 'default_password_reset',
419 'default_extern_activate'])
420 'default_extern_activate'])
420 self.sa.commit()
421 self.sa.commit()
421 except (DatabaseError,):
422 except (DatabaseError,):
422 log.error(traceback.format_exc())
423 log.error(traceback.format_exc())
423 self.sa.rollback()
424 self.sa.rollback()
424 raise
425 raise
425
426
426 def update_object_permissions(self, form_result):
427 def update_object_permissions(self, form_result):
427 if 'perm_user_id' in form_result:
428 if 'perm_user_id' in form_result:
428 perm_user = User.get(safe_int(form_result['perm_user_id']))
429 perm_user = User.get(safe_int(form_result['perm_user_id']))
429 else:
430 else:
430 # used mostly to do lookup for default user
431 # used mostly to do lookup for default user
431 perm_user = User.get_by_username(form_result['perm_user_name'])
432 perm_user = User.get_by_username(form_result['perm_user_name'])
432 try:
433 try:
433
434
434 # stage 2 reset defaults and set them from form data
435 # stage 2 reset defaults and set them from form data
435 self._set_new_user_perms(perm_user, form_result, preserve=[
436 self._set_new_user_perms(perm_user, form_result, preserve=[
436 'default_repo_group_create',
437 'default_repo_group_create',
437 'default_user_group_create',
438 'default_user_group_create',
438 'default_repo_create_on_write',
439 'default_repo_create_on_write',
439 'default_repo_create',
440 'default_repo_create',
440 'default_fork_create',
441 'default_fork_create',
441 'default_inherit_default_permissions',
442 'default_inherit_default_permissions',
442
443
443 'default_register',
444 'default_register',
444 'default_password_reset',
445 'default_password_reset',
445 'default_extern_activate'])
446 'default_extern_activate'])
446
447
447 # overwrite default repo permissions
448 # overwrite default repo permissions
448 if form_result['overwrite_default_repo']:
449 if form_result['overwrite_default_repo']:
449 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
450 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
450 _def = Permission.get_by_key('repository.' + _def_name)
451 _def = Permission.get_by_key('repository.' + _def_name)
451 for r2p in self.sa.query(UserRepoToPerm)\
452 for r2p in self.sa.query(UserRepoToPerm)\
452 .filter(UserRepoToPerm.user == perm_user)\
453 .filter(UserRepoToPerm.user == perm_user)\
453 .all():
454 .all():
454 # don't reset PRIVATE repositories
455 # don't reset PRIVATE repositories
455 if not r2p.repository.private:
456 if not r2p.repository.private:
456 r2p.permission = _def
457 r2p.permission = _def
457 self.sa.add(r2p)
458 self.sa.add(r2p)
458
459
459 # overwrite default repo group permissions
460 # overwrite default repo group permissions
460 if form_result['overwrite_default_group']:
461 if form_result['overwrite_default_group']:
461 _def_name = form_result['default_group_perm'].split('group.')[-1]
462 _def_name = form_result['default_group_perm'].split('group.')[-1]
462 _def = Permission.get_by_key('group.' + _def_name)
463 _def = Permission.get_by_key('group.' + _def_name)
463 for g2p in self.sa.query(UserRepoGroupToPerm)\
464 for g2p in self.sa.query(UserRepoGroupToPerm)\
464 .filter(UserRepoGroupToPerm.user == perm_user)\
465 .filter(UserRepoGroupToPerm.user == perm_user)\
465 .all():
466 .all():
466 g2p.permission = _def
467 g2p.permission = _def
467 self.sa.add(g2p)
468 self.sa.add(g2p)
468
469
469 # overwrite default user group permissions
470 # overwrite default user group permissions
470 if form_result['overwrite_default_user_group']:
471 if form_result['overwrite_default_user_group']:
471 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
472 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
472 # user groups
473 # user groups
473 _def = Permission.get_by_key('usergroup.' + _def_name)
474 _def = Permission.get_by_key('usergroup.' + _def_name)
474 for g2p in self.sa.query(UserUserGroupToPerm)\
475 for g2p in self.sa.query(UserUserGroupToPerm)\
475 .filter(UserUserGroupToPerm.user == perm_user)\
476 .filter(UserUserGroupToPerm.user == perm_user)\
476 .all():
477 .all():
477 g2p.permission = _def
478 g2p.permission = _def
478 self.sa.add(g2p)
479 self.sa.add(g2p)
479 self.sa.commit()
480 self.sa.commit()
480 except (DatabaseError,):
481 except (DatabaseError,):
481 log.exception('Failed to set default object permissions')
482 log.exception('Failed to set default object permissions')
482 self.sa.rollback()
483 self.sa.rollback()
483 raise
484 raise
@@ -1,206 +1,213 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
18 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
19 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
19 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
20 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
20 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
21 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
21 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
22 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
22 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
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']);
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 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
24 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
25 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
25 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
26 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
26 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
27 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
27 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
28 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
28 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
29 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
29 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
31 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 pyroutes.register('favicon', '/favicon.ico', []);
32 pyroutes.register('favicon', '/favicon.ico', []);
33 pyroutes.register('robots', '/robots.txt', []);
33 pyroutes.register('robots', '/robots.txt', []);
34 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
34 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
35 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
35 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
36 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
36 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
37 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
37 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
38 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
38 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
39 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
39 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
40 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
40 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
41 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
41 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
42 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
42 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
43 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
43 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
44 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
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 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
45 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
46 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
46 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
47 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
47 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
48 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
48 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
49 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
49 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
50 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
50 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
51 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
51 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
52 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
52 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
53 pyroutes.register('admin_home', '/_admin', []);
53 pyroutes.register('admin_home', '/_admin', []);
54 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
54 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
55 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
55 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
56 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
56 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
57 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
57 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
59 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
59 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
60 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
60 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
61 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
61 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
62 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
62 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
63 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
63 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
64 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
64 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
65 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
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 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
72 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
73 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
67 pyroutes.register('users', '/_admin/users', []);
74 pyroutes.register('users', '/_admin/users', []);
68 pyroutes.register('users_data', '/_admin/users_data', []);
75 pyroutes.register('users_data', '/_admin/users_data', []);
69 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
76 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
70 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
77 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
71 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
78 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
72 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
79 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
73 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
80 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
74 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
81 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
75 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
82 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
76 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
83 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
77 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
84 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
78 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
85 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
79 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
86 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
80 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
87 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
81 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
88 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
82 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
89 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
83 pyroutes.register('channelstream_proxy', '/_channelstream', []);
90 pyroutes.register('channelstream_proxy', '/_channelstream', []);
84 pyroutes.register('login', '/_admin/login', []);
91 pyroutes.register('login', '/_admin/login', []);
85 pyroutes.register('logout', '/_admin/logout', []);
92 pyroutes.register('logout', '/_admin/logout', []);
86 pyroutes.register('register', '/_admin/register', []);
93 pyroutes.register('register', '/_admin/register', []);
87 pyroutes.register('reset_password', '/_admin/password_reset', []);
94 pyroutes.register('reset_password', '/_admin/password_reset', []);
88 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
95 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
89 pyroutes.register('home', '/', []);
96 pyroutes.register('home', '/', []);
90 pyroutes.register('user_autocomplete_data', '/_users', []);
97 pyroutes.register('user_autocomplete_data', '/_users', []);
91 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
98 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
92 pyroutes.register('repo_list_data', '/_repos', []);
99 pyroutes.register('repo_list_data', '/_repos', []);
93 pyroutes.register('goto_switcher_data', '/_goto_data', []);
100 pyroutes.register('goto_switcher_data', '/_goto_data', []);
94 pyroutes.register('journal', '/_admin/journal', []);
101 pyroutes.register('journal', '/_admin/journal', []);
95 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
102 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
96 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
103 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
97 pyroutes.register('journal_public', '/_admin/public_journal', []);
104 pyroutes.register('journal_public', '/_admin/public_journal', []);
98 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
105 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
99 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
106 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
100 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
107 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
101 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
108 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
102 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
109 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
103 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
110 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
104 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
111 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
105 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
112 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
106 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
113 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
107 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
114 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
108 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
115 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
109 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
111 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
118 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
112 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
124 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
125 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
127 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
128 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
122 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
129 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
123 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
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 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
136 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
130 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
137 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
131 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
132 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
139 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
133 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
141 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
135 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
142 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
136 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
143 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
137 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
144 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
138 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
145 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
139 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
146 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
140 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
147 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
141 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
148 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
142 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
149 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
143 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
150 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
144 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
151 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
145 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
152 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
146 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
153 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
147 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
154 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
148 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
155 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
149 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
156 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
150 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
157 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
151 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
158 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
152 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
159 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
153 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
160 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
154 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
161 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
155 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
162 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
156 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
163 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
157 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
164 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
158 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
165 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
159 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
166 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
160 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
167 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
161 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
168 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
162 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
169 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
163 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
170 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
164 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
171 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
165 pyroutes.register('search', '/_admin/search', []);
172 pyroutes.register('search', '/_admin/search', []);
166 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
173 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
167 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
174 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
168 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
175 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
169 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
176 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
170 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
177 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
171 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
178 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
172 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
179 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
173 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
180 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
174 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
181 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
175 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
182 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
176 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
183 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
177 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
184 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
178 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
185 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
179 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
186 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
180 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
187 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
181 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
188 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
182 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
189 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
183 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
190 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
184 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
191 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
185 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
192 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
186 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
193 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
187 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
194 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
188 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
195 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
189 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
196 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
190 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
197 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
191 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
198 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
192 pyroutes.register('gists_show', '/_admin/gists', []);
199 pyroutes.register('gists_show', '/_admin/gists', []);
193 pyroutes.register('gists_new', '/_admin/gists/new', []);
200 pyroutes.register('gists_new', '/_admin/gists/new', []);
194 pyroutes.register('gists_create', '/_admin/gists/create', []);
201 pyroutes.register('gists_create', '/_admin/gists/create', []);
195 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
202 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
196 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
203 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
197 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
204 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
198 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
205 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
199 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
206 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
200 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
207 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
201 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
208 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
202 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
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 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
210 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
204 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
211 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
205 pyroutes.register('apiv2', '/_admin/api', []);
212 pyroutes.register('apiv2', '/_admin/api', []);
206 }
213 }
@@ -1,56 +1,56 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Permissions Administration')}
5 ${_('Permissions Administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 &raquo;
13 &raquo;
14 ${_('Permissions')}
14 ${_('Permissions')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='admin')}
18 ${self.menu_items(active='admin')}
19 </%def>
19 </%def>
20
20
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27
27
28 <div class="sidebar-col-wrapper scw-small">
28 <div class="sidebar-col-wrapper scw-small">
29 ##main
29 ##main
30 <div class="sidebar">
30 <div class="sidebar">
31 <ul class="nav nav-pills nav-stacked">
31 <ul class="nav nav-pills nav-stacked">
32 <li class="${'active' if c.active=='application' else ''}">
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 </li>
34 </li>
35 <li class="${'active' if c.active=='global' else ''}">
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 </li>
37 </li>
38 <li class="${'active' if c.active=='objects' else ''}">
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 </li>
40 </li>
41 <li class="${'active' if c.active=='ips' else ''}">
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 </li>
43 </li>
44 <li class="${'active' if c.active=='perms' else ''}">
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 </li>
46 </li>
47 </ul>
47 </ul>
48 </div>
48 </div>
49
49
50 <div class="main-content-full-width">
50 <div class="main-content-full-width">
51 <%include file="/admin/permissions/permissions_${c.active}.mako"/>
51 <%include file="/admin/permissions/permissions_${c.active}.mako"/>
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55
55
56 </%def>
56 </%def>
@@ -1,81 +1,81 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
3 <h3 class="panel-title">${_('System Wide Application Permissions')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
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 <div class="form">
7 <div class="form">
8 <!-- fields -->
8 <!-- fields -->
9 <div class="fields">
9 <div class="fields">
10 <div class="field">
10 <div class="field">
11 <div class="label label-checkbox">
11 <div class="label label-checkbox">
12 <label for="anonymous">${_('Anonymous Access')}:</label>
12 <label for="anonymous">${_('Anonymous Access')}:</label>
13 </div>
13 </div>
14 <div class="checkboxes">
14 <div class="checkboxes">
15 <div class="checkbox">
15 <div class="checkbox">
16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
16 ${h.checkbox('anonymous',True)} Allow Anonymous Access
17 </div>
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 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field">
22 <div class="field">
23 <div class="label label-select">
23 <div class="label label-select">
24 <label for="default_register">${_('Registration')}:</label>
24 <label for="default_register">${_('Registration')}:</label>
25 </div>
25 </div>
26 <div class="select">
26 <div class="select">
27 ${h.select('default_register','',c.register_choices)}
27 ${h.select('default_register','',c.register_choices)}
28 </div>
28 </div>
29 </div>
29 </div>
30
30
31 <div class="field">
31 <div class="field">
32 <div class="label label-select">
32 <div class="label label-select">
33 <label for="default_password_reset">${_('Password Reset')}:</label>
33 <label for="default_password_reset">${_('Password Reset')}:</label>
34 </div>
34 </div>
35 <div class="select">
35 <div class="select">
36 ${h.select('default_password_reset','',c.password_reset_choices)}
36 ${h.select('default_password_reset','',c.password_reset_choices)}
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="default_register_message">${_('Registration Page Message')}:</label>
42 <label for="default_register_message">${_('Registration Page Message')}:</label>
43 </div>
43 </div>
44 <div class="textarea text-area editor" >
44 <div class="textarea text-area editor" >
45 ${h.textarea('default_register_message', class_="medium", )}
45 ${h.textarea('default_register_message', class_="medium", )}
46 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
46 <span class="help-block">${_('Custom message to be displayed on the registration page. HTML syntax is supported.')}</span>
47 </div>
47 </div>
48 </div>
48 </div>
49
49
50 <div class="field">
50 <div class="field">
51 <div class="label">
51 <div class="label">
52 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
52 <label for="default_extern_activate">${_('External Authentication Account Activation')}:</label>
53 </div>
53 </div>
54 <div class="select">
54 <div class="select">
55 ${h.select('default_extern_activate','',c.extern_activate_choices)}
55 ${h.select('default_extern_activate','',c.extern_activate_choices)}
56 </div>
56 </div>
57 </div>
57 </div>
58 <div class="buttons">
58 <div class="buttons">
59 ${h.submit('save',_('Save'),class_="btn")}
59 ${h.submit('save',_('Save'),class_="btn")}
60 ${h.reset('reset',_('Reset'),class_="btn")}
60 ${h.reset('reset',_('Reset'),class_="btn")}
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64 ${h.end_form()}
64 ${h.end_form()}
65 </div>
65 </div>
66 </div>
66 </div>
67
67
68 <script>
68 <script>
69 $(document).ready(function(){
69 $(document).ready(function(){
70 var select2Options = {
70 var select2Options = {
71 containerCssClass: 'drop-menu',
71 containerCssClass: 'drop-menu',
72 dropdownCssClass: 'drop-menu-dropdown',
72 dropdownCssClass: 'drop-menu-dropdown',
73 dropdownAutoWidth: true,
73 dropdownAutoWidth: true,
74 minimumResultsForSearch: -1
74 minimumResultsForSearch: -1
75 };
75 };
76
76
77 $("#default_register").select2(select2Options);
77 $("#default_register").select2(select2Options);
78 $("#default_password_reset").select2(select2Options);
78 $("#default_password_reset").select2(select2Options);
79 $("#default_extern_activate").select2(select2Options);
79 $("#default_extern_activate").select2(select2Options);
80 });
80 });
81 </script>
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 <div class="form permissions-global">
3 <div class="form permissions-global">
4 <!-- fields -->
4 <!-- fields -->
5 <div class="fields">
5 <div class="fields">
6 <%namespace name="dpb" file="/base/default_perms_box.mako"/>
6 <%namespace name="dpb" file="/base/default_perms_box.mako"/>
7 ${dpb.default_perms_radios(global_permissions_template = True)}
7 ${dpb.default_perms_radios(global_permissions_template = True)}
8 </div>
8 </div>
9 </div>
9 </div>
10 ${h.end_form()}
10 ${h.end_form()}
@@ -1,67 +1,70 b''
1
1
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Default IP Whitelist For All Users')}</h3>
5 <h3 class="panel-title">${_('Default IP Whitelist For All Users')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="ips_wrap">
8 <div class="ips_wrap">
9 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
10
11
9 <table class="rctable ip-whitelist">
12 <table class="rctable ip-whitelist">
10 <tr>
13 <tr>
11 <th>IP Address</th>
14 <th>IP Address</th>
12 <th>IP Range</th>
15 <th>IP Range</th>
13 <th>Description</th>
16 <th>Description</th>
14 <th></th>
17 <th></th>
15 </tr>
18 </tr>
16 %if c.user_ip_map:
19 %if c.user_ip_map:
17 %for ip in c.user_ip_map:
20 %for ip in c.user_ip_map:
18 <tr>
21 <tr>
19 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
22 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
23 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 <td class="td-description"><div class="ip">${ip.description}</div></td>
24 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 <td class="td-action">
25 <td class="td-action">
23 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
26 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
24 ${h.hidden('del_ip_id',ip.ip_id)}
27 ${h.hidden('del_ip_id',ip.ip_id)}
25 ${h.hidden('default_user', 'True')}
28 ${h.hidden('default_user', 'True')}
26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
29 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
27 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
30 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
28 ${h.end_form()}
31 ${h.end_form()}
29 </td>
32 </td>
30 </tr>
33 </tr>
31 %endfor
34 %endfor
32 %else:
35 %else:
33 <tr>
36 <tr>
34 <td class="ip">${_('All IP addresses are allowed')}</td>
37 <td class="ip">${_('All IP addresses are allowed')}</td>
35 <td></td>
38 <td></td>
36 <td></td>
39 <td></td>
37 <td></td>
40 <td></td>
38 </tr>
41 </tr>
39 %endif
42 %endif
40 </table>
43 </table>
41 </div>
44 </div>
42
45
43 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
46 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
44 <div class="form">
47 <div class="form">
45 <!-- fields -->
48 <!-- fields -->
46 <div class="fields">
49 <div class="fields">
47 <div class="field">
50 <div class="field">
48 <div class="label">
51 <div class="label">
49 <label for="new_ip">${_('New IP Address')}:</label>
52 <label for="new_ip">${_('New IP Address')}:</label>
50 </div>
53 </div>
51 <div class="input">
54 <div class="input">
52 ${h.hidden('default_user', 'True')}
55 ${h.hidden('default_user', 'True')}
53 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
56 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
54 <span class="help-block">${_('Enter a comma separated list of IP Addresses like 127.0.0.1,\n'
57 <span class="help-block">${_('Enter a comma separated list of IP Addresses like 127.0.0.1,\n'
55 'or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n'
58 'or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n'
56 'To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax')}</span>
59 'To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax')}</span>
57 </div>
60 </div>
58 </div>
61 </div>
59 <div class="buttons">
62 <div class="buttons">
60 ${h.submit('save',_('Add'),class_="btn")}
63 ${h.submit('save',_('Add'),class_="btn")}
61 ${h.reset('reset',_('Reset'),class_="btn")}
64 ${h.reset('reset',_('Reset'),class_="btn")}
62 </div>
65 </div>
63 </div>
66 </div>
64 </div>
67 </div>
65 ${h.end_form()}
68 ${h.end_form()}
66 </div>
69 </div>
67 </div>
70 </div>
@@ -1,77 +1,77 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Default Permissions for Repositories, User Groups and Repository Groups.')}</h3>
3 <h3 class="panel-title">${_('Default Permissions for Repositories, User Groups and Repository Groups.')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
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.')}
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 </p>
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 <div class="form">
9 <div class="form">
10 <div class="fields">
10 <div class="fields">
11 <div class="field">
11 <div class="field">
12 <div class="label">
12 <div class="label">
13 <label for="default_repo_perm">${_('Repository')}:</label>
13 <label for="default_repo_perm">${_('Repository')}:</label>
14 </div>
14 </div>
15 <div class="select">
15 <div class="select">
16 ${h.select('default_repo_perm','',c.repo_perms_choices)}
16 ${h.select('default_repo_perm','',c.repo_perms_choices)}
17
17
18 ${h.checkbox('overwrite_default_repo','true')}
18 ${h.checkbox('overwrite_default_repo','true')}
19 <label for="overwrite_default_repo">
19 <label for="overwrite_default_repo">
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'))}">
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 ${_('Overwrite Existing Settings')}
21 ${_('Overwrite Existing Settings')}
22 </span>
22 </span>
23 </label>
23 </label>
24 </div>
24 </div>
25 </div>
25 </div>
26 <div class="field">
26 <div class="field">
27 <div class="label">
27 <div class="label">
28 <label for="default_group_perm">${_('Repository Groups')}:</label>
28 <label for="default_group_perm">${_('Repository Groups')}:</label>
29 </div>
29 </div>
30 <div class="select">
30 <div class="select">
31 ${h.select('default_group_perm','',c.group_perms_choices)}
31 ${h.select('default_group_perm','',c.group_perms_choices)}
32 ${h.checkbox('overwrite_default_group','true')}
32 ${h.checkbox('overwrite_default_group','true')}
33 <label for="overwrite_default_group">
33 <label for="overwrite_default_group">
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'))}">
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 ${_('Overwrite Existing Settings')}
35 ${_('Overwrite Existing Settings')}
36 </span>
36 </span>
37 </label>
37 </label>
38 </div>
38 </div>
39 </div>
39 </div>
40 <div class="field">
40 <div class="field">
41 <div class="label">
41 <div class="label">
42 <label for="default_group_perm">${_('User Groups')}:</label>
42 <label for="default_group_perm">${_('User Groups')}:</label>
43 </div>
43 </div>
44 <div class="select">
44 <div class="select">
45 ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
45 ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
46 ${h.checkbox('overwrite_default_user_group','true')}
46 ${h.checkbox('overwrite_default_user_group','true')}
47 <label for="overwrite_default_user_group">
47 <label for="overwrite_default_user_group">
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'))}">
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 ${_('Overwrite Existing Settings')}
49 ${_('Overwrite Existing Settings')}
50 </span>
50 </span>
51 </label>
51 </label>
52 </div>
52 </div>
53 </div>
53 </div>
54
54
55 <div class="buttons">
55 <div class="buttons">
56 ${h.submit('save',_('Save'),class_="btn")}
56 ${h.submit('save',_('Save'),class_="btn")}
57 ${h.reset('reset',_('Reset'),class_="btn")}
57 ${h.reset('reset',_('Reset'),class_="btn")}
58 </div>
58 </div>
59 </div>
59 </div>
60 </div>
60 </div>
61 ${h.end_form()}
61 ${h.end_form()}
62 </div>
62 </div>
63 </div>
63 </div>
64
64
65 <script>
65 <script>
66 $(document).ready(function(){
66 $(document).ready(function(){
67 var select2Options = {
67 var select2Options = {
68 containerCssClass: 'drop-menu',
68 containerCssClass: 'drop-menu',
69 dropdownCssClass: 'drop-menu-dropdown',
69 dropdownCssClass: 'drop-menu-dropdown',
70 dropdownAutoWidth: true,
70 dropdownAutoWidth: true,
71 minimumResultsForSearch: -1
71 minimumResultsForSearch: -1
72 };
72 };
73 $("#default_repo_perm").select2(select2Options);
73 $("#default_repo_perm").select2(select2Options);
74 $("#default_group_perm").select2(select2Options);
74 $("#default_group_perm").select2(select2Options);
75 $("#default_user_group_perm").select2(select2Options);
75 $("#default_user_group_perm").select2(select2Options);
76 });
76 });
77 </script>
77 </script>
@@ -1,78 +1,78 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Custom IP Whitelist')}</h3>
3 <h3 class="panel-title">${_('Custom IP Whitelist')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="ips_wrap">
6 <div class="ips_wrap">
7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
8 <table class="rctable ip-whitelist">
8 <table class="rctable ip-whitelist">
9 <tr>
9 <tr>
10 <th>${_('IP Address')}</th>
10 <th>${_('IP Address')}</th>
11 <th>${_('IP Range')}</th>
11 <th>${_('IP Range')}</th>
12 <th>${_('Description')}</th>
12 <th>${_('Description')}</th>
13 <th></th>
13 <th></th>
14 </tr>
14 </tr>
15 %if c.default_user_ip_map and c.inherit_default_ips:
15 %if c.default_user_ip_map and c.inherit_default_ips:
16 %for ip in c.default_user_ip_map:
16 %for ip in c.default_user_ip_map:
17 <tr>
17 <tr>
18 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
18 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
19 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
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 <td></td>
21 <td></td>
22 </tr>
22 </tr>
23 %endfor
23 %endfor
24 %endif
24 %endif
25
25
26 %if c.user_ip_map:
26 %if c.user_ip_map:
27 %for ip in c.user_ip_map:
27 %for ip in c.user_ip_map:
28 <tr>
28 <tr>
29 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
29 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
31 <td class="td-description"><div class="ip">${ip.description}</div></td>
31 <td class="td-description"><div class="ip">${ip.description}</div></td>
32 <td class="td-action">
32 <td class="td-action">
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
34 ${h.hidden('del_ip_id', ip.ip_id)}
34 ${h.hidden('del_ip_id', ip.ip_id)}
35 ${h.submit('remove_', _('Delete'),id="remove_ip_%s" % ip.ip_id,
35 ${h.submit('remove_', _('Delete'),id="remove_ip_%s" % ip.ip_id,
36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
37 ${h.end_form()}
37 ${h.end_form()}
38 </td>
38 </td>
39 </tr>
39 </tr>
40 %endfor
40 %endfor
41 %endif
41 %endif
42 %if not c.default_user_ip_map and not c.user_ip_map:
42 %if not c.default_user_ip_map and not c.user_ip_map:
43 <tr>
43 <tr>
44 <td><h2 class="ip">${_('All IP addresses are allowed')}</h2></td>
44 <td><h2 class="ip">${_('All IP addresses are allowed')}</h2></td>
45 <td></td>
45 <td></td>
46 <td></td>
46 <td></td>
47 <td></td>
47 <td></td>
48 </tr>
48 </tr>
49 %endif
49 %endif
50 </table>
50 </table>
51 </div>
51 </div>
52
52
53 <div>
53 <div>
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
55 <div class="form">
55 <div class="form">
56 <!-- fields -->
56 <!-- fields -->
57 <div class="fields">
57 <div class="fields">
58 <div class="field">
58 <div class="field">
59 <div class="label">
59 <div class="label">
60 <label for="new_ip">${_('New IP Address')}:</label>
60 <label for="new_ip">${_('New IP Address')}:</label>
61 </div>
61 </div>
62 <div class="input">
62 <div class="input">
63 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
63 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
64 <span class="help-block">${_('Enter comma separated list of ip addresses like 127.0.0.1,\n'
64 <span class="help-block">${_('Enter comma separated list of ip addresses like 127.0.0.1,\n'
65 'or use a ip address with a mask 127.0.0.1/24, to create a network range.\n'
65 'or use a ip address with a mask 127.0.0.1/24, to create a network range.\n'
66 'To specify multiple address range use 127.0.0.1-127.0.0.10 syntax')}</span>
66 'To specify multiple address range use 127.0.0.1-127.0.0.10 syntax')}</span>
67 </div>
67 </div>
68 </div>
68 </div>
69 <div class="buttons">
69 <div class="buttons">
70 ${h.submit('save',_('Add'),class_="btn btn-small")}
70 ${h.submit('save',_('Add'),class_="btn btn-small")}
71 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
71 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 ${h.end_form()}
75 ${h.end_form()}
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
@@ -1,600 +1,600 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
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 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${h.tooltip(title)}">
93 <dd title="${h.tooltip(title)}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
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>
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 <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>
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 <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>
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 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
233 </li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
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)}">
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 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink dropdown">
246 <a class="menulink dropdown">
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 </a>
248 </a>
249 <ul class="submenu">
249 <ul class="submenu">
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 %endif
252 %endif
253 %if c.rhodecode_db_repo.fork:
253 %if c.rhodecode_db_repo.fork:
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)}">
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 ${_('Compare fork')}</a></li>
255 ${_('Compare fork')}</a></li>
256 %endif
256 %endif
257
257
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259
259
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 %if c.rhodecode_db_repo.locked[0]:
261 %if c.rhodecode_db_repo.locked[0]:
262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 %else:
263 %else:
264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 %endif
265 %endif
266 %endif
266 %endif
267 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 %endif
271 %endif
272 %endif
272 %endif
273 </ul>
273 </ul>
274 </li>
274 </li>
275 </ul>
275 </ul>
276 </div>
276 </div>
277 <div class="clear"></div>
277 <div class="clear"></div>
278 </div>
278 </div>
279 <!--- END CONTEXT BAR -->
279 <!--- END CONTEXT BAR -->
280
280
281 </%def>
281 </%def>
282
282
283 <%def name="usermenu(active=False)">
283 <%def name="usermenu(active=False)">
284 ## USER MENU
284 ## USER MENU
285 <li id="quick_login_li" class="${'active' if active else ''}">
285 <li id="quick_login_li" class="${'active' if active else ''}">
286 <a id="quick_login_link" class="menulink childs">
286 <a id="quick_login_link" class="menulink childs">
287 ${gravatar(c.rhodecode_user.email, 20)}
287 ${gravatar(c.rhodecode_user.email, 20)}
288 <span class="user">
288 <span class="user">
289 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 %else:
291 %else:
292 <span>${_('Sign in')}</span>
292 <span>${_('Sign in')}</span>
293 %endif
293 %endif
294 </span>
294 </span>
295 </a>
295 </a>
296
296
297 <div class="user-menu submenu">
297 <div class="user-menu submenu">
298 <div id="quick_login">
298 <div id="quick_login">
299 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 <h4>${_('Sign in to your account')}</h4>
300 <h4>${_('Sign in to your account')}</h4>
301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 <div class="form form-vertical">
302 <div class="form form-vertical">
303 <div class="fields">
303 <div class="fields">
304 <div class="field">
304 <div class="field">
305 <div class="label">
305 <div class="label">
306 <label for="username">${_('Username')}:</label>
306 <label for="username">${_('Username')}:</label>
307 </div>
307 </div>
308 <div class="input">
308 <div class="input">
309 ${h.text('username',class_='focus',tabindex=1)}
309 ${h.text('username',class_='focus',tabindex=1)}
310 </div>
310 </div>
311
311
312 </div>
312 </div>
313 <div class="field">
313 <div class="field">
314 <div class="label">
314 <div class="label">
315 <label for="password">${_('Password')}:</label>
315 <label for="password">${_('Password')}:</label>
316 %if h.HasPermissionAny('hg.password_reset.enabled')():
316 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 %endif
318 %endif
319 </div>
319 </div>
320 <div class="input">
320 <div class="input">
321 ${h.password('password',class_='focus',tabindex=2)}
321 ${h.password('password',class_='focus',tabindex=2)}
322 </div>
322 </div>
323 </div>
323 </div>
324 <div class="buttons">
324 <div class="buttons">
325 <div class="register">
325 <div class="register">
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 %endif
328 %endif
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 </div>
330 </div>
331 <div class="submit">
331 <div class="submit">
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 </div>
333 </div>
334 </div>
334 </div>
335 </div>
335 </div>
336 </div>
336 </div>
337 ${h.end_form()}
337 ${h.end_form()}
338 %else:
338 %else:
339 <div class="">
339 <div class="">
340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 <div class="email">${c.rhodecode_user.email}</div>
342 <div class="email">${c.rhodecode_user.email}</div>
343 </div>
343 </div>
344 <div class="">
344 <div class="">
345 <ol class="links">
345 <ol class="links">
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 % if c.rhodecode_user.personal_repo_group:
347 % if c.rhodecode_user.personal_repo_group:
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>
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 % endif
349 % endif
350 <li class="logout">
350 <li class="logout">
351 ${h.secure_form(h.route_path('logout'), request=request)}
351 ${h.secure_form(h.route_path('logout'), request=request)}
352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 ${h.end_form()}
353 ${h.end_form()}
354 </li>
354 </li>
355 </ol>
355 </ol>
356 </div>
356 </div>
357 %endif
357 %endif
358 </div>
358 </div>
359 </div>
359 </div>
360 %if c.rhodecode_user.username != h.DEFAULT_USER:
360 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 <div class="pill_container">
361 <div class="pill_container">
362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
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 </div>
363 </div>
364 % endif
364 % endif
365 </li>
365 </li>
366 </%def>
366 </%def>
367
367
368 <%def name="menu_items(active=None)">
368 <%def name="menu_items(active=None)">
369 <%
369 <%
370 def is_active(selected):
370 def is_active(selected):
371 if selected == active:
371 if selected == active:
372 return "active"
372 return "active"
373 return ""
373 return ""
374 %>
374 %>
375 <ul id="quick" class="main_nav navigation horizontal-list">
375 <ul id="quick" class="main_nav navigation horizontal-list">
376 <!-- repo switcher -->
376 <!-- repo switcher -->
377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 <input id="repo_switcher" name="repo_switcher" type="hidden">
378 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 </li>
379 </li>
380
380
381 ## ROOT MENU
381 ## ROOT MENU
382 %if c.rhodecode_user.username != h.DEFAULT_USER:
382 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 <li class="${is_active('journal')}">
383 <li class="${is_active('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
385 <div class="menulabel">${_('Journal')}</div>
385 <div class="menulabel">${_('Journal')}</div>
386 </a>
386 </a>
387 </li>
387 </li>
388 %else:
388 %else:
389 <li class="${is_active('journal')}">
389 <li class="${is_active('journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
391 <div class="menulabel">${_('Public journal')}</div>
391 <div class="menulabel">${_('Public journal')}</div>
392 </a>
392 </a>
393 </li>
393 </li>
394 %endif
394 %endif
395 <li class="${is_active('gists')}">
395 <li class="${is_active('gists')}">
396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
397 <div class="menulabel">${_('Gists')}</div>
397 <div class="menulabel">${_('Gists')}</div>
398 </a>
398 </a>
399 </li>
399 </li>
400 <li class="${is_active('search')}">
400 <li class="${is_active('search')}">
401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 <div class="menulabel">${_('Search')}</div>
402 <div class="menulabel">${_('Search')}</div>
403 </a>
403 </a>
404 </li>
404 </li>
405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 <li class="${is_active('admin')}">
406 <li class="${is_active('admin')}">
407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 </a>
409 </a>
410 ${admin_menu()}
410 ${admin_menu()}
411 </li>
411 </li>
412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 <li class="${is_active('admin')}">
413 <li class="${is_active('admin')}">
414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 </a>
416 </a>
417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 c.rhodecode_user.repository_groups_admin,
418 c.rhodecode_user.repository_groups_admin,
419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 </li>
420 </li>
421 % endif
421 % endif
422 % if c.debug_style:
422 % if c.debug_style:
423 <li class="${is_active('debug_style')}">
423 <li class="${is_active('debug_style')}">
424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
425 <div class="menulabel">${_('Style')}</div>
425 <div class="menulabel">${_('Style')}</div>
426 </a>
426 </a>
427 </li>
427 </li>
428 % endif
428 % endif
429 ## render extra user menu
429 ## render extra user menu
430 ${usermenu(active=(active=='my_account'))}
430 ${usermenu(active=(active=='my_account'))}
431 </ul>
431 </ul>
432
432
433 <script type="text/javascript">
433 <script type="text/javascript">
434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435
435
436 /*format the look of items in the list*/
436 /*format the look of items in the list*/
437 var format = function(state, escapeMarkup){
437 var format = function(state, escapeMarkup){
438 if (!state.id){
438 if (!state.id){
439 return state.text; // optgroup
439 return state.text; // optgroup
440 }
440 }
441 var obj_dict = state.obj;
441 var obj_dict = state.obj;
442 var tmpl = '';
442 var tmpl = '';
443
443
444 if(obj_dict && state.type == 'repo'){
444 if(obj_dict && state.type == 'repo'){
445 if(obj_dict['repo_type'] === 'hg'){
445 if(obj_dict['repo_type'] === 'hg'){
446 tmpl += '<i class="icon-hg"></i> ';
446 tmpl += '<i class="icon-hg"></i> ';
447 }
447 }
448 else if(obj_dict['repo_type'] === 'git'){
448 else if(obj_dict['repo_type'] === 'git'){
449 tmpl += '<i class="icon-git"></i> ';
449 tmpl += '<i class="icon-git"></i> ';
450 }
450 }
451 else if(obj_dict['repo_type'] === 'svn'){
451 else if(obj_dict['repo_type'] === 'svn'){
452 tmpl += '<i class="icon-svn"></i> ';
452 tmpl += '<i class="icon-svn"></i> ';
453 }
453 }
454 if(obj_dict['private']){
454 if(obj_dict['private']){
455 tmpl += '<i class="icon-lock" ></i> ';
455 tmpl += '<i class="icon-lock" ></i> ';
456 }
456 }
457 else if(visual_show_public_icon){
457 else if(visual_show_public_icon){
458 tmpl += '<i class="icon-unlock-alt"></i> ';
458 tmpl += '<i class="icon-unlock-alt"></i> ';
459 }
459 }
460 }
460 }
461 if(obj_dict && state.type == 'commit') {
461 if(obj_dict && state.type == 'commit') {
462 tmpl += '<i class="icon-tag"></i>';
462 tmpl += '<i class="icon-tag"></i>';
463 }
463 }
464 if(obj_dict && state.type == 'group'){
464 if(obj_dict && state.type == 'group'){
465 tmpl += '<i class="icon-folder-close"></i> ';
465 tmpl += '<i class="icon-folder-close"></i> ';
466 }
466 }
467 tmpl += escapeMarkup(state.text);
467 tmpl += escapeMarkup(state.text);
468 return tmpl;
468 return tmpl;
469 };
469 };
470
470
471 var formatResult = function(result, container, query, escapeMarkup) {
471 var formatResult = function(result, container, query, escapeMarkup) {
472 return format(result, escapeMarkup);
472 return format(result, escapeMarkup);
473 };
473 };
474
474
475 var formatSelection = function(data, container, escapeMarkup) {
475 var formatSelection = function(data, container, escapeMarkup) {
476 return format(data, escapeMarkup);
476 return format(data, escapeMarkup);
477 };
477 };
478
478
479 $("#repo_switcher").select2({
479 $("#repo_switcher").select2({
480 cachedDataSource: {},
480 cachedDataSource: {},
481 minimumInputLength: 2,
481 minimumInputLength: 2,
482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 dropdownAutoWidth: true,
483 dropdownAutoWidth: true,
484 formatResult: formatResult,
484 formatResult: formatResult,
485 formatSelection: formatSelection,
485 formatSelection: formatSelection,
486 containerCssClass: "repo-switcher",
486 containerCssClass: "repo-switcher",
487 dropdownCssClass: "repo-switcher-dropdown",
487 dropdownCssClass: "repo-switcher-dropdown",
488 escapeMarkup: function(m){
488 escapeMarkup: function(m){
489 // don't escape our custom placeholder
489 // don't escape our custom placeholder
490 if(m.substr(0,23) == '<div class="menulabel">'){
490 if(m.substr(0,23) == '<div class="menulabel">'){
491 return m;
491 return m;
492 }
492 }
493
493
494 return Select2.util.escapeMarkup(m);
494 return Select2.util.escapeMarkup(m);
495 },
495 },
496 query: $.debounce(250, function(query){
496 query: $.debounce(250, function(query){
497 self = this;
497 self = this;
498 var cacheKey = query.term;
498 var cacheKey = query.term;
499 var cachedData = self.cachedDataSource[cacheKey];
499 var cachedData = self.cachedDataSource[cacheKey];
500
500
501 if (cachedData) {
501 if (cachedData) {
502 query.callback({results: cachedData.results});
502 query.callback({results: cachedData.results});
503 } else {
503 } else {
504 $.ajax({
504 $.ajax({
505 url: pyroutes.url('goto_switcher_data'),
505 url: pyroutes.url('goto_switcher_data'),
506 data: {'query': query.term},
506 data: {'query': query.term},
507 dataType: 'json',
507 dataType: 'json',
508 type: 'GET',
508 type: 'GET',
509 success: function(data) {
509 success: function(data) {
510 self.cachedDataSource[cacheKey] = data;
510 self.cachedDataSource[cacheKey] = data;
511 query.callback({results: data.results});
511 query.callback({results: data.results});
512 },
512 },
513 error: function(data, textStatus, errorThrown) {
513 error: function(data, textStatus, errorThrown) {
514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
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 $("#repo_switcher").on('select2-selecting', function(e){
521 $("#repo_switcher").on('select2-selecting', function(e){
522 e.preventDefault();
522 e.preventDefault();
523 window.location = e.choice.url;
523 window.location = e.choice.url;
524 });
524 });
525
525
526 </script>
526 </script>
527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 </%def>
528 </%def>
529
529
530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 <div class="modal-dialog">
531 <div class="modal-dialog">
532 <div class="modal-content">
532 <div class="modal-content">
533 <div class="modal-header">
533 <div class="modal-header">
534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 </div>
536 </div>
537 <div class="modal-body">
537 <div class="modal-body">
538 <div class="block-left">
538 <div class="block-left">
539 <table class="keyboard-mappings">
539 <table class="keyboard-mappings">
540 <tbody>
540 <tbody>
541 <tr>
541 <tr>
542 <th></th>
542 <th></th>
543 <th>${_('Site-wide shortcuts')}</th>
543 <th>${_('Site-wide shortcuts')}</th>
544 </tr>
544 </tr>
545 <%
545 <%
546 elems = [
546 elems = [
547 ('/', 'Open quick search box'),
547 ('/', 'Open quick search box'),
548 ('g h', 'Goto home page'),
548 ('g h', 'Goto home page'),
549 ('g g', 'Goto my private gists page'),
549 ('g g', 'Goto my private gists page'),
550 ('g G', 'Goto my public gists page'),
550 ('g G', 'Goto my public gists page'),
551 ('n r', 'New repository page'),
551 ('n r', 'New repository page'),
552 ('n g', 'New gist page'),
552 ('n g', 'New gist page'),
553 ]
553 ]
554 %>
554 %>
555 %for key, desc in elems:
555 %for key, desc in elems:
556 <tr>
556 <tr>
557 <td class="keys">
557 <td class="keys">
558 <span class="key tag">${key}</span>
558 <span class="key tag">${key}</span>
559 </td>
559 </td>
560 <td>${desc}</td>
560 <td>${desc}</td>
561 </tr>
561 </tr>
562 %endfor
562 %endfor
563 </tbody>
563 </tbody>
564 </table>
564 </table>
565 </div>
565 </div>
566 <div class="block-left">
566 <div class="block-left">
567 <table class="keyboard-mappings">
567 <table class="keyboard-mappings">
568 <tbody>
568 <tbody>
569 <tr>
569 <tr>
570 <th></th>
570 <th></th>
571 <th>${_('Repositories')}</th>
571 <th>${_('Repositories')}</th>
572 </tr>
572 </tr>
573 <%
573 <%
574 elems = [
574 elems = [
575 ('g s', 'Goto summary page'),
575 ('g s', 'Goto summary page'),
576 ('g c', 'Goto changelog page'),
576 ('g c', 'Goto changelog page'),
577 ('g f', 'Goto files page'),
577 ('g f', 'Goto files page'),
578 ('g F', 'Goto files page with file search activated'),
578 ('g F', 'Goto files page with file search activated'),
579 ('g p', 'Goto pull requests page'),
579 ('g p', 'Goto pull requests page'),
580 ('g o', 'Goto repository settings'),
580 ('g o', 'Goto repository settings'),
581 ('g O', 'Goto repository permissions settings'),
581 ('g O', 'Goto repository permissions settings'),
582 ]
582 ]
583 %>
583 %>
584 %for key, desc in elems:
584 %for key, desc in elems:
585 <tr>
585 <tr>
586 <td class="keys">
586 <td class="keys">
587 <span class="key tag">${key}</span>
587 <span class="key tag">${key}</span>
588 </td>
588 </td>
589 <td>${desc}</td>
589 <td>${desc}</td>
590 </tr>
590 </tr>
591 %endfor
591 %endfor
592 </tbody>
592 </tbody>
593 </table>
593 </table>
594 </div>
594 </div>
595 </div>
595 </div>
596 <div class="modal-footer">
596 <div class="modal-footer">
597 </div>
597 </div>
598 </div><!-- /.modal-content -->
598 </div><!-- /.modal-content -->
599 </div><!-- /.modal-dialog -->
599 </div><!-- /.modal-dialog -->
600 </div><!-- /.modal -->
600 </div><!-- /.modal -->
@@ -1,156 +1,156 b''
1 ## snippet for displaying default permission box
1 ## snippet for displaying default permission box
2 ## usage:
2 ## usage:
3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
4 ## ${dpb.default_perms_box(<url_to_form>)}
4 ## ${dpb.default_perms_box(<url_to_form>)}
5 ## ${dpb.default_perms_radios()}
5 ## ${dpb.default_perms_radios()}
6
6
7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
7 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
8 <div class="main-content-full-width">
8 <div class="main-content-full-width">
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10
10
11 ## displayed according to checkbox selection
11 ## displayed according to checkbox selection
12 <div class="panel-heading">
12 <div class="panel-heading">
13 %if not global_permissions_template:
13 %if not global_permissions_template:
14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
16 %else:
16 %else:
17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
18 %endif
18 %endif
19 </div>
19 </div>
20
20
21 <div class="panel-body">
21 <div class="panel-body">
22 %if global_permissions_template:
22 %if global_permissions_template:
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>
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 %endif
24 %endif
25 <div class="field">
25 <div class="field">
26 <div class="label">
26 <div class="label">
27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
27 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
28 </div>
28 </div>
29 <div class="radios">
29 <div class="radios">
30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
30 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
31 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
32 <span class="help-block">${_('Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups.')}</span>
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 </div>
33 </div>
34 </div>
34 </div>
35 <div class="field">
35 <div class="field">
36 <div class="label">
36 <div class="label">
37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
37 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
38 </div>
38 </div>
39 <div class="radios">
39 <div class="radios">
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)}
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 ${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)}
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 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
42 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
43 </div>
43 </div>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
47 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
48 </div>
48 </div>
49 <div class="radios">
49 <div class="radios">
50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
50 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
51 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
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>
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 </div>
53 </div>
54 </div>
54 </div>
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
57 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
58 </div>
58 </div>
59 <div class="radios">
59 <div class="radios">
60 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[1][0], label=c.repo_group_create_choices[1][1], **kwargs)}
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 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[0][0], label=c.repo_group_create_choices[0][1], **kwargs)}
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 <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>
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 </div>
63 </div>
64 </div>
64 </div>
65 <div class="field">
65 <div class="field">
66 <div class="label">
66 <div class="label">
67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
67 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
68 </div>
68 </div>
69 <div class="radios">
69 <div class="radios">
70 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[1][0], label=c.user_group_create_choices[1][1], **kwargs)}
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 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[0][0], label=c.user_group_create_choices[0][1], **kwargs)}
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 <span class="help-block">${_('Permission to allow user group creation.')}</span>
72 <span class="help-block">${_('Permission to allow user group creation.')}</span>
73 </div>
73 </div>
74 </div>
74 </div>
75
75
76 <div class="field">
76 <div class="field">
77 <div class="label">
77 <div class="label">
78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
78 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
79 </div>
79 </div>
80 <div class="radios">
80 <div class="radios">
81 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[1][0], label=c.inherit_default_permission_choices[1][1], **kwargs)}
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 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[0][0], label=c.inherit_default_permission_choices[0][1], **kwargs)}
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 <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>
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 </div>
84 </div>
85 </div>
85 </div>
86
86
87 <div class="buttons">
87 <div class="buttons">
88 ${h.submit('save',_('Save'),class_="btn")}
88 ${h.submit('save',_('Save'),class_="btn")}
89 ${h.reset('reset',_('Reset'),class_="btn")}
89 ${h.reset('reset',_('Reset'),class_="btn")}
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 </%def>
94 </%def>
95
95
96 <%def name="default_perms_box(form_url)">
96 <%def name="default_perms_box(form_url)">
97 ${h.secure_form(form_url, method='put')}
97 ${h.secure_form(form_url, method='put')}
98 <div class="form">
98 <div class="form">
99 <div class="fields">
99 <div class="fields">
100 <div class="field panel panel-default panel-body">
100 <div class="field panel panel-default panel-body">
101 <div class="label label-checkbox">
101 <div class="label label-checkbox">
102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
102 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
103 </div>
103 </div>
104 <div class="checkboxes">
104 <div class="checkboxes">
105 ${h.checkbox('inherit_default_permissions',value=True)}
105 ${h.checkbox('inherit_default_permissions',value=True)}
106 <span class="help-block">
106 <span class="help-block">
107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
107 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
108 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
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 </span>
110 </span>
111 </div>
111 </div>
112 </div>
112 </div>
113
113
114 ## INHERITED permissions == the user permissions in admin
114 ## INHERITED permissions == the user permissions in admin
115 ## if inherit checkbox is set this is displayed in non-edit mode
115 ## if inherit checkbox is set this is displayed in non-edit mode
116 <div class="inherit_overlay_default">
116 <div class="inherit_overlay_default">
117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
117 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
118 </div>
118 </div>
119
119
120 ## CUSTOM permissions
120 ## CUSTOM permissions
121 <div class="inherit_overlay">
121 <div class="inherit_overlay">
122 ${default_perms_radios(global_permissions_template = False)}
122 ${default_perms_radios(global_permissions_template = False)}
123 </div>
123 </div>
124 </div>
124 </div>
125 </div>
125 </div>
126 ${h.end_form()}
126 ${h.end_form()}
127
127
128
128
129 ## JS
129 ## JS
130 <script>
130 <script>
131 var show_custom_perms = function(inherit_default){
131 var show_custom_perms = function(inherit_default){
132 if(inherit_default) {
132 if(inherit_default) {
133 $('.inherit_overlay_default').show();
133 $('.inherit_overlay_default').show();
134 $('.inherit_overlay').hide();
134 $('.inherit_overlay').hide();
135 }
135 }
136 else {
136 else {
137 $('.inherit_overlay').show();
137 $('.inherit_overlay').show();
138 $('.inherit_overlay_default').hide();
138 $('.inherit_overlay_default').hide();
139 }
139 }
140 };
140 };
141 $(document).ready(function(e){
141 $(document).ready(function(e){
142 var inherit_checkbox = $('#inherit_default_permissions');
142 var inherit_checkbox = $('#inherit_default_permissions');
143 var defaults = inherit_checkbox.prop('checked');
143 var defaults = inherit_checkbox.prop('checked');
144 show_custom_perms(defaults);
144 show_custom_perms(defaults);
145 inherit_checkbox.on('change', function(){
145 inherit_checkbox.on('change', function(){
146 if($(this).prop('checked')){
146 if($(this).prop('checked')){
147 show_custom_perms(true);
147 show_custom_perms(true);
148 }
148 }
149 else{
149 else{
150 show_custom_perms(false);
150 show_custom_perms(false);
151 }
151 }
152 })
152 })
153 })
153 })
154 </script>
154 </script>
155
155
156 </%def>
156 </%def>
@@ -1,207 +1,207 b''
1 ## snippet for displaying permissions overview for users
1 ## snippet for displaying permissions overview for users
2 ## usage:
2 ## usage:
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 ## ${p.perms_summary(c.perm_user.permissions)}
4 ## ${p.perms_summary(c.perm_user.permissions)}
5
5
6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
7 <div id="perms" class="table fields">
7 <div id="perms" class="table fields">
8 %for section in sorted(permissions.keys()):
8 %for section in sorted(permissions.keys()):
9 <div class="panel panel-default">
9 <div class="panel panel-default">
10 <div class="panel-heading">
10 <div class="panel-heading">
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
12 </div>
12 </div>
13 <div class="panel-body">
13 <div class="panel-body">
14 <div class="perms_section_head field">
14 <div class="perms_section_head field">
15 <div class="radios">
15 <div class="radios">
16 %if section != 'global':
16 %if section != 'global':
17 <span class="permissions_boxes">
17 <span class="permissions_boxes">
18 <span class="desc">${_('show')}: </span>
18 <span class="desc">${_('show')}: </span>
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>
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 ${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>
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 ${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>
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 ${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>
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 </span>
23 </span>
24 %endif
24 %endif
25 </div>
25 </div>
26 </div>
26 </div>
27 <div class="field">
27 <div class="field">
28 %if not permissions[section]:
28 %if not permissions[section]:
29 <p class="empty_data help-block">${_('No permissions defined')}</p>
29 <p class="empty_data help-block">${_('No permissions defined')}</p>
30 %else:
30 %else:
31 <div id='tbl_list_wrap_${section}'>
31 <div id='tbl_list_wrap_${section}'>
32 <table id="tbl_list_${section}" class="rctable">
32 <table id="tbl_list_${section}" class="rctable">
33 ## global permission box
33 ## global permission box
34 %if section == 'global':
34 %if section == 'global':
35 <thead>
35 <thead>
36 <tr>
36 <tr>
37 <th colspan="2" class="left">${_('Permission')}</th>
37 <th colspan="2" class="left">${_('Permission')}</th>
38 %if actions:
38 %if actions:
39 <th>${_('Edit Permission')}</th>
39 <th>${_('Edit Permission')}</th>
40 %endif
40 %endif
41 </thead>
41 </thead>
42 <tbody>
42 <tbody>
43
43
44 <%
44 <%
45 def get_section_perms(prefix, opts):
45 def get_section_perms(prefix, opts):
46 _selected = []
46 _selected = []
47 for op in opts:
47 for op in opts:
48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
49 _selected.append(op)
49 _selected.append(op)
50 admin = 'hg.admin' in opts
50 admin = 'hg.admin' in opts
51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
52 return admin, _selected_vals, _selected
52 return admin, _selected_vals, _selected
53 %>
53 %>
54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
55 <tr>
55 <tr>
56 <td class="td-tags">
56 <td class="td-tags">
57 ${lbl}
57 ${lbl}
58 </td>
58 </td>
59 <td class="td-tags">
59 <td class="td-tags">
60 %if val[0]:
60 %if val[0]:
61 %if not val_lbl:
61 %if not val_lbl:
62 ${h.bool2icon(True)}
62 ${h.bool2icon(True)}
63 %else:
63 %else:
64 <span class="perm_tag admin">${val_lbl}.admin</span>
64 <span class="perm_tag admin">${val_lbl}.admin</span>
65 %endif
65 %endif
66 %else:
66 %else:
67 %if not val_lbl:
67 %if not val_lbl:
68 ${h.bool2icon({'false': False,
68 ${h.bool2icon({'false': False,
69 'true': True,
69 'true': True,
70 'none': False,
70 'none': False,
71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
72 %else:
72 %else:
73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
74 %endif
74 %endif
75 %endif
75 %endif
76 </td>
76 </td>
77 %if actions:
77 %if actions:
78 <td class="td-action">
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 </td>
80 </td>
81 %endif
81 %endif
82 </tr>
82 </tr>
83 </%def>
83 </%def>
84
84
85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
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'))}
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.url('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.url('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'))}
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.url('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.url('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.url('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 </tbody>
97 </tbody>
98 %else:
98 %else:
99 ## none/read/write/admin permissions on groups/repos etc
99 ## none/read/write/admin permissions on groups/repos etc
100 <thead>
100 <thead>
101 <tr>
101 <tr>
102 <th>${_('Name')}</th>
102 <th>${_('Name')}</th>
103 <th>${_('Permission')}</th>
103 <th>${_('Permission')}</th>
104 %if actions:
104 %if actions:
105 <th>${_('Edit Permission')}</th>
105 <th>${_('Edit Permission')}</th>
106 %endif
106 %endif
107 </thead>
107 </thead>
108 <tbody class="section_${section}">
108 <tbody class="section_${section}">
109 <%
109 <%
110 def sorter(permissions):
110 def sorter(permissions):
111 def custom_sorter(item):
111 def custom_sorter(item):
112 ## read/write/admin
112 ## read/write/admin
113 section = item[1].split('.')[-1]
113 section = item[1].split('.')[-1]
114 section_importance = {'none': u'0',
114 section_importance = {'none': u'0',
115 'read': u'1',
115 'read': u'1',
116 'write':u'2',
116 'write':u'2',
117 'admin':u'3'}.get(section)
117 'admin':u'3'}.get(section)
118 ## sort by group importance+name
118 ## sort by group importance+name
119 return section_importance+item[0]
119 return section_importance+item[0]
120 return sorted(permissions, key=custom_sorter)
120 return sorted(permissions, key=custom_sorter)
121 %>
121 %>
122 %for k, section_perm in sorter(permissions[section].items()):
122 %for k, section_perm in sorter(permissions[section].items()):
123 %if section_perm.split('.')[-1] != 'none' or show_all:
123 %if section_perm.split('.')[-1] != 'none' or show_all:
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
125 <td class="td-componentname">
125 <td class="td-componentname">
126 %if section == 'repositories':
126 %if section == 'repositories':
127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
127 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
128 %elif section == 'repositories_groups':
128 %elif section == 'repositories_groups':
129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
129 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
130 %elif section == 'user_groups':
130 %elif section == 'user_groups':
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
132 ${k}
132 ${k}
133 %endif
133 %endif
134 </td>
134 </td>
135 <td class="td-tags">
135 <td class="td-tags">
136 %if hasattr(permissions[section], 'perm_origin_stack'):
136 %if hasattr(permissions[section], 'perm_origin_stack'):
137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
139 ${perm} (${origin})
139 ${perm} (${origin})
140 </span>
140 </span>
141 %endfor
141 %endfor
142 %else:
142 %else:
143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
144 %endif
144 %endif
145 </td>
145 </td>
146 %if actions:
146 %if actions:
147 <td class="td-action">
147 <td class="td-action">
148 %if section == 'repositories':
148 %if section == 'repositories':
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.route_path('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 %elif section == 'repositories_groups':
150 %elif section == 'repositories_groups':
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 %elif section == 'user_groups':
152 %elif section == 'user_groups':
153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
154 %endif
154 %endif
155 </td>
155 </td>
156 %endif
156 %endif
157 </tr>
157 </tr>
158 %endif
158 %endif
159 %endfor
159 %endfor
160
160
161 <tr id="empty_${section}" class="noborder" style="display:none;">
161 <tr id="empty_${section}" class="noborder" style="display:none;">
162 <td colspan="6">${_('No permission defined')}</td>
162 <td colspan="6">${_('No permission defined')}</td>
163 </tr>
163 </tr>
164
164
165 </tbody>
165 </tbody>
166 %endif
166 %endif
167 </table>
167 </table>
168 </div>
168 </div>
169 %endif
169 %endif
170 </div>
170 </div>
171 </div>
171 </div>
172 </div>
172 </div>
173 %endfor
173 %endfor
174 </div>
174 </div>
175
175
176 <script>
176 <script>
177 $(document).ready(function(){
177 $(document).ready(function(){
178 var show_empty = function(section){
178 var show_empty = function(section){
179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
180 if(visible == 0){
180 if(visible == 0){
181 $('#empty_{0}'.format(section)).show();
181 $('#empty_{0}'.format(section)).show();
182 }
182 }
183 else{
183 else{
184 $('#empty_{0}'.format(section)).hide();
184 $('#empty_{0}'.format(section)).hide();
185 }
185 }
186 };
186 };
187 $('.perm_filter').on('change', function(e){
187 $('.perm_filter').on('change', function(e){
188 var self = this;
188 var self = this;
189 var section = $(this).attr('section');
189 var section = $(this).attr('section');
190
190
191 var opts = {};
191 var opts = {};
192 var elems = $('.filter_' + section).each(function(el){
192 var elems = $('.filter_' + section).each(function(el){
193 var perm_type = $(this).attr('perm_type');
193 var perm_type = $(this).attr('perm_type');
194 var checked = this.checked;
194 var checked = this.checked;
195 opts[perm_type] = checked;
195 opts[perm_type] = checked;
196 if(checked){
196 if(checked){
197 $('.'+section+'_'+perm_type).show();
197 $('.'+section+'_'+perm_type).show();
198 }
198 }
199 else{
199 else{
200 $('.'+section+'_'+perm_type).hide();
200 $('.'+section+'_'+perm_type).hide();
201 }
201 }
202 });
202 });
203 show_empty(section);
203 show_empty(section);
204 })
204 })
205 })
205 })
206 </script>
206 </script>
207 </%def>
207 </%def>
@@ -1,265 +1,265 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import time
22 import time
23 import logging
23 import logging
24 import datetime
24 import datetime
25 import hashlib
25 import hashlib
26 import tempfile
26 import tempfile
27 from os.path import join as jn
27 from os.path import join as jn
28
28
29 from tempfile import _RandomNameSequence
29 from tempfile import _RandomNameSequence
30
30
31 from paste.deploy import loadapp
31 from paste.deploy import loadapp
32 from paste.script.appinstall import SetupCommand
32 from paste.script.appinstall import SetupCommand
33
33
34 import pylons
34 import pylons
35 import pylons.test
35 import pylons.test
36 from pylons import config, url
36 from pylons import config, url
37 from pylons.i18n.translation import _get_translator
37 from pylons.i18n.translation import _get_translator
38 from pylons.util import ContextObj
38 from pylons.util import ContextObj
39
39
40 from routes.util import URLGenerator
40 from routes.util import URLGenerator
41 from nose.plugins.skip import SkipTest
41 from nose.plugins.skip import SkipTest
42 import pytest
42 import pytest
43
43
44 from rhodecode import is_windows
44 from rhodecode import is_windows
45 from rhodecode.config.routing import ADMIN_PREFIX
45 from rhodecode.config.routing import ADMIN_PREFIX
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.db import User
47 from rhodecode.model.db import User
48 from rhodecode.lib import auth
48 from rhodecode.lib import auth
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib.helpers import flash, link_to
50 from rhodecode.lib.helpers import flash, link_to
50 from rhodecode.lib.utils2 import safe_unicode, safe_str
51 from rhodecode.lib.utils2 import safe_unicode, safe_str
51
52
52
53
53 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
54
55
55 __all__ = [
56 __all__ = [
56 'get_new_dir', 'TestController', 'SkipTest',
57 'get_new_dir', 'TestController', 'SkipTest',
57 'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
58 'url', 'link_to', 'ldap_lib_installed', 'clear_all_caches',
58 'assert_session_flash', 'login_user', 'no_newline_id_generator',
59 'assert_session_flash', 'login_user', 'no_newline_id_generator',
59 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
60 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'SVN_REPO',
60 'NEW_HG_REPO', 'NEW_GIT_REPO',
61 'NEW_HG_REPO', 'NEW_GIT_REPO',
61 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
62 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
62 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
63 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
63 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
64 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
64 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
65 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
65 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
66 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
66 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
67 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'SCM_TESTS',
67 ]
68 ]
68
69
69 # Invoke websetup with the current config file
70 # Invoke websetup with the current config file
70 # SetupCommand('setup-app').run([config_file])
71 # SetupCommand('setup-app').run([config_file])
71
72
72 # SOME GLOBALS FOR TESTS
73 # SOME GLOBALS FOR TESTS
73 TEST_DIR = tempfile.gettempdir()
74 TEST_DIR = tempfile.gettempdir()
74
75
75 TESTS_TMP_PATH = jn(TEST_DIR, 'rc_test_%s' % _RandomNameSequence().next())
76 TESTS_TMP_PATH = jn(TEST_DIR, 'rc_test_%s' % _RandomNameSequence().next())
76 TEST_USER_ADMIN_LOGIN = 'test_admin'
77 TEST_USER_ADMIN_LOGIN = 'test_admin'
77 TEST_USER_ADMIN_PASS = 'test12'
78 TEST_USER_ADMIN_PASS = 'test12'
78 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
79 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
79
80
80 TEST_USER_REGULAR_LOGIN = 'test_regular'
81 TEST_USER_REGULAR_LOGIN = 'test_regular'
81 TEST_USER_REGULAR_PASS = 'test12'
82 TEST_USER_REGULAR_PASS = 'test12'
82 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
83 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
83
84
84 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
85 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
85 TEST_USER_REGULAR2_PASS = 'test12'
86 TEST_USER_REGULAR2_PASS = 'test12'
86 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
87 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
87
88
88 HG_REPO = 'vcs_test_hg'
89 HG_REPO = 'vcs_test_hg'
89 GIT_REPO = 'vcs_test_git'
90 GIT_REPO = 'vcs_test_git'
90 SVN_REPO = 'vcs_test_svn'
91 SVN_REPO = 'vcs_test_svn'
91
92
92 NEW_HG_REPO = 'vcs_test_hg_new'
93 NEW_HG_REPO = 'vcs_test_hg_new'
93 NEW_GIT_REPO = 'vcs_test_git_new'
94 NEW_GIT_REPO = 'vcs_test_git_new'
94
95
95 HG_FORK = 'vcs_test_hg_fork'
96 HG_FORK = 'vcs_test_hg_fork'
96 GIT_FORK = 'vcs_test_git_fork'
97 GIT_FORK = 'vcs_test_git_fork'
97
98
98 ## VCS
99 ## VCS
99 SCM_TESTS = ['hg', 'git']
100 SCM_TESTS = ['hg', 'git']
100 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
101 uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
101
102
102 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
103 TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
103 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
104 TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
104 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
105 TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
105
106
106 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
107 TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
107 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
108 TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
108 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
109 TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
109
110
110 TEST_REPO_PREFIX = 'vcs-test'
111 TEST_REPO_PREFIX = 'vcs-test'
111
112
112
113
113 # skip ldap tests if LDAP lib is not installed
114 # skip ldap tests if LDAP lib is not installed
114 ldap_lib_installed = False
115 ldap_lib_installed = False
115 try:
116 try:
116 import ldap
117 import ldap
117 ldap_lib_installed = True
118 ldap_lib_installed = True
118 except ImportError:
119 except ImportError:
119 # means that python-ldap is not installed
120 # means that python-ldap is not installed
120 pass
121 pass
121
122
122
123
123 def clear_all_caches():
124 def clear_all_caches():
124 from beaker.cache import cache_managers
125 from beaker.cache import cache_managers
125 for _cache in cache_managers.values():
126 for _cache in cache_managers.values():
126 _cache.clear()
127 _cache.clear()
127
128
128
129
129 def get_new_dir(title):
130 def get_new_dir(title):
130 """
131 """
131 Returns always new directory path.
132 Returns always new directory path.
132 """
133 """
133 from rhodecode.tests.vcs.utils import get_normalized_path
134 from rhodecode.tests.vcs.utils import get_normalized_path
134 name_parts = [TEST_REPO_PREFIX]
135 name_parts = [TEST_REPO_PREFIX]
135 if title:
136 if title:
136 name_parts.append(title)
137 name_parts.append(title)
137 hex_str = hashlib.sha1('%s %s' % (os.getpid(), time.time())).hexdigest()
138 hex_str = hashlib.sha1('%s %s' % (os.getpid(), time.time())).hexdigest()
138 name_parts.append(hex_str)
139 name_parts.append(hex_str)
139 name = '-'.join(name_parts)
140 name = '-'.join(name_parts)
140 path = os.path.join(TEST_DIR, name)
141 path = os.path.join(TEST_DIR, name)
141 return get_normalized_path(path)
142 return get_normalized_path(path)
142
143
143
144
144 @pytest.mark.usefixtures('app', 'index_location')
145 @pytest.mark.usefixtures('app', 'index_location')
145 class TestController(object):
146 class TestController(object):
146
147
147 maxDiff = None
148 maxDiff = None
148
149
149 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
150 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
150 password=TEST_USER_ADMIN_PASS):
151 password=TEST_USER_ADMIN_PASS):
151 self._logged_username = username
152 self._logged_username = username
152 self._session = login_user_session(self.app, username, password)
153 self._session = login_user_session(self.app, username, password)
153 self.csrf_token = auth.get_csrf_token(self._session)
154 self.csrf_token = auth.get_csrf_token(self._session)
154
155
155 return self._session['rhodecode_user']
156 return self._session['rhodecode_user']
156
157
157 def logout_user(self):
158 def logout_user(self):
158 logout_user_session(self.app, auth.get_csrf_token(self._session))
159 logout_user_session(self.app, auth.get_csrf_token(self._session))
159 self.csrf_token = None
160 self.csrf_token = None
160 self._logged_username = None
161 self._logged_username = None
161 self._session = None
162 self._session = None
162
163
163 def _get_logged_user(self):
164 def _get_logged_user(self):
164 return User.get_by_username(self._logged_username)
165 return User.get_by_username(self._logged_username)
165
166
166
167
167 def login_user_session(
168 def login_user_session(
168 app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
169 app, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
169 from rhodecode.tests.functional.test_login import login_url
170
170 response = app.post(
171 response = app.post(
171 login_url,
172 h.route_path('login'),
172 {'username': username, 'password': password})
173 {'username': username, 'password': password})
173 if 'invalid user name' in response.body:
174 if 'invalid user name' in response.body:
174 pytest.fail('could not login using %s %s' % (username, password))
175 pytest.fail('could not login using %s %s' % (username, password))
175
176
176 assert response.status == '302 Found'
177 assert response.status == '302 Found'
177 response = response.follow()
178 response = response.follow()
178 assert response.status == '200 OK'
179 assert response.status == '200 OK'
179
180
180 session = response.get_session_from_response()
181 session = response.get_session_from_response()
181 assert 'rhodecode_user' in session
182 assert 'rhodecode_user' in session
182 rc_user = session['rhodecode_user']
183 rc_user = session['rhodecode_user']
183 assert rc_user.get('username') == username
184 assert rc_user.get('username') == username
184 assert rc_user.get('is_authenticated')
185 assert rc_user.get('is_authenticated')
185
186
186 return session
187 return session
187
188
188
189
189 def logout_user_session(app, csrf_token):
190 def logout_user_session(app, csrf_token):
190 from rhodecode.tests.functional.test_login import logut_url
191 app.post(h.route_path('logout'), {'csrf_token': csrf_token}, status=302)
191 app.post(logut_url, {'csrf_token': csrf_token}, status=302)
192
192
193
193
194 def login_user(app, username=TEST_USER_ADMIN_LOGIN,
194 def login_user(app, username=TEST_USER_ADMIN_LOGIN,
195 password=TEST_USER_ADMIN_PASS):
195 password=TEST_USER_ADMIN_PASS):
196 return login_user_session(app, username, password)['rhodecode_user']
196 return login_user_session(app, username, password)['rhodecode_user']
197
197
198
198
199 def assert_session_flash(response=None, msg=None, category=None, no_=None):
199 def assert_session_flash(response=None, msg=None, category=None, no_=None):
200 """
200 """
201 Assert on a flash message in the current session.
201 Assert on a flash message in the current session.
202
202
203 :param msg: Required. The expected message. Will be evaluated if a
203 :param msg: Required. The expected message. Will be evaluated if a
204 :class:`LazyString` is passed in.
204 :class:`LazyString` is passed in.
205 :param response: Optional. For functional testing, pass in the response
205 :param response: Optional. For functional testing, pass in the response
206 object. Otherwise don't pass in any value.
206 object. Otherwise don't pass in any value.
207 :param category: Optional. If passed, the message category will be
207 :param category: Optional. If passed, the message category will be
208 checked as well.
208 checked as well.
209 :param no_: Optional. If passed, the message will be checked to NOT be in the
209 :param no_: Optional. If passed, the message will be checked to NOT be in the
210 flash session
210 flash session
211 """
211 """
212 if msg is None and no_ is None:
212 if msg is None and no_ is None:
213 raise ValueError("Parameter msg or no_ is required.")
213 raise ValueError("Parameter msg or no_ is required.")
214
214
215 if msg and no_:
215 if msg and no_:
216 raise ValueError("Please specify either msg or no_, but not both")
216 raise ValueError("Please specify either msg or no_, but not both")
217
217
218 messages = flash.pop_messages()
218 messages = flash.pop_messages()
219 msg = _eval_if_lazy(msg)
219 msg = _eval_if_lazy(msg)
220
220
221 assert messages, 'unable to find message `%s` in empty flash list' % msg
221 assert messages, 'unable to find message `%s` in empty flash list' % msg
222 message = messages[0]
222 message = messages[0]
223
223
224 message_text = _eval_if_lazy(message.message) or ''
224 message_text = _eval_if_lazy(message.message) or ''
225
225
226 if no_:
226 if no_:
227 if no_ in message_text:
227 if no_ in message_text:
228 msg = u'msg `%s` found in session flash.' % (no_,)
228 msg = u'msg `%s` found in session flash.' % (no_,)
229 pytest.fail(safe_str(msg))
229 pytest.fail(safe_str(msg))
230 else:
230 else:
231 if msg not in message_text:
231 if msg not in message_text:
232 fail_msg = u'msg `%s` not found in session ' \
232 fail_msg = u'msg `%s` not found in session ' \
233 u'flash: got `%s` (type:%s) instead' % (
233 u'flash: got `%s` (type:%s) instead' % (
234 msg, message_text, type(message_text))
234 msg, message_text, type(message_text))
235
235
236 pytest.fail(safe_str(fail_msg))
236 pytest.fail(safe_str(fail_msg))
237 if category:
237 if category:
238 assert category == message.category
238 assert category == message.category
239
239
240
240
241 def _eval_if_lazy(value):
241 def _eval_if_lazy(value):
242 return value.eval() if hasattr(value, 'eval') else value
242 return value.eval() if hasattr(value, 'eval') else value
243
243
244
244
245 def assert_session_flash_is_empty(response):
245 def assert_session_flash_is_empty(response):
246 assert 'flash' in response.session, 'Response session has no flash key'
246 assert 'flash' in response.session, 'Response session has no flash key'
247
247
248 msg = 'flash messages are present in session:%s' % \
248 msg = 'flash messages are present in session:%s' % \
249 response.session['flash'][0]
249 response.session['flash'][0]
250 pytest.fail(safe_str(msg))
250 pytest.fail(safe_str(msg))
251
251
252
252
253 def no_newline_id_generator(test_name):
253 def no_newline_id_generator(test_name):
254 """
254 """
255 Generates a test name without spaces or newlines characters. Used for
255 Generates a test name without spaces or newlines characters. Used for
256 nicer output of progress of test
256 nicer output of progress of test
257 """
257 """
258 org_name = test_name
258 org_name = test_name
259 test_name = test_name\
259 test_name = test_name\
260 .replace('\n', '_N') \
260 .replace('\n', '_N') \
261 .replace('\r', '_N') \
261 .replace('\r', '_N') \
262 .replace('\t', '_T') \
262 .replace('\t', '_T') \
263 .replace(' ', '_S')
263 .replace(' ', '_S')
264
264
265 return test_name or 'test-with-empty-name'
265 return test_name or 'test-with-empty-name'
@@ -1,1107 +1,1106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23 from webob.exc import HTTPNotFound
23 from webob.exc import HTTPNotFound
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.vcs.nodes import FileNode
26 from rhodecode.lib.vcs.nodes import FileNode
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.model.changeset_status import ChangesetStatusModel
28 from rhodecode.model.changeset_status import ChangesetStatusModel
29 from rhodecode.model.db import (
29 from rhodecode.model.db import (
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
30 PullRequest, ChangesetStatus, UserLog, Notification, ChangesetComment)
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.model.pull_request import PullRequestModel
32 from rhodecode.model.pull_request import PullRequestModel
33 from rhodecode.model.user import UserModel
33 from rhodecode.model.user import UserModel
34 from rhodecode.tests import (
34 from rhodecode.tests import (
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
35 assert_session_flash, url, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN)
36 from rhodecode.tests.utils import AssertResponse
36 from rhodecode.tests.utils import AssertResponse
37
37
38
38
39 def route_path(name, params=None, **kwargs):
39 def route_path(name, params=None, **kwargs):
40 import urllib
40 import urllib
41
41
42 base_url = {
42 base_url = {
43 'repo_changelog':'/{repo_name}/changelog',
43 'repo_changelog':'/{repo_name}/changelog',
44 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}',
44 'repo_changelog_file':'/{repo_name}/changelog/{commit_id}/{f_path}',
45 }[name].format(**kwargs)
45 }[name].format(**kwargs)
46
46
47 if params:
47 if params:
48 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
49 return base_url
49 return base_url
50
50
51
51
52 @pytest.mark.usefixtures('app', 'autologin_user')
52 @pytest.mark.usefixtures('app', 'autologin_user')
53 @pytest.mark.backends("git", "hg")
53 @pytest.mark.backends("git", "hg")
54 class TestPullrequestsController(object):
54 class TestPullrequestsController(object):
55
55
56 def test_index(self, backend):
56 def test_index(self, backend):
57 self.app.get(url(
57 self.app.get(url(
58 controller='pullrequests', action='index',
58 controller='pullrequests', action='index',
59 repo_name=backend.repo_name))
59 repo_name=backend.repo_name))
60
60
61 def test_option_menu_create_pull_request_exists(self, backend):
61 def test_option_menu_create_pull_request_exists(self, backend):
62 repo_name = backend.repo_name
62 repo_name = backend.repo_name
63 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
63 response = self.app.get(h.route_path('repo_summary', repo_name=repo_name))
64
64
65 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
65 create_pr_link = '<a href="%s">Create Pull Request</a>' % url(
66 'pullrequest', repo_name=repo_name)
66 'pullrequest', repo_name=repo_name)
67 response.mustcontain(create_pr_link)
67 response.mustcontain(create_pr_link)
68
68
69 def test_create_pr_form_with_raw_commit_id(self, backend):
69 def test_create_pr_form_with_raw_commit_id(self, backend):
70 repo = backend.repo
70 repo = backend.repo
71
71
72 self.app.get(
72 self.app.get(
73 url(controller='pullrequests', action='index',
73 url(controller='pullrequests', action='index',
74 repo_name=repo.repo_name,
74 repo_name=repo.repo_name,
75 commit=repo.get_commit().raw_id),
75 commit=repo.get_commit().raw_id),
76 status=200)
76 status=200)
77
77
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
78 @pytest.mark.parametrize('pr_merge_enabled', [True, False])
79 def test_show(self, pr_util, pr_merge_enabled):
79 def test_show(self, pr_util, pr_merge_enabled):
80 pull_request = pr_util.create_pull_request(
80 pull_request = pr_util.create_pull_request(
81 mergeable=pr_merge_enabled, enable_notifications=False)
81 mergeable=pr_merge_enabled, enable_notifications=False)
82
82
83 response = self.app.get(url(
83 response = self.app.get(url(
84 controller='pullrequests', action='show',
84 controller='pullrequests', action='show',
85 repo_name=pull_request.target_repo.scm_instance().name,
85 repo_name=pull_request.target_repo.scm_instance().name,
86 pull_request_id=str(pull_request.pull_request_id)))
86 pull_request_id=str(pull_request.pull_request_id)))
87
87
88 for commit_id in pull_request.revisions:
88 for commit_id in pull_request.revisions:
89 response.mustcontain(commit_id)
89 response.mustcontain(commit_id)
90
90
91 assert pull_request.target_ref_parts.type in response
91 assert pull_request.target_ref_parts.type in response
92 assert pull_request.target_ref_parts.name in response
92 assert pull_request.target_ref_parts.name in response
93 target_clone_url = pull_request.target_repo.clone_url()
93 target_clone_url = pull_request.target_repo.clone_url()
94 assert target_clone_url in response
94 assert target_clone_url in response
95
95
96 assert 'class="pull-request-merge"' in response
96 assert 'class="pull-request-merge"' in response
97 assert (
97 assert (
98 'Server-side pull request merging is disabled.'
98 'Server-side pull request merging is disabled.'
99 in response) != pr_merge_enabled
99 in response) != pr_merge_enabled
100
100
101 def test_close_status_visibility(self, pr_util, user_util, csrf_token):
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 # Logout
102 # Logout
104 response = self.app.post(
103 response = self.app.post(
105 logut_url,
104 h.route_path('logout'),
106 params={'csrf_token': csrf_token})
105 params={'csrf_token': csrf_token})
107 # Login as regular user
106 # Login as regular user
108 response = self.app.post(login_url,
107 response = self.app.post(h.route_path('login'),
109 {'username': TEST_USER_REGULAR_LOGIN,
108 {'username': TEST_USER_REGULAR_LOGIN,
110 'password': 'test12'})
109 'password': 'test12'})
111
110
112 pull_request = pr_util.create_pull_request(
111 pull_request = pr_util.create_pull_request(
113 author=TEST_USER_REGULAR_LOGIN)
112 author=TEST_USER_REGULAR_LOGIN)
114
113
115 response = self.app.get(url(
114 response = self.app.get(url(
116 controller='pullrequests', action='show',
115 controller='pullrequests', action='show',
117 repo_name=pull_request.target_repo.scm_instance().name,
116 repo_name=pull_request.target_repo.scm_instance().name,
118 pull_request_id=str(pull_request.pull_request_id)))
117 pull_request_id=str(pull_request.pull_request_id)))
119
118
120 response.mustcontain('Server-side pull request merging is disabled.')
119 response.mustcontain('Server-side pull request merging is disabled.')
121
120
122 assert_response = response.assert_response()
121 assert_response = response.assert_response()
123 # for regular user without a merge permissions, we don't see it
122 # for regular user without a merge permissions, we don't see it
124 assert_response.no_element_exists('#close-pull-request-action')
123 assert_response.no_element_exists('#close-pull-request-action')
125
124
126 user_util.grant_user_permission_to_repo(
125 user_util.grant_user_permission_to_repo(
127 pull_request.target_repo,
126 pull_request.target_repo,
128 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
127 UserModel().get_by_username(TEST_USER_REGULAR_LOGIN),
129 'repository.write')
128 'repository.write')
130 response = self.app.get(url(
129 response = self.app.get(url(
131 controller='pullrequests', action='show',
130 controller='pullrequests', action='show',
132 repo_name=pull_request.target_repo.scm_instance().name,
131 repo_name=pull_request.target_repo.scm_instance().name,
133 pull_request_id=str(pull_request.pull_request_id)))
132 pull_request_id=str(pull_request.pull_request_id)))
134
133
135 response.mustcontain('Server-side pull request merging is disabled.')
134 response.mustcontain('Server-side pull request merging is disabled.')
136
135
137 assert_response = response.assert_response()
136 assert_response = response.assert_response()
138 # now regular user has a merge permissions, we have CLOSE button
137 # now regular user has a merge permissions, we have CLOSE button
139 assert_response.one_element_exists('#close-pull-request-action')
138 assert_response.one_element_exists('#close-pull-request-action')
140
139
141 def test_show_invalid_commit_id(self, pr_util):
140 def test_show_invalid_commit_id(self, pr_util):
142 # Simulating invalid revisions which will cause a lookup error
141 # Simulating invalid revisions which will cause a lookup error
143 pull_request = pr_util.create_pull_request()
142 pull_request = pr_util.create_pull_request()
144 pull_request.revisions = ['invalid']
143 pull_request.revisions = ['invalid']
145 Session().add(pull_request)
144 Session().add(pull_request)
146 Session().commit()
145 Session().commit()
147
146
148 response = self.app.get(url(
147 response = self.app.get(url(
149 controller='pullrequests', action='show',
148 controller='pullrequests', action='show',
150 repo_name=pull_request.target_repo.scm_instance().name,
149 repo_name=pull_request.target_repo.scm_instance().name,
151 pull_request_id=str(pull_request.pull_request_id)))
150 pull_request_id=str(pull_request.pull_request_id)))
152
151
153 for commit_id in pull_request.revisions:
152 for commit_id in pull_request.revisions:
154 response.mustcontain(commit_id)
153 response.mustcontain(commit_id)
155
154
156 def test_show_invalid_source_reference(self, pr_util):
155 def test_show_invalid_source_reference(self, pr_util):
157 pull_request = pr_util.create_pull_request()
156 pull_request = pr_util.create_pull_request()
158 pull_request.source_ref = 'branch:b:invalid'
157 pull_request.source_ref = 'branch:b:invalid'
159 Session().add(pull_request)
158 Session().add(pull_request)
160 Session().commit()
159 Session().commit()
161
160
162 self.app.get(url(
161 self.app.get(url(
163 controller='pullrequests', action='show',
162 controller='pullrequests', action='show',
164 repo_name=pull_request.target_repo.scm_instance().name,
163 repo_name=pull_request.target_repo.scm_instance().name,
165 pull_request_id=str(pull_request.pull_request_id)))
164 pull_request_id=str(pull_request.pull_request_id)))
166
165
167 def test_edit_title_description(self, pr_util, csrf_token):
166 def test_edit_title_description(self, pr_util, csrf_token):
168 pull_request = pr_util.create_pull_request()
167 pull_request = pr_util.create_pull_request()
169 pull_request_id = pull_request.pull_request_id
168 pull_request_id = pull_request.pull_request_id
170
169
171 response = self.app.post(
170 response = self.app.post(
172 url(controller='pullrequests', action='update',
171 url(controller='pullrequests', action='update',
173 repo_name=pull_request.target_repo.repo_name,
172 repo_name=pull_request.target_repo.repo_name,
174 pull_request_id=str(pull_request_id)),
173 pull_request_id=str(pull_request_id)),
175 params={
174 params={
176 'edit_pull_request': 'true',
175 'edit_pull_request': 'true',
177 '_method': 'put',
176 '_method': 'put',
178 'title': 'New title',
177 'title': 'New title',
179 'description': 'New description',
178 'description': 'New description',
180 'csrf_token': csrf_token})
179 'csrf_token': csrf_token})
181
180
182 assert_session_flash(
181 assert_session_flash(
183 response, u'Pull request title & description updated.',
182 response, u'Pull request title & description updated.',
184 category='success')
183 category='success')
185
184
186 pull_request = PullRequest.get(pull_request_id)
185 pull_request = PullRequest.get(pull_request_id)
187 assert pull_request.title == 'New title'
186 assert pull_request.title == 'New title'
188 assert pull_request.description == 'New description'
187 assert pull_request.description == 'New description'
189
188
190 def test_edit_title_description_closed(self, pr_util, csrf_token):
189 def test_edit_title_description_closed(self, pr_util, csrf_token):
191 pull_request = pr_util.create_pull_request()
190 pull_request = pr_util.create_pull_request()
192 pull_request_id = pull_request.pull_request_id
191 pull_request_id = pull_request.pull_request_id
193 pr_util.close()
192 pr_util.close()
194
193
195 response = self.app.post(
194 response = self.app.post(
196 url(controller='pullrequests', action='update',
195 url(controller='pullrequests', action='update',
197 repo_name=pull_request.target_repo.repo_name,
196 repo_name=pull_request.target_repo.repo_name,
198 pull_request_id=str(pull_request_id)),
197 pull_request_id=str(pull_request_id)),
199 params={
198 params={
200 'edit_pull_request': 'true',
199 'edit_pull_request': 'true',
201 '_method': 'put',
200 '_method': 'put',
202 'title': 'New title',
201 'title': 'New title',
203 'description': 'New description',
202 'description': 'New description',
204 'csrf_token': csrf_token})
203 'csrf_token': csrf_token})
205
204
206 assert_session_flash(
205 assert_session_flash(
207 response, u'Cannot update closed pull requests.',
206 response, u'Cannot update closed pull requests.',
208 category='error')
207 category='error')
209
208
210 def test_update_invalid_source_reference(self, pr_util, csrf_token):
209 def test_update_invalid_source_reference(self, pr_util, csrf_token):
211 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
210 from rhodecode.lib.vcs.backends.base import UpdateFailureReason
212
211
213 pull_request = pr_util.create_pull_request()
212 pull_request = pr_util.create_pull_request()
214 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
213 pull_request.source_ref = 'branch:invalid-branch:invalid-commit-id'
215 Session().add(pull_request)
214 Session().add(pull_request)
216 Session().commit()
215 Session().commit()
217
216
218 pull_request_id = pull_request.pull_request_id
217 pull_request_id = pull_request.pull_request_id
219
218
220 response = self.app.post(
219 response = self.app.post(
221 url(controller='pullrequests', action='update',
220 url(controller='pullrequests', action='update',
222 repo_name=pull_request.target_repo.repo_name,
221 repo_name=pull_request.target_repo.repo_name,
223 pull_request_id=str(pull_request_id)),
222 pull_request_id=str(pull_request_id)),
224 params={'update_commits': 'true', '_method': 'put',
223 params={'update_commits': 'true', '_method': 'put',
225 'csrf_token': csrf_token})
224 'csrf_token': csrf_token})
226
225
227 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
226 expected_msg = PullRequestModel.UPDATE_STATUS_MESSAGES[
228 UpdateFailureReason.MISSING_SOURCE_REF]
227 UpdateFailureReason.MISSING_SOURCE_REF]
229 assert_session_flash(response, expected_msg, category='error')
228 assert_session_flash(response, expected_msg, category='error')
230
229
231 def test_missing_target_reference(self, pr_util, csrf_token):
230 def test_missing_target_reference(self, pr_util, csrf_token):
232 from rhodecode.lib.vcs.backends.base import MergeFailureReason
231 from rhodecode.lib.vcs.backends.base import MergeFailureReason
233 pull_request = pr_util.create_pull_request(
232 pull_request = pr_util.create_pull_request(
234 approved=True, mergeable=True)
233 approved=True, mergeable=True)
235 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
234 pull_request.target_ref = 'branch:invalid-branch:invalid-commit-id'
236 Session().add(pull_request)
235 Session().add(pull_request)
237 Session().commit()
236 Session().commit()
238
237
239 pull_request_id = pull_request.pull_request_id
238 pull_request_id = pull_request.pull_request_id
240 pull_request_url = url(
239 pull_request_url = url(
241 controller='pullrequests', action='show',
240 controller='pullrequests', action='show',
242 repo_name=pull_request.target_repo.repo_name,
241 repo_name=pull_request.target_repo.repo_name,
243 pull_request_id=str(pull_request_id))
242 pull_request_id=str(pull_request_id))
244
243
245 response = self.app.get(pull_request_url)
244 response = self.app.get(pull_request_url)
246
245
247 assertr = AssertResponse(response)
246 assertr = AssertResponse(response)
248 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
247 expected_msg = PullRequestModel.MERGE_STATUS_MESSAGES[
249 MergeFailureReason.MISSING_TARGET_REF]
248 MergeFailureReason.MISSING_TARGET_REF]
250 assertr.element_contains(
249 assertr.element_contains(
251 'span[data-role="merge-message"]', str(expected_msg))
250 'span[data-role="merge-message"]', str(expected_msg))
252
251
253 def test_comment_and_close_pull_request_custom_message_approved(
252 def test_comment_and_close_pull_request_custom_message_approved(
254 self, pr_util, csrf_token, xhr_header):
253 self, pr_util, csrf_token, xhr_header):
255
254
256 pull_request = pr_util.create_pull_request(approved=True)
255 pull_request = pr_util.create_pull_request(approved=True)
257 pull_request_id = pull_request.pull_request_id
256 pull_request_id = pull_request.pull_request_id
258 author = pull_request.user_id
257 author = pull_request.user_id
259 repo = pull_request.target_repo.repo_id
258 repo = pull_request.target_repo.repo_id
260
259
261 self.app.post(
260 self.app.post(
262 url(controller='pullrequests',
261 url(controller='pullrequests',
263 action='comment',
262 action='comment',
264 repo_name=pull_request.target_repo.scm_instance().name,
263 repo_name=pull_request.target_repo.scm_instance().name,
265 pull_request_id=str(pull_request_id)),
264 pull_request_id=str(pull_request_id)),
266 params={
265 params={
267 'close_pull_request': '1',
266 'close_pull_request': '1',
268 'text': 'Closing a PR',
267 'text': 'Closing a PR',
269 'csrf_token': csrf_token},
268 'csrf_token': csrf_token},
270 extra_environ=xhr_header,)
269 extra_environ=xhr_header,)
271
270
272 journal = UserLog.query()\
271 journal = UserLog.query()\
273 .filter(UserLog.user_id == author)\
272 .filter(UserLog.user_id == author)\
274 .filter(UserLog.repository_id == repo) \
273 .filter(UserLog.repository_id == repo) \
275 .order_by('user_log_id') \
274 .order_by('user_log_id') \
276 .all()
275 .all()
277 assert journal[-1].action == 'repo.pull_request.close'
276 assert journal[-1].action == 'repo.pull_request.close'
278
277
279 pull_request = PullRequest.get(pull_request_id)
278 pull_request = PullRequest.get(pull_request_id)
280 assert pull_request.is_closed()
279 assert pull_request.is_closed()
281
280
282 status = ChangesetStatusModel().get_status(
281 status = ChangesetStatusModel().get_status(
283 pull_request.source_repo, pull_request=pull_request)
282 pull_request.source_repo, pull_request=pull_request)
284 assert status == ChangesetStatus.STATUS_APPROVED
283 assert status == ChangesetStatus.STATUS_APPROVED
285 comments = ChangesetComment().query() \
284 comments = ChangesetComment().query() \
286 .filter(ChangesetComment.pull_request == pull_request) \
285 .filter(ChangesetComment.pull_request == pull_request) \
287 .order_by(ChangesetComment.comment_id.asc())\
286 .order_by(ChangesetComment.comment_id.asc())\
288 .all()
287 .all()
289 assert comments[-1].text == 'Closing a PR'
288 assert comments[-1].text == 'Closing a PR'
290
289
291 def test_comment_force_close_pull_request_rejected(
290 def test_comment_force_close_pull_request_rejected(
292 self, pr_util, csrf_token, xhr_header):
291 self, pr_util, csrf_token, xhr_header):
293 pull_request = pr_util.create_pull_request()
292 pull_request = pr_util.create_pull_request()
294 pull_request_id = pull_request.pull_request_id
293 pull_request_id = pull_request.pull_request_id
295 PullRequestModel().update_reviewers(
294 PullRequestModel().update_reviewers(
296 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
295 pull_request_id, [(1, ['reason'], False), (2, ['reason2'], False)],
297 pull_request.author)
296 pull_request.author)
298 author = pull_request.user_id
297 author = pull_request.user_id
299 repo = pull_request.target_repo.repo_id
298 repo = pull_request.target_repo.repo_id
300
299
301 self.app.post(
300 self.app.post(
302 url(controller='pullrequests',
301 url(controller='pullrequests',
303 action='comment',
302 action='comment',
304 repo_name=pull_request.target_repo.scm_instance().name,
303 repo_name=pull_request.target_repo.scm_instance().name,
305 pull_request_id=str(pull_request_id)),
304 pull_request_id=str(pull_request_id)),
306 params={
305 params={
307 'close_pull_request': '1',
306 'close_pull_request': '1',
308 'csrf_token': csrf_token},
307 'csrf_token': csrf_token},
309 extra_environ=xhr_header)
308 extra_environ=xhr_header)
310
309
311 pull_request = PullRequest.get(pull_request_id)
310 pull_request = PullRequest.get(pull_request_id)
312
311
313 journal = UserLog.query()\
312 journal = UserLog.query()\
314 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
313 .filter(UserLog.user_id == author, UserLog.repository_id == repo) \
315 .order_by('user_log_id') \
314 .order_by('user_log_id') \
316 .all()
315 .all()
317 assert journal[-1].action == 'repo.pull_request.close'
316 assert journal[-1].action == 'repo.pull_request.close'
318
317
319 # check only the latest status, not the review status
318 # check only the latest status, not the review status
320 status = ChangesetStatusModel().get_status(
319 status = ChangesetStatusModel().get_status(
321 pull_request.source_repo, pull_request=pull_request)
320 pull_request.source_repo, pull_request=pull_request)
322 assert status == ChangesetStatus.STATUS_REJECTED
321 assert status == ChangesetStatus.STATUS_REJECTED
323
322
324 def test_comment_and_close_pull_request(
323 def test_comment_and_close_pull_request(
325 self, pr_util, csrf_token, xhr_header):
324 self, pr_util, csrf_token, xhr_header):
326 pull_request = pr_util.create_pull_request()
325 pull_request = pr_util.create_pull_request()
327 pull_request_id = pull_request.pull_request_id
326 pull_request_id = pull_request.pull_request_id
328
327
329 response = self.app.post(
328 response = self.app.post(
330 url(controller='pullrequests',
329 url(controller='pullrequests',
331 action='comment',
330 action='comment',
332 repo_name=pull_request.target_repo.scm_instance().name,
331 repo_name=pull_request.target_repo.scm_instance().name,
333 pull_request_id=str(pull_request.pull_request_id)),
332 pull_request_id=str(pull_request.pull_request_id)),
334 params={
333 params={
335 'close_pull_request': 'true',
334 'close_pull_request': 'true',
336 'csrf_token': csrf_token},
335 'csrf_token': csrf_token},
337 extra_environ=xhr_header)
336 extra_environ=xhr_header)
338
337
339 assert response.json
338 assert response.json
340
339
341 pull_request = PullRequest.get(pull_request_id)
340 pull_request = PullRequest.get(pull_request_id)
342 assert pull_request.is_closed()
341 assert pull_request.is_closed()
343
342
344 # check only the latest status, not the review status
343 # check only the latest status, not the review status
345 status = ChangesetStatusModel().get_status(
344 status = ChangesetStatusModel().get_status(
346 pull_request.source_repo, pull_request=pull_request)
345 pull_request.source_repo, pull_request=pull_request)
347 assert status == ChangesetStatus.STATUS_REJECTED
346 assert status == ChangesetStatus.STATUS_REJECTED
348
347
349 def test_create_pull_request(self, backend, csrf_token):
348 def test_create_pull_request(self, backend, csrf_token):
350 commits = [
349 commits = [
351 {'message': 'ancestor'},
350 {'message': 'ancestor'},
352 {'message': 'change'},
351 {'message': 'change'},
353 {'message': 'change2'},
352 {'message': 'change2'},
354 ]
353 ]
355 commit_ids = backend.create_master_repo(commits)
354 commit_ids = backend.create_master_repo(commits)
356 target = backend.create_repo(heads=['ancestor'])
355 target = backend.create_repo(heads=['ancestor'])
357 source = backend.create_repo(heads=['change2'])
356 source = backend.create_repo(heads=['change2'])
358
357
359 response = self.app.post(
358 response = self.app.post(
360 url(
359 url(
361 controller='pullrequests',
360 controller='pullrequests',
362 action='create',
361 action='create',
363 repo_name=source.repo_name
362 repo_name=source.repo_name
364 ),
363 ),
365 [
364 [
366 ('source_repo', source.repo_name),
365 ('source_repo', source.repo_name),
367 ('source_ref', 'branch:default:' + commit_ids['change2']),
366 ('source_ref', 'branch:default:' + commit_ids['change2']),
368 ('target_repo', target.repo_name),
367 ('target_repo', target.repo_name),
369 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
368 ('target_ref', 'branch:default:' + commit_ids['ancestor']),
370 ('common_ancestor', commit_ids['ancestor']),
369 ('common_ancestor', commit_ids['ancestor']),
371 ('pullrequest_desc', 'Description'),
370 ('pullrequest_desc', 'Description'),
372 ('pullrequest_title', 'Title'),
371 ('pullrequest_title', 'Title'),
373 ('__start__', 'review_members:sequence'),
372 ('__start__', 'review_members:sequence'),
374 ('__start__', 'reviewer:mapping'),
373 ('__start__', 'reviewer:mapping'),
375 ('user_id', '1'),
374 ('user_id', '1'),
376 ('__start__', 'reasons:sequence'),
375 ('__start__', 'reasons:sequence'),
377 ('reason', 'Some reason'),
376 ('reason', 'Some reason'),
378 ('__end__', 'reasons:sequence'),
377 ('__end__', 'reasons:sequence'),
379 ('mandatory', 'False'),
378 ('mandatory', 'False'),
380 ('__end__', 'reviewer:mapping'),
379 ('__end__', 'reviewer:mapping'),
381 ('__end__', 'review_members:sequence'),
380 ('__end__', 'review_members:sequence'),
382 ('__start__', 'revisions:sequence'),
381 ('__start__', 'revisions:sequence'),
383 ('revisions', commit_ids['change']),
382 ('revisions', commit_ids['change']),
384 ('revisions', commit_ids['change2']),
383 ('revisions', commit_ids['change2']),
385 ('__end__', 'revisions:sequence'),
384 ('__end__', 'revisions:sequence'),
386 ('user', ''),
385 ('user', ''),
387 ('csrf_token', csrf_token),
386 ('csrf_token', csrf_token),
388 ],
387 ],
389 status=302)
388 status=302)
390
389
391 location = response.headers['Location']
390 location = response.headers['Location']
392 pull_request_id = location.rsplit('/', 1)[1]
391 pull_request_id = location.rsplit('/', 1)[1]
393 assert pull_request_id != 'new'
392 assert pull_request_id != 'new'
394 pull_request = PullRequest.get(int(pull_request_id))
393 pull_request = PullRequest.get(int(pull_request_id))
395
394
396 # check that we have now both revisions
395 # check that we have now both revisions
397 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
396 assert pull_request.revisions == [commit_ids['change2'], commit_ids['change']]
398 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
397 assert pull_request.source_ref == 'branch:default:' + commit_ids['change2']
399 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
398 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
400 assert pull_request.target_ref == expected_target_ref
399 assert pull_request.target_ref == expected_target_ref
401
400
402 def test_reviewer_notifications(self, backend, csrf_token):
401 def test_reviewer_notifications(self, backend, csrf_token):
403 # We have to use the app.post for this test so it will create the
402 # We have to use the app.post for this test so it will create the
404 # notifications properly with the new PR
403 # notifications properly with the new PR
405 commits = [
404 commits = [
406 {'message': 'ancestor',
405 {'message': 'ancestor',
407 'added': [FileNode('file_A', content='content_of_ancestor')]},
406 'added': [FileNode('file_A', content='content_of_ancestor')]},
408 {'message': 'change',
407 {'message': 'change',
409 'added': [FileNode('file_a', content='content_of_change')]},
408 'added': [FileNode('file_a', content='content_of_change')]},
410 {'message': 'change-child'},
409 {'message': 'change-child'},
411 {'message': 'ancestor-child', 'parents': ['ancestor'],
410 {'message': 'ancestor-child', 'parents': ['ancestor'],
412 'added': [
411 'added': [
413 FileNode('file_B', content='content_of_ancestor_child')]},
412 FileNode('file_B', content='content_of_ancestor_child')]},
414 {'message': 'ancestor-child-2'},
413 {'message': 'ancestor-child-2'},
415 ]
414 ]
416 commit_ids = backend.create_master_repo(commits)
415 commit_ids = backend.create_master_repo(commits)
417 target = backend.create_repo(heads=['ancestor-child'])
416 target = backend.create_repo(heads=['ancestor-child'])
418 source = backend.create_repo(heads=['change'])
417 source = backend.create_repo(heads=['change'])
419
418
420 response = self.app.post(
419 response = self.app.post(
421 url(
420 url(
422 controller='pullrequests',
421 controller='pullrequests',
423 action='create',
422 action='create',
424 repo_name=source.repo_name
423 repo_name=source.repo_name
425 ),
424 ),
426 [
425 [
427 ('source_repo', source.repo_name),
426 ('source_repo', source.repo_name),
428 ('source_ref', 'branch:default:' + commit_ids['change']),
427 ('source_ref', 'branch:default:' + commit_ids['change']),
429 ('target_repo', target.repo_name),
428 ('target_repo', target.repo_name),
430 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
429 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
431 ('common_ancestor', commit_ids['ancestor']),
430 ('common_ancestor', commit_ids['ancestor']),
432 ('pullrequest_desc', 'Description'),
431 ('pullrequest_desc', 'Description'),
433 ('pullrequest_title', 'Title'),
432 ('pullrequest_title', 'Title'),
434 ('__start__', 'review_members:sequence'),
433 ('__start__', 'review_members:sequence'),
435 ('__start__', 'reviewer:mapping'),
434 ('__start__', 'reviewer:mapping'),
436 ('user_id', '2'),
435 ('user_id', '2'),
437 ('__start__', 'reasons:sequence'),
436 ('__start__', 'reasons:sequence'),
438 ('reason', 'Some reason'),
437 ('reason', 'Some reason'),
439 ('__end__', 'reasons:sequence'),
438 ('__end__', 'reasons:sequence'),
440 ('mandatory', 'False'),
439 ('mandatory', 'False'),
441 ('__end__', 'reviewer:mapping'),
440 ('__end__', 'reviewer:mapping'),
442 ('__end__', 'review_members:sequence'),
441 ('__end__', 'review_members:sequence'),
443 ('__start__', 'revisions:sequence'),
442 ('__start__', 'revisions:sequence'),
444 ('revisions', commit_ids['change']),
443 ('revisions', commit_ids['change']),
445 ('__end__', 'revisions:sequence'),
444 ('__end__', 'revisions:sequence'),
446 ('user', ''),
445 ('user', ''),
447 ('csrf_token', csrf_token),
446 ('csrf_token', csrf_token),
448 ],
447 ],
449 status=302)
448 status=302)
450
449
451 location = response.headers['Location']
450 location = response.headers['Location']
452
451
453 pull_request_id = location.rsplit('/', 1)[1]
452 pull_request_id = location.rsplit('/', 1)[1]
454 assert pull_request_id != 'new'
453 assert pull_request_id != 'new'
455 pull_request = PullRequest.get(int(pull_request_id))
454 pull_request = PullRequest.get(int(pull_request_id))
456
455
457 # Check that a notification was made
456 # Check that a notification was made
458 notifications = Notification.query()\
457 notifications = Notification.query()\
459 .filter(Notification.created_by == pull_request.author.user_id,
458 .filter(Notification.created_by == pull_request.author.user_id,
460 Notification.type_ == Notification.TYPE_PULL_REQUEST,
459 Notification.type_ == Notification.TYPE_PULL_REQUEST,
461 Notification.subject.contains(
460 Notification.subject.contains(
462 "wants you to review pull request #%s" % pull_request_id))
461 "wants you to review pull request #%s" % pull_request_id))
463 assert len(notifications.all()) == 1
462 assert len(notifications.all()) == 1
464
463
465 # Change reviewers and check that a notification was made
464 # Change reviewers and check that a notification was made
466 PullRequestModel().update_reviewers(
465 PullRequestModel().update_reviewers(
467 pull_request.pull_request_id, [(1, [], False)],
466 pull_request.pull_request_id, [(1, [], False)],
468 pull_request.author)
467 pull_request.author)
469 assert len(notifications.all()) == 2
468 assert len(notifications.all()) == 2
470
469
471 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
470 def test_create_pull_request_stores_ancestor_commit_id(self, backend,
472 csrf_token):
471 csrf_token):
473 commits = [
472 commits = [
474 {'message': 'ancestor',
473 {'message': 'ancestor',
475 'added': [FileNode('file_A', content='content_of_ancestor')]},
474 'added': [FileNode('file_A', content='content_of_ancestor')]},
476 {'message': 'change',
475 {'message': 'change',
477 'added': [FileNode('file_a', content='content_of_change')]},
476 'added': [FileNode('file_a', content='content_of_change')]},
478 {'message': 'change-child'},
477 {'message': 'change-child'},
479 {'message': 'ancestor-child', 'parents': ['ancestor'],
478 {'message': 'ancestor-child', 'parents': ['ancestor'],
480 'added': [
479 'added': [
481 FileNode('file_B', content='content_of_ancestor_child')]},
480 FileNode('file_B', content='content_of_ancestor_child')]},
482 {'message': 'ancestor-child-2'},
481 {'message': 'ancestor-child-2'},
483 ]
482 ]
484 commit_ids = backend.create_master_repo(commits)
483 commit_ids = backend.create_master_repo(commits)
485 target = backend.create_repo(heads=['ancestor-child'])
484 target = backend.create_repo(heads=['ancestor-child'])
486 source = backend.create_repo(heads=['change'])
485 source = backend.create_repo(heads=['change'])
487
486
488 response = self.app.post(
487 response = self.app.post(
489 url(
488 url(
490 controller='pullrequests',
489 controller='pullrequests',
491 action='create',
490 action='create',
492 repo_name=source.repo_name
491 repo_name=source.repo_name
493 ),
492 ),
494 [
493 [
495 ('source_repo', source.repo_name),
494 ('source_repo', source.repo_name),
496 ('source_ref', 'branch:default:' + commit_ids['change']),
495 ('source_ref', 'branch:default:' + commit_ids['change']),
497 ('target_repo', target.repo_name),
496 ('target_repo', target.repo_name),
498 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
497 ('target_ref', 'branch:default:' + commit_ids['ancestor-child']),
499 ('common_ancestor', commit_ids['ancestor']),
498 ('common_ancestor', commit_ids['ancestor']),
500 ('pullrequest_desc', 'Description'),
499 ('pullrequest_desc', 'Description'),
501 ('pullrequest_title', 'Title'),
500 ('pullrequest_title', 'Title'),
502 ('__start__', 'review_members:sequence'),
501 ('__start__', 'review_members:sequence'),
503 ('__start__', 'reviewer:mapping'),
502 ('__start__', 'reviewer:mapping'),
504 ('user_id', '1'),
503 ('user_id', '1'),
505 ('__start__', 'reasons:sequence'),
504 ('__start__', 'reasons:sequence'),
506 ('reason', 'Some reason'),
505 ('reason', 'Some reason'),
507 ('__end__', 'reasons:sequence'),
506 ('__end__', 'reasons:sequence'),
508 ('mandatory', 'False'),
507 ('mandatory', 'False'),
509 ('__end__', 'reviewer:mapping'),
508 ('__end__', 'reviewer:mapping'),
510 ('__end__', 'review_members:sequence'),
509 ('__end__', 'review_members:sequence'),
511 ('__start__', 'revisions:sequence'),
510 ('__start__', 'revisions:sequence'),
512 ('revisions', commit_ids['change']),
511 ('revisions', commit_ids['change']),
513 ('__end__', 'revisions:sequence'),
512 ('__end__', 'revisions:sequence'),
514 ('user', ''),
513 ('user', ''),
515 ('csrf_token', csrf_token),
514 ('csrf_token', csrf_token),
516 ],
515 ],
517 status=302)
516 status=302)
518
517
519 location = response.headers['Location']
518 location = response.headers['Location']
520
519
521 pull_request_id = location.rsplit('/', 1)[1]
520 pull_request_id = location.rsplit('/', 1)[1]
522 assert pull_request_id != 'new'
521 assert pull_request_id != 'new'
523 pull_request = PullRequest.get(int(pull_request_id))
522 pull_request = PullRequest.get(int(pull_request_id))
524
523
525 # target_ref has to point to the ancestor's commit_id in order to
524 # target_ref has to point to the ancestor's commit_id in order to
526 # show the correct diff
525 # show the correct diff
527 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
526 expected_target_ref = 'branch:default:' + commit_ids['ancestor']
528 assert pull_request.target_ref == expected_target_ref
527 assert pull_request.target_ref == expected_target_ref
529
528
530 # Check generated diff contents
529 # Check generated diff contents
531 response = response.follow()
530 response = response.follow()
532 assert 'content_of_ancestor' not in response.body
531 assert 'content_of_ancestor' not in response.body
533 assert 'content_of_ancestor-child' not in response.body
532 assert 'content_of_ancestor-child' not in response.body
534 assert 'content_of_change' in response.body
533 assert 'content_of_change' in response.body
535
534
536 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
535 def test_merge_pull_request_enabled(self, pr_util, csrf_token):
537 # Clear any previous calls to rcextensions
536 # Clear any previous calls to rcextensions
538 rhodecode.EXTENSIONS.calls.clear()
537 rhodecode.EXTENSIONS.calls.clear()
539
538
540 pull_request = pr_util.create_pull_request(
539 pull_request = pr_util.create_pull_request(
541 approved=True, mergeable=True)
540 approved=True, mergeable=True)
542 pull_request_id = pull_request.pull_request_id
541 pull_request_id = pull_request.pull_request_id
543 repo_name = pull_request.target_repo.scm_instance().name,
542 repo_name = pull_request.target_repo.scm_instance().name,
544
543
545 response = self.app.post(
544 response = self.app.post(
546 url(controller='pullrequests',
545 url(controller='pullrequests',
547 action='merge',
546 action='merge',
548 repo_name=str(repo_name[0]),
547 repo_name=str(repo_name[0]),
549 pull_request_id=str(pull_request_id)),
548 pull_request_id=str(pull_request_id)),
550 params={'csrf_token': csrf_token}).follow()
549 params={'csrf_token': csrf_token}).follow()
551
550
552 pull_request = PullRequest.get(pull_request_id)
551 pull_request = PullRequest.get(pull_request_id)
553
552
554 assert response.status_int == 200
553 assert response.status_int == 200
555 assert pull_request.is_closed()
554 assert pull_request.is_closed()
556 assert_pull_request_status(
555 assert_pull_request_status(
557 pull_request, ChangesetStatus.STATUS_APPROVED)
556 pull_request, ChangesetStatus.STATUS_APPROVED)
558
557
559 # Check the relevant log entries were added
558 # Check the relevant log entries were added
560 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
559 user_logs = UserLog.query().order_by('-user_log_id').limit(3)
561 actions = [log.action for log in user_logs]
560 actions = [log.action for log in user_logs]
562 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
561 pr_commit_ids = PullRequestModel()._get_commit_ids(pull_request)
563 expected_actions = [
562 expected_actions = [
564 u'repo.pull_request.close',
563 u'repo.pull_request.close',
565 u'repo.pull_request.merge',
564 u'repo.pull_request.merge',
566 u'repo.pull_request.comment.create'
565 u'repo.pull_request.comment.create'
567 ]
566 ]
568 assert actions == expected_actions
567 assert actions == expected_actions
569
568
570 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
569 user_logs = UserLog.query().order_by('-user_log_id').limit(4)
571 actions = [log for log in user_logs]
570 actions = [log for log in user_logs]
572 assert actions[-1].action == 'user.push'
571 assert actions[-1].action == 'user.push'
573 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
572 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
574
573
575 # Check post_push rcextension was really executed
574 # Check post_push rcextension was really executed
576 push_calls = rhodecode.EXTENSIONS.calls['post_push']
575 push_calls = rhodecode.EXTENSIONS.calls['post_push']
577 assert len(push_calls) == 1
576 assert len(push_calls) == 1
578 unused_last_call_args, last_call_kwargs = push_calls[0]
577 unused_last_call_args, last_call_kwargs = push_calls[0]
579 assert last_call_kwargs['action'] == 'push'
578 assert last_call_kwargs['action'] == 'push'
580 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
579 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
581
580
582 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
581 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
583 pull_request = pr_util.create_pull_request(mergeable=False)
582 pull_request = pr_util.create_pull_request(mergeable=False)
584 pull_request_id = pull_request.pull_request_id
583 pull_request_id = pull_request.pull_request_id
585 pull_request = PullRequest.get(pull_request_id)
584 pull_request = PullRequest.get(pull_request_id)
586
585
587 response = self.app.post(
586 response = self.app.post(
588 url(controller='pullrequests',
587 url(controller='pullrequests',
589 action='merge',
588 action='merge',
590 repo_name=pull_request.target_repo.scm_instance().name,
589 repo_name=pull_request.target_repo.scm_instance().name,
591 pull_request_id=str(pull_request.pull_request_id)),
590 pull_request_id=str(pull_request.pull_request_id)),
592 params={'csrf_token': csrf_token}).follow()
591 params={'csrf_token': csrf_token}).follow()
593
592
594 assert response.status_int == 200
593 assert response.status_int == 200
595 response.mustcontain(
594 response.mustcontain(
596 'Merge is not currently possible because of below failed checks.')
595 'Merge is not currently possible because of below failed checks.')
597 response.mustcontain('Server-side pull request merging is disabled.')
596 response.mustcontain('Server-side pull request merging is disabled.')
598
597
599 @pytest.mark.skip_backends('svn')
598 @pytest.mark.skip_backends('svn')
600 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
599 def test_merge_pull_request_not_approved(self, pr_util, csrf_token):
601 pull_request = pr_util.create_pull_request(mergeable=True)
600 pull_request = pr_util.create_pull_request(mergeable=True)
602 pull_request_id = pull_request.pull_request_id
601 pull_request_id = pull_request.pull_request_id
603 repo_name = pull_request.target_repo.scm_instance().name,
602 repo_name = pull_request.target_repo.scm_instance().name,
604
603
605 response = self.app.post(
604 response = self.app.post(
606 url(controller='pullrequests',
605 url(controller='pullrequests',
607 action='merge',
606 action='merge',
608 repo_name=str(repo_name[0]),
607 repo_name=str(repo_name[0]),
609 pull_request_id=str(pull_request_id)),
608 pull_request_id=str(pull_request_id)),
610 params={'csrf_token': csrf_token}).follow()
609 params={'csrf_token': csrf_token}).follow()
611
610
612 assert response.status_int == 200
611 assert response.status_int == 200
613
612
614 response.mustcontain(
613 response.mustcontain(
615 'Merge is not currently possible because of below failed checks.')
614 'Merge is not currently possible because of below failed checks.')
616 response.mustcontain('Pull request reviewer approval is pending.')
615 response.mustcontain('Pull request reviewer approval is pending.')
617
616
618 def test_update_source_revision(self, backend, csrf_token):
617 def test_update_source_revision(self, backend, csrf_token):
619 commits = [
618 commits = [
620 {'message': 'ancestor'},
619 {'message': 'ancestor'},
621 {'message': 'change'},
620 {'message': 'change'},
622 {'message': 'change-2'},
621 {'message': 'change-2'},
623 ]
622 ]
624 commit_ids = backend.create_master_repo(commits)
623 commit_ids = backend.create_master_repo(commits)
625 target = backend.create_repo(heads=['ancestor'])
624 target = backend.create_repo(heads=['ancestor'])
626 source = backend.create_repo(heads=['change'])
625 source = backend.create_repo(heads=['change'])
627
626
628 # create pr from a in source to A in target
627 # create pr from a in source to A in target
629 pull_request = PullRequest()
628 pull_request = PullRequest()
630 pull_request.source_repo = source
629 pull_request.source_repo = source
631 # TODO: johbo: Make sure that we write the source ref this way!
630 # TODO: johbo: Make sure that we write the source ref this way!
632 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
631 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
633 branch=backend.default_branch_name, commit_id=commit_ids['change'])
632 branch=backend.default_branch_name, commit_id=commit_ids['change'])
634 pull_request.target_repo = target
633 pull_request.target_repo = target
635
634
636 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
635 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
637 branch=backend.default_branch_name,
636 branch=backend.default_branch_name,
638 commit_id=commit_ids['ancestor'])
637 commit_id=commit_ids['ancestor'])
639 pull_request.revisions = [commit_ids['change']]
638 pull_request.revisions = [commit_ids['change']]
640 pull_request.title = u"Test"
639 pull_request.title = u"Test"
641 pull_request.description = u"Description"
640 pull_request.description = u"Description"
642 pull_request.author = UserModel().get_by_username(
641 pull_request.author = UserModel().get_by_username(
643 TEST_USER_ADMIN_LOGIN)
642 TEST_USER_ADMIN_LOGIN)
644 Session().add(pull_request)
643 Session().add(pull_request)
645 Session().commit()
644 Session().commit()
646 pull_request_id = pull_request.pull_request_id
645 pull_request_id = pull_request.pull_request_id
647
646
648 # source has ancestor - change - change-2
647 # source has ancestor - change - change-2
649 backend.pull_heads(source, heads=['change-2'])
648 backend.pull_heads(source, heads=['change-2'])
650
649
651 # update PR
650 # update PR
652 self.app.post(
651 self.app.post(
653 url(controller='pullrequests', action='update',
652 url(controller='pullrequests', action='update',
654 repo_name=target.repo_name,
653 repo_name=target.repo_name,
655 pull_request_id=str(pull_request_id)),
654 pull_request_id=str(pull_request_id)),
656 params={'update_commits': 'true', '_method': 'put',
655 params={'update_commits': 'true', '_method': 'put',
657 'csrf_token': csrf_token})
656 'csrf_token': csrf_token})
658
657
659 # check that we have now both revisions
658 # check that we have now both revisions
660 pull_request = PullRequest.get(pull_request_id)
659 pull_request = PullRequest.get(pull_request_id)
661 assert pull_request.revisions == [
660 assert pull_request.revisions == [
662 commit_ids['change-2'], commit_ids['change']]
661 commit_ids['change-2'], commit_ids['change']]
663
662
664 # TODO: johbo: this should be a test on its own
663 # TODO: johbo: this should be a test on its own
665 response = self.app.get(url(
664 response = self.app.get(url(
666 controller='pullrequests', action='index',
665 controller='pullrequests', action='index',
667 repo_name=target.repo_name))
666 repo_name=target.repo_name))
668 assert response.status_int == 200
667 assert response.status_int == 200
669 assert 'Pull request updated to' in response.body
668 assert 'Pull request updated to' in response.body
670 assert 'with 1 added, 0 removed commits.' in response.body
669 assert 'with 1 added, 0 removed commits.' in response.body
671
670
672 def test_update_target_revision(self, backend, csrf_token):
671 def test_update_target_revision(self, backend, csrf_token):
673 commits = [
672 commits = [
674 {'message': 'ancestor'},
673 {'message': 'ancestor'},
675 {'message': 'change'},
674 {'message': 'change'},
676 {'message': 'ancestor-new', 'parents': ['ancestor']},
675 {'message': 'ancestor-new', 'parents': ['ancestor']},
677 {'message': 'change-rebased'},
676 {'message': 'change-rebased'},
678 ]
677 ]
679 commit_ids = backend.create_master_repo(commits)
678 commit_ids = backend.create_master_repo(commits)
680 target = backend.create_repo(heads=['ancestor'])
679 target = backend.create_repo(heads=['ancestor'])
681 source = backend.create_repo(heads=['change'])
680 source = backend.create_repo(heads=['change'])
682
681
683 # create pr from a in source to A in target
682 # create pr from a in source to A in target
684 pull_request = PullRequest()
683 pull_request = PullRequest()
685 pull_request.source_repo = source
684 pull_request.source_repo = source
686 # TODO: johbo: Make sure that we write the source ref this way!
685 # TODO: johbo: Make sure that we write the source ref this way!
687 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
686 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
688 branch=backend.default_branch_name, commit_id=commit_ids['change'])
687 branch=backend.default_branch_name, commit_id=commit_ids['change'])
689 pull_request.target_repo = target
688 pull_request.target_repo = target
690 # TODO: johbo: Target ref should be branch based, since tip can jump
689 # TODO: johbo: Target ref should be branch based, since tip can jump
691 # from branch to branch
690 # from branch to branch
692 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
691 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
693 branch=backend.default_branch_name,
692 branch=backend.default_branch_name,
694 commit_id=commit_ids['ancestor'])
693 commit_id=commit_ids['ancestor'])
695 pull_request.revisions = [commit_ids['change']]
694 pull_request.revisions = [commit_ids['change']]
696 pull_request.title = u"Test"
695 pull_request.title = u"Test"
697 pull_request.description = u"Description"
696 pull_request.description = u"Description"
698 pull_request.author = UserModel().get_by_username(
697 pull_request.author = UserModel().get_by_username(
699 TEST_USER_ADMIN_LOGIN)
698 TEST_USER_ADMIN_LOGIN)
700 Session().add(pull_request)
699 Session().add(pull_request)
701 Session().commit()
700 Session().commit()
702 pull_request_id = pull_request.pull_request_id
701 pull_request_id = pull_request.pull_request_id
703
702
704 # target has ancestor - ancestor-new
703 # target has ancestor - ancestor-new
705 # source has ancestor - ancestor-new - change-rebased
704 # source has ancestor - ancestor-new - change-rebased
706 backend.pull_heads(target, heads=['ancestor-new'])
705 backend.pull_heads(target, heads=['ancestor-new'])
707 backend.pull_heads(source, heads=['change-rebased'])
706 backend.pull_heads(source, heads=['change-rebased'])
708
707
709 # update PR
708 # update PR
710 self.app.post(
709 self.app.post(
711 url(controller='pullrequests', action='update',
710 url(controller='pullrequests', action='update',
712 repo_name=target.repo_name,
711 repo_name=target.repo_name,
713 pull_request_id=str(pull_request_id)),
712 pull_request_id=str(pull_request_id)),
714 params={'update_commits': 'true', '_method': 'put',
713 params={'update_commits': 'true', '_method': 'put',
715 'csrf_token': csrf_token},
714 'csrf_token': csrf_token},
716 status=200)
715 status=200)
717
716
718 # check that we have now both revisions
717 # check that we have now both revisions
719 pull_request = PullRequest.get(pull_request_id)
718 pull_request = PullRequest.get(pull_request_id)
720 assert pull_request.revisions == [commit_ids['change-rebased']]
719 assert pull_request.revisions == [commit_ids['change-rebased']]
721 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
720 assert pull_request.target_ref == 'branch:{branch}:{commit_id}'.format(
722 branch=backend.default_branch_name,
721 branch=backend.default_branch_name,
723 commit_id=commit_ids['ancestor-new'])
722 commit_id=commit_ids['ancestor-new'])
724
723
725 # TODO: johbo: This should be a test on its own
724 # TODO: johbo: This should be a test on its own
726 response = self.app.get(url(
725 response = self.app.get(url(
727 controller='pullrequests', action='index',
726 controller='pullrequests', action='index',
728 repo_name=target.repo_name))
727 repo_name=target.repo_name))
729 assert response.status_int == 200
728 assert response.status_int == 200
730 assert 'Pull request updated to' in response.body
729 assert 'Pull request updated to' in response.body
731 assert 'with 1 added, 1 removed commits.' in response.body
730 assert 'with 1 added, 1 removed commits.' in response.body
732
731
733 def test_update_of_ancestor_reference(self, backend, csrf_token):
732 def test_update_of_ancestor_reference(self, backend, csrf_token):
734 commits = [
733 commits = [
735 {'message': 'ancestor'},
734 {'message': 'ancestor'},
736 {'message': 'change'},
735 {'message': 'change'},
737 {'message': 'change-2'},
736 {'message': 'change-2'},
738 {'message': 'ancestor-new', 'parents': ['ancestor']},
737 {'message': 'ancestor-new', 'parents': ['ancestor']},
739 {'message': 'change-rebased'},
738 {'message': 'change-rebased'},
740 ]
739 ]
741 commit_ids = backend.create_master_repo(commits)
740 commit_ids = backend.create_master_repo(commits)
742 target = backend.create_repo(heads=['ancestor'])
741 target = backend.create_repo(heads=['ancestor'])
743 source = backend.create_repo(heads=['change'])
742 source = backend.create_repo(heads=['change'])
744
743
745 # create pr from a in source to A in target
744 # create pr from a in source to A in target
746 pull_request = PullRequest()
745 pull_request = PullRequest()
747 pull_request.source_repo = source
746 pull_request.source_repo = source
748 # TODO: johbo: Make sure that we write the source ref this way!
747 # TODO: johbo: Make sure that we write the source ref this way!
749 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
748 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
750 branch=backend.default_branch_name,
749 branch=backend.default_branch_name,
751 commit_id=commit_ids['change'])
750 commit_id=commit_ids['change'])
752 pull_request.target_repo = target
751 pull_request.target_repo = target
753 # TODO: johbo: Target ref should be branch based, since tip can jump
752 # TODO: johbo: Target ref should be branch based, since tip can jump
754 # from branch to branch
753 # from branch to branch
755 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
754 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
756 branch=backend.default_branch_name,
755 branch=backend.default_branch_name,
757 commit_id=commit_ids['ancestor'])
756 commit_id=commit_ids['ancestor'])
758 pull_request.revisions = [commit_ids['change']]
757 pull_request.revisions = [commit_ids['change']]
759 pull_request.title = u"Test"
758 pull_request.title = u"Test"
760 pull_request.description = u"Description"
759 pull_request.description = u"Description"
761 pull_request.author = UserModel().get_by_username(
760 pull_request.author = UserModel().get_by_username(
762 TEST_USER_ADMIN_LOGIN)
761 TEST_USER_ADMIN_LOGIN)
763 Session().add(pull_request)
762 Session().add(pull_request)
764 Session().commit()
763 Session().commit()
765 pull_request_id = pull_request.pull_request_id
764 pull_request_id = pull_request.pull_request_id
766
765
767 # target has ancestor - ancestor-new
766 # target has ancestor - ancestor-new
768 # source has ancestor - ancestor-new - change-rebased
767 # source has ancestor - ancestor-new - change-rebased
769 backend.pull_heads(target, heads=['ancestor-new'])
768 backend.pull_heads(target, heads=['ancestor-new'])
770 backend.pull_heads(source, heads=['change-rebased'])
769 backend.pull_heads(source, heads=['change-rebased'])
771
770
772 # update PR
771 # update PR
773 self.app.post(
772 self.app.post(
774 url(controller='pullrequests', action='update',
773 url(controller='pullrequests', action='update',
775 repo_name=target.repo_name,
774 repo_name=target.repo_name,
776 pull_request_id=str(pull_request_id)),
775 pull_request_id=str(pull_request_id)),
777 params={'update_commits': 'true', '_method': 'put',
776 params={'update_commits': 'true', '_method': 'put',
778 'csrf_token': csrf_token},
777 'csrf_token': csrf_token},
779 status=200)
778 status=200)
780
779
781 # Expect the target reference to be updated correctly
780 # Expect the target reference to be updated correctly
782 pull_request = PullRequest.get(pull_request_id)
781 pull_request = PullRequest.get(pull_request_id)
783 assert pull_request.revisions == [commit_ids['change-rebased']]
782 assert pull_request.revisions == [commit_ids['change-rebased']]
784 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
783 expected_target_ref = 'branch:{branch}:{commit_id}'.format(
785 branch=backend.default_branch_name,
784 branch=backend.default_branch_name,
786 commit_id=commit_ids['ancestor-new'])
785 commit_id=commit_ids['ancestor-new'])
787 assert pull_request.target_ref == expected_target_ref
786 assert pull_request.target_ref == expected_target_ref
788
787
789 def test_remove_pull_request_branch(self, backend_git, csrf_token):
788 def test_remove_pull_request_branch(self, backend_git, csrf_token):
790 branch_name = 'development'
789 branch_name = 'development'
791 commits = [
790 commits = [
792 {'message': 'initial-commit'},
791 {'message': 'initial-commit'},
793 {'message': 'old-feature'},
792 {'message': 'old-feature'},
794 {'message': 'new-feature', 'branch': branch_name},
793 {'message': 'new-feature', 'branch': branch_name},
795 ]
794 ]
796 repo = backend_git.create_repo(commits)
795 repo = backend_git.create_repo(commits)
797 commit_ids = backend_git.commit_ids
796 commit_ids = backend_git.commit_ids
798
797
799 pull_request = PullRequest()
798 pull_request = PullRequest()
800 pull_request.source_repo = repo
799 pull_request.source_repo = repo
801 pull_request.target_repo = repo
800 pull_request.target_repo = repo
802 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
801 pull_request.source_ref = 'branch:{branch}:{commit_id}'.format(
803 branch=branch_name, commit_id=commit_ids['new-feature'])
802 branch=branch_name, commit_id=commit_ids['new-feature'])
804 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
803 pull_request.target_ref = 'branch:{branch}:{commit_id}'.format(
805 branch=backend_git.default_branch_name,
804 branch=backend_git.default_branch_name,
806 commit_id=commit_ids['old-feature'])
805 commit_id=commit_ids['old-feature'])
807 pull_request.revisions = [commit_ids['new-feature']]
806 pull_request.revisions = [commit_ids['new-feature']]
808 pull_request.title = u"Test"
807 pull_request.title = u"Test"
809 pull_request.description = u"Description"
808 pull_request.description = u"Description"
810 pull_request.author = UserModel().get_by_username(
809 pull_request.author = UserModel().get_by_username(
811 TEST_USER_ADMIN_LOGIN)
810 TEST_USER_ADMIN_LOGIN)
812 Session().add(pull_request)
811 Session().add(pull_request)
813 Session().commit()
812 Session().commit()
814
813
815 vcs = repo.scm_instance()
814 vcs = repo.scm_instance()
816 vcs.remove_ref('refs/heads/{}'.format(branch_name))
815 vcs.remove_ref('refs/heads/{}'.format(branch_name))
817
816
818 response = self.app.get(url(
817 response = self.app.get(url(
819 controller='pullrequests', action='show',
818 controller='pullrequests', action='show',
820 repo_name=repo.repo_name,
819 repo_name=repo.repo_name,
821 pull_request_id=str(pull_request.pull_request_id)))
820 pull_request_id=str(pull_request.pull_request_id)))
822
821
823 assert response.status_int == 200
822 assert response.status_int == 200
824 assert_response = AssertResponse(response)
823 assert_response = AssertResponse(response)
825 assert_response.element_contains(
824 assert_response.element_contains(
826 '#changeset_compare_view_content .alert strong',
825 '#changeset_compare_view_content .alert strong',
827 'Missing commits')
826 'Missing commits')
828 assert_response.element_contains(
827 assert_response.element_contains(
829 '#changeset_compare_view_content .alert',
828 '#changeset_compare_view_content .alert',
830 'This pull request cannot be displayed, because one or more'
829 'This pull request cannot be displayed, because one or more'
831 ' commits no longer exist in the source repository.')
830 ' commits no longer exist in the source repository.')
832
831
833 def test_strip_commits_from_pull_request(
832 def test_strip_commits_from_pull_request(
834 self, backend, pr_util, csrf_token):
833 self, backend, pr_util, csrf_token):
835 commits = [
834 commits = [
836 {'message': 'initial-commit'},
835 {'message': 'initial-commit'},
837 {'message': 'old-feature'},
836 {'message': 'old-feature'},
838 {'message': 'new-feature', 'parents': ['initial-commit']},
837 {'message': 'new-feature', 'parents': ['initial-commit']},
839 ]
838 ]
840 pull_request = pr_util.create_pull_request(
839 pull_request = pr_util.create_pull_request(
841 commits, target_head='initial-commit', source_head='new-feature',
840 commits, target_head='initial-commit', source_head='new-feature',
842 revisions=['new-feature'])
841 revisions=['new-feature'])
843
842
844 vcs = pr_util.source_repository.scm_instance()
843 vcs = pr_util.source_repository.scm_instance()
845 if backend.alias == 'git':
844 if backend.alias == 'git':
846 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
845 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
847 else:
846 else:
848 vcs.strip(pr_util.commit_ids['new-feature'])
847 vcs.strip(pr_util.commit_ids['new-feature'])
849
848
850 response = self.app.get(url(
849 response = self.app.get(url(
851 controller='pullrequests', action='show',
850 controller='pullrequests', action='show',
852 repo_name=pr_util.target_repository.repo_name,
851 repo_name=pr_util.target_repository.repo_name,
853 pull_request_id=str(pull_request.pull_request_id)))
852 pull_request_id=str(pull_request.pull_request_id)))
854
853
855 assert response.status_int == 200
854 assert response.status_int == 200
856 assert_response = AssertResponse(response)
855 assert_response = AssertResponse(response)
857 assert_response.element_contains(
856 assert_response.element_contains(
858 '#changeset_compare_view_content .alert strong',
857 '#changeset_compare_view_content .alert strong',
859 'Missing commits')
858 'Missing commits')
860 assert_response.element_contains(
859 assert_response.element_contains(
861 '#changeset_compare_view_content .alert',
860 '#changeset_compare_view_content .alert',
862 'This pull request cannot be displayed, because one or more'
861 'This pull request cannot be displayed, because one or more'
863 ' commits no longer exist in the source repository.')
862 ' commits no longer exist in the source repository.')
864 assert_response.element_contains(
863 assert_response.element_contains(
865 '#update_commits',
864 '#update_commits',
866 'Update commits')
865 'Update commits')
867
866
868 def test_strip_commits_and_update(
867 def test_strip_commits_and_update(
869 self, backend, pr_util, csrf_token):
868 self, backend, pr_util, csrf_token):
870 commits = [
869 commits = [
871 {'message': 'initial-commit'},
870 {'message': 'initial-commit'},
872 {'message': 'old-feature'},
871 {'message': 'old-feature'},
873 {'message': 'new-feature', 'parents': ['old-feature']},
872 {'message': 'new-feature', 'parents': ['old-feature']},
874 ]
873 ]
875 pull_request = pr_util.create_pull_request(
874 pull_request = pr_util.create_pull_request(
876 commits, target_head='old-feature', source_head='new-feature',
875 commits, target_head='old-feature', source_head='new-feature',
877 revisions=['new-feature'], mergeable=True)
876 revisions=['new-feature'], mergeable=True)
878
877
879 vcs = pr_util.source_repository.scm_instance()
878 vcs = pr_util.source_repository.scm_instance()
880 if backend.alias == 'git':
879 if backend.alias == 'git':
881 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
880 vcs.strip(pr_util.commit_ids['new-feature'], branch_name='master')
882 else:
881 else:
883 vcs.strip(pr_util.commit_ids['new-feature'])
882 vcs.strip(pr_util.commit_ids['new-feature'])
884
883
885 response = self.app.post(
884 response = self.app.post(
886 url(controller='pullrequests', action='update',
885 url(controller='pullrequests', action='update',
887 repo_name=pull_request.target_repo.repo_name,
886 repo_name=pull_request.target_repo.repo_name,
888 pull_request_id=str(pull_request.pull_request_id)),
887 pull_request_id=str(pull_request.pull_request_id)),
889 params={'update_commits': 'true', '_method': 'put',
888 params={'update_commits': 'true', '_method': 'put',
890 'csrf_token': csrf_token})
889 'csrf_token': csrf_token})
891
890
892 assert response.status_int == 200
891 assert response.status_int == 200
893 assert response.body == 'true'
892 assert response.body == 'true'
894
893
895 # Make sure that after update, it won't raise 500 errors
894 # Make sure that after update, it won't raise 500 errors
896 response = self.app.get(url(
895 response = self.app.get(url(
897 controller='pullrequests', action='show',
896 controller='pullrequests', action='show',
898 repo_name=pr_util.target_repository.repo_name,
897 repo_name=pr_util.target_repository.repo_name,
899 pull_request_id=str(pull_request.pull_request_id)))
898 pull_request_id=str(pull_request.pull_request_id)))
900
899
901 assert response.status_int == 200
900 assert response.status_int == 200
902 assert_response = AssertResponse(response)
901 assert_response = AssertResponse(response)
903 assert_response.element_contains(
902 assert_response.element_contains(
904 '#changeset_compare_view_content .alert strong',
903 '#changeset_compare_view_content .alert strong',
905 'Missing commits')
904 'Missing commits')
906
905
907 def test_branch_is_a_link(self, pr_util):
906 def test_branch_is_a_link(self, pr_util):
908 pull_request = pr_util.create_pull_request()
907 pull_request = pr_util.create_pull_request()
909 pull_request.source_ref = 'branch:origin:1234567890abcdef'
908 pull_request.source_ref = 'branch:origin:1234567890abcdef'
910 pull_request.target_ref = 'branch:target:abcdef1234567890'
909 pull_request.target_ref = 'branch:target:abcdef1234567890'
911 Session().add(pull_request)
910 Session().add(pull_request)
912 Session().commit()
911 Session().commit()
913
912
914 response = self.app.get(url(
913 response = self.app.get(url(
915 controller='pullrequests', action='show',
914 controller='pullrequests', action='show',
916 repo_name=pull_request.target_repo.scm_instance().name,
915 repo_name=pull_request.target_repo.scm_instance().name,
917 pull_request_id=str(pull_request.pull_request_id)))
916 pull_request_id=str(pull_request.pull_request_id)))
918 assert response.status_int == 200
917 assert response.status_int == 200
919 assert_response = AssertResponse(response)
918 assert_response = AssertResponse(response)
920
919
921 origin = assert_response.get_element('.pr-origininfo .tag')
920 origin = assert_response.get_element('.pr-origininfo .tag')
922 origin_children = origin.getchildren()
921 origin_children = origin.getchildren()
923 assert len(origin_children) == 1
922 assert len(origin_children) == 1
924 target = assert_response.get_element('.pr-targetinfo .tag')
923 target = assert_response.get_element('.pr-targetinfo .tag')
925 target_children = target.getchildren()
924 target_children = target.getchildren()
926 assert len(target_children) == 1
925 assert len(target_children) == 1
927
926
928 expected_origin_link = route_path(
927 expected_origin_link = route_path(
929 'repo_changelog',
928 'repo_changelog',
930 repo_name=pull_request.source_repo.scm_instance().name,
929 repo_name=pull_request.source_repo.scm_instance().name,
931 params=dict(branch='origin'))
930 params=dict(branch='origin'))
932 expected_target_link = route_path(
931 expected_target_link = route_path(
933 'repo_changelog',
932 'repo_changelog',
934 repo_name=pull_request.target_repo.scm_instance().name,
933 repo_name=pull_request.target_repo.scm_instance().name,
935 params=dict(branch='target'))
934 params=dict(branch='target'))
936 assert origin_children[0].attrib['href'] == expected_origin_link
935 assert origin_children[0].attrib['href'] == expected_origin_link
937 assert origin_children[0].text == 'branch: origin'
936 assert origin_children[0].text == 'branch: origin'
938 assert target_children[0].attrib['href'] == expected_target_link
937 assert target_children[0].attrib['href'] == expected_target_link
939 assert target_children[0].text == 'branch: target'
938 assert target_children[0].text == 'branch: target'
940
939
941 def test_bookmark_is_not_a_link(self, pr_util):
940 def test_bookmark_is_not_a_link(self, pr_util):
942 pull_request = pr_util.create_pull_request()
941 pull_request = pr_util.create_pull_request()
943 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
942 pull_request.source_ref = 'bookmark:origin:1234567890abcdef'
944 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
943 pull_request.target_ref = 'bookmark:target:abcdef1234567890'
945 Session().add(pull_request)
944 Session().add(pull_request)
946 Session().commit()
945 Session().commit()
947
946
948 response = self.app.get(url(
947 response = self.app.get(url(
949 controller='pullrequests', action='show',
948 controller='pullrequests', action='show',
950 repo_name=pull_request.target_repo.scm_instance().name,
949 repo_name=pull_request.target_repo.scm_instance().name,
951 pull_request_id=str(pull_request.pull_request_id)))
950 pull_request_id=str(pull_request.pull_request_id)))
952 assert response.status_int == 200
951 assert response.status_int == 200
953 assert_response = AssertResponse(response)
952 assert_response = AssertResponse(response)
954
953
955 origin = assert_response.get_element('.pr-origininfo .tag')
954 origin = assert_response.get_element('.pr-origininfo .tag')
956 assert origin.text.strip() == 'bookmark: origin'
955 assert origin.text.strip() == 'bookmark: origin'
957 assert origin.getchildren() == []
956 assert origin.getchildren() == []
958
957
959 target = assert_response.get_element('.pr-targetinfo .tag')
958 target = assert_response.get_element('.pr-targetinfo .tag')
960 assert target.text.strip() == 'bookmark: target'
959 assert target.text.strip() == 'bookmark: target'
961 assert target.getchildren() == []
960 assert target.getchildren() == []
962
961
963 def test_tag_is_not_a_link(self, pr_util):
962 def test_tag_is_not_a_link(self, pr_util):
964 pull_request = pr_util.create_pull_request()
963 pull_request = pr_util.create_pull_request()
965 pull_request.source_ref = 'tag:origin:1234567890abcdef'
964 pull_request.source_ref = 'tag:origin:1234567890abcdef'
966 pull_request.target_ref = 'tag:target:abcdef1234567890'
965 pull_request.target_ref = 'tag:target:abcdef1234567890'
967 Session().add(pull_request)
966 Session().add(pull_request)
968 Session().commit()
967 Session().commit()
969
968
970 response = self.app.get(url(
969 response = self.app.get(url(
971 controller='pullrequests', action='show',
970 controller='pullrequests', action='show',
972 repo_name=pull_request.target_repo.scm_instance().name,
971 repo_name=pull_request.target_repo.scm_instance().name,
973 pull_request_id=str(pull_request.pull_request_id)))
972 pull_request_id=str(pull_request.pull_request_id)))
974 assert response.status_int == 200
973 assert response.status_int == 200
975 assert_response = AssertResponse(response)
974 assert_response = AssertResponse(response)
976
975
977 origin = assert_response.get_element('.pr-origininfo .tag')
976 origin = assert_response.get_element('.pr-origininfo .tag')
978 assert origin.text.strip() == 'tag: origin'
977 assert origin.text.strip() == 'tag: origin'
979 assert origin.getchildren() == []
978 assert origin.getchildren() == []
980
979
981 target = assert_response.get_element('.pr-targetinfo .tag')
980 target = assert_response.get_element('.pr-targetinfo .tag')
982 assert target.text.strip() == 'tag: target'
981 assert target.text.strip() == 'tag: target'
983 assert target.getchildren() == []
982 assert target.getchildren() == []
984
983
985 @pytest.mark.parametrize('mergeable', [True, False])
984 @pytest.mark.parametrize('mergeable', [True, False])
986 def test_shadow_repository_link(
985 def test_shadow_repository_link(
987 self, mergeable, pr_util, http_host_only_stub):
986 self, mergeable, pr_util, http_host_only_stub):
988 """
987 """
989 Check that the pull request summary page displays a link to the shadow
988 Check that the pull request summary page displays a link to the shadow
990 repository if the pull request is mergeable. If it is not mergeable
989 repository if the pull request is mergeable. If it is not mergeable
991 the link should not be displayed.
990 the link should not be displayed.
992 """
991 """
993 pull_request = pr_util.create_pull_request(
992 pull_request = pr_util.create_pull_request(
994 mergeable=mergeable, enable_notifications=False)
993 mergeable=mergeable, enable_notifications=False)
995 target_repo = pull_request.target_repo.scm_instance()
994 target_repo = pull_request.target_repo.scm_instance()
996 pr_id = pull_request.pull_request_id
995 pr_id = pull_request.pull_request_id
997 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
996 shadow_url = '{host}/{repo}/pull-request/{pr_id}/repository'.format(
998 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
997 host=http_host_only_stub, repo=target_repo.name, pr_id=pr_id)
999
998
1000 response = self.app.get(url(
999 response = self.app.get(url(
1001 controller='pullrequests', action='show',
1000 controller='pullrequests', action='show',
1002 repo_name=target_repo.name,
1001 repo_name=target_repo.name,
1003 pull_request_id=str(pr_id)))
1002 pull_request_id=str(pr_id)))
1004
1003
1005 assertr = AssertResponse(response)
1004 assertr = AssertResponse(response)
1006 if mergeable:
1005 if mergeable:
1007 assertr.element_value_contains(
1006 assertr.element_value_contains(
1008 'div.pr-mergeinfo input', shadow_url)
1007 'div.pr-mergeinfo input', shadow_url)
1009 assertr.element_value_contains(
1008 assertr.element_value_contains(
1010 'div.pr-mergeinfo input', 'pr-merge')
1009 'div.pr-mergeinfo input', 'pr-merge')
1011 else:
1010 else:
1012 assertr.no_element_exists('div.pr-mergeinfo')
1011 assertr.no_element_exists('div.pr-mergeinfo')
1013
1012
1014
1013
1015 @pytest.mark.usefixtures('app')
1014 @pytest.mark.usefixtures('app')
1016 @pytest.mark.backends("git", "hg")
1015 @pytest.mark.backends("git", "hg")
1017 class TestPullrequestsControllerDelete(object):
1016 class TestPullrequestsControllerDelete(object):
1018 def test_pull_request_delete_button_permissions_admin(
1017 def test_pull_request_delete_button_permissions_admin(
1019 self, autologin_user, user_admin, pr_util):
1018 self, autologin_user, user_admin, pr_util):
1020 pull_request = pr_util.create_pull_request(
1019 pull_request = pr_util.create_pull_request(
1021 author=user_admin.username, enable_notifications=False)
1020 author=user_admin.username, enable_notifications=False)
1022
1021
1023 response = self.app.get(url(
1022 response = self.app.get(url(
1024 controller='pullrequests', action='show',
1023 controller='pullrequests', action='show',
1025 repo_name=pull_request.target_repo.scm_instance().name,
1024 repo_name=pull_request.target_repo.scm_instance().name,
1026 pull_request_id=str(pull_request.pull_request_id)))
1025 pull_request_id=str(pull_request.pull_request_id)))
1027
1026
1028 response.mustcontain('id="delete_pullrequest"')
1027 response.mustcontain('id="delete_pullrequest"')
1029 response.mustcontain('Confirm to delete this pull request')
1028 response.mustcontain('Confirm to delete this pull request')
1030
1029
1031 def test_pull_request_delete_button_permissions_owner(
1030 def test_pull_request_delete_button_permissions_owner(
1032 self, autologin_regular_user, user_regular, pr_util):
1031 self, autologin_regular_user, user_regular, pr_util):
1033 pull_request = pr_util.create_pull_request(
1032 pull_request = pr_util.create_pull_request(
1034 author=user_regular.username, enable_notifications=False)
1033 author=user_regular.username, enable_notifications=False)
1035
1034
1036 response = self.app.get(url(
1035 response = self.app.get(url(
1037 controller='pullrequests', action='show',
1036 controller='pullrequests', action='show',
1038 repo_name=pull_request.target_repo.scm_instance().name,
1037 repo_name=pull_request.target_repo.scm_instance().name,
1039 pull_request_id=str(pull_request.pull_request_id)))
1038 pull_request_id=str(pull_request.pull_request_id)))
1040
1039
1041 response.mustcontain('id="delete_pullrequest"')
1040 response.mustcontain('id="delete_pullrequest"')
1042 response.mustcontain('Confirm to delete this pull request')
1041 response.mustcontain('Confirm to delete this pull request')
1043
1042
1044 def test_pull_request_delete_button_permissions_forbidden(
1043 def test_pull_request_delete_button_permissions_forbidden(
1045 self, autologin_regular_user, user_regular, user_admin, pr_util):
1044 self, autologin_regular_user, user_regular, user_admin, pr_util):
1046 pull_request = pr_util.create_pull_request(
1045 pull_request = pr_util.create_pull_request(
1047 author=user_admin.username, enable_notifications=False)
1046 author=user_admin.username, enable_notifications=False)
1048
1047
1049 response = self.app.get(url(
1048 response = self.app.get(url(
1050 controller='pullrequests', action='show',
1049 controller='pullrequests', action='show',
1051 repo_name=pull_request.target_repo.scm_instance().name,
1050 repo_name=pull_request.target_repo.scm_instance().name,
1052 pull_request_id=str(pull_request.pull_request_id)))
1051 pull_request_id=str(pull_request.pull_request_id)))
1053 response.mustcontain(no=['id="delete_pullrequest"'])
1052 response.mustcontain(no=['id="delete_pullrequest"'])
1054 response.mustcontain(no=['Confirm to delete this pull request'])
1053 response.mustcontain(no=['Confirm to delete this pull request'])
1055
1054
1056 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1055 def test_pull_request_delete_button_permissions_can_update_cannot_delete(
1057 self, autologin_regular_user, user_regular, user_admin, pr_util,
1056 self, autologin_regular_user, user_regular, user_admin, pr_util,
1058 user_util):
1057 user_util):
1059
1058
1060 pull_request = pr_util.create_pull_request(
1059 pull_request = pr_util.create_pull_request(
1061 author=user_admin.username, enable_notifications=False)
1060 author=user_admin.username, enable_notifications=False)
1062
1061
1063 user_util.grant_user_permission_to_repo(
1062 user_util.grant_user_permission_to_repo(
1064 pull_request.target_repo, user_regular,
1063 pull_request.target_repo, user_regular,
1065 'repository.write')
1064 'repository.write')
1066
1065
1067 response = self.app.get(url(
1066 response = self.app.get(url(
1068 controller='pullrequests', action='show',
1067 controller='pullrequests', action='show',
1069 repo_name=pull_request.target_repo.scm_instance().name,
1068 repo_name=pull_request.target_repo.scm_instance().name,
1070 pull_request_id=str(pull_request.pull_request_id)))
1069 pull_request_id=str(pull_request.pull_request_id)))
1071
1070
1072 response.mustcontain('id="open_edit_pullrequest"')
1071 response.mustcontain('id="open_edit_pullrequest"')
1073 response.mustcontain('id="delete_pullrequest"')
1072 response.mustcontain('id="delete_pullrequest"')
1074 response.mustcontain(no=['Confirm to delete this pull request'])
1073 response.mustcontain(no=['Confirm to delete this pull request'])
1075
1074
1076 def test_delete_comment_returns_404_if_comment_does_not_exist(
1075 def test_delete_comment_returns_404_if_comment_does_not_exist(
1077 self, autologin_user, pr_util, user_admin):
1076 self, autologin_user, pr_util, user_admin):
1078
1077
1079 pull_request = pr_util.create_pull_request(
1078 pull_request = pr_util.create_pull_request(
1080 author=user_admin.username, enable_notifications=False)
1079 author=user_admin.username, enable_notifications=False)
1081
1080
1082 self.app.get(url(
1081 self.app.get(url(
1083 controller='pullrequests', action='delete_comment',
1082 controller='pullrequests', action='delete_comment',
1084 repo_name=pull_request.target_repo.scm_instance().name,
1083 repo_name=pull_request.target_repo.scm_instance().name,
1085 comment_id=1024404), status=404)
1084 comment_id=1024404), status=404)
1086
1085
1087
1086
1088 def assert_pull_request_status(pull_request, expected_status):
1087 def assert_pull_request_status(pull_request, expected_status):
1089 status = ChangesetStatusModel().calculated_review_status(
1088 status = ChangesetStatusModel().calculated_review_status(
1090 pull_request=pull_request)
1089 pull_request=pull_request)
1091 assert status == expected_status
1090 assert status == expected_status
1092
1091
1093
1092
1094 @pytest.mark.parametrize('action', ['index', 'create'])
1093 @pytest.mark.parametrize('action', ['index', 'create'])
1095 @pytest.mark.usefixtures("autologin_user")
1094 @pytest.mark.usefixtures("autologin_user")
1096 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1095 def test_redirects_to_repo_summary_for_svn_repositories(backend_svn, app, action):
1097 response = app.get(url(
1096 response = app.get(url(
1098 controller='pullrequests', action=action,
1097 controller='pullrequests', action=action,
1099 repo_name=backend_svn.repo_name))
1098 repo_name=backend_svn.repo_name))
1100 assert response.status_int == 302
1099 assert response.status_int == 302
1101
1100
1102 # Not allowed, redirect to the summary
1101 # Not allowed, redirect to the summary
1103 redirected = response.follow()
1102 redirected = response.follow()
1104 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1103 summary_url = h.route_path('repo_summary', repo_name=backend_svn.repo_name)
1105
1104
1106 # URL adds leading slash and path doesn't have it
1105 # URL adds leading slash and path doesn't have it
1107 assert redirected.request.path == summary_url
1106 assert redirected.request.path == summary_url
@@ -1,426 +1,431 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import threading
21 import threading
22 import time
22 import time
23 import logging
23 import logging
24 import os.path
24 import os.path
25 import subprocess32
25 import subprocess32
26 import tempfile
26 import tempfile
27 import urllib2
27 import urllib2
28 from lxml.html import fromstring, tostring
28 from lxml.html import fromstring, tostring
29 from lxml.cssselect import CSSSelector
29 from lxml.cssselect import CSSSelector
30 from urlparse import urlparse, parse_qsl
30 from urlparse import urlparse, parse_qsl
31 from urllib import unquote_plus
31 from urllib import unquote_plus
32 import webob
32 import webob
33
33
34 from webtest.app import TestResponse, TestApp, string_types
34 from webtest.app import TestResponse, TestApp, string_types
35 from webtest.compat import print_stderr
35 from webtest.compat import print_stderr
36
36
37 import pytest
37 import pytest
38 import rc_testdata
38 import rc_testdata
39
39
40 from rhodecode.model.db import User, Repository
40 from rhodecode.model.db import User, Repository
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
43 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class CustomTestResponse(TestResponse):
50 class CustomTestResponse(TestResponse):
51 def _save_output(self, out):
51 def _save_output(self, out):
52 f = tempfile.NamedTemporaryFile(
52 f = tempfile.NamedTemporaryFile(
53 delete=False, prefix='rc-test-', suffix='.html')
53 delete=False, prefix='rc-test-', suffix='.html')
54 f.write(out)
54 f.write(out)
55 return f.name
55 return f.name
56
56
57 def mustcontain(self, *strings, **kw):
57 def mustcontain(self, *strings, **kw):
58 """
58 """
59 Assert that the response contains all of the strings passed
59 Assert that the response contains all of the strings passed
60 in as arguments.
60 in as arguments.
61
61
62 Equivalent to::
62 Equivalent to::
63
63
64 assert string in res
64 assert string in res
65 """
65 """
66 if 'no' in kw:
66 if 'no' in kw:
67 no = kw['no']
67 no = kw['no']
68 del kw['no']
68 del kw['no']
69 if isinstance(no, string_types):
69 if isinstance(no, string_types):
70 no = [no]
70 no = [no]
71 else:
71 else:
72 no = []
72 no = []
73 if kw:
73 if kw:
74 raise TypeError(
74 raise TypeError(
75 "The only keyword argument allowed is 'no'")
75 "The only keyword argument allowed is 'no'")
76
76
77 f = self._save_output(str(self))
77 f = self._save_output(str(self))
78
78
79 for s in strings:
79 for s in strings:
80 if not s in self:
80 if not s in self:
81 print_stderr("Actual response (no %r):" % s)
81 print_stderr("Actual response (no %r):" % s)
82 print_stderr(str(self))
82 print_stderr(str(self))
83 raise IndexError(
83 raise IndexError(
84 "Body does not contain string %r, output saved as %s" % (
84 "Body does not contain string %r, output saved as %s" % (
85 s, f))
85 s, f))
86
86
87 for no_s in no:
87 for no_s in no:
88 if no_s in self:
88 if no_s in self:
89 print_stderr("Actual response (has %r)" % no_s)
89 print_stderr("Actual response (has %r)" % no_s)
90 print_stderr(str(self))
90 print_stderr(str(self))
91 raise IndexError(
91 raise IndexError(
92 "Body contains bad string %r, output saved as %s" % (
92 "Body contains bad string %r, output saved as %s" % (
93 no_s, f))
93 no_s, f))
94
94
95 def assert_response(self):
95 def assert_response(self):
96 return AssertResponse(self)
96 return AssertResponse(self)
97
97
98 def get_session_from_response(self):
98 def get_session_from_response(self):
99 """
99 """
100 This returns the session from a response object. Pylons has some magic
100 This returns the session from a response object. Pylons has some magic
101 to make the session available as `response.session`. But pyramid
101 to make the session available as `response.session`. But pyramid
102 doesn't expose it.
102 doesn't expose it.
103 """
103 """
104 return self.request.environ['beaker.session']
104 return self.request.environ['beaker.session']
105
105
106
106
107 class TestRequest(webob.BaseRequest):
107 class TestRequest(webob.BaseRequest):
108
108
109 # for py.test
109 # for py.test
110 disabled = True
110 disabled = True
111 ResponseClass = CustomTestResponse
111 ResponseClass = CustomTestResponse
112
112
113
113
114 class CustomTestApp(TestApp):
114 class CustomTestApp(TestApp):
115 """
115 """
116 Custom app to make mustcontain more usefull
116 Custom app to make mustcontain more usefull
117 """
117 """
118 RequestClass = TestRequest
118 RequestClass = TestRequest
119
119
120
120
121 def set_anonymous_access(enabled):
121 def set_anonymous_access(enabled):
122 """(Dis)allows anonymous access depending on parameter `enabled`"""
122 """(Dis)allows anonymous access depending on parameter `enabled`"""
123 user = User.get_default_user()
123 user = User.get_default_user()
124 user.active = enabled
124 user.active = enabled
125 Session().add(user)
125 Session().add(user)
126 Session().commit()
126 Session().commit()
127 time.sleep(1.5) # must sleep for cache (1s to expire)
127 time.sleep(1.5) # must sleep for cache (1s to expire)
128 log.info('anonymous access is now: %s', enabled)
128 log.info('anonymous access is now: %s', enabled)
129 assert enabled == User.get_default_user().active, (
129 assert enabled == User.get_default_user().active, (
130 'Cannot set anonymous access')
130 'Cannot set anonymous access')
131
131
132
132
133 def check_xfail_backends(node, backend_alias):
133 def check_xfail_backends(node, backend_alias):
134 # Using "xfail_backends" here intentionally, since this marks work
134 # Using "xfail_backends" here intentionally, since this marks work
135 # which is "to be done" soon.
135 # which is "to be done" soon.
136 skip_marker = node.get_marker('xfail_backends')
136 skip_marker = node.get_marker('xfail_backends')
137 if skip_marker and backend_alias in skip_marker.args:
137 if skip_marker and backend_alias in skip_marker.args:
138 msg = "Support for backend %s to be developed." % (backend_alias, )
138 msg = "Support for backend %s to be developed." % (backend_alias, )
139 msg = skip_marker.kwargs.get('reason', msg)
139 msg = skip_marker.kwargs.get('reason', msg)
140 pytest.xfail(msg)
140 pytest.xfail(msg)
141
141
142
142
143 def check_skip_backends(node, backend_alias):
143 def check_skip_backends(node, backend_alias):
144 # Using "skip_backends" here intentionally, since this marks work which is
144 # Using "skip_backends" here intentionally, since this marks work which is
145 # not supported.
145 # not supported.
146 skip_marker = node.get_marker('skip_backends')
146 skip_marker = node.get_marker('skip_backends')
147 if skip_marker and backend_alias in skip_marker.args:
147 if skip_marker and backend_alias in skip_marker.args:
148 msg = "Feature not supported for backend %s." % (backend_alias, )
148 msg = "Feature not supported for backend %s." % (backend_alias, )
149 msg = skip_marker.kwargs.get('reason', msg)
149 msg = skip_marker.kwargs.get('reason', msg)
150 pytest.skip(msg)
150 pytest.skip(msg)
151
151
152
152
153 def extract_git_repo_from_dump(dump_name, repo_name):
153 def extract_git_repo_from_dump(dump_name, repo_name):
154 """Create git repo `repo_name` from dump `dump_name`."""
154 """Create git repo `repo_name` from dump `dump_name`."""
155 repos_path = ScmModel().repos_path
155 repos_path = ScmModel().repos_path
156 target_path = os.path.join(repos_path, repo_name)
156 target_path = os.path.join(repos_path, repo_name)
157 rc_testdata.extract_git_dump(dump_name, target_path)
157 rc_testdata.extract_git_dump(dump_name, target_path)
158 return target_path
158 return target_path
159
159
160
160
161 def extract_hg_repo_from_dump(dump_name, repo_name):
161 def extract_hg_repo_from_dump(dump_name, repo_name):
162 """Create hg repo `repo_name` from dump `dump_name`."""
162 """Create hg repo `repo_name` from dump `dump_name`."""
163 repos_path = ScmModel().repos_path
163 repos_path = ScmModel().repos_path
164 target_path = os.path.join(repos_path, repo_name)
164 target_path = os.path.join(repos_path, repo_name)
165 rc_testdata.extract_hg_dump(dump_name, target_path)
165 rc_testdata.extract_hg_dump(dump_name, target_path)
166 return target_path
166 return target_path
167
167
168
168
169 def extract_svn_repo_from_dump(dump_name, repo_name):
169 def extract_svn_repo_from_dump(dump_name, repo_name):
170 """Create a svn repo `repo_name` from dump `dump_name`."""
170 """Create a svn repo `repo_name` from dump `dump_name`."""
171 repos_path = ScmModel().repos_path
171 repos_path = ScmModel().repos_path
172 target_path = os.path.join(repos_path, repo_name)
172 target_path = os.path.join(repos_path, repo_name)
173 SubversionRepository(target_path, create=True)
173 SubversionRepository(target_path, create=True)
174 _load_svn_dump_into_repo(dump_name, target_path)
174 _load_svn_dump_into_repo(dump_name, target_path)
175 return target_path
175 return target_path
176
176
177
177
178 def assert_message_in_log(log_records, message, levelno, module):
178 def assert_message_in_log(log_records, message, levelno, module):
179 messages = [
179 messages = [
180 r.message for r in log_records
180 r.message for r in log_records
181 if r.module == module and r.levelno == levelno
181 if r.module == module and r.levelno == levelno
182 ]
182 ]
183 assert message in messages
183 assert message in messages
184
184
185
185
186 def _load_svn_dump_into_repo(dump_name, repo_path):
186 def _load_svn_dump_into_repo(dump_name, repo_path):
187 """
187 """
188 Utility to populate a svn repository with a named dump
188 Utility to populate a svn repository with a named dump
189
189
190 Currently the dumps are in rc_testdata. They might later on be
190 Currently the dumps are in rc_testdata. They might later on be
191 integrated with the main repository once they stabilize more.
191 integrated with the main repository once they stabilize more.
192 """
192 """
193 dump = rc_testdata.load_svn_dump(dump_name)
193 dump = rc_testdata.load_svn_dump(dump_name)
194 load_dump = subprocess32.Popen(
194 load_dump = subprocess32.Popen(
195 ['svnadmin', 'load', repo_path],
195 ['svnadmin', 'load', repo_path],
196 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
196 stdin=subprocess32.PIPE, stdout=subprocess32.PIPE,
197 stderr=subprocess32.PIPE)
197 stderr=subprocess32.PIPE)
198 out, err = load_dump.communicate(dump)
198 out, err = load_dump.communicate(dump)
199 if load_dump.returncode != 0:
199 if load_dump.returncode != 0:
200 log.error("Output of load_dump command: %s", out)
200 log.error("Output of load_dump command: %s", out)
201 log.error("Error output of load_dump command: %s", err)
201 log.error("Error output of load_dump command: %s", err)
202 raise Exception(
202 raise Exception(
203 'Failed to load dump "%s" into repository at path "%s".'
203 'Failed to load dump "%s" into repository at path "%s".'
204 % (dump_name, repo_path))
204 % (dump_name, repo_path))
205
205
206
206
207 class AssertResponse(object):
207 class AssertResponse(object):
208 """
208 """
209 Utility that helps to assert things about a given HTML response.
209 Utility that helps to assert things about a given HTML response.
210 """
210 """
211
211
212 def __init__(self, response):
212 def __init__(self, response):
213 self.response = response
213 self.response = response
214
214
215 def get_imports(self):
215 def get_imports(self):
216 return fromstring, tostring, CSSSelector
216 return fromstring, tostring, CSSSelector
217
217
218 def one_element_exists(self, css_selector):
218 def one_element_exists(self, css_selector):
219 self.get_element(css_selector)
219 self.get_element(css_selector)
220
220
221 def no_element_exists(self, css_selector):
221 def no_element_exists(self, css_selector):
222 assert not self._get_elements(css_selector)
222 assert not self._get_elements(css_selector)
223
223
224 def element_equals_to(self, css_selector, expected_content):
224 def element_equals_to(self, css_selector, expected_content):
225 element = self.get_element(css_selector)
225 element = self.get_element(css_selector)
226 element_text = self._element_to_string(element)
226 element_text = self._element_to_string(element)
227 assert expected_content in element_text
227 assert expected_content in element_text
228
228
229 def element_contains(self, css_selector, expected_content):
229 def element_contains(self, css_selector, expected_content):
230 element = self.get_element(css_selector)
230 element = self.get_element(css_selector)
231 assert expected_content in element.text_content()
231 assert expected_content in element.text_content()
232
232
233 def element_value_contains(self, css_selector, expected_content):
233 def element_value_contains(self, css_selector, expected_content):
234 element = self.get_element(css_selector)
234 element = self.get_element(css_selector)
235 assert expected_content in element.value
235 assert expected_content in element.value
236
236
237 def contains_one_link(self, link_text, href):
237 def contains_one_link(self, link_text, href):
238 fromstring, tostring, CSSSelector = self.get_imports()
238 fromstring, tostring, CSSSelector = self.get_imports()
239 doc = fromstring(self.response.body)
239 doc = fromstring(self.response.body)
240 sel = CSSSelector('a[href]')
240 sel = CSSSelector('a[href]')
241 elements = [
241 elements = [
242 e for e in sel(doc) if e.text_content().strip() == link_text]
242 e for e in sel(doc) if e.text_content().strip() == link_text]
243 assert len(elements) == 1, "Did not find link or found multiple links"
243 assert len(elements) == 1, "Did not find link or found multiple links"
244 self._ensure_url_equal(elements[0].attrib.get('href'), href)
244 self._ensure_url_equal(elements[0].attrib.get('href'), href)
245
245
246 def contains_one_anchor(self, anchor_id):
246 def contains_one_anchor(self, anchor_id):
247 fromstring, tostring, CSSSelector = self.get_imports()
247 fromstring, tostring, CSSSelector = self.get_imports()
248 doc = fromstring(self.response.body)
248 doc = fromstring(self.response.body)
249 sel = CSSSelector('#' + anchor_id)
249 sel = CSSSelector('#' + anchor_id)
250 elements = sel(doc)
250 elements = sel(doc)
251 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
251 assert len(elements) == 1, 'cannot find 1 element {}'.format(anchor_id)
252
252
253 def _ensure_url_equal(self, found, expected):
253 def _ensure_url_equal(self, found, expected):
254 assert _Url(found) == _Url(expected)
254 assert _Url(found) == _Url(expected)
255
255
256 def get_element(self, css_selector):
256 def get_element(self, css_selector):
257 elements = self._get_elements(css_selector)
257 elements = self._get_elements(css_selector)
258 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
258 assert len(elements) == 1, 'cannot find 1 element {}'.format(css_selector)
259 return elements[0]
259 return elements[0]
260
260
261 def get_elements(self, css_selector):
261 def get_elements(self, css_selector):
262 return self._get_elements(css_selector)
262 return self._get_elements(css_selector)
263
263
264 def _get_elements(self, css_selector):
264 def _get_elements(self, css_selector):
265 fromstring, tostring, CSSSelector = self.get_imports()
265 fromstring, tostring, CSSSelector = self.get_imports()
266 doc = fromstring(self.response.body)
266 doc = fromstring(self.response.body)
267 sel = CSSSelector(css_selector)
267 sel = CSSSelector(css_selector)
268 elements = sel(doc)
268 elements = sel(doc)
269 return elements
269 return elements
270
270
271 def _element_to_string(self, element):
271 def _element_to_string(self, element):
272 fromstring, tostring, CSSSelector = self.get_imports()
272 fromstring, tostring, CSSSelector = self.get_imports()
273 return tostring(element)
273 return tostring(element)
274
274
275
275
276 class _Url(object):
276 class _Url(object):
277 """
277 """
278 A url object that can be compared with other url orbjects
278 A url object that can be compared with other url orbjects
279 without regard to the vagaries of encoding, escaping, and ordering
279 without regard to the vagaries of encoding, escaping, and ordering
280 of parameters in query strings.
280 of parameters in query strings.
281
281
282 Inspired by
282 Inspired by
283 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
283 http://stackoverflow.com/questions/5371992/comparing-two-urls-in-python
284 """
284 """
285
285
286 def __init__(self, url):
286 def __init__(self, url):
287 parts = urlparse(url)
287 parts = urlparse(url)
288 _query = frozenset(parse_qsl(parts.query))
288 _query = frozenset(parse_qsl(parts.query))
289 _path = unquote_plus(parts.path)
289 _path = unquote_plus(parts.path)
290 parts = parts._replace(query=_query, path=_path)
290 parts = parts._replace(query=_query, path=_path)
291 self.parts = parts
291 self.parts = parts
292
292
293 def __eq__(self, other):
293 def __eq__(self, other):
294 return self.parts == other.parts
294 return self.parts == other.parts
295
295
296 def __hash__(self):
296 def __hash__(self):
297 return hash(self.parts)
297 return hash(self.parts)
298
298
299
299
300 def run_test_concurrently(times, raise_catched_exc=True):
300 def run_test_concurrently(times, raise_catched_exc=True):
301 """
301 """
302 Add this decorator to small pieces of code that you want to test
302 Add this decorator to small pieces of code that you want to test
303 concurrently
303 concurrently
304
304
305 ex:
305 ex:
306
306
307 @test_concurrently(25)
307 @test_concurrently(25)
308 def my_test_function():
308 def my_test_function():
309 ...
309 ...
310 """
310 """
311 def test_concurrently_decorator(test_func):
311 def test_concurrently_decorator(test_func):
312 def wrapper(*args, **kwargs):
312 def wrapper(*args, **kwargs):
313 exceptions = []
313 exceptions = []
314
314
315 def call_test_func():
315 def call_test_func():
316 try:
316 try:
317 test_func(*args, **kwargs)
317 test_func(*args, **kwargs)
318 except Exception as e:
318 except Exception as e:
319 exceptions.append(e)
319 exceptions.append(e)
320 if raise_catched_exc:
320 if raise_catched_exc:
321 raise
321 raise
322 threads = []
322 threads = []
323 for i in range(times):
323 for i in range(times):
324 threads.append(threading.Thread(target=call_test_func))
324 threads.append(threading.Thread(target=call_test_func))
325 for t in threads:
325 for t in threads:
326 t.start()
326 t.start()
327 for t in threads:
327 for t in threads:
328 t.join()
328 t.join()
329 if exceptions:
329 if exceptions:
330 raise Exception(
330 raise Exception(
331 'test_concurrently intercepted %s exceptions: %s' % (
331 'test_concurrently intercepted %s exceptions: %s' % (
332 len(exceptions), exceptions))
332 len(exceptions), exceptions))
333 return wrapper
333 return wrapper
334 return test_concurrently_decorator
334 return test_concurrently_decorator
335
335
336
336
337 def wait_for_url(url, timeout=10):
337 def wait_for_url(url, timeout=10):
338 """
338 """
339 Wait until URL becomes reachable.
339 Wait until URL becomes reachable.
340
340
341 It polls the URL until the timeout is reached or it became reachable.
341 It polls the URL until the timeout is reached or it became reachable.
342 If will call to `py.test.fail` in case the URL is not reachable.
342 If will call to `py.test.fail` in case the URL is not reachable.
343 """
343 """
344 timeout = time.time() + timeout
344 timeout = time.time() + timeout
345 last = 0
345 last = 0
346 wait = 0.1
346 wait = 0.1
347
347
348 while timeout > last:
348 while timeout > last:
349 last = time.time()
349 last = time.time()
350 if is_url_reachable(url):
350 if is_url_reachable(url):
351 break
351 break
352 elif (last + wait) > time.time():
352 elif (last + wait) > time.time():
353 # Go to sleep because not enough time has passed since last check.
353 # Go to sleep because not enough time has passed since last check.
354 time.sleep(wait)
354 time.sleep(wait)
355 else:
355 else:
356 pytest.fail("Timeout while waiting for URL {}".format(url))
356 pytest.fail("Timeout while waiting for URL {}".format(url))
357
357
358
358
359 def is_url_reachable(url):
359 def is_url_reachable(url):
360 try:
360 try:
361 urllib2.urlopen(url)
361 urllib2.urlopen(url)
362 except urllib2.URLError:
362 except urllib2.URLError:
363 return False
363 return False
364 return True
364 return True
365
365
366
366
367 def repo_on_filesystem(repo_name):
367 def repo_on_filesystem(repo_name):
368 from rhodecode.lib import vcs
368 from rhodecode.lib import vcs
369 from rhodecode.tests import TESTS_TMP_PATH
369 from rhodecode.tests import TESTS_TMP_PATH
370 repo = vcs.get_vcs_instance(
370 repo = vcs.get_vcs_instance(
371 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
371 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
372 return repo is not None
372 return repo is not None
373
373
374
374
375 def commit_change(
375 def commit_change(
376 repo, filename, content, message, vcs_type, parent=None, newfile=False):
376 repo, filename, content, message, vcs_type, parent=None, newfile=False):
377 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
377 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
378
378
379 repo = Repository.get_by_repo_name(repo)
379 repo = Repository.get_by_repo_name(repo)
380 _commit = parent
380 _commit = parent
381 if not parent:
381 if not parent:
382 _commit = EmptyCommit(alias=vcs_type)
382 _commit = EmptyCommit(alias=vcs_type)
383
383
384 if newfile:
384 if newfile:
385 nodes = {
385 nodes = {
386 filename: {
386 filename: {
387 'content': content
387 'content': content
388 }
388 }
389 }
389 }
390 commit = ScmModel().create_nodes(
390 commit = ScmModel().create_nodes(
391 user=TEST_USER_ADMIN_LOGIN, repo=repo,
391 user=TEST_USER_ADMIN_LOGIN, repo=repo,
392 message=message,
392 message=message,
393 nodes=nodes,
393 nodes=nodes,
394 parent_commit=_commit,
394 parent_commit=_commit,
395 author=TEST_USER_ADMIN_LOGIN,
395 author=TEST_USER_ADMIN_LOGIN,
396 )
396 )
397 else:
397 else:
398 commit = ScmModel().commit_change(
398 commit = ScmModel().commit_change(
399 repo=repo.scm_instance(), repo_name=repo.repo_name,
399 repo=repo.scm_instance(), repo_name=repo.repo_name,
400 commit=parent, user=TEST_USER_ADMIN_LOGIN,
400 commit=parent, user=TEST_USER_ADMIN_LOGIN,
401 author=TEST_USER_ADMIN_LOGIN,
401 author=TEST_USER_ADMIN_LOGIN,
402 message=message,
402 message=message,
403 content=content,
403 content=content,
404 f_path=filename
404 f_path=filename
405 )
405 )
406 return commit
406 return commit
407
407
408
408
409 def add_test_routes(config):
409 def add_test_routes(config):
410 """
410 """
411 Adds test routing that can be used in different functional tests
411 Adds test routing that can be used in different functional tests
412 """
412 """
413 from rhodecode.apps._base import ADMIN_PREFIX
414
413 config.add_route(name='home', pattern='/')
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 config.add_route(name='repo_summary', pattern='/{repo_name}')
419 config.add_route(name='repo_summary', pattern='/{repo_name}')
415 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
420 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
416 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
421 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
417
422
418 config.add_route(name='pullrequest_show',
423 config.add_route(name='pullrequest_show',
419 pattern='/{repo_name}/pull-request/{pull_request_id}')
424 pattern='/{repo_name}/pull-request/{pull_request_id}')
420 config.add_route(name='pull_requests_global',
425 config.add_route(name='pull_requests_global',
421 pattern='/pull-request/{pull_request_id}')
426 pattern='/pull-request/{pull_request_id}')
422 config.add_route(name='repo_commit',
427 config.add_route(name='repo_commit',
423 pattern='/{repo_name}/changeset/{commit_id}')
428 pattern='/{repo_name}/changeset/{commit_id}')
424
429
425 config.add_route(name='repo_files',
430 config.add_route(name='repo_files',
426 pattern='/{repo_name}/files/{commit_id}/{f_path}')
431 pattern='/{repo_name}/files/{commit_id}/{f_path}')
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now