##// END OF EJS Templates
admin: moved auth tokens into pyramid view....
marcink -
r1518:6474fb97 default
parent child Browse files
Show More
@@ -0,0 +1,141 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
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.db import User
34 from rhodecode.model.meta import Session
35
36 log = logging.getLogger(__name__)
37
38
39 class AdminUsersView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
41 """
42 This view has alternative version inside EE, if modified please take a look
43 in there as well.
44 """
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48 c.auth_user = self.request.user
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 self._register_global_c(c)
51 return c
52
53 def _redirect_for_default_user(self, username):
54 _ = self.request.translate
55 if username == User.DEFAULT_USER:
56 h.flash(_("You can't edit this user"), category='warning')
57 # TODO(marcink): redirect to 'users' admin panel once this
58 # is a pyramid view
59 raise HTTPFound('/')
60
61 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
63 @view_config(
64 route_name='edit_user_auth_tokens', request_method='GET',
65 renderer='rhodecode:templates/admin/users/user_edit.mako')
66 def auth_tokens(self):
67 _ = self.request.translate
68 c = self.load_default_context()
69
70 user_id = self.request.matchdict.get('user_id')
71 c.user = User.get_or_404(user_id, pyramid_exc=True)
72 self._redirect_for_default_user(c.user.username)
73
74 c.active = 'auth_tokens'
75
76 c.lifetime_values = [
77 (str(-1), _('forever')),
78 (str(5), _('5 minutes')),
79 (str(60), _('1 hour')),
80 (str(60 * 24), _('1 day')),
81 (str(60 * 24 * 30), _('1 month')),
82 ]
83 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
84 c.role_values = [
85 (x, AuthTokenModel.cls._get_role_name(x))
86 for x in AuthTokenModel.cls.ROLES]
87 c.role_options = [(c.role_values, _("Role"))]
88 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
89 c.user.user_id, show_expired=True)
90 return self._get_template_context(c)
91
92 def maybe_attach_token_scope(self, token):
93 # implemented in EE edition
94 pass
95
96 @LoginRequired()
97 @HasPermissionAllDecorator('hg.admin')
98 @CSRFRequired()
99 @view_config(
100 route_name='edit_user_auth_tokens_add', request_method='POST')
101 def auth_tokens_add(self):
102 _ = self.request.translate
103 c = self.load_default_context()
104
105 user_id = self.request.matchdict.get('user_id')
106 c.user = User.get_or_404(user_id, pyramid_exc=True)
107 self._redirect_for_default_user(c.user.username)
108
109 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
110 description = self.request.POST.get('description')
111 role = self.request.POST.get('role')
112
113 token = AuthTokenModel().create(
114 c.user.user_id, description, lifetime, role)
115 self.maybe_attach_token_scope(token)
116 Session().commit()
117
118 h.flash(_("Auth token successfully created"), category='success')
119 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
120
121 @LoginRequired()
122 @HasPermissionAllDecorator('hg.admin')
123 @CSRFRequired()
124 @view_config(
125 route_name='edit_user_auth_tokens_delete', request_method='POST')
126 def auth_tokens_delete(self):
127 _ = self.request.translate
128 c = self.load_default_context()
129
130 user_id = self.request.matchdict.get('user_id')
131 c.user = User.get_or_404(user_id, pyramid_exc=True)
132 self._redirect_for_default_user(c.user.username)
133
134 del_auth_token = self.request.POST.get('del_auth_token')
135
136 if del_auth_token:
137 AuthTokenModel().delete(del_auth_token, c.user.user_id)
138 Session().commit()
139 h.flash(_("Auth token successfully deleted"), category='success')
140
141 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
@@ -1,57 +1,68 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def includeme(config):
28 28 settings = config.get_settings()
29 29
30 30 # Create admin navigation registry and add it to the pyramid registry.
31 31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 33 config.registry.registerUtility(navigation_registry)
34 34
35 35 config.add_route(
36 36 name='admin_settings_open_source',
37 37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 38 config.add_route(
39 39 name='admin_settings_vcs_svn_generate_cfg',
40 40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41 41
42 42 config.add_route(
43 43 name='admin_settings_system',
44 44 pattern=ADMIN_PREFIX + '/settings/system')
45 45 config.add_route(
46 46 name='admin_settings_system_update',
47 47 pattern=ADMIN_PREFIX + '/settings/system/updates')
48 48
49 49 config.add_route(
50 50 name='admin_settings_sessions',
51 51 pattern=ADMIN_PREFIX + '/settings/sessions')
52 52 config.add_route(
53 53 name='admin_settings_sessions_cleanup',
54 54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
55 55
56 # user auth tokens
57 config.add_route(
58 name='edit_user_auth_tokens',
59 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens')
60 config.add_route(
61 name='edit_user_auth_tokens_add',
62 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/new')
63 config.add_route(
64 name='edit_user_auth_tokens_delete',
65 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/delete')
66
56 67 # Scan module for configuration decorators.
57 68 config.scan()
@@ -1,1163 +1,1156 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 from rhodecode.config import routing_links
36 36
37 37 # prefix for non repository related links needs to be prefixed with `/`
38 38 ADMIN_PREFIX = '/_admin'
39 39 STATIC_FILE_PREFIX = '/_static'
40 40
41 41 # Default requirements for URL parts
42 42 URL_NAME_REQUIREMENTS = {
43 43 # group name can have a slash in them, but they must not end with a slash
44 44 'group_name': r'.*?[^/]',
45 45 'repo_group_name': r'.*?[^/]',
46 46 # repo names can have a slash in them, but they must not end with a slash
47 47 'repo_name': r'.*?[^/]',
48 48 # file path eats up everything at the end
49 49 'f_path': r'.*',
50 50 # reference types
51 51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 53 }
54 54
55 55
56 56 def add_route_requirements(route_path, requirements):
57 57 """
58 58 Adds regex requirements to pyramid routes using a mapping dict
59 59
60 60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 61 '/{action}/{id:\d+}'
62 62
63 63 """
64 64 for key, regex in requirements.items():
65 65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 66 return route_path
67 67
68 68
69 69 class JSRoutesMapper(Mapper):
70 70 """
71 71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 72 """
73 73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 75 def __init__(self, *args, **kw):
76 76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 77 self._jsroutes = []
78 78
79 79 def connect(self, *args, **kw):
80 80 """
81 81 Wrapper for connect to take an extra argument jsroute=True
82 82
83 83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 84 """
85 85 if kw.pop('jsroute', False):
86 86 if not self._named_route_regex.match(args[0]):
87 87 raise Exception('only named routes can be added to pyroutes')
88 88 self._jsroutes.append(args[0])
89 89
90 90 super(JSRoutesMapper, self).connect(*args, **kw)
91 91
92 92 def _extract_route_information(self, route):
93 93 """
94 94 Convert a route into tuple(name, path, args), eg:
95 95 ('show_user', '/profile/%(username)s', ['username'])
96 96 """
97 97 routepath = route.routepath
98 98 def replace(matchobj):
99 99 if matchobj.group(1):
100 100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 101 else:
102 102 return "%%(%s)s" % matchobj.group(2)
103 103
104 104 routepath = self._argument_prog.sub(replace, routepath)
105 105 return (
106 106 route.name,
107 107 routepath,
108 108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 109 for arg in self._argument_prog.findall(route.routepath)]
110 110 )
111 111
112 112 def jsroutes(self):
113 113 """
114 114 Return a list of pyroutes.js compatible routes
115 115 """
116 116 for route_name in self._jsroutes:
117 117 yield self._extract_route_information(self._routenames[route_name])
118 118
119 119
120 120 def make_map(config):
121 121 """Create, configure and return the routes Mapper"""
122 122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 123 always_scan=config['debug'])
124 124 rmap.minimization = False
125 125 rmap.explicit = False
126 126
127 127 from rhodecode.lib.utils2 import str2bool
128 128 from rhodecode.model import repo, repo_group
129 129
130 130 def check_repo(environ, match_dict):
131 131 """
132 132 check for valid repository for proper 404 handling
133 133
134 134 :param environ:
135 135 :param match_dict:
136 136 """
137 137 repo_name = match_dict.get('repo_name')
138 138
139 139 if match_dict.get('f_path'):
140 140 # fix for multiple initial slashes that causes errors
141 141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 142 repo_model = repo.RepoModel()
143 143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 144 # if we match quickly from database, short circuit the operation,
145 145 # and validate repo based on the type.
146 146 if by_name_match:
147 147 return True
148 148
149 149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 150 if by_id_match:
151 151 repo_name = by_id_match.repo_name
152 152 match_dict['repo_name'] = repo_name
153 153 return True
154 154
155 155 return False
156 156
157 157 def check_group(environ, match_dict):
158 158 """
159 159 check for valid repository group path for proper 404 handling
160 160
161 161 :param environ:
162 162 :param match_dict:
163 163 """
164 164 repo_group_name = match_dict.get('group_name')
165 165 repo_group_model = repo_group.RepoGroupModel()
166 166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 167 if by_name_match:
168 168 return True
169 169
170 170 return False
171 171
172 172 def check_user_group(environ, match_dict):
173 173 """
174 174 check for valid user group for proper 404 handling
175 175
176 176 :param environ:
177 177 :param match_dict:
178 178 """
179 179 return True
180 180
181 181 def check_int(environ, match_dict):
182 182 return match_dict.get('id').isdigit()
183 183
184 184
185 185 #==========================================================================
186 186 # CUSTOM ROUTES HERE
187 187 #==========================================================================
188 188
189 189 # MAIN PAGE
190 190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 192 action='goto_switcher_data')
193 193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 194 action='repo_list_data')
195 195
196 196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 197 action='user_autocomplete_data', jsroute=True)
198 198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 199 action='user_group_autocomplete_data', jsroute=True)
200 200
201 201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
202 202 rmap.connect('rst_help',
203 203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
204 204 _static=True)
205 205 rmap.connect('markdown_help',
206 206 'http://daringfireball.net/projects/markdown/syntax',
207 207 _static=True)
208 208 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
209 209 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
210 210 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
211 211 # TODO: anderson - making this a static link since redirect won't play
212 212 # nice with POST requests
213 213 rmap.connect('enterprise_license_convert_from_old',
214 214 'https://rhodecode.com/u/license-upgrade',
215 215 _static=True)
216 216
217 217 routing_links.connect_redirection_links(rmap)
218 218
219 219 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
220 220 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
221 221
222 222 # ADMIN REPOSITORY ROUTES
223 223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 224 controller='admin/repos') as m:
225 225 m.connect('repos', '/repos',
226 226 action='create', conditions={'method': ['POST']})
227 227 m.connect('repos', '/repos',
228 228 action='index', conditions={'method': ['GET']})
229 229 m.connect('new_repo', '/create_repository', jsroute=True,
230 230 action='create_repository', conditions={'method': ['GET']})
231 231 m.connect('/repos/{repo_name}',
232 232 action='update', conditions={'method': ['PUT'],
233 233 'function': check_repo},
234 234 requirements=URL_NAME_REQUIREMENTS)
235 235 m.connect('delete_repo', '/repos/{repo_name}',
236 236 action='delete', conditions={'method': ['DELETE']},
237 237 requirements=URL_NAME_REQUIREMENTS)
238 238 m.connect('repo', '/repos/{repo_name}',
239 239 action='show', conditions={'method': ['GET'],
240 240 'function': check_repo},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242
243 243 # ADMIN REPOSITORY GROUPS ROUTES
244 244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 245 controller='admin/repo_groups') as m:
246 246 m.connect('repo_groups', '/repo_groups',
247 247 action='create', conditions={'method': ['POST']})
248 248 m.connect('repo_groups', '/repo_groups',
249 249 action='index', conditions={'method': ['GET']})
250 250 m.connect('new_repo_group', '/repo_groups/new',
251 251 action='new', conditions={'method': ['GET']})
252 252 m.connect('update_repo_group', '/repo_groups/{group_name}',
253 253 action='update', conditions={'method': ['PUT'],
254 254 'function': check_group},
255 255 requirements=URL_NAME_REQUIREMENTS)
256 256
257 257 # EXTRAS REPO GROUP ROUTES
258 258 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
259 259 action='edit',
260 260 conditions={'method': ['GET'], 'function': check_group},
261 261 requirements=URL_NAME_REQUIREMENTS)
262 262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 263 action='edit',
264 264 conditions={'method': ['PUT'], 'function': check_group},
265 265 requirements=URL_NAME_REQUIREMENTS)
266 266
267 267 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
268 268 action='edit_repo_group_advanced',
269 269 conditions={'method': ['GET'], 'function': check_group},
270 270 requirements=URL_NAME_REQUIREMENTS)
271 271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 272 action='edit_repo_group_advanced',
273 273 conditions={'method': ['PUT'], 'function': check_group},
274 274 requirements=URL_NAME_REQUIREMENTS)
275 275
276 276 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
277 277 action='edit_repo_group_perms',
278 278 conditions={'method': ['GET'], 'function': check_group},
279 279 requirements=URL_NAME_REQUIREMENTS)
280 280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 281 action='update_perms',
282 282 conditions={'method': ['PUT'], 'function': check_group},
283 283 requirements=URL_NAME_REQUIREMENTS)
284 284
285 285 m.connect('delete_repo_group', '/repo_groups/{group_name}',
286 286 action='delete', conditions={'method': ['DELETE'],
287 287 'function': check_group},
288 288 requirements=URL_NAME_REQUIREMENTS)
289 289
290 290 # ADMIN USER ROUTES
291 291 with rmap.submapper(path_prefix=ADMIN_PREFIX,
292 292 controller='admin/users') as m:
293 293 m.connect('users', '/users',
294 294 action='create', conditions={'method': ['POST']})
295 295 m.connect('users', '/users',
296 296 action='index', conditions={'method': ['GET']})
297 297 m.connect('new_user', '/users/new',
298 298 action='new', conditions={'method': ['GET']})
299 299 m.connect('update_user', '/users/{user_id}',
300 300 action='update', conditions={'method': ['PUT']})
301 301 m.connect('delete_user', '/users/{user_id}',
302 302 action='delete', conditions={'method': ['DELETE']})
303 303 m.connect('edit_user', '/users/{user_id}/edit',
304 304 action='edit', conditions={'method': ['GET']}, jsroute=True)
305 305 m.connect('user', '/users/{user_id}',
306 306 action='show', conditions={'method': ['GET']})
307 307 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
308 308 action='reset_password', conditions={'method': ['POST']})
309 309 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
310 310 action='create_personal_repo_group', conditions={'method': ['POST']})
311 311
312 312 # EXTRAS USER ROUTES
313 313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
314 314 action='edit_advanced', conditions={'method': ['GET']})
315 315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 316 action='update_advanced', conditions={'method': ['PUT']})
317 317
318 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
319 action='edit_auth_tokens', conditions={'method': ['GET']})
320 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
321 action='add_auth_token', conditions={'method': ['PUT']})
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='delete_auth_token', conditions={'method': ['DELETE']})
324
325 318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
326 319 action='edit_global_perms', conditions={'method': ['GET']})
327 320 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
328 321 action='update_global_perms', conditions={'method': ['PUT']})
329 322
330 323 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
331 324 action='edit_perms_summary', conditions={'method': ['GET']})
332 325
333 326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
334 327 action='edit_emails', conditions={'method': ['GET']})
335 328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
336 329 action='add_email', conditions={'method': ['PUT']})
337 330 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 331 action='delete_email', conditions={'method': ['DELETE']})
339 332
340 333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
341 334 action='edit_ips', conditions={'method': ['GET']})
342 335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
343 336 action='add_ip', conditions={'method': ['PUT']})
344 337 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 338 action='delete_ip', conditions={'method': ['DELETE']})
346 339
347 340 # ADMIN USER GROUPS REST ROUTES
348 341 with rmap.submapper(path_prefix=ADMIN_PREFIX,
349 342 controller='admin/user_groups') as m:
350 343 m.connect('users_groups', '/user_groups',
351 344 action='create', conditions={'method': ['POST']})
352 345 m.connect('users_groups', '/user_groups',
353 346 action='index', conditions={'method': ['GET']})
354 347 m.connect('new_users_group', '/user_groups/new',
355 348 action='new', conditions={'method': ['GET']})
356 349 m.connect('update_users_group', '/user_groups/{user_group_id}',
357 350 action='update', conditions={'method': ['PUT']})
358 351 m.connect('delete_users_group', '/user_groups/{user_group_id}',
359 352 action='delete', conditions={'method': ['DELETE']})
360 353 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
361 354 action='edit', conditions={'method': ['GET']},
362 355 function=check_user_group)
363 356
364 357 # EXTRAS USER GROUP ROUTES
365 358 m.connect('edit_user_group_global_perms',
366 359 '/user_groups/{user_group_id}/edit/global_permissions',
367 360 action='edit_global_perms', conditions={'method': ['GET']})
368 361 m.connect('edit_user_group_global_perms',
369 362 '/user_groups/{user_group_id}/edit/global_permissions',
370 363 action='update_global_perms', conditions={'method': ['PUT']})
371 364 m.connect('edit_user_group_perms_summary',
372 365 '/user_groups/{user_group_id}/edit/permissions_summary',
373 366 action='edit_perms_summary', conditions={'method': ['GET']})
374 367
375 368 m.connect('edit_user_group_perms',
376 369 '/user_groups/{user_group_id}/edit/permissions',
377 370 action='edit_perms', conditions={'method': ['GET']})
378 371 m.connect('edit_user_group_perms',
379 372 '/user_groups/{user_group_id}/edit/permissions',
380 373 action='update_perms', conditions={'method': ['PUT']})
381 374
382 375 m.connect('edit_user_group_advanced',
383 376 '/user_groups/{user_group_id}/edit/advanced',
384 377 action='edit_advanced', conditions={'method': ['GET']})
385 378
386 379 m.connect('edit_user_group_members',
387 380 '/user_groups/{user_group_id}/edit/members', jsroute=True,
388 381 action='user_group_members', conditions={'method': ['GET']})
389 382
390 383 # ADMIN PERMISSIONS ROUTES
391 384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
392 385 controller='admin/permissions') as m:
393 386 m.connect('admin_permissions_application', '/permissions/application',
394 387 action='permission_application_update', conditions={'method': ['POST']})
395 388 m.connect('admin_permissions_application', '/permissions/application',
396 389 action='permission_application', conditions={'method': ['GET']})
397 390
398 391 m.connect('admin_permissions_global', '/permissions/global',
399 392 action='permission_global_update', conditions={'method': ['POST']})
400 393 m.connect('admin_permissions_global', '/permissions/global',
401 394 action='permission_global', conditions={'method': ['GET']})
402 395
403 396 m.connect('admin_permissions_object', '/permissions/object',
404 397 action='permission_objects_update', conditions={'method': ['POST']})
405 398 m.connect('admin_permissions_object', '/permissions/object',
406 399 action='permission_objects', conditions={'method': ['GET']})
407 400
408 401 m.connect('admin_permissions_ips', '/permissions/ips',
409 402 action='permission_ips', conditions={'method': ['POST']})
410 403 m.connect('admin_permissions_ips', '/permissions/ips',
411 404 action='permission_ips', conditions={'method': ['GET']})
412 405
413 406 m.connect('admin_permissions_overview', '/permissions/overview',
414 407 action='permission_perms', conditions={'method': ['GET']})
415 408
416 409 # ADMIN DEFAULTS REST ROUTES
417 410 with rmap.submapper(path_prefix=ADMIN_PREFIX,
418 411 controller='admin/defaults') as m:
419 412 m.connect('admin_defaults_repositories', '/defaults/repositories',
420 413 action='update_repository_defaults', conditions={'method': ['POST']})
421 414 m.connect('admin_defaults_repositories', '/defaults/repositories',
422 415 action='index', conditions={'method': ['GET']})
423 416
424 417 # ADMIN DEBUG STYLE ROUTES
425 418 if str2bool(config.get('debug_style')):
426 419 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
427 420 controller='debug_style') as m:
428 421 m.connect('debug_style_home', '',
429 422 action='index', conditions={'method': ['GET']})
430 423 m.connect('debug_style_template', '/t/{t_path}',
431 424 action='template', conditions={'method': ['GET']})
432 425
433 426 # ADMIN SETTINGS ROUTES
434 427 with rmap.submapper(path_prefix=ADMIN_PREFIX,
435 428 controller='admin/settings') as m:
436 429
437 430 # default
438 431 m.connect('admin_settings', '/settings',
439 432 action='settings_global_update',
440 433 conditions={'method': ['POST']})
441 434 m.connect('admin_settings', '/settings',
442 435 action='settings_global', conditions={'method': ['GET']})
443 436
444 437 m.connect('admin_settings_vcs', '/settings/vcs',
445 438 action='settings_vcs_update',
446 439 conditions={'method': ['POST']})
447 440 m.connect('admin_settings_vcs', '/settings/vcs',
448 441 action='settings_vcs',
449 442 conditions={'method': ['GET']})
450 443 m.connect('admin_settings_vcs', '/settings/vcs',
451 444 action='delete_svn_pattern',
452 445 conditions={'method': ['DELETE']})
453 446
454 447 m.connect('admin_settings_mapping', '/settings/mapping',
455 448 action='settings_mapping_update',
456 449 conditions={'method': ['POST']})
457 450 m.connect('admin_settings_mapping', '/settings/mapping',
458 451 action='settings_mapping', conditions={'method': ['GET']})
459 452
460 453 m.connect('admin_settings_global', '/settings/global',
461 454 action='settings_global_update',
462 455 conditions={'method': ['POST']})
463 456 m.connect('admin_settings_global', '/settings/global',
464 457 action='settings_global', conditions={'method': ['GET']})
465 458
466 459 m.connect('admin_settings_visual', '/settings/visual',
467 460 action='settings_visual_update',
468 461 conditions={'method': ['POST']})
469 462 m.connect('admin_settings_visual', '/settings/visual',
470 463 action='settings_visual', conditions={'method': ['GET']})
471 464
472 465 m.connect('admin_settings_issuetracker',
473 466 '/settings/issue-tracker', action='settings_issuetracker',
474 467 conditions={'method': ['GET']})
475 468 m.connect('admin_settings_issuetracker_save',
476 469 '/settings/issue-tracker/save',
477 470 action='settings_issuetracker_save',
478 471 conditions={'method': ['POST']})
479 472 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
480 473 action='settings_issuetracker_test',
481 474 conditions={'method': ['POST']})
482 475 m.connect('admin_issuetracker_delete',
483 476 '/settings/issue-tracker/delete',
484 477 action='settings_issuetracker_delete',
485 478 conditions={'method': ['DELETE']})
486 479
487 480 m.connect('admin_settings_email', '/settings/email',
488 481 action='settings_email_update',
489 482 conditions={'method': ['POST']})
490 483 m.connect('admin_settings_email', '/settings/email',
491 484 action='settings_email', conditions={'method': ['GET']})
492 485
493 486 m.connect('admin_settings_hooks', '/settings/hooks',
494 487 action='settings_hooks_update',
495 488 conditions={'method': ['POST', 'DELETE']})
496 489 m.connect('admin_settings_hooks', '/settings/hooks',
497 490 action='settings_hooks', conditions={'method': ['GET']})
498 491
499 492 m.connect('admin_settings_search', '/settings/search',
500 493 action='settings_search', conditions={'method': ['GET']})
501 494
502 495 m.connect('admin_settings_supervisor', '/settings/supervisor',
503 496 action='settings_supervisor', conditions={'method': ['GET']})
504 497 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
505 498 action='settings_supervisor_log', conditions={'method': ['GET']})
506 499
507 500 m.connect('admin_settings_labs', '/settings/labs',
508 501 action='settings_labs_update',
509 502 conditions={'method': ['POST']})
510 503 m.connect('admin_settings_labs', '/settings/labs',
511 504 action='settings_labs', conditions={'method': ['GET']})
512 505
513 506 # ADMIN MY ACCOUNT
514 507 with rmap.submapper(path_prefix=ADMIN_PREFIX,
515 508 controller='admin/my_account') as m:
516 509
517 510 m.connect('my_account', '/my_account',
518 511 action='my_account', conditions={'method': ['GET']})
519 512 m.connect('my_account_edit', '/my_account/edit',
520 513 action='my_account_edit', conditions={'method': ['GET']})
521 514 m.connect('my_account', '/my_account',
522 515 action='my_account_update', conditions={'method': ['POST']})
523 516
524 517 m.connect('my_account_password', '/my_account/password',
525 518 action='my_account_password', conditions={'method': ['GET', 'POST']})
526 519
527 520 m.connect('my_account_repos', '/my_account/repos',
528 521 action='my_account_repos', conditions={'method': ['GET']})
529 522
530 523 m.connect('my_account_watched', '/my_account/watched',
531 524 action='my_account_watched', conditions={'method': ['GET']})
532 525
533 526 m.connect('my_account_pullrequests', '/my_account/pull_requests',
534 527 action='my_account_pullrequests', conditions={'method': ['GET']})
535 528
536 529 m.connect('my_account_perms', '/my_account/perms',
537 530 action='my_account_perms', conditions={'method': ['GET']})
538 531
539 532 m.connect('my_account_emails', '/my_account/emails',
540 533 action='my_account_emails', conditions={'method': ['GET']})
541 534 m.connect('my_account_emails', '/my_account/emails',
542 535 action='my_account_emails_add', conditions={'method': ['POST']})
543 536 m.connect('my_account_emails', '/my_account/emails',
544 537 action='my_account_emails_delete', conditions={'method': ['DELETE']})
545 538
546 539 m.connect('my_account_notifications', '/my_account/notifications',
547 540 action='my_notifications',
548 541 conditions={'method': ['GET']})
549 542 m.connect('my_account_notifications_toggle_visibility',
550 543 '/my_account/toggle_visibility',
551 544 action='my_notifications_toggle_visibility',
552 545 conditions={'method': ['POST']})
553 546 m.connect('my_account_notifications_test_channelstream',
554 547 '/my_account/test_channelstream',
555 548 action='my_account_notifications_test_channelstream',
556 549 conditions={'method': ['POST']})
557 550
558 551 # NOTIFICATION REST ROUTES
559 552 with rmap.submapper(path_prefix=ADMIN_PREFIX,
560 553 controller='admin/notifications') as m:
561 554 m.connect('notifications', '/notifications',
562 555 action='index', conditions={'method': ['GET']})
563 556 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
564 557 action='mark_all_read', conditions={'method': ['POST']})
565 558 m.connect('/notifications/{notification_id}',
566 559 action='update', conditions={'method': ['PUT']})
567 560 m.connect('/notifications/{notification_id}',
568 561 action='delete', conditions={'method': ['DELETE']})
569 562 m.connect('notification', '/notifications/{notification_id}',
570 563 action='show', conditions={'method': ['GET']})
571 564
572 565 # ADMIN GIST
573 566 with rmap.submapper(path_prefix=ADMIN_PREFIX,
574 567 controller='admin/gists') as m:
575 568 m.connect('gists', '/gists',
576 569 action='create', conditions={'method': ['POST']})
577 570 m.connect('gists', '/gists', jsroute=True,
578 571 action='index', conditions={'method': ['GET']})
579 572 m.connect('new_gist', '/gists/new', jsroute=True,
580 573 action='new', conditions={'method': ['GET']})
581 574
582 575 m.connect('/gists/{gist_id}',
583 576 action='delete', conditions={'method': ['DELETE']})
584 577 m.connect('edit_gist', '/gists/{gist_id}/edit',
585 578 action='edit_form', conditions={'method': ['GET']})
586 579 m.connect('edit_gist', '/gists/{gist_id}/edit',
587 580 action='edit', conditions={'method': ['POST']})
588 581 m.connect(
589 582 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
590 583 action='check_revision', conditions={'method': ['GET']})
591 584
592 585 m.connect('gist', '/gists/{gist_id}',
593 586 action='show', conditions={'method': ['GET']})
594 587 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
595 588 revision='tip',
596 589 action='show', conditions={'method': ['GET']})
597 590 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
598 591 revision='tip',
599 592 action='show', conditions={'method': ['GET']})
600 593 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
601 594 revision='tip',
602 595 action='show', conditions={'method': ['GET']},
603 596 requirements=URL_NAME_REQUIREMENTS)
604 597
605 598 # ADMIN MAIN PAGES
606 599 with rmap.submapper(path_prefix=ADMIN_PREFIX,
607 600 controller='admin/admin') as m:
608 601 m.connect('admin_home', '', action='index')
609 602 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
610 603 action='add_repo')
611 604 m.connect(
612 605 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
613 606 action='pull_requests')
614 607 m.connect(
615 608 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
616 609 action='pull_requests')
617 610 m.connect(
618 611 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
619 612 action='pull_requests')
620 613
621 614 # USER JOURNAL
622 615 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
623 616 controller='journal', action='index')
624 617 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
625 618 controller='journal', action='journal_rss')
626 619 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
627 620 controller='journal', action='journal_atom')
628 621
629 622 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
630 623 controller='journal', action='public_journal')
631 624
632 625 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
633 626 controller='journal', action='public_journal_rss')
634 627
635 628 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
636 629 controller='journal', action='public_journal_rss')
637 630
638 631 rmap.connect('public_journal_atom',
639 632 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
640 633 action='public_journal_atom')
641 634
642 635 rmap.connect('public_journal_atom_old',
643 636 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
644 637 action='public_journal_atom')
645 638
646 639 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
647 640 controller='journal', action='toggle_following', jsroute=True,
648 641 conditions={'method': ['POST']})
649 642
650 643 # FULL TEXT SEARCH
651 644 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
652 645 controller='search')
653 646 rmap.connect('search_repo_home', '/{repo_name}/search',
654 647 controller='search',
655 648 action='index',
656 649 conditions={'function': check_repo},
657 650 requirements=URL_NAME_REQUIREMENTS)
658 651
659 652 # FEEDS
660 653 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
661 654 controller='feed', action='rss',
662 655 conditions={'function': check_repo},
663 656 requirements=URL_NAME_REQUIREMENTS)
664 657
665 658 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
666 659 controller='feed', action='atom',
667 660 conditions={'function': check_repo},
668 661 requirements=URL_NAME_REQUIREMENTS)
669 662
670 663 #==========================================================================
671 664 # REPOSITORY ROUTES
672 665 #==========================================================================
673 666
674 667 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
675 668 controller='admin/repos', action='repo_creating',
676 669 requirements=URL_NAME_REQUIREMENTS)
677 670 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
678 671 controller='admin/repos', action='repo_check',
679 672 requirements=URL_NAME_REQUIREMENTS)
680 673
681 674 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
682 675 controller='summary', action='repo_stats',
683 676 conditions={'function': check_repo},
684 677 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
685 678
686 679 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
687 680 controller='summary', action='repo_refs_data',
688 681 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
689 682 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
690 683 controller='summary', action='repo_refs_changelog_data',
691 684 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
692 685 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
693 686 controller='summary', action='repo_default_reviewers_data',
694 687 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
695 688
696 689 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
697 690 controller='changeset', revision='tip',
698 691 conditions={'function': check_repo},
699 692 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
700 693 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
701 694 controller='changeset', revision='tip', action='changeset_children',
702 695 conditions={'function': check_repo},
703 696 requirements=URL_NAME_REQUIREMENTS)
704 697 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
705 698 controller='changeset', revision='tip', action='changeset_parents',
706 699 conditions={'function': check_repo},
707 700 requirements=URL_NAME_REQUIREMENTS)
708 701
709 702 # repo edit options
710 703 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
711 704 controller='admin/repos', action='edit',
712 705 conditions={'method': ['GET'], 'function': check_repo},
713 706 requirements=URL_NAME_REQUIREMENTS)
714 707
715 708 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
716 709 jsroute=True,
717 710 controller='admin/repos', action='edit_permissions',
718 711 conditions={'method': ['GET'], 'function': check_repo},
719 712 requirements=URL_NAME_REQUIREMENTS)
720 713 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
721 714 controller='admin/repos', action='edit_permissions_update',
722 715 conditions={'method': ['PUT'], 'function': check_repo},
723 716 requirements=URL_NAME_REQUIREMENTS)
724 717
725 718 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
726 719 controller='admin/repos', action='edit_fields',
727 720 conditions={'method': ['GET'], 'function': check_repo},
728 721 requirements=URL_NAME_REQUIREMENTS)
729 722 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
730 723 controller='admin/repos', action='create_repo_field',
731 724 conditions={'method': ['PUT'], 'function': check_repo},
732 725 requirements=URL_NAME_REQUIREMENTS)
733 726 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
734 727 controller='admin/repos', action='delete_repo_field',
735 728 conditions={'method': ['DELETE'], 'function': check_repo},
736 729 requirements=URL_NAME_REQUIREMENTS)
737 730
738 731 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
739 732 controller='admin/repos', action='edit_advanced',
740 733 conditions={'method': ['GET'], 'function': check_repo},
741 734 requirements=URL_NAME_REQUIREMENTS)
742 735
743 736 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
744 737 controller='admin/repos', action='edit_advanced_locking',
745 738 conditions={'method': ['PUT'], 'function': check_repo},
746 739 requirements=URL_NAME_REQUIREMENTS)
747 740 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
748 741 controller='admin/repos', action='toggle_locking',
749 742 conditions={'method': ['GET'], 'function': check_repo},
750 743 requirements=URL_NAME_REQUIREMENTS)
751 744
752 745 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
753 746 controller='admin/repos', action='edit_advanced_journal',
754 747 conditions={'method': ['PUT'], 'function': check_repo},
755 748 requirements=URL_NAME_REQUIREMENTS)
756 749
757 750 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
758 751 controller='admin/repos', action='edit_advanced_fork',
759 752 conditions={'method': ['PUT'], 'function': check_repo},
760 753 requirements=URL_NAME_REQUIREMENTS)
761 754
762 755 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
763 756 controller='admin/repos', action='edit_caches_form',
764 757 conditions={'method': ['GET'], 'function': check_repo},
765 758 requirements=URL_NAME_REQUIREMENTS)
766 759 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
767 760 controller='admin/repos', action='edit_caches',
768 761 conditions={'method': ['PUT'], 'function': check_repo},
769 762 requirements=URL_NAME_REQUIREMENTS)
770 763
771 764 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
772 765 controller='admin/repos', action='edit_remote_form',
773 766 conditions={'method': ['GET'], 'function': check_repo},
774 767 requirements=URL_NAME_REQUIREMENTS)
775 768 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
776 769 controller='admin/repos', action='edit_remote',
777 770 conditions={'method': ['PUT'], 'function': check_repo},
778 771 requirements=URL_NAME_REQUIREMENTS)
779 772
780 773 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
781 774 controller='admin/repos', action='edit_statistics_form',
782 775 conditions={'method': ['GET'], 'function': check_repo},
783 776 requirements=URL_NAME_REQUIREMENTS)
784 777 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
785 778 controller='admin/repos', action='edit_statistics',
786 779 conditions={'method': ['PUT'], 'function': check_repo},
787 780 requirements=URL_NAME_REQUIREMENTS)
788 781 rmap.connect('repo_settings_issuetracker',
789 782 '/{repo_name}/settings/issue-tracker',
790 783 controller='admin/repos', action='repo_issuetracker',
791 784 conditions={'method': ['GET'], 'function': check_repo},
792 785 requirements=URL_NAME_REQUIREMENTS)
793 786 rmap.connect('repo_issuetracker_test',
794 787 '/{repo_name}/settings/issue-tracker/test',
795 788 controller='admin/repos', action='repo_issuetracker_test',
796 789 conditions={'method': ['POST'], 'function': check_repo},
797 790 requirements=URL_NAME_REQUIREMENTS)
798 791 rmap.connect('repo_issuetracker_delete',
799 792 '/{repo_name}/settings/issue-tracker/delete',
800 793 controller='admin/repos', action='repo_issuetracker_delete',
801 794 conditions={'method': ['DELETE'], 'function': check_repo},
802 795 requirements=URL_NAME_REQUIREMENTS)
803 796 rmap.connect('repo_issuetracker_save',
804 797 '/{repo_name}/settings/issue-tracker/save',
805 798 controller='admin/repos', action='repo_issuetracker_save',
806 799 conditions={'method': ['POST'], 'function': check_repo},
807 800 requirements=URL_NAME_REQUIREMENTS)
808 801 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
809 802 controller='admin/repos', action='repo_settings_vcs_update',
810 803 conditions={'method': ['POST'], 'function': check_repo},
811 804 requirements=URL_NAME_REQUIREMENTS)
812 805 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
813 806 controller='admin/repos', action='repo_settings_vcs',
814 807 conditions={'method': ['GET'], 'function': check_repo},
815 808 requirements=URL_NAME_REQUIREMENTS)
816 809 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
817 810 controller='admin/repos', action='repo_delete_svn_pattern',
818 811 conditions={'method': ['DELETE'], 'function': check_repo},
819 812 requirements=URL_NAME_REQUIREMENTS)
820 813 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
821 814 controller='admin/repos', action='repo_settings_pullrequest',
822 815 conditions={'method': ['GET', 'POST'], 'function': check_repo},
823 816 requirements=URL_NAME_REQUIREMENTS)
824 817
825 818 # still working url for backward compat.
826 819 rmap.connect('raw_changeset_home_depraced',
827 820 '/{repo_name}/raw-changeset/{revision}',
828 821 controller='changeset', action='changeset_raw',
829 822 revision='tip', conditions={'function': check_repo},
830 823 requirements=URL_NAME_REQUIREMENTS)
831 824
832 825 # new URLs
833 826 rmap.connect('changeset_raw_home',
834 827 '/{repo_name}/changeset-diff/{revision}',
835 828 controller='changeset', action='changeset_raw',
836 829 revision='tip', conditions={'function': check_repo},
837 830 requirements=URL_NAME_REQUIREMENTS)
838 831
839 832 rmap.connect('changeset_patch_home',
840 833 '/{repo_name}/changeset-patch/{revision}',
841 834 controller='changeset', action='changeset_patch',
842 835 revision='tip', conditions={'function': check_repo},
843 836 requirements=URL_NAME_REQUIREMENTS)
844 837
845 838 rmap.connect('changeset_download_home',
846 839 '/{repo_name}/changeset-download/{revision}',
847 840 controller='changeset', action='changeset_download',
848 841 revision='tip', conditions={'function': check_repo},
849 842 requirements=URL_NAME_REQUIREMENTS)
850 843
851 844 rmap.connect('changeset_comment',
852 845 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
853 846 controller='changeset', revision='tip', action='comment',
854 847 conditions={'function': check_repo},
855 848 requirements=URL_NAME_REQUIREMENTS)
856 849
857 850 rmap.connect('changeset_comment_preview',
858 851 '/{repo_name}/changeset/comment/preview', jsroute=True,
859 852 controller='changeset', action='preview_comment',
860 853 conditions={'function': check_repo, 'method': ['POST']},
861 854 requirements=URL_NAME_REQUIREMENTS)
862 855
863 856 rmap.connect('changeset_comment_delete',
864 857 '/{repo_name}/changeset/comment/{comment_id}/delete',
865 858 controller='changeset', action='delete_comment',
866 859 conditions={'function': check_repo, 'method': ['DELETE']},
867 860 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
868 861
869 862 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
870 863 controller='changeset', action='changeset_info',
871 864 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
872 865
873 866 rmap.connect('compare_home',
874 867 '/{repo_name}/compare',
875 868 controller='compare', action='index',
876 869 conditions={'function': check_repo},
877 870 requirements=URL_NAME_REQUIREMENTS)
878 871
879 872 rmap.connect('compare_url',
880 873 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
881 874 controller='compare', action='compare',
882 875 conditions={'function': check_repo},
883 876 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
884 877
885 878 rmap.connect('pullrequest_home',
886 879 '/{repo_name}/pull-request/new', controller='pullrequests',
887 880 action='index', conditions={'function': check_repo,
888 881 'method': ['GET']},
889 882 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
890 883
891 884 rmap.connect('pullrequest',
892 885 '/{repo_name}/pull-request/new', controller='pullrequests',
893 886 action='create', conditions={'function': check_repo,
894 887 'method': ['POST']},
895 888 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
896 889
897 890 rmap.connect('pullrequest_repo_refs',
898 891 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
899 892 controller='pullrequests',
900 893 action='get_repo_refs',
901 894 conditions={'function': check_repo, 'method': ['GET']},
902 895 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903 896
904 897 rmap.connect('pullrequest_repo_destinations',
905 898 '/{repo_name}/pull-request/repo-destinations',
906 899 controller='pullrequests',
907 900 action='get_repo_destinations',
908 901 conditions={'function': check_repo, 'method': ['GET']},
909 902 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
910 903
911 904 rmap.connect('pullrequest_show',
912 905 '/{repo_name}/pull-request/{pull_request_id}',
913 906 controller='pullrequests',
914 907 action='show', conditions={'function': check_repo,
915 908 'method': ['GET']},
916 909 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
917 910
918 911 rmap.connect('pullrequest_update',
919 912 '/{repo_name}/pull-request/{pull_request_id}',
920 913 controller='pullrequests',
921 914 action='update', conditions={'function': check_repo,
922 915 'method': ['PUT']},
923 916 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
924 917
925 918 rmap.connect('pullrequest_merge',
926 919 '/{repo_name}/pull-request/{pull_request_id}',
927 920 controller='pullrequests',
928 921 action='merge', conditions={'function': check_repo,
929 922 'method': ['POST']},
930 923 requirements=URL_NAME_REQUIREMENTS)
931 924
932 925 rmap.connect('pullrequest_delete',
933 926 '/{repo_name}/pull-request/{pull_request_id}',
934 927 controller='pullrequests',
935 928 action='delete', conditions={'function': check_repo,
936 929 'method': ['DELETE']},
937 930 requirements=URL_NAME_REQUIREMENTS)
938 931
939 932 rmap.connect('pullrequest_show_all',
940 933 '/{repo_name}/pull-request',
941 934 controller='pullrequests',
942 935 action='show_all', conditions={'function': check_repo,
943 936 'method': ['GET']},
944 937 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
945 938
946 939 rmap.connect('pullrequest_comment',
947 940 '/{repo_name}/pull-request-comment/{pull_request_id}',
948 941 controller='pullrequests',
949 942 action='comment', conditions={'function': check_repo,
950 943 'method': ['POST']},
951 944 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
952 945
953 946 rmap.connect('pullrequest_comment_delete',
954 947 '/{repo_name}/pull-request-comment/{comment_id}/delete',
955 948 controller='pullrequests', action='delete_comment',
956 949 conditions={'function': check_repo, 'method': ['DELETE']},
957 950 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
958 951
959 952 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
960 953 controller='summary', conditions={'function': check_repo},
961 954 requirements=URL_NAME_REQUIREMENTS)
962 955
963 956 rmap.connect('branches_home', '/{repo_name}/branches',
964 957 controller='branches', conditions={'function': check_repo},
965 958 requirements=URL_NAME_REQUIREMENTS)
966 959
967 960 rmap.connect('tags_home', '/{repo_name}/tags',
968 961 controller='tags', conditions={'function': check_repo},
969 962 requirements=URL_NAME_REQUIREMENTS)
970 963
971 964 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
972 965 controller='bookmarks', conditions={'function': check_repo},
973 966 requirements=URL_NAME_REQUIREMENTS)
974 967
975 968 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
976 969 controller='changelog', conditions={'function': check_repo},
977 970 requirements=URL_NAME_REQUIREMENTS)
978 971
979 972 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
980 973 controller='changelog', action='changelog_summary',
981 974 conditions={'function': check_repo},
982 975 requirements=URL_NAME_REQUIREMENTS)
983 976
984 977 rmap.connect('changelog_file_home',
985 978 '/{repo_name}/changelog/{revision}/{f_path}',
986 979 controller='changelog', f_path=None,
987 980 conditions={'function': check_repo},
988 981 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
989 982
990 983 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
991 984 controller='changelog', action='changelog_elements',
992 985 conditions={'function': check_repo},
993 986 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
994 987
995 988 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
996 989 controller='files', revision='tip', f_path='',
997 990 conditions={'function': check_repo},
998 991 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
999 992
1000 993 rmap.connect('files_home_simple_catchrev',
1001 994 '/{repo_name}/files/{revision}',
1002 995 controller='files', revision='tip', f_path='',
1003 996 conditions={'function': check_repo},
1004 997 requirements=URL_NAME_REQUIREMENTS)
1005 998
1006 999 rmap.connect('files_home_simple_catchall',
1007 1000 '/{repo_name}/files',
1008 1001 controller='files', revision='tip', f_path='',
1009 1002 conditions={'function': check_repo},
1010 1003 requirements=URL_NAME_REQUIREMENTS)
1011 1004
1012 1005 rmap.connect('files_history_home',
1013 1006 '/{repo_name}/history/{revision}/{f_path}',
1014 1007 controller='files', action='history', revision='tip', f_path='',
1015 1008 conditions={'function': check_repo},
1016 1009 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1017 1010
1018 1011 rmap.connect('files_authors_home',
1019 1012 '/{repo_name}/authors/{revision}/{f_path}',
1020 1013 controller='files', action='authors', revision='tip', f_path='',
1021 1014 conditions={'function': check_repo},
1022 1015 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1023 1016
1024 1017 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1025 1018 controller='files', action='diff', f_path='',
1026 1019 conditions={'function': check_repo},
1027 1020 requirements=URL_NAME_REQUIREMENTS)
1028 1021
1029 1022 rmap.connect('files_diff_2way_home',
1030 1023 '/{repo_name}/diff-2way/{f_path}',
1031 1024 controller='files', action='diff_2way', f_path='',
1032 1025 conditions={'function': check_repo},
1033 1026 requirements=URL_NAME_REQUIREMENTS)
1034 1027
1035 1028 rmap.connect('files_rawfile_home',
1036 1029 '/{repo_name}/rawfile/{revision}/{f_path}',
1037 1030 controller='files', action='rawfile', revision='tip',
1038 1031 f_path='', conditions={'function': check_repo},
1039 1032 requirements=URL_NAME_REQUIREMENTS)
1040 1033
1041 1034 rmap.connect('files_raw_home',
1042 1035 '/{repo_name}/raw/{revision}/{f_path}',
1043 1036 controller='files', action='raw', revision='tip', f_path='',
1044 1037 conditions={'function': check_repo},
1045 1038 requirements=URL_NAME_REQUIREMENTS)
1046 1039
1047 1040 rmap.connect('files_render_home',
1048 1041 '/{repo_name}/render/{revision}/{f_path}',
1049 1042 controller='files', action='index', revision='tip', f_path='',
1050 1043 rendered=True, conditions={'function': check_repo},
1051 1044 requirements=URL_NAME_REQUIREMENTS)
1052 1045
1053 1046 rmap.connect('files_annotate_home',
1054 1047 '/{repo_name}/annotate/{revision}/{f_path}',
1055 1048 controller='files', action='index', revision='tip',
1056 1049 f_path='', annotate=True, conditions={'function': check_repo},
1057 1050 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1058 1051
1059 1052 rmap.connect('files_annotate_previous',
1060 1053 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1061 1054 controller='files', action='annotate_previous', revision='tip',
1062 1055 f_path='', annotate=True, conditions={'function': check_repo},
1063 1056 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1064 1057
1065 1058 rmap.connect('files_edit',
1066 1059 '/{repo_name}/edit/{revision}/{f_path}',
1067 1060 controller='files', action='edit', revision='tip',
1068 1061 f_path='',
1069 1062 conditions={'function': check_repo, 'method': ['POST']},
1070 1063 requirements=URL_NAME_REQUIREMENTS)
1071 1064
1072 1065 rmap.connect('files_edit_home',
1073 1066 '/{repo_name}/edit/{revision}/{f_path}',
1074 1067 controller='files', action='edit_home', revision='tip',
1075 1068 f_path='', conditions={'function': check_repo},
1076 1069 requirements=URL_NAME_REQUIREMENTS)
1077 1070
1078 1071 rmap.connect('files_add',
1079 1072 '/{repo_name}/add/{revision}/{f_path}',
1080 1073 controller='files', action='add', revision='tip',
1081 1074 f_path='',
1082 1075 conditions={'function': check_repo, 'method': ['POST']},
1083 1076 requirements=URL_NAME_REQUIREMENTS)
1084 1077
1085 1078 rmap.connect('files_add_home',
1086 1079 '/{repo_name}/add/{revision}/{f_path}',
1087 1080 controller='files', action='add_home', revision='tip',
1088 1081 f_path='', conditions={'function': check_repo},
1089 1082 requirements=URL_NAME_REQUIREMENTS)
1090 1083
1091 1084 rmap.connect('files_delete',
1092 1085 '/{repo_name}/delete/{revision}/{f_path}',
1093 1086 controller='files', action='delete', revision='tip',
1094 1087 f_path='',
1095 1088 conditions={'function': check_repo, 'method': ['POST']},
1096 1089 requirements=URL_NAME_REQUIREMENTS)
1097 1090
1098 1091 rmap.connect('files_delete_home',
1099 1092 '/{repo_name}/delete/{revision}/{f_path}',
1100 1093 controller='files', action='delete_home', revision='tip',
1101 1094 f_path='', conditions={'function': check_repo},
1102 1095 requirements=URL_NAME_REQUIREMENTS)
1103 1096
1104 1097 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1105 1098 controller='files', action='archivefile',
1106 1099 conditions={'function': check_repo},
1107 1100 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1108 1101
1109 1102 rmap.connect('files_nodelist_home',
1110 1103 '/{repo_name}/nodelist/{revision}/{f_path}',
1111 1104 controller='files', action='nodelist',
1112 1105 conditions={'function': check_repo},
1113 1106 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1114 1107
1115 1108 rmap.connect('files_nodetree_full',
1116 1109 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1117 1110 controller='files', action='nodetree_full',
1118 1111 conditions={'function': check_repo},
1119 1112 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1120 1113
1121 1114 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1122 1115 controller='forks', action='fork_create',
1123 1116 conditions={'function': check_repo, 'method': ['POST']},
1124 1117 requirements=URL_NAME_REQUIREMENTS)
1125 1118
1126 1119 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1127 1120 controller='forks', action='fork',
1128 1121 conditions={'function': check_repo},
1129 1122 requirements=URL_NAME_REQUIREMENTS)
1130 1123
1131 1124 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1132 1125 controller='forks', action='forks',
1133 1126 conditions={'function': check_repo},
1134 1127 requirements=URL_NAME_REQUIREMENTS)
1135 1128
1136 1129 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1137 1130 controller='followers', action='followers',
1138 1131 conditions={'function': check_repo},
1139 1132 requirements=URL_NAME_REQUIREMENTS)
1140 1133
1141 1134 # must be here for proper group/repo catching pattern
1142 1135 _connect_with_slash(
1143 1136 rmap, 'repo_group_home', '/{group_name}',
1144 1137 controller='home', action='index_repo_group',
1145 1138 conditions={'function': check_group},
1146 1139 requirements=URL_NAME_REQUIREMENTS)
1147 1140
1148 1141 # catch all, at the end
1149 1142 _connect_with_slash(
1150 1143 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1151 1144 controller='summary', action='index',
1152 1145 conditions={'function': check_repo},
1153 1146 requirements=URL_NAME_REQUIREMENTS)
1154 1147
1155 1148 return rmap
1156 1149
1157 1150
1158 1151 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1159 1152 """
1160 1153 Connect a route with an optional trailing slash in `path`.
1161 1154 """
1162 1155 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1163 1156 mapper.connect(name, path, *args, **kwargs)
@@ -1,741 +1,677 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Users crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, tmpl_context as c, url, config
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.lib.exceptions import (
35 35 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
36 36 UserOwnsUserGroupsException, UserCreationError)
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib import auth
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasPermissionAllDecorator, AuthUser, generate_auth_token)
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.model.auth_token import AuthTokenModel
43 43
44 44 from rhodecode.model.db import (
45 45 PullRequestReviewers, User, UserEmailMap, UserIpMap, RepoGroup)
46 46 from rhodecode.model.forms import (
47 47 UserForm, UserPermissionsForm, UserIndividualPermissionsForm)
48 48 from rhodecode.model.repo_group import RepoGroupModel
49 49 from rhodecode.model.user import UserModel
50 50 from rhodecode.model.meta import Session
51 51 from rhodecode.model.permission import PermissionModel
52 52 from rhodecode.lib.utils import action_logger
53 53 from rhodecode.lib.ext_json import json
54 54 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class UsersController(BaseController):
60 60 """REST Controller styled on the Atom Publishing Protocol"""
61 61
62 62 @LoginRequired()
63 63 def __before__(self):
64 64 super(UsersController, self).__before__()
65 65 c.available_permissions = config['available_permissions']
66 66 c.allowed_languages = [
67 67 ('en', 'English (en)'),
68 68 ('de', 'German (de)'),
69 69 ('fr', 'French (fr)'),
70 70 ('it', 'Italian (it)'),
71 71 ('ja', 'Japanese (ja)'),
72 72 ('pl', 'Polish (pl)'),
73 73 ('pt', 'Portuguese (pt)'),
74 74 ('ru', 'Russian (ru)'),
75 75 ('zh', 'Chinese (zh)'),
76 76 ]
77 77 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
78 78
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 def index(self):
81 81 """GET /users: All items in the collection"""
82 82 # url('users')
83 83
84 84 from rhodecode.lib.utils import PartialRenderer
85 85 _render = PartialRenderer('data_table/_dt_elements.mako')
86 86
87 87 def username(user_id, username):
88 88 return _render("user_name", user_id, username)
89 89
90 90 def user_actions(user_id, username):
91 91 return _render("user_actions", user_id, username)
92 92
93 93 # json generate
94 94 c.users_list = User.query()\
95 95 .filter(User.username != User.DEFAULT_USER) \
96 96 .all()
97 97
98 98 users_data = []
99 99 for user in c.users_list:
100 100 users_data.append({
101 101 "username": h.gravatar_with_user(user.username),
102 102 "username_raw": user.username,
103 103 "email": user.email,
104 104 "first_name": h.escape(user.name),
105 105 "last_name": h.escape(user.lastname),
106 106 "last_login": h.format_date(user.last_login),
107 107 "last_login_raw": datetime_to_time(user.last_login),
108 108 "last_activity": h.format_date(
109 109 h.time_to_datetime(user.user_data.get('last_activity', 0))),
110 110 "last_activity_raw": user.user_data.get('last_activity', 0),
111 111 "active": h.bool2icon(user.active),
112 112 "active_raw": user.active,
113 113 "admin": h.bool2icon(user.admin),
114 114 "admin_raw": user.admin,
115 115 "extern_type": user.extern_type,
116 116 "extern_name": user.extern_name,
117 117 "action": user_actions(user.user_id, user.username),
118 118 })
119 119
120 120
121 121 c.data = json.dumps(users_data)
122 122 return render('admin/users/users.mako')
123 123
124 124 def _get_personal_repo_group_template_vars(self):
125 125 DummyUser = AttributeDict({
126 126 'username': '${username}',
127 127 'user_id': '${user_id}',
128 128 })
129 129 c.default_create_repo_group = RepoGroupModel() \
130 130 .get_default_create_personal_repo_group()
131 131 c.personal_repo_group_name = RepoGroupModel() \
132 132 .get_personal_group_name(DummyUser)
133 133
134 134 @HasPermissionAllDecorator('hg.admin')
135 135 @auth.CSRFRequired()
136 136 def create(self):
137 137 """POST /users: Create a new item"""
138 138 # url('users')
139 139 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
140 140 user_model = UserModel()
141 141 user_form = UserForm()()
142 142 try:
143 143 form_result = user_form.to_python(dict(request.POST))
144 144 user = user_model.create(form_result)
145 145 Session().flush()
146 146 username = form_result['username']
147 147 action_logger(c.rhodecode_user, 'admin_created_user:%s' % username,
148 148 None, self.ip_addr, self.sa)
149 149
150 150 user_link = h.link_to(h.escape(username),
151 151 url('edit_user',
152 152 user_id=user.user_id))
153 153 h.flash(h.literal(_('Created user %(user_link)s')
154 154 % {'user_link': user_link}), category='success')
155 155 Session().commit()
156 156 except formencode.Invalid as errors:
157 157 self._get_personal_repo_group_template_vars()
158 158 return htmlfill.render(
159 159 render('admin/users/user_add.mako'),
160 160 defaults=errors.value,
161 161 errors=errors.error_dict or {},
162 162 prefix_error=False,
163 163 encoding="UTF-8",
164 164 force_defaults=False)
165 165 except UserCreationError as e:
166 166 h.flash(e, 'error')
167 167 except Exception:
168 168 log.exception("Exception creation of user")
169 169 h.flash(_('Error occurred during creation of user %s')
170 170 % request.POST.get('username'), category='error')
171 171 return redirect(url('users'))
172 172
173 173 @HasPermissionAllDecorator('hg.admin')
174 174 def new(self):
175 175 """GET /users/new: Form to create a new item"""
176 176 # url('new_user')
177 177 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
178 178 self._get_personal_repo_group_template_vars()
179 179 return render('admin/users/user_add.mako')
180 180
181 181 @HasPermissionAllDecorator('hg.admin')
182 182 @auth.CSRFRequired()
183 183 def update(self, user_id):
184 184 """PUT /users/user_id: Update an existing item"""
185 185 # Forms posted to this method should contain a hidden field:
186 186 # <input type="hidden" name="_method" value="PUT" />
187 187 # Or using helpers:
188 188 # h.form(url('update_user', user_id=ID),
189 189 # method='put')
190 190 # url('user', user_id=ID)
191 191 user_id = safe_int(user_id)
192 192 c.user = User.get_or_404(user_id)
193 193 c.active = 'profile'
194 194 c.extern_type = c.user.extern_type
195 195 c.extern_name = c.user.extern_name
196 196 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
197 197 available_languages = [x[0] for x in c.allowed_languages]
198 198 _form = UserForm(edit=True, available_languages=available_languages,
199 199 old_data={'user_id': user_id,
200 200 'email': c.user.email})()
201 201 form_result = {}
202 202 try:
203 203 form_result = _form.to_python(dict(request.POST))
204 204 skip_attrs = ['extern_type', 'extern_name']
205 205 # TODO: plugin should define if username can be updated
206 206 if c.extern_type != "rhodecode":
207 207 # forbid updating username for external accounts
208 208 skip_attrs.append('username')
209 209
210 210 UserModel().update_user(user_id, skip_attrs=skip_attrs, **form_result)
211 211 usr = form_result['username']
212 212 action_logger(c.rhodecode_user, 'admin_updated_user:%s' % usr,
213 213 None, self.ip_addr, self.sa)
214 214 h.flash(_('User updated successfully'), category='success')
215 215 Session().commit()
216 216 except formencode.Invalid as errors:
217 217 defaults = errors.value
218 218 e = errors.error_dict or {}
219 219
220 220 return htmlfill.render(
221 221 render('admin/users/user_edit.mako'),
222 222 defaults=defaults,
223 223 errors=e,
224 224 prefix_error=False,
225 225 encoding="UTF-8",
226 226 force_defaults=False)
227 227 except UserCreationError as e:
228 228 h.flash(e, 'error')
229 229 except Exception:
230 230 log.exception("Exception updating user")
231 231 h.flash(_('Error occurred during update of user %s')
232 232 % form_result.get('username'), category='error')
233 233 return redirect(url('edit_user', user_id=user_id))
234 234
235 235 @HasPermissionAllDecorator('hg.admin')
236 236 @auth.CSRFRequired()
237 237 def delete(self, user_id):
238 238 """DELETE /users/user_id: Delete an existing item"""
239 239 # Forms posted to this method should contain a hidden field:
240 240 # <input type="hidden" name="_method" value="DELETE" />
241 241 # Or using helpers:
242 242 # h.form(url('delete_user', user_id=ID),
243 243 # method='delete')
244 244 # url('user', user_id=ID)
245 245 user_id = safe_int(user_id)
246 246 c.user = User.get_or_404(user_id)
247 247
248 248 _repos = c.user.repositories
249 249 _repo_groups = c.user.repository_groups
250 250 _user_groups = c.user.user_groups
251 251
252 252 handle_repos = None
253 253 handle_repo_groups = None
254 254 handle_user_groups = None
255 255 # dummy call for flash of handle
256 256 set_handle_flash_repos = lambda: None
257 257 set_handle_flash_repo_groups = lambda: None
258 258 set_handle_flash_user_groups = lambda: None
259 259
260 260 if _repos and request.POST.get('user_repos'):
261 261 do = request.POST['user_repos']
262 262 if do == 'detach':
263 263 handle_repos = 'detach'
264 264 set_handle_flash_repos = lambda: h.flash(
265 265 _('Detached %s repositories') % len(_repos),
266 266 category='success')
267 267 elif do == 'delete':
268 268 handle_repos = 'delete'
269 269 set_handle_flash_repos = lambda: h.flash(
270 270 _('Deleted %s repositories') % len(_repos),
271 271 category='success')
272 272
273 273 if _repo_groups and request.POST.get('user_repo_groups'):
274 274 do = request.POST['user_repo_groups']
275 275 if do == 'detach':
276 276 handle_repo_groups = 'detach'
277 277 set_handle_flash_repo_groups = lambda: h.flash(
278 278 _('Detached %s repository groups') % len(_repo_groups),
279 279 category='success')
280 280 elif do == 'delete':
281 281 handle_repo_groups = 'delete'
282 282 set_handle_flash_repo_groups = lambda: h.flash(
283 283 _('Deleted %s repository groups') % len(_repo_groups),
284 284 category='success')
285 285
286 286 if _user_groups and request.POST.get('user_user_groups'):
287 287 do = request.POST['user_user_groups']
288 288 if do == 'detach':
289 289 handle_user_groups = 'detach'
290 290 set_handle_flash_user_groups = lambda: h.flash(
291 291 _('Detached %s user groups') % len(_user_groups),
292 292 category='success')
293 293 elif do == 'delete':
294 294 handle_user_groups = 'delete'
295 295 set_handle_flash_user_groups = lambda: h.flash(
296 296 _('Deleted %s user groups') % len(_user_groups),
297 297 category='success')
298 298
299 299 try:
300 300 UserModel().delete(c.user, handle_repos=handle_repos,
301 301 handle_repo_groups=handle_repo_groups,
302 302 handle_user_groups=handle_user_groups)
303 303 Session().commit()
304 304 set_handle_flash_repos()
305 305 set_handle_flash_repo_groups()
306 306 set_handle_flash_user_groups()
307 307 h.flash(_('Successfully deleted user'), category='success')
308 308 except (UserOwnsReposException, UserOwnsRepoGroupsException,
309 309 UserOwnsUserGroupsException, DefaultUserException) as e:
310 310 h.flash(e, category='warning')
311 311 except Exception:
312 312 log.exception("Exception during deletion of user")
313 313 h.flash(_('An error occurred during deletion of user'),
314 314 category='error')
315 315 return redirect(url('users'))
316 316
317 317 @HasPermissionAllDecorator('hg.admin')
318 318 @auth.CSRFRequired()
319 319 def reset_password(self, user_id):
320 320 """
321 321 toggle reset password flag for this user
322 322
323 323 :param user_id:
324 324 """
325 325 user_id = safe_int(user_id)
326 326 c.user = User.get_or_404(user_id)
327 327 try:
328 328 old_value = c.user.user_data.get('force_password_change')
329 329 c.user.update_userdata(force_password_change=not old_value)
330 330 Session().commit()
331 331 if old_value:
332 332 msg = _('Force password change disabled for user')
333 333 else:
334 334 msg = _('Force password change enabled for user')
335 335 h.flash(msg, category='success')
336 336 except Exception:
337 337 log.exception("Exception during password reset for user")
338 338 h.flash(_('An error occurred during password reset for user'),
339 339 category='error')
340 340
341 341 return redirect(url('edit_user_advanced', user_id=user_id))
342 342
343 343 @HasPermissionAllDecorator('hg.admin')
344 344 @auth.CSRFRequired()
345 345 def create_personal_repo_group(self, user_id):
346 346 """
347 347 Create personal repository group for this user
348 348
349 349 :param user_id:
350 350 """
351 351 from rhodecode.model.repo_group import RepoGroupModel
352 352
353 353 user_id = safe_int(user_id)
354 354 c.user = User.get_or_404(user_id)
355 355 personal_repo_group = RepoGroup.get_user_personal_repo_group(
356 356 c.user.user_id)
357 357 if personal_repo_group:
358 358 return redirect(url('edit_user_advanced', user_id=user_id))
359 359
360 360 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
361 361 c.user)
362 362 named_personal_group = RepoGroup.get_by_group_name(
363 363 personal_repo_group_name)
364 364 try:
365 365
366 366 if named_personal_group and named_personal_group.user_id == c.user.user_id:
367 367 # migrate the same named group, and mark it as personal
368 368 named_personal_group.personal = True
369 369 Session().add(named_personal_group)
370 370 Session().commit()
371 371 msg = _('Linked repository group `%s` as personal' % (
372 372 personal_repo_group_name,))
373 373 h.flash(msg, category='success')
374 374 elif not named_personal_group:
375 375 RepoGroupModel().create_personal_repo_group(c.user)
376 376
377 377 msg = _('Created repository group `%s`' % (
378 378 personal_repo_group_name,))
379 379 h.flash(msg, category='success')
380 380 else:
381 381 msg = _('Repository group `%s` is already taken' % (
382 382 personal_repo_group_name,))
383 383 h.flash(msg, category='warning')
384 384 except Exception:
385 385 log.exception("Exception during repository group creation")
386 386 msg = _(
387 387 'An error occurred during repository group creation for user')
388 388 h.flash(msg, category='error')
389 389 Session().rollback()
390 390
391 391 return redirect(url('edit_user_advanced', user_id=user_id))
392 392
393 393 @HasPermissionAllDecorator('hg.admin')
394 394 def show(self, user_id):
395 395 """GET /users/user_id: Show a specific item"""
396 396 # url('user', user_id=ID)
397 397 User.get_or_404(-1)
398 398
399 399 @HasPermissionAllDecorator('hg.admin')
400 400 def edit(self, user_id):
401 401 """GET /users/user_id/edit: Form to edit an existing item"""
402 402 # url('edit_user', user_id=ID)
403 403 user_id = safe_int(user_id)
404 404 c.user = User.get_or_404(user_id)
405 405 if c.user.username == User.DEFAULT_USER:
406 406 h.flash(_("You can't edit this user"), category='warning')
407 407 return redirect(url('users'))
408 408
409 409 c.active = 'profile'
410 410 c.extern_type = c.user.extern_type
411 411 c.extern_name = c.user.extern_name
412 412 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
413 413
414 414 defaults = c.user.get_dict()
415 415 defaults.update({'language': c.user.user_data.get('language')})
416 416 return htmlfill.render(
417 417 render('admin/users/user_edit.mako'),
418 418 defaults=defaults,
419 419 encoding="UTF-8",
420 420 force_defaults=False)
421 421
422 422 @HasPermissionAllDecorator('hg.admin')
423 423 def edit_advanced(self, user_id):
424 424 user_id = safe_int(user_id)
425 425 user = c.user = User.get_or_404(user_id)
426 426 if user.username == User.DEFAULT_USER:
427 427 h.flash(_("You can't edit this user"), category='warning')
428 428 return redirect(url('users'))
429 429
430 430 c.active = 'advanced'
431 431 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
432 432 c.personal_repo_group = c.perm_user.personal_repo_group
433 433 c.personal_repo_group_name = RepoGroupModel()\
434 434 .get_personal_group_name(user)
435 435 c.first_admin = User.get_first_super_admin()
436 436 defaults = user.get_dict()
437 437
438 438 # Interim workaround if the user participated on any pull requests as a
439 439 # reviewer.
440 440 has_review = bool(PullRequestReviewers.query().filter(
441 441 PullRequestReviewers.user_id == user_id).first())
442 442 c.can_delete_user = not has_review
443 443 c.can_delete_user_message = _(
444 444 'The user participates as reviewer in pull requests and '
445 445 'cannot be deleted. You can set the user to '
446 446 '"inactive" instead of deleting it.') if has_review else ''
447 447
448 448 return htmlfill.render(
449 449 render('admin/users/user_edit.mako'),
450 450 defaults=defaults,
451 451 encoding="UTF-8",
452 452 force_defaults=False)
453 453
454 454 @HasPermissionAllDecorator('hg.admin')
455 def edit_auth_tokens(self, user_id):
456 user_id = safe_int(user_id)
457 c.user = User.get_or_404(user_id)
458 if c.user.username == User.DEFAULT_USER:
459 h.flash(_("You can't edit this user"), category='warning')
460 return redirect(url('users'))
461
462 c.active = 'auth_tokens'
463 show_expired = True
464 c.lifetime_values = [
465 (str(-1), _('forever')),
466 (str(5), _('5 minutes')),
467 (str(60), _('1 hour')),
468 (str(60 * 24), _('1 day')),
469 (str(60 * 24 * 30), _('1 month')),
470 ]
471 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
472 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
473 for x in AuthTokenModel.cls.ROLES]
474 c.role_options = [(c.role_values, _("Role"))]
475 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
476 c.user.user_id, show_expired=show_expired)
477 defaults = c.user.get_dict()
478 return htmlfill.render(
479 render('admin/users/user_edit.mako'),
480 defaults=defaults,
481 encoding="UTF-8",
482 force_defaults=False)
483
484 @HasPermissionAllDecorator('hg.admin')
485 @auth.CSRFRequired()
486 def add_auth_token(self, user_id):
487 user_id = safe_int(user_id)
488 c.user = User.get_or_404(user_id)
489 if c.user.username == User.DEFAULT_USER:
490 h.flash(_("You can't edit this user"), category='warning')
491 return redirect(url('users'))
492
493 lifetime = safe_int(request.POST.get('lifetime'), -1)
494 description = request.POST.get('description')
495 role = request.POST.get('role')
496 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
497 Session().commit()
498 h.flash(_("Auth token successfully created"), category='success')
499 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
500
501 @HasPermissionAllDecorator('hg.admin')
502 @auth.CSRFRequired()
503 def delete_auth_token(self, user_id):
504 user_id = safe_int(user_id)
505 c.user = User.get_or_404(user_id)
506 if c.user.username == User.DEFAULT_USER:
507 h.flash(_("You can't edit this user"), category='warning')
508 return redirect(url('users'))
509
510 del_auth_token = request.POST.get('del_auth_token')
511 if del_auth_token:
512 AuthTokenModel().delete(del_auth_token, c.user.user_id)
513 Session().commit()
514 h.flash(_("Auth token successfully deleted"), category='success')
515
516 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
517
518 @HasPermissionAllDecorator('hg.admin')
519 455 def edit_global_perms(self, user_id):
520 456 user_id = safe_int(user_id)
521 457 c.user = User.get_or_404(user_id)
522 458 if c.user.username == User.DEFAULT_USER:
523 459 h.flash(_("You can't edit this user"), category='warning')
524 460 return redirect(url('users'))
525 461
526 462 c.active = 'global_perms'
527 463
528 464 c.default_user = User.get_default_user()
529 465 defaults = c.user.get_dict()
530 466 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
531 467 defaults.update(c.default_user.get_default_perms())
532 468 defaults.update(c.user.get_default_perms())
533 469
534 470 return htmlfill.render(
535 471 render('admin/users/user_edit.mako'),
536 472 defaults=defaults,
537 473 encoding="UTF-8",
538 474 force_defaults=False)
539 475
540 476 @HasPermissionAllDecorator('hg.admin')
541 477 @auth.CSRFRequired()
542 478 def update_global_perms(self, user_id):
543 479 """PUT /users_perm/user_id: Update an existing item"""
544 480 # url('user_perm', user_id=ID, method='put')
545 481 user_id = safe_int(user_id)
546 482 user = User.get_or_404(user_id)
547 483 c.active = 'global_perms'
548 484 try:
549 485 # first stage that verifies the checkbox
550 486 _form = UserIndividualPermissionsForm()
551 487 form_result = _form.to_python(dict(request.POST))
552 488 inherit_perms = form_result['inherit_default_permissions']
553 489 user.inherit_default_permissions = inherit_perms
554 490 Session().add(user)
555 491
556 492 if not inherit_perms:
557 493 # only update the individual ones if we un check the flag
558 494 _form = UserPermissionsForm(
559 495 [x[0] for x in c.repo_create_choices],
560 496 [x[0] for x in c.repo_create_on_write_choices],
561 497 [x[0] for x in c.repo_group_create_choices],
562 498 [x[0] for x in c.user_group_create_choices],
563 499 [x[0] for x in c.fork_choices],
564 500 [x[0] for x in c.inherit_default_permission_choices])()
565 501
566 502 form_result = _form.to_python(dict(request.POST))
567 503 form_result.update({'perm_user_id': user.user_id})
568 504
569 505 PermissionModel().update_user_permissions(form_result)
570 506
571 507 Session().commit()
572 508 h.flash(_('User global permissions updated successfully'),
573 509 category='success')
574 510
575 511 Session().commit()
576 512 except formencode.Invalid as errors:
577 513 defaults = errors.value
578 514 c.user = user
579 515 return htmlfill.render(
580 516 render('admin/users/user_edit.mako'),
581 517 defaults=defaults,
582 518 errors=errors.error_dict or {},
583 519 prefix_error=False,
584 520 encoding="UTF-8",
585 521 force_defaults=False)
586 522 except Exception:
587 523 log.exception("Exception during permissions saving")
588 524 h.flash(_('An error occurred during permissions saving'),
589 525 category='error')
590 526 return redirect(url('edit_user_global_perms', user_id=user_id))
591 527
592 528 @HasPermissionAllDecorator('hg.admin')
593 529 def edit_perms_summary(self, user_id):
594 530 user_id = safe_int(user_id)
595 531 c.user = User.get_or_404(user_id)
596 532 if c.user.username == User.DEFAULT_USER:
597 533 h.flash(_("You can't edit this user"), category='warning')
598 534 return redirect(url('users'))
599 535
600 536 c.active = 'perms_summary'
601 537 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
602 538
603 539 return render('admin/users/user_edit.mako')
604 540
605 541 @HasPermissionAllDecorator('hg.admin')
606 542 def edit_emails(self, user_id):
607 543 user_id = safe_int(user_id)
608 544 c.user = User.get_or_404(user_id)
609 545 if c.user.username == User.DEFAULT_USER:
610 546 h.flash(_("You can't edit this user"), category='warning')
611 547 return redirect(url('users'))
612 548
613 549 c.active = 'emails'
614 550 c.user_email_map = UserEmailMap.query() \
615 551 .filter(UserEmailMap.user == c.user).all()
616 552
617 553 defaults = c.user.get_dict()
618 554 return htmlfill.render(
619 555 render('admin/users/user_edit.mako'),
620 556 defaults=defaults,
621 557 encoding="UTF-8",
622 558 force_defaults=False)
623 559
624 560 @HasPermissionAllDecorator('hg.admin')
625 561 @auth.CSRFRequired()
626 562 def add_email(self, user_id):
627 563 """POST /user_emails:Add an existing item"""
628 564 # url('user_emails', user_id=ID, method='put')
629 565 user_id = safe_int(user_id)
630 566 c.user = User.get_or_404(user_id)
631 567
632 568 email = request.POST.get('new_email')
633 569 user_model = UserModel()
634 570
635 571 try:
636 572 user_model.add_extra_email(user_id, email)
637 573 Session().commit()
638 574 h.flash(_("Added new email address `%s` for user account") % email,
639 575 category='success')
640 576 except formencode.Invalid as error:
641 577 msg = error.error_dict['email']
642 578 h.flash(msg, category='error')
643 579 except Exception:
644 580 log.exception("Exception during email saving")
645 581 h.flash(_('An error occurred during email saving'),
646 582 category='error')
647 583 return redirect(url('edit_user_emails', user_id=user_id))
648 584
649 585 @HasPermissionAllDecorator('hg.admin')
650 586 @auth.CSRFRequired()
651 587 def delete_email(self, user_id):
652 588 """DELETE /user_emails_delete/user_id: Delete an existing item"""
653 589 # url('user_emails_delete', user_id=ID, method='delete')
654 590 user_id = safe_int(user_id)
655 591 c.user = User.get_or_404(user_id)
656 592 email_id = request.POST.get('del_email_id')
657 593 user_model = UserModel()
658 594 user_model.delete_extra_email(user_id, email_id)
659 595 Session().commit()
660 596 h.flash(_("Removed email address from user account"), category='success')
661 597 return redirect(url('edit_user_emails', user_id=user_id))
662 598
663 599 @HasPermissionAllDecorator('hg.admin')
664 600 def edit_ips(self, user_id):
665 601 user_id = safe_int(user_id)
666 602 c.user = User.get_or_404(user_id)
667 603 if c.user.username == User.DEFAULT_USER:
668 604 h.flash(_("You can't edit this user"), category='warning')
669 605 return redirect(url('users'))
670 606
671 607 c.active = 'ips'
672 608 c.user_ip_map = UserIpMap.query() \
673 609 .filter(UserIpMap.user == c.user).all()
674 610
675 611 c.inherit_default_ips = c.user.inherit_default_permissions
676 612 c.default_user_ip_map = UserIpMap.query() \
677 613 .filter(UserIpMap.user == User.get_default_user()).all()
678 614
679 615 defaults = c.user.get_dict()
680 616 return htmlfill.render(
681 617 render('admin/users/user_edit.mako'),
682 618 defaults=defaults,
683 619 encoding="UTF-8",
684 620 force_defaults=False)
685 621
686 622 @HasPermissionAllDecorator('hg.admin')
687 623 @auth.CSRFRequired()
688 624 def add_ip(self, user_id):
689 625 """POST /user_ips:Add an existing item"""
690 626 # url('user_ips', user_id=ID, method='put')
691 627
692 628 user_id = safe_int(user_id)
693 629 c.user = User.get_or_404(user_id)
694 630 user_model = UserModel()
695 631 try:
696 632 ip_list = user_model.parse_ip_range(request.POST.get('new_ip'))
697 633 except Exception as e:
698 634 ip_list = []
699 635 log.exception("Exception during ip saving")
700 636 h.flash(_('An error occurred during ip saving:%s' % (e,)),
701 637 category='error')
702 638
703 639 desc = request.POST.get('description')
704 640 added = []
705 641 for ip in ip_list:
706 642 try:
707 643 user_model.add_extra_ip(user_id, ip, desc)
708 644 Session().commit()
709 645 added.append(ip)
710 646 except formencode.Invalid as error:
711 647 msg = error.error_dict['ip']
712 648 h.flash(msg, category='error')
713 649 except Exception:
714 650 log.exception("Exception during ip saving")
715 651 h.flash(_('An error occurred during ip saving'),
716 652 category='error')
717 653 if added:
718 654 h.flash(
719 655 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
720 656 category='success')
721 657 if 'default_user' in request.POST:
722 658 return redirect(url('admin_permissions_ips'))
723 659 return redirect(url('edit_user_ips', user_id=user_id))
724 660
725 661 @HasPermissionAllDecorator('hg.admin')
726 662 @auth.CSRFRequired()
727 663 def delete_ip(self, user_id):
728 664 """DELETE /user_ips_delete/user_id: Delete an existing item"""
729 665 # url('user_ips_delete', user_id=ID, method='delete')
730 666 user_id = safe_int(user_id)
731 667 c.user = User.get_or_404(user_id)
732 668
733 669 ip_id = request.POST.get('del_ip_id')
734 670 user_model = UserModel()
735 671 user_model.delete_extra_ip(user_id, ip_id)
736 672 Session().commit()
737 673 h.flash(_("Removed ip address from user whitelist"), category='success')
738 674
739 675 if 'default_user' in request.POST:
740 676 return redirect(url('admin_permissions_ips'))
741 677 return redirect(url('edit_user_ips', user_id=user_id))
@@ -1,158 +1,160 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 7 <p>
7 8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 10 </p>
10 11 <table class="rctable auth_tokens">
11 %if c.user_auth_tokens:
12 12 <tr>
13 13 <th>${_('Token')}</th>
14 14 <th>${_('Scope')}</th>
15 15 <th>${_('Description')}</th>
16 16 <th>${_('Role')}</th>
17 17 <th>${_('Expiration')}</th>
18 18 <th>${_('Action')}</th>
19 19 </tr>
20 %if c.user_auth_tokens:
20 21 %for auth_token in c.user_auth_tokens:
21 22 <tr class="${'expired' if auth_token.expired else ''}">
22 23 <td class="truncate-wrap td-authtoken">
23 24 <div class="user_auth_tokens truncate autoexpand">
24 25 <code>${auth_token.api_key}</code>
25 26 </div>
26 27 </td>
27 28 <td class="td">${auth_token.scope_humanized}</td>
28 29 <td class="td-wrap">${auth_token.description}</td>
29 30 <td class="td-tags">
30 31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 32 </td>
32 33 <td class="td-exp">
33 34 %if auth_token.expires == -1:
34 35 ${_('never')}
35 36 %else:
36 37 %if auth_token.expired:
37 38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
38 39 %else:
39 40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
40 41 %endif
41 42 %endif
42 43 </td>
43 44 <td class="td-action">
44 45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
45 46 ${h.hidden('del_auth_token',auth_token.api_key)}
46 47 <button class="btn btn-link btn-danger" type="submit"
47 48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
48 49 ${_('Delete')}
49 50 </button>
50 51 ${h.end_form()}
51 52 </td>
52 53 </tr>
53 54 %endfor
54 55 %else:
55 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
56 57 %endif
57 58 </table>
59 </div>
58 60
59 61 <div class="user_auth_tokens">
60 62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
61 63 <div class="form form-vertical">
62 64 <!-- fields -->
63 65 <div class="fields">
64 66 <div class="field">
65 67 <div class="label">
66 68 <label for="new_email">${_('New authentication token')}:</label>
67 69 </div>
68 70 <div class="input">
69 ${h.text('description', placeholder=_('Description'))}
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
70 72 ${h.select('lifetime', '', c.lifetime_options)}
71 73 ${h.select('role', '', c.role_options)}
72 74
73 75 % if c.allow_scoped_tokens:
74 76 ${h.hidden('scope_repo_id')}
75 77 % else:
76 78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
77 79 % endif
78 80 </div>
79 81 <p class="help-block">
80 82 ${_('Repository scope works only with tokens with VCS type.')}
81 83 </p>
82 84 </div>
83 85 <div class="buttons">
84 86 ${h.submit('save',_('Add'),class_="btn")}
85 87 ${h.reset('reset',_('Reset'),class_="btn")}
86 88 </div>
87 89 </div>
88 90 </div>
89 91 ${h.end_form()}
90 92 </div>
91 93 </div>
92 94 </div>
93 95 <script>
94 96 $(document).ready(function(){
95 97
96 98 var select2Options = {
97 99 'containerCssClass': "drop-menu",
98 100 'dropdownCssClass': "drop-menu-dropdown",
99 101 'dropdownAutoWidth': true
100 102 };
101 103 $("#lifetime").select2(select2Options);
102 104 $("#role").select2(select2Options);
103 105
104 106 var repoFilter = function(data) {
105 107 var results = [];
106 108
107 109 if (!data.results[0]) {
108 110 return data
109 111 }
110 112
111 113 $.each(data.results[0].children, function() {
112 114 // replace name to ID for submision
113 115 this.id = this.obj.repo_id;
114 116 results.push(this);
115 117 });
116 118
117 119 data.results[0].children = results;
118 120 return data;
119 121 };
120 122
121 123 $("#scope_repo_id_disabled").select2(select2Options);
122 124
123 125 $("#scope_repo_id").select2({
124 126 cachedDataSource: {},
125 127 minimumInputLength: 2,
126 128 placeholder: "${_('repository scope')}",
127 129 dropdownAutoWidth: true,
128 130 containerCssClass: "drop-menu",
129 131 dropdownCssClass: "drop-menu-dropdown",
130 132 formatResult: formatResult,
131 133 query: $.debounce(250, function(query){
132 134 self = this;
133 135 var cacheKey = query.term;
134 136 var cachedData = self.cachedDataSource[cacheKey];
135 137
136 138 if (cachedData) {
137 139 query.callback({results: cachedData.results});
138 140 } else {
139 141 $.ajax({
140 142 url: "${h.url('repo_list_data')}",
141 143 data: {'query': query.term},
142 144 dataType: 'json',
143 145 type: 'GET',
144 146 success: function(data) {
145 147 data = repoFilter(data);
146 148 self.cachedDataSource[cacheKey] = data;
147 149 query.callback({results: data.results});
148 150 },
149 151 error: function(data, textStatus, errorThrown) {
150 152 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
151 153 }
152 154 })
153 155 }
154 156 })
155 157 });
156 158
157 159 });
158 160 </script>
@@ -1,49 +1,49 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s user settings') % c.user.username}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Users'),h.url('users'))}
15 15 &raquo;
16 16 ${c.user.username}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box user_settings">
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28
29 29 ##main
30 30 <div class="sidebar-col-wrapper">
31 31 <div class="sidebar">
32 32 <ul class="nav nav-pills nav-stacked">
33 33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
35 35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
36 36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
37 37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
38 38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
39 39 <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
40 40 </ul>
41 41 </div>
42 42
43 43 <div class="main-content-full-width">
44 44 <%include file="/admin/users/user_edit_${c.active}.mako"/>
45 45 </div>
46 46 </div>
47 47 </div>
48 48
49 49 </%def>
@@ -1,97 +1,157 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Access Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="apikeys_wrap">
7 7 <p>
8 8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 ${_('Additionally scope for VCS type token can narrow the use to chosen repository.')}
11 10 </p>
12 11 <table class="rctable auth_tokens">
13 12 <tr>
14 13 <th>${_('Token')}</th>
15 14 <th>${_('Scope')}</th>
16 15 <th>${_('Description')}</th>
17 16 <th>${_('Role')}</th>
18 17 <th>${_('Expiration')}</th>
19 18 <th>${_('Action')}</th>
20 19 </tr>
21 20 %if c.user_auth_tokens:
22 21 %for auth_token in c.user_auth_tokens:
23 22 <tr class="${'expired' if auth_token.expired else ''}">
24 23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
25 24 <td class="td">${auth_token.scope_humanized}</td>
26 25 <td class="td-wrap">${auth_token.description}</td>
27 26 <td class="td-tags">
28 <span class="tag">${auth_token.role_humanized}</span>
27 <span class="tag disabled">${auth_token.role_humanized}</span>
29 28 </td>
30 29 <td class="td-exp">
31 30 %if auth_token.expires == -1:
32 31 ${_('never')}
33 32 %else:
34 33 %if auth_token.expired:
35 34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
36 35 %else:
37 36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
38 37 %endif
39 38 %endif
40 39 </td>
41 <td>
42 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id),method='delete')}
40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='post')}
43 42 ${h.hidden('del_auth_token',auth_token.api_key)}
44 43 <button class="btn btn-link btn-danger" type="submit"
45 44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
46 45 ${_('Delete')}
47 46 </button>
48 47 ${h.end_form()}
49 48 </td>
50 49 </tr>
51 50 %endfor
52 51 %else:
53 52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
54 53 %endif
55 54 </table>
56 55 </div>
57 56
58 57 <div class="user_auth_tokens">
59 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id), method='put')}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='post')}
60 59 <div class="form form-vertical">
61 60 <!-- fields -->
62 61 <div class="fields">
63 62 <div class="field">
64 63 <div class="label">
65 64 <label for="new_email">${_('New authentication token')}:</label>
66 65 </div>
67 66 <div class="input">
68 67 ${h.text('description', class_='medium', placeholder=_('Description'))}
69 68 ${h.select('lifetime', '', c.lifetime_options)}
70 69 ${h.select('role', '', c.role_options)}
70
71 % if c.allow_scoped_tokens:
72 ${h.hidden('scope_repo_id')}
73 % else:
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 % endif
71 76 </div>
77 <p class="help-block">
78 ${_('Repository scope works only with tokens with VCS type.')}
79 </p>
72 80 </div>
73 81 <div class="buttons">
74 ${h.submit('save',_('Add'),class_="btn btn-small")}
75 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
82 ${h.submit('save',_('Add'),class_="btn")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
76 84 </div>
77 85 </div>
78 86 </div>
79 87 ${h.end_form()}
80 88 </div>
81 89 </div>
82 90 </div>
83 91
84 92 <script>
93
85 94 $(document).ready(function(){
86 $("#lifetime").select2({
95 var select2Options = {
87 96 'containerCssClass': "drop-menu",
88 97 'dropdownCssClass': "drop-menu-dropdown",
89 98 'dropdownAutoWidth': true
99 };
100 $("#lifetime").select2(select2Options);
101 $("#role").select2(select2Options);
102
103 var repoFilter = function(data) {
104 var results = [];
105
106 if (!data.results[0]) {
107 return data
108 }
109
110 $.each(data.results[0].children, function() {
111 // replace name to ID for submision
112 this.id = this.obj.repo_id;
113 results.push(this);
90 114 });
91 $("#role").select2({
92 'containerCssClass': "drop-menu",
93 'dropdownCssClass': "drop-menu-dropdown",
94 'dropdownAutoWidth': true
115
116 data.results[0].children = results;
117 return data;
118 };
119
120 $("#scope_repo_id_disabled").select2(select2Options);
121
122 $("#scope_repo_id").select2({
123 cachedDataSource: {},
124 minimumInputLength: 2,
125 placeholder: "${_('repository scope')}",
126 dropdownAutoWidth: true,
127 containerCssClass: "drop-menu",
128 dropdownCssClass: "drop-menu-dropdown",
129 formatResult: formatResult,
130 query: $.debounce(250, function(query){
131 self = this;
132 var cacheKey = query.term;
133 var cachedData = self.cachedDataSource[cacheKey];
134
135 if (cachedData) {
136 query.callback({results: cachedData.results});
137 } else {
138 $.ajax({
139 url: "${h.url('repo_list_data')}",
140 data: {'query': query.term},
141 dataType: 'json',
142 type: 'GET',
143 success: function(data) {
144 data = repoFilter(data);
145 self.cachedDataSource[cacheKey] = data;
146 query.callback({results: data.results});
147 },
148 error: function(data, textStatus, errorThrown) {
149 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
150 }
151 })
152 }
153 })
95 154 });
96 })
155
156 });
97 157 </script>
General Comments 0
You need to be logged in to leave comments. Login now