##// END OF EJS Templates
user-groups: added info, and new panel to control user group synchronization....
marcink -
r1600:081c7a81 default
parent child Browse files
Show More
@@ -1,1149 +1,1153 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('new_user', '/users/new',
296 296 action='new', conditions={'method': ['GET']})
297 297 m.connect('update_user', '/users/{user_id}',
298 298 action='update', conditions={'method': ['PUT']})
299 299 m.connect('delete_user', '/users/{user_id}',
300 300 action='delete', conditions={'method': ['DELETE']})
301 301 m.connect('edit_user', '/users/{user_id}/edit',
302 302 action='edit', conditions={'method': ['GET']}, jsroute=True)
303 303 m.connect('user', '/users/{user_id}',
304 304 action='show', conditions={'method': ['GET']})
305 305 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
306 306 action='reset_password', conditions={'method': ['POST']})
307 307 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
308 308 action='create_personal_repo_group', conditions={'method': ['POST']})
309 309
310 310 # EXTRAS USER ROUTES
311 311 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
312 312 action='edit_advanced', conditions={'method': ['GET']})
313 313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
314 314 action='update_advanced', conditions={'method': ['PUT']})
315 315
316 316 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
317 317 action='edit_global_perms', conditions={'method': ['GET']})
318 318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
319 319 action='update_global_perms', conditions={'method': ['PUT']})
320 320
321 321 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
322 322 action='edit_perms_summary', conditions={'method': ['GET']})
323 323
324 324 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
325 325 action='edit_emails', conditions={'method': ['GET']})
326 326 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
327 327 action='add_email', conditions={'method': ['PUT']})
328 328 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
329 329 action='delete_email', conditions={'method': ['DELETE']})
330 330
331 331 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
332 332 action='edit_ips', conditions={'method': ['GET']})
333 333 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
334 334 action='add_ip', conditions={'method': ['PUT']})
335 335 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
336 336 action='delete_ip', conditions={'method': ['DELETE']})
337 337
338 338 # ADMIN USER GROUPS REST ROUTES
339 339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
340 340 controller='admin/user_groups') as m:
341 341 m.connect('users_groups', '/user_groups',
342 342 action='create', conditions={'method': ['POST']})
343 343 m.connect('users_groups', '/user_groups',
344 344 action='index', conditions={'method': ['GET']})
345 345 m.connect('new_users_group', '/user_groups/new',
346 346 action='new', conditions={'method': ['GET']})
347 347 m.connect('update_users_group', '/user_groups/{user_group_id}',
348 348 action='update', conditions={'method': ['PUT']})
349 349 m.connect('delete_users_group', '/user_groups/{user_group_id}',
350 350 action='delete', conditions={'method': ['DELETE']})
351 351 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
352 352 action='edit', conditions={'method': ['GET']},
353 353 function=check_user_group)
354 354
355 355 # EXTRAS USER GROUP ROUTES
356 356 m.connect('edit_user_group_global_perms',
357 357 '/user_groups/{user_group_id}/edit/global_permissions',
358 358 action='edit_global_perms', conditions={'method': ['GET']})
359 359 m.connect('edit_user_group_global_perms',
360 360 '/user_groups/{user_group_id}/edit/global_permissions',
361 361 action='update_global_perms', conditions={'method': ['PUT']})
362 362 m.connect('edit_user_group_perms_summary',
363 363 '/user_groups/{user_group_id}/edit/permissions_summary',
364 364 action='edit_perms_summary', conditions={'method': ['GET']})
365 365
366 366 m.connect('edit_user_group_perms',
367 367 '/user_groups/{user_group_id}/edit/permissions',
368 368 action='edit_perms', conditions={'method': ['GET']})
369 369 m.connect('edit_user_group_perms',
370 370 '/user_groups/{user_group_id}/edit/permissions',
371 371 action='update_perms', conditions={'method': ['PUT']})
372 372
373 373 m.connect('edit_user_group_advanced',
374 374 '/user_groups/{user_group_id}/edit/advanced',
375 375 action='edit_advanced', conditions={'method': ['GET']})
376 376
377 m.connect('edit_user_group_advanced_sync',
378 '/user_groups/{user_group_id}/edit/advanced/sync',
379 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
380
377 381 m.connect('edit_user_group_members',
378 382 '/user_groups/{user_group_id}/edit/members', jsroute=True,
379 383 action='user_group_members', conditions={'method': ['GET']})
380 384
381 385 # ADMIN PERMISSIONS ROUTES
382 386 with rmap.submapper(path_prefix=ADMIN_PREFIX,
383 387 controller='admin/permissions') as m:
384 388 m.connect('admin_permissions_application', '/permissions/application',
385 389 action='permission_application_update', conditions={'method': ['POST']})
386 390 m.connect('admin_permissions_application', '/permissions/application',
387 391 action='permission_application', conditions={'method': ['GET']})
388 392
389 393 m.connect('admin_permissions_global', '/permissions/global',
390 394 action='permission_global_update', conditions={'method': ['POST']})
391 395 m.connect('admin_permissions_global', '/permissions/global',
392 396 action='permission_global', conditions={'method': ['GET']})
393 397
394 398 m.connect('admin_permissions_object', '/permissions/object',
395 399 action='permission_objects_update', conditions={'method': ['POST']})
396 400 m.connect('admin_permissions_object', '/permissions/object',
397 401 action='permission_objects', conditions={'method': ['GET']})
398 402
399 403 m.connect('admin_permissions_ips', '/permissions/ips',
400 404 action='permission_ips', conditions={'method': ['POST']})
401 405 m.connect('admin_permissions_ips', '/permissions/ips',
402 406 action='permission_ips', conditions={'method': ['GET']})
403 407
404 408 m.connect('admin_permissions_overview', '/permissions/overview',
405 409 action='permission_perms', conditions={'method': ['GET']})
406 410
407 411 # ADMIN DEFAULTS REST ROUTES
408 412 with rmap.submapper(path_prefix=ADMIN_PREFIX,
409 413 controller='admin/defaults') as m:
410 414 m.connect('admin_defaults_repositories', '/defaults/repositories',
411 415 action='update_repository_defaults', conditions={'method': ['POST']})
412 416 m.connect('admin_defaults_repositories', '/defaults/repositories',
413 417 action='index', conditions={'method': ['GET']})
414 418
415 419 # ADMIN DEBUG STYLE ROUTES
416 420 if str2bool(config.get('debug_style')):
417 421 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
418 422 controller='debug_style') as m:
419 423 m.connect('debug_style_home', '',
420 424 action='index', conditions={'method': ['GET']})
421 425 m.connect('debug_style_template', '/t/{t_path}',
422 426 action='template', conditions={'method': ['GET']})
423 427
424 428 # ADMIN SETTINGS ROUTES
425 429 with rmap.submapper(path_prefix=ADMIN_PREFIX,
426 430 controller='admin/settings') as m:
427 431
428 432 # default
429 433 m.connect('admin_settings', '/settings',
430 434 action='settings_global_update',
431 435 conditions={'method': ['POST']})
432 436 m.connect('admin_settings', '/settings',
433 437 action='settings_global', conditions={'method': ['GET']})
434 438
435 439 m.connect('admin_settings_vcs', '/settings/vcs',
436 440 action='settings_vcs_update',
437 441 conditions={'method': ['POST']})
438 442 m.connect('admin_settings_vcs', '/settings/vcs',
439 443 action='settings_vcs',
440 444 conditions={'method': ['GET']})
441 445 m.connect('admin_settings_vcs', '/settings/vcs',
442 446 action='delete_svn_pattern',
443 447 conditions={'method': ['DELETE']})
444 448
445 449 m.connect('admin_settings_mapping', '/settings/mapping',
446 450 action='settings_mapping_update',
447 451 conditions={'method': ['POST']})
448 452 m.connect('admin_settings_mapping', '/settings/mapping',
449 453 action='settings_mapping', conditions={'method': ['GET']})
450 454
451 455 m.connect('admin_settings_global', '/settings/global',
452 456 action='settings_global_update',
453 457 conditions={'method': ['POST']})
454 458 m.connect('admin_settings_global', '/settings/global',
455 459 action='settings_global', conditions={'method': ['GET']})
456 460
457 461 m.connect('admin_settings_visual', '/settings/visual',
458 462 action='settings_visual_update',
459 463 conditions={'method': ['POST']})
460 464 m.connect('admin_settings_visual', '/settings/visual',
461 465 action='settings_visual', conditions={'method': ['GET']})
462 466
463 467 m.connect('admin_settings_issuetracker',
464 468 '/settings/issue-tracker', action='settings_issuetracker',
465 469 conditions={'method': ['GET']})
466 470 m.connect('admin_settings_issuetracker_save',
467 471 '/settings/issue-tracker/save',
468 472 action='settings_issuetracker_save',
469 473 conditions={'method': ['POST']})
470 474 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
471 475 action='settings_issuetracker_test',
472 476 conditions={'method': ['POST']})
473 477 m.connect('admin_issuetracker_delete',
474 478 '/settings/issue-tracker/delete',
475 479 action='settings_issuetracker_delete',
476 480 conditions={'method': ['DELETE']})
477 481
478 482 m.connect('admin_settings_email', '/settings/email',
479 483 action='settings_email_update',
480 484 conditions={'method': ['POST']})
481 485 m.connect('admin_settings_email', '/settings/email',
482 486 action='settings_email', conditions={'method': ['GET']})
483 487
484 488 m.connect('admin_settings_hooks', '/settings/hooks',
485 489 action='settings_hooks_update',
486 490 conditions={'method': ['POST', 'DELETE']})
487 491 m.connect('admin_settings_hooks', '/settings/hooks',
488 492 action='settings_hooks', conditions={'method': ['GET']})
489 493
490 494 m.connect('admin_settings_search', '/settings/search',
491 495 action='settings_search', conditions={'method': ['GET']})
492 496
493 497 m.connect('admin_settings_supervisor', '/settings/supervisor',
494 498 action='settings_supervisor', conditions={'method': ['GET']})
495 499 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
496 500 action='settings_supervisor_log', conditions={'method': ['GET']})
497 501
498 502 m.connect('admin_settings_labs', '/settings/labs',
499 503 action='settings_labs_update',
500 504 conditions={'method': ['POST']})
501 505 m.connect('admin_settings_labs', '/settings/labs',
502 506 action='settings_labs', conditions={'method': ['GET']})
503 507
504 508 # ADMIN MY ACCOUNT
505 509 with rmap.submapper(path_prefix=ADMIN_PREFIX,
506 510 controller='admin/my_account') as m:
507 511
508 512 m.connect('my_account_edit', '/my_account/edit',
509 513 action='my_account_edit', conditions={'method': ['GET']})
510 514 m.connect('my_account', '/my_account/update',
511 515 action='my_account_update', conditions={'method': ['POST']})
512 516
513 517 # NOTE(marcink): this needs to be kept for password force flag to be
514 518 # handler, remove after migration to pyramid
515 519 m.connect('my_account_password', '/my_account/password',
516 520 action='my_account_password', conditions={'method': ['GET']})
517 521
518 522 m.connect('my_account_repos', '/my_account/repos',
519 523 action='my_account_repos', conditions={'method': ['GET']})
520 524
521 525 m.connect('my_account_watched', '/my_account/watched',
522 526 action='my_account_watched', conditions={'method': ['GET']})
523 527
524 528 m.connect('my_account_pullrequests', '/my_account/pull_requests',
525 529 action='my_account_pullrequests', conditions={'method': ['GET']})
526 530
527 531 m.connect('my_account_perms', '/my_account/perms',
528 532 action='my_account_perms', conditions={'method': ['GET']})
529 533
530 534 m.connect('my_account_emails', '/my_account/emails',
531 535 action='my_account_emails', conditions={'method': ['GET']})
532 536 m.connect('my_account_emails', '/my_account/emails',
533 537 action='my_account_emails_add', conditions={'method': ['POST']})
534 538 m.connect('my_account_emails', '/my_account/emails',
535 539 action='my_account_emails_delete', conditions={'method': ['DELETE']})
536 540
537 541 m.connect('my_account_notifications', '/my_account/notifications',
538 542 action='my_notifications',
539 543 conditions={'method': ['GET']})
540 544 m.connect('my_account_notifications_toggle_visibility',
541 545 '/my_account/toggle_visibility',
542 546 action='my_notifications_toggle_visibility',
543 547 conditions={'method': ['POST']})
544 548 m.connect('my_account_notifications_test_channelstream',
545 549 '/my_account/test_channelstream',
546 550 action='my_account_notifications_test_channelstream',
547 551 conditions={'method': ['POST']})
548 552
549 553 # NOTIFICATION REST ROUTES
550 554 with rmap.submapper(path_prefix=ADMIN_PREFIX,
551 555 controller='admin/notifications') as m:
552 556 m.connect('notifications', '/notifications',
553 557 action='index', conditions={'method': ['GET']})
554 558 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
555 559 action='mark_all_read', conditions={'method': ['POST']})
556 560 m.connect('/notifications/{notification_id}',
557 561 action='update', conditions={'method': ['PUT']})
558 562 m.connect('/notifications/{notification_id}',
559 563 action='delete', conditions={'method': ['DELETE']})
560 564 m.connect('notification', '/notifications/{notification_id}',
561 565 action='show', conditions={'method': ['GET']})
562 566
563 567 # ADMIN GIST
564 568 with rmap.submapper(path_prefix=ADMIN_PREFIX,
565 569 controller='admin/gists') as m:
566 570 m.connect('gists', '/gists',
567 571 action='create', conditions={'method': ['POST']})
568 572 m.connect('gists', '/gists', jsroute=True,
569 573 action='index', conditions={'method': ['GET']})
570 574 m.connect('new_gist', '/gists/new', jsroute=True,
571 575 action='new', conditions={'method': ['GET']})
572 576
573 577 m.connect('/gists/{gist_id}',
574 578 action='delete', conditions={'method': ['DELETE']})
575 579 m.connect('edit_gist', '/gists/{gist_id}/edit',
576 580 action='edit_form', conditions={'method': ['GET']})
577 581 m.connect('edit_gist', '/gists/{gist_id}/edit',
578 582 action='edit', conditions={'method': ['POST']})
579 583 m.connect(
580 584 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
581 585 action='check_revision', conditions={'method': ['GET']})
582 586
583 587 m.connect('gist', '/gists/{gist_id}',
584 588 action='show', conditions={'method': ['GET']})
585 589 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
586 590 revision='tip',
587 591 action='show', conditions={'method': ['GET']})
588 592 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
589 593 revision='tip',
590 594 action='show', conditions={'method': ['GET']})
591 595 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
592 596 revision='tip',
593 597 action='show', conditions={'method': ['GET']},
594 598 requirements=URL_NAME_REQUIREMENTS)
595 599
596 600 # ADMIN MAIN PAGES
597 601 with rmap.submapper(path_prefix=ADMIN_PREFIX,
598 602 controller='admin/admin') as m:
599 603 m.connect('admin_home', '', action='index')
600 604 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
601 605 action='add_repo')
602 606 m.connect(
603 607 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
604 608 action='pull_requests')
605 609 m.connect(
606 610 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
607 611 action='pull_requests')
608 612 m.connect(
609 613 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
610 614 action='pull_requests')
611 615
612 616 # USER JOURNAL
613 617 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
614 618 controller='journal', action='index')
615 619 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
616 620 controller='journal', action='journal_rss')
617 621 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
618 622 controller='journal', action='journal_atom')
619 623
620 624 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
621 625 controller='journal', action='public_journal')
622 626
623 627 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
624 628 controller='journal', action='public_journal_rss')
625 629
626 630 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
627 631 controller='journal', action='public_journal_rss')
628 632
629 633 rmap.connect('public_journal_atom',
630 634 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
631 635 action='public_journal_atom')
632 636
633 637 rmap.connect('public_journal_atom_old',
634 638 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
635 639 action='public_journal_atom')
636 640
637 641 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
638 642 controller='journal', action='toggle_following', jsroute=True,
639 643 conditions={'method': ['POST']})
640 644
641 645 # FULL TEXT SEARCH
642 646 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
643 647 controller='search')
644 648 rmap.connect('search_repo_home', '/{repo_name}/search',
645 649 controller='search',
646 650 action='index',
647 651 conditions={'function': check_repo},
648 652 requirements=URL_NAME_REQUIREMENTS)
649 653
650 654 # FEEDS
651 655 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
652 656 controller='feed', action='rss',
653 657 conditions={'function': check_repo},
654 658 requirements=URL_NAME_REQUIREMENTS)
655 659
656 660 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
657 661 controller='feed', action='atom',
658 662 conditions={'function': check_repo},
659 663 requirements=URL_NAME_REQUIREMENTS)
660 664
661 665 #==========================================================================
662 666 # REPOSITORY ROUTES
663 667 #==========================================================================
664 668
665 669 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
666 670 controller='admin/repos', action='repo_creating',
667 671 requirements=URL_NAME_REQUIREMENTS)
668 672 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
669 673 controller='admin/repos', action='repo_check',
670 674 requirements=URL_NAME_REQUIREMENTS)
671 675
672 676 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
673 677 controller='summary', action='repo_stats',
674 678 conditions={'function': check_repo},
675 679 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
676 680
677 681 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
678 682 controller='summary', action='repo_refs_data',
679 683 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
680 684 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
681 685 controller='summary', action='repo_refs_changelog_data',
682 686 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
683 687 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
684 688 controller='summary', action='repo_default_reviewers_data',
685 689 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
686 690
687 691 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
688 692 controller='changeset', revision='tip',
689 693 conditions={'function': check_repo},
690 694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
691 695 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
692 696 controller='changeset', revision='tip', action='changeset_children',
693 697 conditions={'function': check_repo},
694 698 requirements=URL_NAME_REQUIREMENTS)
695 699 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
696 700 controller='changeset', revision='tip', action='changeset_parents',
697 701 conditions={'function': check_repo},
698 702 requirements=URL_NAME_REQUIREMENTS)
699 703
700 704 # repo edit options
701 705 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
702 706 controller='admin/repos', action='edit',
703 707 conditions={'method': ['GET'], 'function': check_repo},
704 708 requirements=URL_NAME_REQUIREMENTS)
705 709
706 710 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
707 711 jsroute=True,
708 712 controller='admin/repos', action='edit_permissions',
709 713 conditions={'method': ['GET'], 'function': check_repo},
710 714 requirements=URL_NAME_REQUIREMENTS)
711 715 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
712 716 controller='admin/repos', action='edit_permissions_update',
713 717 conditions={'method': ['PUT'], 'function': check_repo},
714 718 requirements=URL_NAME_REQUIREMENTS)
715 719
716 720 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
717 721 controller='admin/repos', action='edit_fields',
718 722 conditions={'method': ['GET'], 'function': check_repo},
719 723 requirements=URL_NAME_REQUIREMENTS)
720 724 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
721 725 controller='admin/repos', action='create_repo_field',
722 726 conditions={'method': ['PUT'], 'function': check_repo},
723 727 requirements=URL_NAME_REQUIREMENTS)
724 728 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
725 729 controller='admin/repos', action='delete_repo_field',
726 730 conditions={'method': ['DELETE'], 'function': check_repo},
727 731 requirements=URL_NAME_REQUIREMENTS)
728 732
729 733 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
730 734 controller='admin/repos', action='edit_advanced',
731 735 conditions={'method': ['GET'], 'function': check_repo},
732 736 requirements=URL_NAME_REQUIREMENTS)
733 737
734 738 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
735 739 controller='admin/repos', action='edit_advanced_locking',
736 740 conditions={'method': ['PUT'], 'function': check_repo},
737 741 requirements=URL_NAME_REQUIREMENTS)
738 742 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
739 743 controller='admin/repos', action='toggle_locking',
740 744 conditions={'method': ['GET'], 'function': check_repo},
741 745 requirements=URL_NAME_REQUIREMENTS)
742 746
743 747 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
744 748 controller='admin/repos', action='edit_advanced_journal',
745 749 conditions={'method': ['PUT'], 'function': check_repo},
746 750 requirements=URL_NAME_REQUIREMENTS)
747 751
748 752 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
749 753 controller='admin/repos', action='edit_advanced_fork',
750 754 conditions={'method': ['PUT'], 'function': check_repo},
751 755 requirements=URL_NAME_REQUIREMENTS)
752 756
753 757 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
754 758 controller='admin/repos', action='edit_caches_form',
755 759 conditions={'method': ['GET'], 'function': check_repo},
756 760 requirements=URL_NAME_REQUIREMENTS)
757 761 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
758 762 controller='admin/repos', action='edit_caches',
759 763 conditions={'method': ['PUT'], 'function': check_repo},
760 764 requirements=URL_NAME_REQUIREMENTS)
761 765
762 766 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
763 767 controller='admin/repos', action='edit_remote_form',
764 768 conditions={'method': ['GET'], 'function': check_repo},
765 769 requirements=URL_NAME_REQUIREMENTS)
766 770 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
767 771 controller='admin/repos', action='edit_remote',
768 772 conditions={'method': ['PUT'], 'function': check_repo},
769 773 requirements=URL_NAME_REQUIREMENTS)
770 774
771 775 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
772 776 controller='admin/repos', action='edit_statistics_form',
773 777 conditions={'method': ['GET'], 'function': check_repo},
774 778 requirements=URL_NAME_REQUIREMENTS)
775 779 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
776 780 controller='admin/repos', action='edit_statistics',
777 781 conditions={'method': ['PUT'], 'function': check_repo},
778 782 requirements=URL_NAME_REQUIREMENTS)
779 783 rmap.connect('repo_settings_issuetracker',
780 784 '/{repo_name}/settings/issue-tracker',
781 785 controller='admin/repos', action='repo_issuetracker',
782 786 conditions={'method': ['GET'], 'function': check_repo},
783 787 requirements=URL_NAME_REQUIREMENTS)
784 788 rmap.connect('repo_issuetracker_test',
785 789 '/{repo_name}/settings/issue-tracker/test',
786 790 controller='admin/repos', action='repo_issuetracker_test',
787 791 conditions={'method': ['POST'], 'function': check_repo},
788 792 requirements=URL_NAME_REQUIREMENTS)
789 793 rmap.connect('repo_issuetracker_delete',
790 794 '/{repo_name}/settings/issue-tracker/delete',
791 795 controller='admin/repos', action='repo_issuetracker_delete',
792 796 conditions={'method': ['DELETE'], 'function': check_repo},
793 797 requirements=URL_NAME_REQUIREMENTS)
794 798 rmap.connect('repo_issuetracker_save',
795 799 '/{repo_name}/settings/issue-tracker/save',
796 800 controller='admin/repos', action='repo_issuetracker_save',
797 801 conditions={'method': ['POST'], 'function': check_repo},
798 802 requirements=URL_NAME_REQUIREMENTS)
799 803 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
800 804 controller='admin/repos', action='repo_settings_vcs_update',
801 805 conditions={'method': ['POST'], 'function': check_repo},
802 806 requirements=URL_NAME_REQUIREMENTS)
803 807 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
804 808 controller='admin/repos', action='repo_settings_vcs',
805 809 conditions={'method': ['GET'], 'function': check_repo},
806 810 requirements=URL_NAME_REQUIREMENTS)
807 811 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
808 812 controller='admin/repos', action='repo_delete_svn_pattern',
809 813 conditions={'method': ['DELETE'], 'function': check_repo},
810 814 requirements=URL_NAME_REQUIREMENTS)
811 815 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
812 816 controller='admin/repos', action='repo_settings_pullrequest',
813 817 conditions={'method': ['GET', 'POST'], 'function': check_repo},
814 818 requirements=URL_NAME_REQUIREMENTS)
815 819
816 820 # still working url for backward compat.
817 821 rmap.connect('raw_changeset_home_depraced',
818 822 '/{repo_name}/raw-changeset/{revision}',
819 823 controller='changeset', action='changeset_raw',
820 824 revision='tip', conditions={'function': check_repo},
821 825 requirements=URL_NAME_REQUIREMENTS)
822 826
823 827 # new URLs
824 828 rmap.connect('changeset_raw_home',
825 829 '/{repo_name}/changeset-diff/{revision}',
826 830 controller='changeset', action='changeset_raw',
827 831 revision='tip', conditions={'function': check_repo},
828 832 requirements=URL_NAME_REQUIREMENTS)
829 833
830 834 rmap.connect('changeset_patch_home',
831 835 '/{repo_name}/changeset-patch/{revision}',
832 836 controller='changeset', action='changeset_patch',
833 837 revision='tip', conditions={'function': check_repo},
834 838 requirements=URL_NAME_REQUIREMENTS)
835 839
836 840 rmap.connect('changeset_download_home',
837 841 '/{repo_name}/changeset-download/{revision}',
838 842 controller='changeset', action='changeset_download',
839 843 revision='tip', conditions={'function': check_repo},
840 844 requirements=URL_NAME_REQUIREMENTS)
841 845
842 846 rmap.connect('changeset_comment',
843 847 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
844 848 controller='changeset', revision='tip', action='comment',
845 849 conditions={'function': check_repo},
846 850 requirements=URL_NAME_REQUIREMENTS)
847 851
848 852 rmap.connect('changeset_comment_preview',
849 853 '/{repo_name}/changeset/comment/preview', jsroute=True,
850 854 controller='changeset', action='preview_comment',
851 855 conditions={'function': check_repo, 'method': ['POST']},
852 856 requirements=URL_NAME_REQUIREMENTS)
853 857
854 858 rmap.connect('changeset_comment_delete',
855 859 '/{repo_name}/changeset/comment/{comment_id}/delete',
856 860 controller='changeset', action='delete_comment',
857 861 conditions={'function': check_repo, 'method': ['DELETE']},
858 862 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
859 863
860 864 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
861 865 controller='changeset', action='changeset_info',
862 866 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
863 867
864 868 rmap.connect('compare_home',
865 869 '/{repo_name}/compare',
866 870 controller='compare', action='index',
867 871 conditions={'function': check_repo},
868 872 requirements=URL_NAME_REQUIREMENTS)
869 873
870 874 rmap.connect('compare_url',
871 875 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
872 876 controller='compare', action='compare',
873 877 conditions={'function': check_repo},
874 878 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875 879
876 880 rmap.connect('pullrequest_home',
877 881 '/{repo_name}/pull-request/new', controller='pullrequests',
878 882 action='index', conditions={'function': check_repo,
879 883 'method': ['GET']},
880 884 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
881 885
882 886 rmap.connect('pullrequest',
883 887 '/{repo_name}/pull-request/new', controller='pullrequests',
884 888 action='create', conditions={'function': check_repo,
885 889 'method': ['POST']},
886 890 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
887 891
888 892 rmap.connect('pullrequest_repo_refs',
889 893 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
890 894 controller='pullrequests',
891 895 action='get_repo_refs',
892 896 conditions={'function': check_repo, 'method': ['GET']},
893 897 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894 898
895 899 rmap.connect('pullrequest_repo_destinations',
896 900 '/{repo_name}/pull-request/repo-destinations',
897 901 controller='pullrequests',
898 902 action='get_repo_destinations',
899 903 conditions={'function': check_repo, 'method': ['GET']},
900 904 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
901 905
902 906 rmap.connect('pullrequest_show',
903 907 '/{repo_name}/pull-request/{pull_request_id}',
904 908 controller='pullrequests',
905 909 action='show', conditions={'function': check_repo,
906 910 'method': ['GET']},
907 911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
908 912
909 913 rmap.connect('pullrequest_update',
910 914 '/{repo_name}/pull-request/{pull_request_id}',
911 915 controller='pullrequests',
912 916 action='update', conditions={'function': check_repo,
913 917 'method': ['PUT']},
914 918 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
915 919
916 920 rmap.connect('pullrequest_merge',
917 921 '/{repo_name}/pull-request/{pull_request_id}',
918 922 controller='pullrequests',
919 923 action='merge', conditions={'function': check_repo,
920 924 'method': ['POST']},
921 925 requirements=URL_NAME_REQUIREMENTS)
922 926
923 927 rmap.connect('pullrequest_delete',
924 928 '/{repo_name}/pull-request/{pull_request_id}',
925 929 controller='pullrequests',
926 930 action='delete', conditions={'function': check_repo,
927 931 'method': ['DELETE']},
928 932 requirements=URL_NAME_REQUIREMENTS)
929 933
930 934 rmap.connect('pullrequest_show_all',
931 935 '/{repo_name}/pull-request',
932 936 controller='pullrequests',
933 937 action='show_all', conditions={'function': check_repo,
934 938 'method': ['GET']},
935 939 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
936 940
937 941 rmap.connect('pullrequest_comment',
938 942 '/{repo_name}/pull-request-comment/{pull_request_id}',
939 943 controller='pullrequests',
940 944 action='comment', conditions={'function': check_repo,
941 945 'method': ['POST']},
942 946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
943 947
944 948 rmap.connect('pullrequest_comment_delete',
945 949 '/{repo_name}/pull-request-comment/{comment_id}/delete',
946 950 controller='pullrequests', action='delete_comment',
947 951 conditions={'function': check_repo, 'method': ['DELETE']},
948 952 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
949 953
950 954 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
951 955 controller='summary', conditions={'function': check_repo},
952 956 requirements=URL_NAME_REQUIREMENTS)
953 957
954 958 rmap.connect('branches_home', '/{repo_name}/branches',
955 959 controller='branches', conditions={'function': check_repo},
956 960 requirements=URL_NAME_REQUIREMENTS)
957 961
958 962 rmap.connect('tags_home', '/{repo_name}/tags',
959 963 controller='tags', conditions={'function': check_repo},
960 964 requirements=URL_NAME_REQUIREMENTS)
961 965
962 966 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
963 967 controller='bookmarks', conditions={'function': check_repo},
964 968 requirements=URL_NAME_REQUIREMENTS)
965 969
966 970 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
967 971 controller='changelog', conditions={'function': check_repo},
968 972 requirements=URL_NAME_REQUIREMENTS)
969 973
970 974 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
971 975 controller='changelog', action='changelog_summary',
972 976 conditions={'function': check_repo},
973 977 requirements=URL_NAME_REQUIREMENTS)
974 978
975 979 rmap.connect('changelog_file_home',
976 980 '/{repo_name}/changelog/{revision}/{f_path}',
977 981 controller='changelog', f_path=None,
978 982 conditions={'function': check_repo},
979 983 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
980 984
981 985 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
982 986 controller='changelog', action='changelog_elements',
983 987 conditions={'function': check_repo},
984 988 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
985 989
986 990 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
987 991 controller='files', revision='tip', f_path='',
988 992 conditions={'function': check_repo},
989 993 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
990 994
991 995 rmap.connect('files_home_simple_catchrev',
992 996 '/{repo_name}/files/{revision}',
993 997 controller='files', revision='tip', f_path='',
994 998 conditions={'function': check_repo},
995 999 requirements=URL_NAME_REQUIREMENTS)
996 1000
997 1001 rmap.connect('files_home_simple_catchall',
998 1002 '/{repo_name}/files',
999 1003 controller='files', revision='tip', f_path='',
1000 1004 conditions={'function': check_repo},
1001 1005 requirements=URL_NAME_REQUIREMENTS)
1002 1006
1003 1007 rmap.connect('files_history_home',
1004 1008 '/{repo_name}/history/{revision}/{f_path}',
1005 1009 controller='files', action='history', revision='tip', f_path='',
1006 1010 conditions={'function': check_repo},
1007 1011 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1008 1012
1009 1013 rmap.connect('files_authors_home',
1010 1014 '/{repo_name}/authors/{revision}/{f_path}',
1011 1015 controller='files', action='authors', revision='tip', f_path='',
1012 1016 conditions={'function': check_repo},
1013 1017 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1014 1018
1015 1019 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1016 1020 controller='files', action='diff', f_path='',
1017 1021 conditions={'function': check_repo},
1018 1022 requirements=URL_NAME_REQUIREMENTS)
1019 1023
1020 1024 rmap.connect('files_diff_2way_home',
1021 1025 '/{repo_name}/diff-2way/{f_path}',
1022 1026 controller='files', action='diff_2way', f_path='',
1023 1027 conditions={'function': check_repo},
1024 1028 requirements=URL_NAME_REQUIREMENTS)
1025 1029
1026 1030 rmap.connect('files_rawfile_home',
1027 1031 '/{repo_name}/rawfile/{revision}/{f_path}',
1028 1032 controller='files', action='rawfile', revision='tip',
1029 1033 f_path='', conditions={'function': check_repo},
1030 1034 requirements=URL_NAME_REQUIREMENTS)
1031 1035
1032 1036 rmap.connect('files_raw_home',
1033 1037 '/{repo_name}/raw/{revision}/{f_path}',
1034 1038 controller='files', action='raw', revision='tip', f_path='',
1035 1039 conditions={'function': check_repo},
1036 1040 requirements=URL_NAME_REQUIREMENTS)
1037 1041
1038 1042 rmap.connect('files_render_home',
1039 1043 '/{repo_name}/render/{revision}/{f_path}',
1040 1044 controller='files', action='index', revision='tip', f_path='',
1041 1045 rendered=True, conditions={'function': check_repo},
1042 1046 requirements=URL_NAME_REQUIREMENTS)
1043 1047
1044 1048 rmap.connect('files_annotate_home',
1045 1049 '/{repo_name}/annotate/{revision}/{f_path}',
1046 1050 controller='files', action='index', revision='tip',
1047 1051 f_path='', annotate=True, conditions={'function': check_repo},
1048 1052 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1049 1053
1050 1054 rmap.connect('files_annotate_previous',
1051 1055 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1052 1056 controller='files', action='annotate_previous', revision='tip',
1053 1057 f_path='', annotate=True, conditions={'function': check_repo},
1054 1058 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1055 1059
1056 1060 rmap.connect('files_edit',
1057 1061 '/{repo_name}/edit/{revision}/{f_path}',
1058 1062 controller='files', action='edit', revision='tip',
1059 1063 f_path='',
1060 1064 conditions={'function': check_repo, 'method': ['POST']},
1061 1065 requirements=URL_NAME_REQUIREMENTS)
1062 1066
1063 1067 rmap.connect('files_edit_home',
1064 1068 '/{repo_name}/edit/{revision}/{f_path}',
1065 1069 controller='files', action='edit_home', revision='tip',
1066 1070 f_path='', conditions={'function': check_repo},
1067 1071 requirements=URL_NAME_REQUIREMENTS)
1068 1072
1069 1073 rmap.connect('files_add',
1070 1074 '/{repo_name}/add/{revision}/{f_path}',
1071 1075 controller='files', action='add', revision='tip',
1072 1076 f_path='',
1073 1077 conditions={'function': check_repo, 'method': ['POST']},
1074 1078 requirements=URL_NAME_REQUIREMENTS)
1075 1079
1076 1080 rmap.connect('files_add_home',
1077 1081 '/{repo_name}/add/{revision}/{f_path}',
1078 1082 controller='files', action='add_home', revision='tip',
1079 1083 f_path='', conditions={'function': check_repo},
1080 1084 requirements=URL_NAME_REQUIREMENTS)
1081 1085
1082 1086 rmap.connect('files_delete',
1083 1087 '/{repo_name}/delete/{revision}/{f_path}',
1084 1088 controller='files', action='delete', revision='tip',
1085 1089 f_path='',
1086 1090 conditions={'function': check_repo, 'method': ['POST']},
1087 1091 requirements=URL_NAME_REQUIREMENTS)
1088 1092
1089 1093 rmap.connect('files_delete_home',
1090 1094 '/{repo_name}/delete/{revision}/{f_path}',
1091 1095 controller='files', action='delete_home', revision='tip',
1092 1096 f_path='', conditions={'function': check_repo},
1093 1097 requirements=URL_NAME_REQUIREMENTS)
1094 1098
1095 1099 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1096 1100 controller='files', action='archivefile',
1097 1101 conditions={'function': check_repo},
1098 1102 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1099 1103
1100 1104 rmap.connect('files_nodelist_home',
1101 1105 '/{repo_name}/nodelist/{revision}/{f_path}',
1102 1106 controller='files', action='nodelist',
1103 1107 conditions={'function': check_repo},
1104 1108 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105 1109
1106 1110 rmap.connect('files_nodetree_full',
1107 1111 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1108 1112 controller='files', action='nodetree_full',
1109 1113 conditions={'function': check_repo},
1110 1114 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 1115
1112 1116 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1113 1117 controller='forks', action='fork_create',
1114 1118 conditions={'function': check_repo, 'method': ['POST']},
1115 1119 requirements=URL_NAME_REQUIREMENTS)
1116 1120
1117 1121 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1118 1122 controller='forks', action='fork',
1119 1123 conditions={'function': check_repo},
1120 1124 requirements=URL_NAME_REQUIREMENTS)
1121 1125
1122 1126 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1123 1127 controller='forks', action='forks',
1124 1128 conditions={'function': check_repo},
1125 1129 requirements=URL_NAME_REQUIREMENTS)
1126 1130
1127 1131 # must be here for proper group/repo catching pattern
1128 1132 _connect_with_slash(
1129 1133 rmap, 'repo_group_home', '/{group_name}',
1130 1134 controller='home', action='index_repo_group',
1131 1135 conditions={'function': check_group},
1132 1136 requirements=URL_NAME_REQUIREMENTS)
1133 1137
1134 1138 # catch all, at the end
1135 1139 _connect_with_slash(
1136 1140 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1137 1141 controller='summary', action='index',
1138 1142 conditions={'function': check_repo},
1139 1143 requirements=URL_NAME_REQUIREMENTS)
1140 1144
1141 1145 return rmap
1142 1146
1143 1147
1144 1148 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1145 1149 """
1146 1150 Connect a route with an optional trailing slash in `path`.
1147 1151 """
1148 1152 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1149 1153 mapper.connect(name, path, *args, **kwargs)
@@ -1,487 +1,517 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 User Groups crud controller for pylons
23 23 """
24 24
25 25 import logging
26 26 import formencode
27 27
28 28 import peppercorn
29 29 from formencode import htmlfill
30 30 from pylons import request, tmpl_context as c, url, config
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib import auth
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.exceptions import UserGroupAssignedException,\
39 39 RepoGroupAssignmentError
40 40 from rhodecode.lib.utils import jsonify, action_logger
41 41 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
44 44 HasPermissionAnyDecorator, XHRRequired)
45 45 from rhodecode.lib.base import BaseController, render
46 46 from rhodecode.model.permission import PermissionModel
47 47 from rhodecode.model.scm import UserGroupList
48 48 from rhodecode.model.user_group import UserGroupModel
49 49 from rhodecode.model.db import (
50 50 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
51 51 from rhodecode.model.forms import (
52 52 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
53 53 UserPermissionsForm)
54 54 from rhodecode.model.meta import Session
55 55 from rhodecode.lib.utils import action_logger
56 56 from rhodecode.lib.ext_json import json
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 class UserGroupsController(BaseController):
62 62 """REST Controller styled on the Atom Publishing Protocol"""
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 super(UserGroupsController, self).__before__()
67 67 c.available_permissions = config['available_permissions']
68 68 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 69
70 70 def __load_data(self, user_group_id):
71 71 c.group_members_obj = [x.user for x in c.user_group.members]
72 72 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 74
75 75 def __load_defaults(self, user_group_id):
76 76 """
77 77 Load defaults settings for edit, and update
78 78
79 79 :param user_group_id:
80 80 """
81 81 user_group = UserGroup.get_or_404(user_group_id)
82 82 data = user_group.get_dict()
83 83 # fill owner
84 84 if user_group.user:
85 85 data.update({'user': user_group.user.username})
86 86 else:
87 87 replacement_user = User.get_first_super_admin().username
88 88 data.update({'user': replacement_user})
89 89 return data
90 90
91 91 def _revoke_perms_on_yourself(self, form_result):
92 92 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 93 form_result['perm_updates'])
94 94 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 95 form_result['perm_additions'])
96 96 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 97 form_result['perm_deletions'])
98 98 admin_perm = 'usergroup.admin'
99 99 if _updates and _updates[0][1] != admin_perm or \
100 100 _additions and _additions[0][1] != admin_perm or \
101 101 _deletions and _deletions[0][1] != admin_perm:
102 102 return True
103 103 return False
104 104
105 105 # permission check inside
106 106 @NotAnonymous()
107 107 def index(self):
108 108 """GET /users_groups: All items in the collection"""
109 109 # url('users_groups')
110 110
111 111 from rhodecode.lib.utils import PartialRenderer
112 112 _render = PartialRenderer('data_table/_dt_elements.mako')
113 113
114 114 def user_group_name(user_group_id, user_group_name):
115 115 return _render("user_group_name", user_group_id, user_group_name)
116 116
117 117 def user_group_actions(user_group_id, user_group_name):
118 118 return _render("user_group_actions", user_group_id, user_group_name)
119 119
120 ## json generate
120 # json generate
121 121 group_iter = UserGroupList(UserGroup.query().all(),
122 122 perm_set=['usergroup.admin'])
123 123
124 124 user_groups_data = []
125 125 for user_gr in group_iter:
126 126 user_groups_data.append({
127 127 "group_name": user_group_name(
128 128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
129 129 "group_name_raw": user_gr.users_group_name,
130 130 "desc": h.escape(user_gr.user_group_description),
131 131 "members": len(user_gr.members),
132 "sync": user_gr.group_data.get('extern_type'),
132 133 "active": h.bool2icon(user_gr.users_group_active),
133 134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 135 "action": user_group_actions(
135 136 user_gr.users_group_id, user_gr.users_group_name)
136 137 })
137 138
138 139 c.data = json.dumps(user_groups_data)
139 140 return render('admin/user_groups/user_groups.mako')
140 141
141 142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
142 143 @auth.CSRFRequired()
143 144 def create(self):
144 145 """POST /users_groups: Create a new item"""
145 146 # url('users_groups')
146 147
147 148 users_group_form = UserGroupForm()()
148 149 try:
149 150 form_result = users_group_form.to_python(dict(request.POST))
150 151 user_group = UserGroupModel().create(
151 152 name=form_result['users_group_name'],
152 153 description=form_result['user_group_description'],
153 154 owner=c.rhodecode_user.user_id,
154 155 active=form_result['users_group_active'])
155 156 Session().flush()
156 157
157 158 user_group_name = form_result['users_group_name']
158 159 action_logger(c.rhodecode_user,
159 160 'admin_created_users_group:%s' % user_group_name,
160 161 None, self.ip_addr, self.sa)
161 162 user_group_link = h.link_to(h.escape(user_group_name),
162 163 url('edit_users_group',
163 164 user_group_id=user_group.users_group_id))
164 165 h.flash(h.literal(_('Created user group %(user_group_link)s')
165 166 % {'user_group_link': user_group_link}),
166 167 category='success')
167 168 Session().commit()
168 169 except formencode.Invalid as errors:
169 170 return htmlfill.render(
170 171 render('admin/user_groups/user_group_add.mako'),
171 172 defaults=errors.value,
172 173 errors=errors.error_dict or {},
173 174 prefix_error=False,
174 175 encoding="UTF-8",
175 176 force_defaults=False)
176 177 except Exception:
177 178 log.exception("Exception creating user group")
178 179 h.flash(_('Error occurred during creation of user group %s') \
179 180 % request.POST.get('users_group_name'), category='error')
180 181
181 182 return redirect(
182 183 url('edit_users_group', user_group_id=user_group.users_group_id))
183 184
184 185 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
185 186 def new(self):
186 187 """GET /user_groups/new: Form to create a new item"""
187 188 # url('new_users_group')
188 189 return render('admin/user_groups/user_group_add.mako')
189 190
190 191 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
191 192 @auth.CSRFRequired()
192 193 def update(self, user_group_id):
193 194 """PUT /user_groups/user_group_id: Update an existing item"""
194 195 # Forms posted to this method should contain a hidden field:
195 196 # <input type="hidden" name="_method" value="PUT" />
196 197 # Or using helpers:
197 198 # h.form(url('users_group', user_group_id=ID),
198 199 # method='put')
199 200 # url('users_group', user_group_id=ID)
200 201
201 202 user_group_id = safe_int(user_group_id)
202 203 c.user_group = UserGroup.get_or_404(user_group_id)
203 204 c.active = 'settings'
204 205 self.__load_data(user_group_id)
205 206
206 207 users_group_form = UserGroupForm(
207 208 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
208 209
209 210 try:
210 211 form_result = users_group_form.to_python(request.POST)
211 212 pstruct = peppercorn.parse(request.POST.items())
212 213 form_result['users_group_members'] = pstruct['user_group_members']
213 214
214 215 UserGroupModel().update(c.user_group, form_result)
215 216 updated_user_group = form_result['users_group_name']
216 217 action_logger(c.rhodecode_user,
217 218 'admin_updated_users_group:%s' % updated_user_group,
218 219 None, self.ip_addr, self.sa)
219 220 h.flash(_('Updated user group %s') % updated_user_group,
220 221 category='success')
221 222 Session().commit()
222 223 except formencode.Invalid as errors:
223 224 defaults = errors.value
224 225 e = errors.error_dict or {}
225 226
226 227 return htmlfill.render(
227 228 render('admin/user_groups/user_group_edit.mako'),
228 229 defaults=defaults,
229 230 errors=e,
230 231 prefix_error=False,
231 232 encoding="UTF-8",
232 233 force_defaults=False)
233 234 except Exception:
234 235 log.exception("Exception during update of user group")
235 236 h.flash(_('Error occurred during update of user group %s')
236 237 % request.POST.get('users_group_name'), category='error')
237 238
238 239 return redirect(url('edit_users_group', user_group_id=user_group_id))
239 240
240 241 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
241 242 @auth.CSRFRequired()
242 243 def delete(self, user_group_id):
243 244 """DELETE /user_groups/user_group_id: Delete an existing item"""
244 245 # Forms posted to this method should contain a hidden field:
245 246 # <input type="hidden" name="_method" value="DELETE" />
246 247 # Or using helpers:
247 248 # h.form(url('users_group', user_group_id=ID),
248 249 # method='delete')
249 250 # url('users_group', user_group_id=ID)
250 251 user_group_id = safe_int(user_group_id)
251 252 c.user_group = UserGroup.get_or_404(user_group_id)
252 253 force = str2bool(request.POST.get('force'))
253 254
254 255 try:
255 256 UserGroupModel().delete(c.user_group, force=force)
256 257 Session().commit()
257 258 h.flash(_('Successfully deleted user group'), category='success')
258 259 except UserGroupAssignedException as e:
259 260 h.flash(str(e), category='error')
260 261 except Exception:
261 262 log.exception("Exception during deletion of user group")
262 263 h.flash(_('An error occurred during deletion of user group'),
263 264 category='error')
264 265 return redirect(url('users_groups'))
265 266
266 267 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
267 268 def edit(self, user_group_id):
268 269 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
269 270 # url('edit_users_group', user_group_id=ID)
270 271
271 272 user_group_id = safe_int(user_group_id)
272 273 c.user_group = UserGroup.get_or_404(user_group_id)
273 274 c.active = 'settings'
274 275 self.__load_data(user_group_id)
275 276
276 277 defaults = self.__load_defaults(user_group_id)
277 278
278 279 return htmlfill.render(
279 280 render('admin/user_groups/user_group_edit.mako'),
280 281 defaults=defaults,
281 282 encoding="UTF-8",
282 283 force_defaults=False
283 284 )
284 285
285 286 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
286 287 def edit_perms(self, user_group_id):
287 288 user_group_id = safe_int(user_group_id)
288 289 c.user_group = UserGroup.get_or_404(user_group_id)
289 290 c.active = 'perms'
290 291
291 292 defaults = {}
292 293 # fill user group users
293 294 for p in c.user_group.user_user_group_to_perm:
294 295 defaults.update({'u_perm_%s' % p.user.user_id:
295 296 p.permission.permission_name})
296 297
297 298 for p in c.user_group.user_group_user_group_to_perm:
298 299 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
299 300 p.permission.permission_name})
300 301
301 302 return htmlfill.render(
302 303 render('admin/user_groups/user_group_edit.mako'),
303 304 defaults=defaults,
304 305 encoding="UTF-8",
305 306 force_defaults=False
306 307 )
307 308
308 309 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
309 310 @auth.CSRFRequired()
310 311 def update_perms(self, user_group_id):
311 312 """
312 313 grant permission for given usergroup
313 314
314 315 :param user_group_id:
315 316 """
316 317 user_group_id = safe_int(user_group_id)
317 318 c.user_group = UserGroup.get_or_404(user_group_id)
318 319 form = UserGroupPermsForm()().to_python(request.POST)
319 320
320 321 if not c.rhodecode_user.is_admin:
321 322 if self._revoke_perms_on_yourself(form):
322 323 msg = _('Cannot change permission for yourself as admin')
323 324 h.flash(msg, category='warning')
324 325 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
325 326
326 327 try:
327 328 UserGroupModel().update_permissions(user_group_id,
328 329 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
329 330 except RepoGroupAssignmentError:
330 331 h.flash(_('Target group cannot be the same'), category='error')
331 332 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
332 333 #TODO: implement this
333 334 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
334 335 # repo_name, self.ip_addr, self.sa)
335 336 Session().commit()
336 337 h.flash(_('User Group permissions updated'), category='success')
337 338 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
338 339
339 340 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
340 341 def edit_perms_summary(self, user_group_id):
341 342 user_group_id = safe_int(user_group_id)
342 343 c.user_group = UserGroup.get_or_404(user_group_id)
343 344 c.active = 'perms_summary'
344 345 permissions = {
345 346 'repositories': {},
346 347 'repositories_groups': {},
347 348 }
348 349 ugroup_repo_perms = UserGroupRepoToPerm.query()\
349 350 .options(joinedload(UserGroupRepoToPerm.permission))\
350 351 .options(joinedload(UserGroupRepoToPerm.repository))\
351 352 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
352 353 .all()
353 354
354 355 for gr in ugroup_repo_perms:
355 356 permissions['repositories'][gr.repository.repo_name] \
356 357 = gr.permission.permission_name
357 358
358 359 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
359 360 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
360 361 .options(joinedload(UserGroupRepoGroupToPerm.group))\
361 362 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
362 363 .all()
363 364
364 365 for gr in ugroup_group_perms:
365 366 permissions['repositories_groups'][gr.group.group_name] \
366 367 = gr.permission.permission_name
367 368 c.permissions = permissions
368 369 return render('admin/user_groups/user_group_edit.mako')
369 370
370 371 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
371 372 def edit_global_perms(self, user_group_id):
372 373 user_group_id = safe_int(user_group_id)
373 374 c.user_group = UserGroup.get_or_404(user_group_id)
374 375 c.active = 'global_perms'
375 376
376 377 c.default_user = User.get_default_user()
377 378 defaults = c.user_group.get_dict()
378 379 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
379 380 defaults.update(c.user_group.get_default_perms())
380 381
381 382 return htmlfill.render(
382 383 render('admin/user_groups/user_group_edit.mako'),
383 384 defaults=defaults,
384 385 encoding="UTF-8",
385 386 force_defaults=False
386 387 )
387 388
388 389 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
389 390 @auth.CSRFRequired()
390 391 def update_global_perms(self, user_group_id):
391 392 """PUT /users_perm/user_group_id: Update an existing item"""
392 393 # url('users_group_perm', user_group_id=ID, method='put')
393 394 user_group_id = safe_int(user_group_id)
394 395 user_group = UserGroup.get_or_404(user_group_id)
395 396 c.active = 'global_perms'
396 397
397 398 try:
398 399 # first stage that verifies the checkbox
399 400 _form = UserIndividualPermissionsForm()
400 401 form_result = _form.to_python(dict(request.POST))
401 402 inherit_perms = form_result['inherit_default_permissions']
402 403 user_group.inherit_default_permissions = inherit_perms
403 404 Session().add(user_group)
404 405
405 406 if not inherit_perms:
406 407 # only update the individual ones if we un check the flag
407 408 _form = UserPermissionsForm(
408 409 [x[0] for x in c.repo_create_choices],
409 410 [x[0] for x in c.repo_create_on_write_choices],
410 411 [x[0] for x in c.repo_group_create_choices],
411 412 [x[0] for x in c.user_group_create_choices],
412 413 [x[0] for x in c.fork_choices],
413 414 [x[0] for x in c.inherit_default_permission_choices])()
414 415
415 416 form_result = _form.to_python(dict(request.POST))
416 417 form_result.update({'perm_user_group_id': user_group.users_group_id})
417 418
418 419 PermissionModel().update_user_group_permissions(form_result)
419 420
420 421 Session().commit()
421 422 h.flash(_('User Group global permissions updated successfully'),
422 423 category='success')
423 424
424 425 except formencode.Invalid as errors:
425 426 defaults = errors.value
426 427 c.user_group = user_group
427 428 return htmlfill.render(
428 429 render('admin/user_groups/user_group_edit.mako'),
429 430 defaults=defaults,
430 431 errors=errors.error_dict or {},
431 432 prefix_error=False,
432 433 encoding="UTF-8",
433 434 force_defaults=False)
434
435 435 except Exception:
436 436 log.exception("Exception during permissions saving")
437 437 h.flash(_('An error occurred during permissions saving'),
438 438 category='error')
439 439
440 440 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
441 441
442 442 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
443 443 def edit_advanced(self, user_group_id):
444 444 user_group_id = safe_int(user_group_id)
445 445 c.user_group = UserGroup.get_or_404(user_group_id)
446 446 c.active = 'advanced'
447 447 c.group_members_obj = sorted(
448 448 (x.user for x in c.user_group.members),
449 449 key=lambda u: u.username.lower())
450 450
451 451 c.group_to_repos = sorted(
452 452 (x.repository for x in c.user_group.users_group_repo_to_perm),
453 453 key=lambda u: u.repo_name.lower())
454 454
455 455 c.group_to_repo_groups = sorted(
456 456 (x.group for x in c.user_group.users_group_repo_group_to_perm),
457 457 key=lambda u: u.group_name.lower())
458 458
459 459 return render('admin/user_groups/user_group_edit.mako')
460 460
461 461 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
462 def edit_advanced_set_synchronization(self, user_group_id):
463 user_group_id = safe_int(user_group_id)
464 user_group = UserGroup.get_or_404(user_group_id)
465
466 existing = user_group.group_data.get('extern_type')
467
468 if existing:
469 new_state = user_group.group_data
470 new_state['extern_type'] = None
471 else:
472 new_state = user_group.group_data
473 new_state['extern_type'] = 'manual'
474 new_state['extern_type_set_by'] = c.rhodecode_user.username
475
476 try:
477 user_group.group_data = new_state
478 Session().add(user_group)
479 Session().commit()
480
481 h.flash(_('User Group synchronization updated successfully'),
482 category='success')
483 except Exception:
484 log.exception("Exception during sync settings saving")
485 h.flash(_('An error occurred during synchronization update'),
486 category='error')
487
488 return redirect(
489 url('edit_user_group_advanced', user_group_id=user_group_id))
490
491 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
462 492 @XHRRequired()
463 493 @jsonify
464 494 def user_group_members(self, user_group_id):
465 495 user_group_id = safe_int(user_group_id)
466 496 user_group = UserGroup.get_or_404(user_group_id)
467 497 group_members_obj = sorted((x.user for x in user_group.members),
468 498 key=lambda u: u.username.lower())
469 499
470 500 group_members = [
471 501 {
472 502 'id': user.user_id,
473 503 'first_name': user.name,
474 504 'last_name': user.lastname,
475 505 'username': user.username,
476 506 'icon_link': h.gravatar_url(user.email, 30),
477 507 'value_display': h.person(user.email),
478 508 'value': user.username,
479 509 'value_type': 'user',
480 510 'active': user.active,
481 511 }
482 512 for user in group_members_obj
483 513 ]
484 514
485 515 return {
486 516 'members': group_members
487 517 }
@@ -1,560 +1,559 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 """
23 23 user group model for RhodeCode
24 24 """
25 25
26 26
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.lib.utils2 import safe_str
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 36 RepoGroupAssignmentError
37 37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class UserGroupModel(BaseModel):
43 43
44 44 cls = UserGroup
45 45
46 46 def _get_user_group(self, user_group):
47 47 return self._get_instance(UserGroup, user_group,
48 48 callback=UserGroup.get_by_group_name)
49 49
50 50 def _create_default_perms(self, user_group):
51 51 # create default permission
52 52 default_perm = 'usergroup.read'
53 53 def_user = User.get_default_user()
54 54 for p in def_user.user_perms:
55 55 if p.permission.permission_name.startswith('usergroup.'):
56 56 default_perm = p.permission.permission_name
57 57 break
58 58
59 59 user_group_to_perm = UserUserGroupToPerm()
60 60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61 61
62 62 user_group_to_perm.user_group = user_group
63 63 user_group_to_perm.user_id = def_user.user_id
64 64 return user_group_to_perm
65 65
66 66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 67 perm_deletions=None, check_perms=True, cur_user=None):
68 68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 69 if not perm_additions:
70 70 perm_additions = []
71 71 if not perm_updates:
72 72 perm_updates = []
73 73 if not perm_deletions:
74 74 perm_deletions = []
75 75
76 76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77 77
78 78 # update permissions
79 79 for member_id, perm, member_type in perm_updates:
80 80 member_id = int(member_id)
81 81 if member_type == 'user':
82 82 # this updates existing one
83 83 self.grant_user_permission(
84 84 user_group=user_group, user=member_id, perm=perm
85 85 )
86 86 else:
87 87 # check if we have permissions to alter this usergroup
88 88 member_name = UserGroup.get(member_id).users_group_name
89 89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 90 self.grant_user_group_permission(
91 91 target_user_group=user_group, user_group=member_id, perm=perm
92 92 )
93 93
94 94 # set new permissions
95 95 for member_id, perm, member_type in perm_additions:
96 96 member_id = int(member_id)
97 97 if member_type == 'user':
98 98 self.grant_user_permission(
99 99 user_group=user_group, user=member_id, perm=perm
100 100 )
101 101 else:
102 102 # check if we have permissions to alter this usergroup
103 103 member_name = UserGroup.get(member_id).users_group_name
104 104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 105 self.grant_user_group_permission(
106 106 target_user_group=user_group, user_group=member_id, perm=perm
107 107 )
108 108
109 109 # delete permissions
110 110 for member_id, perm, member_type in perm_deletions:
111 111 member_id = int(member_id)
112 112 if member_type == 'user':
113 113 self.revoke_user_permission(user_group=user_group, user=member_id)
114 114 else:
115 115 #check if we have permissions to alter this usergroup
116 116 member_name = UserGroup.get(member_id).users_group_name
117 117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 118 self.revoke_user_group_permission(
119 119 target_user_group=user_group, user_group=member_id
120 120 )
121 121
122 122 def get(self, user_group_id, cache=False):
123 123 return UserGroup.get(user_group_id)
124 124
125 125 def get_group(self, user_group):
126 126 return self._get_user_group(user_group)
127 127
128 128 def get_by_name(self, name, cache=False, case_insensitive=False):
129 129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130 130
131 131 def create(self, name, description, owner, active=True, group_data=None):
132 132 try:
133 133 new_user_group = UserGroup()
134 134 new_user_group.user = self._get_user(owner)
135 135 new_user_group.users_group_name = name
136 136 new_user_group.user_group_description = description
137 137 new_user_group.users_group_active = active
138 138 if group_data:
139 139 new_user_group.group_data = group_data
140 140 self.sa.add(new_user_group)
141 141 perm_obj = self._create_default_perms(new_user_group)
142 142 self.sa.add(perm_obj)
143 143
144 144 self.grant_user_permission(user_group=new_user_group,
145 145 user=owner, perm='usergroup.admin')
146 146
147 147 return new_user_group
148 148 except Exception:
149 149 log.error(traceback.format_exc())
150 150 raise
151 151
152 152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 153 members = []
154 154 for user_id in user_id_list:
155 155 member = self._get_membership(user_group.users_group_id, user_id)
156 156 members.append(member)
157 157 return members
158 158
159 159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 160 current_members = user_group.members or []
161 161 current_members_ids = [m.user.user_id for m in current_members]
162 162
163 163 added_members = [
164 164 user_id for user_id in user_id_list
165 165 if user_id not in current_members_ids]
166 166 if user_id_list == []:
167 167 # all members were deleted
168 168 deleted_members = current_members_ids
169 169 else:
170 170 deleted_members = [
171 171 user_id for user_id in current_members_ids
172 172 if user_id not in user_id_list]
173 173
174 174 return (added_members, deleted_members)
175 175
176 176 def _set_users_as_members(self, user_group, user_ids):
177 177 user_group.members = []
178 178 self.sa.flush()
179 179 members = self._get_memberships_for_user_ids(
180 180 user_group, user_ids)
181 181 user_group.members = members
182 182 self.sa.add(user_group)
183 183
184 184 def _update_members_from_user_ids(self, user_group, user_ids):
185 185 added, removed = self._get_added_and_removed_user_ids(
186 186 user_group, user_ids)
187 187 self._set_users_as_members(user_group, user_ids)
188 188 self._log_user_changes('added to', user_group, added)
189 189 self._log_user_changes('removed from', user_group, removed)
190 190
191 191 def _clean_members_data(self, members_data):
192 192 if not members_data:
193 193 members_data = []
194 194
195 195 members = []
196 196 for user in members_data:
197 197 uid = int(user['member_user_id'])
198 198 if uid not in members and user['type'] in ['new', 'existing']:
199 199 members.append(uid)
200 200 return members
201 201
202 202 def update(self, user_group, form_data):
203 203 user_group = self._get_user_group(user_group)
204 204 if 'users_group_name' in form_data:
205 205 user_group.users_group_name = form_data['users_group_name']
206 206 if 'users_group_active' in form_data:
207 207 user_group.users_group_active = form_data['users_group_active']
208 208 if 'user_group_description' in form_data:
209 209 user_group.user_group_description = form_data[
210 210 'user_group_description']
211 211
212 212 # handle owner change
213 213 if 'user' in form_data:
214 214 owner = form_data['user']
215 215 if isinstance(owner, basestring):
216 216 owner = User.get_by_username(form_data['user'])
217 217
218 218 if not isinstance(owner, User):
219 219 raise ValueError(
220 220 'invalid owner for user group: %s' % form_data['user'])
221 221
222 222 user_group.user = owner
223 223
224 224 if 'users_group_members' in form_data:
225 225 members_id_list = self._clean_members_data(
226 226 form_data['users_group_members'])
227 227 self._update_members_from_user_ids(user_group, members_id_list)
228 228
229 229 self.sa.add(user_group)
230 230
231 231 def delete(self, user_group, force=False):
232 232 """
233 233 Deletes repository group, unless force flag is used
234 234 raises exception if there are members in that group, else deletes
235 235 group and users
236 236
237 237 :param user_group:
238 238 :param force:
239 239 """
240 240 user_group = self._get_user_group(user_group)
241 241 try:
242 242 # check if this group is not assigned to repo
243 243 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
244 244 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
245 245 # check if this group is not assigned to repo
246 246 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
247 247 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
248 248
249 249 if (assigned_to_repo or assigned_to_repo_group) and not force:
250 250 assigned = ','.join(map(safe_str,
251 251 assigned_to_repo+assigned_to_repo_group))
252 252
253 253 raise UserGroupAssignedException(
254 254 'UserGroup assigned to %s' % (assigned,))
255 255 self.sa.delete(user_group)
256 256 except Exception:
257 257 log.error(traceback.format_exc())
258 258 raise
259 259
260 260 def _log_user_changes(self, action, user_group, user_or_users):
261 261 users = user_or_users
262 262 if not isinstance(users, (list, tuple)):
263 263 users = [users]
264 264 rhodecode_user = get_current_rhodecode_user()
265 265 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
266 266 group_name = user_group.users_group_name
267 267
268 268 for user_or_user_id in users:
269 269 user = self._get_user(user_or_user_id)
270 270 log_text = 'User {user} {action} {group}'.format(
271 271 action=action, user=user.username, group=group_name)
272 272 log.info('Logging action: {0} by {1} ip:{2}'.format(
273 273 log_text, rhodecode_user, ipaddr))
274 274
275 275 def _find_user_in_group(self, user, user_group):
276 276 user_group_member = None
277 277 for m in user_group.members:
278 278 if m.user_id == user.user_id:
279 279 # Found this user's membership row
280 280 user_group_member = m
281 281 break
282 282
283 283 return user_group_member
284 284
285 285 def _get_membership(self, user_group_id, user_id):
286 286 user_group_member = UserGroupMember(user_group_id, user_id)
287 287 return user_group_member
288 288
289 289 def add_user_to_group(self, user_group, user):
290 290 user_group = self._get_user_group(user_group)
291 291 user = self._get_user(user)
292 292 user_member = self._find_user_in_group(user, user_group)
293 293 if user_member:
294 294 # user already in the group, skip
295 295 return True
296 296
297 297 member = self._get_membership(
298 298 user_group.users_group_id, user.user_id)
299 299 user_group.members.append(member)
300 300
301 301 try:
302 302 self.sa.add(member)
303 303 except Exception:
304 304 # what could go wrong here?
305 305 log.error(traceback.format_exc())
306 306 raise
307 307
308 308 self._log_user_changes('added to', user_group, user)
309 309 return member
310 310
311 311 def remove_user_from_group(self, user_group, user):
312 312 user_group = self._get_user_group(user_group)
313 313 user = self._get_user(user)
314 314 user_group_member = self._find_user_in_group(user, user_group)
315 315
316 316 if not user_group_member:
317 317 # User isn't in that group
318 318 return False
319 319
320 320 try:
321 321 self.sa.delete(user_group_member)
322 322 except Exception:
323 323 log.error(traceback.format_exc())
324 324 raise
325 325
326 326 self._log_user_changes('removed from', user_group, user)
327 327 return True
328 328
329 329 def has_perm(self, user_group, perm):
330 330 user_group = self._get_user_group(user_group)
331 331 perm = self._get_perm(perm)
332 332
333 333 return UserGroupToPerm.query()\
334 334 .filter(UserGroupToPerm.users_group == user_group)\
335 335 .filter(UserGroupToPerm.permission == perm).scalar() is not None
336 336
337 337 def grant_perm(self, user_group, perm):
338 338 user_group = self._get_user_group(user_group)
339 339 perm = self._get_perm(perm)
340 340
341 341 # if this permission is already granted skip it
342 342 _perm = UserGroupToPerm.query()\
343 343 .filter(UserGroupToPerm.users_group == user_group)\
344 344 .filter(UserGroupToPerm.permission == perm)\
345 345 .scalar()
346 346 if _perm:
347 347 return
348 348
349 349 new = UserGroupToPerm()
350 350 new.users_group = user_group
351 351 new.permission = perm
352 352 self.sa.add(new)
353 353 return new
354 354
355 355 def revoke_perm(self, user_group, perm):
356 356 user_group = self._get_user_group(user_group)
357 357 perm = self._get_perm(perm)
358 358
359 359 obj = UserGroupToPerm.query()\
360 360 .filter(UserGroupToPerm.users_group == user_group)\
361 361 .filter(UserGroupToPerm.permission == perm).scalar()
362 362 if obj:
363 363 self.sa.delete(obj)
364 364
365 365 def grant_user_permission(self, user_group, user, perm):
366 366 """
367 367 Grant permission for user on given user group, or update
368 368 existing one if found
369 369
370 370 :param user_group: Instance of UserGroup, users_group_id,
371 371 or users_group_name
372 372 :param user: Instance of User, user_id or username
373 373 :param perm: Instance of Permission, or permission_name
374 374 """
375 375
376 376 user_group = self._get_user_group(user_group)
377 377 user = self._get_user(user)
378 378 permission = self._get_perm(perm)
379 379
380 380 # check if we have that permission already
381 381 obj = self.sa.query(UserUserGroupToPerm)\
382 382 .filter(UserUserGroupToPerm.user == user)\
383 383 .filter(UserUserGroupToPerm.user_group == user_group)\
384 384 .scalar()
385 385 if obj is None:
386 386 # create new !
387 387 obj = UserUserGroupToPerm()
388 388 obj.user_group = user_group
389 389 obj.user = user
390 390 obj.permission = permission
391 391 self.sa.add(obj)
392 392 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
393 393 action_logger_generic(
394 394 'granted permission: {} to user: {} on usergroup: {}'.format(
395 395 perm, user, user_group), namespace='security.usergroup')
396 396
397 397 return obj
398 398
399 399 def revoke_user_permission(self, user_group, user):
400 400 """
401 401 Revoke permission for user on given user group
402 402
403 403 :param user_group: Instance of UserGroup, users_group_id,
404 404 or users_group name
405 405 :param user: Instance of User, user_id or username
406 406 """
407 407
408 408 user_group = self._get_user_group(user_group)
409 409 user = self._get_user(user)
410 410
411 411 obj = self.sa.query(UserUserGroupToPerm)\
412 412 .filter(UserUserGroupToPerm.user == user)\
413 413 .filter(UserUserGroupToPerm.user_group == user_group)\
414 414 .scalar()
415 415 if obj:
416 416 self.sa.delete(obj)
417 417 log.debug('Revoked perm on %s on %s', user_group, user)
418 418 action_logger_generic(
419 419 'revoked permission from user: {} on usergroup: {}'.format(
420 420 user, user_group), namespace='security.usergroup')
421 421
422 422 def grant_user_group_permission(self, target_user_group, user_group, perm):
423 423 """
424 424 Grant user group permission for given target_user_group
425 425
426 426 :param target_user_group:
427 427 :param user_group:
428 428 :param perm:
429 429 """
430 430 target_user_group = self._get_user_group(target_user_group)
431 431 user_group = self._get_user_group(user_group)
432 432 permission = self._get_perm(perm)
433 433 # forbid assigning same user group to itself
434 434 if target_user_group == user_group:
435 435 raise RepoGroupAssignmentError('target repo:%s cannot be '
436 436 'assigned to itself' % target_user_group)
437 437
438 438 # check if we have that permission already
439 439 obj = self.sa.query(UserGroupUserGroupToPerm)\
440 440 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
441 441 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
442 442 .scalar()
443 443 if obj is None:
444 444 # create new !
445 445 obj = UserGroupUserGroupToPerm()
446 446 obj.user_group = user_group
447 447 obj.target_user_group = target_user_group
448 448 obj.permission = permission
449 449 self.sa.add(obj)
450 450 log.debug(
451 451 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
452 452 action_logger_generic(
453 453 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
454 454 perm, user_group, target_user_group),
455 455 namespace='security.usergroup')
456 456
457 457 return obj
458 458
459 459 def revoke_user_group_permission(self, target_user_group, user_group):
460 460 """
461 461 Revoke user group permission for given target_user_group
462 462
463 463 :param target_user_group:
464 464 :param user_group:
465 465 """
466 466 target_user_group = self._get_user_group(target_user_group)
467 467 user_group = self._get_user_group(user_group)
468 468
469 469 obj = self.sa.query(UserGroupUserGroupToPerm)\
470 470 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
471 471 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
472 472 .scalar()
473 473 if obj:
474 474 self.sa.delete(obj)
475 475 log.debug(
476 476 'Revoked perm on %s on %s', target_user_group, user_group)
477 477 action_logger_generic(
478 478 'revoked permission from usergroup: {} on usergroup: {}'.format(
479 479 user_group, target_user_group),
480 480 namespace='security.repogroup')
481 481
482 482 def enforce_groups(self, user, groups, extern_type=None):
483 483 user = self._get_user(user)
484 484 log.debug('Enforcing groups %s on user %s', groups, user)
485 485 current_groups = user.group_member
486 486 # find the external created groups
487 487 externals = [x.users_group for x in current_groups
488 488 if 'extern_type' in x.users_group.group_data]
489 489
490 490 # calculate from what groups user should be removed
491 491 # externals that are not in groups
492 492 for gr in externals:
493 493 if gr.users_group_name not in groups:
494 494 log.debug('Removing user %s from user group %s', user, gr)
495 495 self.remove_user_from_group(gr, user)
496 496
497 497 # now we calculate in which groups user should be == groups params
498 498 owner = User.get_first_super_admin().username
499 499 for gr in set(groups):
500 500 existing_group = UserGroup.get_by_group_name(gr)
501 501 if not existing_group:
502 502 desc = 'Automatically created from plugin:%s' % extern_type
503 503 # we use first admin account to set the owner of the group
504 existing_group = UserGroupModel().create(gr, desc, owner,
505 group_data={'extern_type': extern_type})
504 existing_group = UserGroupModel().create(
505 gr, desc, owner, group_data={'extern_type': extern_type})
506 506
507 507 # we can only add users to special groups created via plugins
508 508 managed = 'extern_type' in existing_group.group_data
509 509 if managed:
510 510 log.debug('Adding user %s to user group %s', user, gr)
511 511 UserGroupModel().add_user_to_group(existing_group, user)
512 512 else:
513 513 log.debug('Skipping addition to group %s since it is '
514 'not managed by auth plugins' % gr)
515
514 'not set to be automatically synchronized' % gr)
516 515
517 516 def change_groups(self, user, groups):
518 517 """
519 518 This method changes user group assignment
520 519 :param user: User
521 520 :param groups: array of UserGroupModel
522 521 :return:
523 522 """
524 523 user = self._get_user(user)
525 524 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
526 525 current_groups = user.group_member
527 526 current_groups = [x.users_group for x in current_groups]
528 527
529 528 # calculate from what groups user should be removed/add
530 529 groups = set(groups)
531 530 current_groups = set(current_groups)
532 531
533 532 groups_to_remove = current_groups - groups
534 533 groups_to_add = groups - current_groups
535 534
536 535 for gr in groups_to_remove:
537 536 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
538 537 self.remove_user_from_group(gr.users_group_name, user.username)
539 538 for gr in groups_to_add:
540 539 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
541 540 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
542 541
543 542 @staticmethod
544 543 def get_user_groups_as_dict(user_group):
545 544 import rhodecode.lib.helpers as h
546 545
547 546 data = {
548 547 'users_group_id': user_group.users_group_id,
549 548 'group_name': user_group.users_group_name,
550 549 'group_description': user_group.user_group_description,
551 550 'active': user_group.users_group_active,
552 551 "owner": user_group.user.username,
553 552 'owner_icon': h.gravatar_url(user_group.user.email, 30),
554 553 "owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
555 554 }
556 555 return data
557 556
558 557
559 558
560 559
@@ -1,38 +1,87 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.user_group.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.user_group.created_on), '', '',),
7 7
8 8 (_('Members'), len(c.group_members_obj),'', [x for x in c.group_members_obj]),
9 (_('Automatic member sync'), 'Yes' if c.user_group.group_data.get('extern_type') else 'No', '', '',),
10
9 11 (_('Assigned to repositories'), len(c.group_to_repos),'', [x for x in c.group_to_repos]),
10 12 (_('Assigned to repo groups'), len(c.group_to_repo_groups), '', [x for x in c.group_to_repo_groups]),
11 13
12 14 ]
13 15 %>
14 16
15 17 <div class="panel panel-default">
16 18 <div class="panel-heading">
17 19 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
18 20 </div>
19 21 <div class="panel-body">
20 22 ${base.dt_info_panel(elems)}
21 23 </div>
24
22 25 </div>
23 26
27 <div class="panel panel-default">
28 <div class="panel-heading">
29 <h3 class="panel-title">${_('Group members sync')}</h3>
30 </div>
31 <div class="panel-body">
32 <% sync_type = c.user_group.group_data.get('extern_type') %>
33
34 % if sync_type:
35 <p>
36 ${_('This group is set to be automatically synchronised.')}<br/>
37 ${_('Each member will be added or removed from this groups once they interact with RhodeCode system.')}<br/>
38 ${_('This group synchronization was set by')}: <strong>${sync_type}</strong>
39 </p>
40 % else:
41 <p>
42 ${_('This group is not set to be automatically synchronised')}
43 </p>
44 % endif
45
46 <div>
47 ${h.secure_form(h.url('edit_user_group_advanced_sync', user_group_id=c.user_group.users_group_id), method='post')}
48 <div class="field">
49 <button class="btn btn-default" type="submit">
50 %if sync_type:
51 ${_('Disable synchronization')}
52 %else:
53 ${_('Enable synchronization')}
54 %endif
55 </button>
56 </div>
57 <div class="field">
58 <span class="help-block">
59 %if sync_type:
60 ${_('User group will no longer synchronize membership')}
61 %else:
62 ${_('User group will start to synchronize membership')}
63 %endif
64 </span>
65 </div>
66 ${h.end_form()}
67 </div>
68
69 </div>
70 </div>
71
72
24 73 <div class="panel panel-danger">
25 74 <div class="panel-heading">
26 75 <h3 class="panel-title">${_('Delete User Group')}</h3>
27 76 </div>
28 77 <div class="panel-body">
29 78 ${h.secure_form(h.url('delete_users_group', user_group_id=c.user_group.users_group_id),method='delete')}
30 79 ${h.hidden('force', 1)}
31 80 <button class="btn btn-small btn-danger" type="submit"
32 81 onclick="return confirm('${_('Confirm to delete user group `%(ugroup)s` with all permission assignments') % {'ugroup': c.user_group.users_group_name}}');">
33 82 <i class="icon-remove-sign"></i>
34 83 ${_('Delete This User Group')}
35 84 </button>
36 85 ${h.end_form()}
37 86 </div>
38 87 </div>
@@ -1,98 +1,100 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('User groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 27 <li>
28 28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 29 </li>
30 30 %endif
31 31 </ul>
32 32 </div>
33 33
34 34 <div id="repos_list_wrap">
35 35 <table id="user_group_list_table" class="display"></table>
36 36 </div>
37 37
38 38 </div>
39 39 <script>
40 40 $(document).ready(function() {
41 41
42 42 var get_datatable_count = function(){
43 43 var api = $('#user_group_list_table').dataTable().api();
44 44 $('#user_group_count').text(api.page.info().recordsDisplay);
45 45 };
46 46
47 47 // user list
48 48 $('#user_group_list_table').DataTable({
49 49 data: ${c.data|n},
50 50 dom: 'rtp',
51 51 pageLength: ${c.visual.admin_grid_items},
52 52 order: [[ 0, "asc" ]],
53 53 columns: [
54 54 { data: {"_": "group_name",
55 55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
56 56 { data: {"_": "desc",
57 57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
58 58 { data: {"_": "members",
59 59 "sort": "members",
60 60 "type": Number}, title: "${_('Members')}", className: "td-number" },
61 { data: {"_": "sync",
62 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
61 63 { data: {"_": "active",
62 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
64 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
63 65 { data: {"_": "owner",
64 66 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
65 67 { data: {"_": "action",
66 68 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
67 69 ],
68 70 language: {
69 71 paginate: DEFAULT_GRID_PAGINATION,
70 72 emptyTable: _gettext("No user groups available yet.")
71 73 },
72 74 "initComplete": function( settings, json ) {
73 75 get_datatable_count();
74 76 }
75 77 });
76 78
77 79 // update the counter when doing search
78 80 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
79 81 get_datatable_count();
80 82 });
81 83
82 84 // filter, filter both grids
83 85 $('#q_filter').on( 'keyup', function () {
84 86 var user_api = $('#user_group_list_table').dataTable().api();
85 87 user_api
86 88 .columns(0)
87 89 .search(this.value)
88 90 .draw();
89 91 });
90 92
91 93 // refilter table if page load via back button
92 94 $("#q_filter").trigger('keyup');
93 95
94 96 });
95 97
96 98 </script>
97 99
98 100 </%def>
@@ -1,233 +1,268 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to)
24 TestController, url, assert_session_flash, link_to, TEST_USER_ADMIN_LOGIN)
25 25 from rhodecode.model.db import User, UserGroup
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.tests.fixture import Fixture
28 28
29 29 TEST_USER_GROUP = 'admins_test'
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 class TestAdminUsersGroupsController(TestController):
35 35
36 36 def test_index(self):
37 37 self.log_user()
38 38 response = self.app.get(url('users_groups'))
39 39 assert response.status_int == 200
40 40
41 41 def test_create(self):
42 42 self.log_user()
43 43 users_group_name = TEST_USER_GROUP
44 44 response = self.app.post(url('users_groups'), {
45 45 'users_group_name': users_group_name,
46 46 'user_group_description': 'DESC',
47 47 'active': True,
48 48 'csrf_token': self.csrf_token})
49 49
50 50 user_group_link = link_to(
51 51 users_group_name,
52 52 url('edit_users_group',
53 53 user_group_id=UserGroup.get_by_group_name(
54 54 users_group_name).users_group_id))
55 55 assert_session_flash(
56 56 response,
57 57 'Created user group %s' % user_group_link)
58 58
59 def test_set_synchronization(self):
60 self.log_user()
61 users_group_name = TEST_USER_GROUP + 'sync'
62 response = self.app.post(url('users_groups'), {
63 'users_group_name': users_group_name,
64 'user_group_description': 'DESC',
65 'active': True,
66 'csrf_token': self.csrf_token})
67
68 group = Session().query(UserGroup).filter(
69 UserGroup.users_group_name == users_group_name).one()
70
71 assert group.group_data.get('extern_type') is None
72
73 # enable
74 self.app.post(
75 url('edit_user_group_advanced_sync', user_group_id=group.users_group_id),
76 params={'csrf_token': self.csrf_token}, status=302)
77
78 group = Session().query(UserGroup).filter(
79 UserGroup.users_group_name == users_group_name).one()
80 assert group.group_data.get('extern_type') == 'manual'
81 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
82
83 # disable
84 self.app.post(
85 url('edit_user_group_advanced_sync',
86 user_group_id=group.users_group_id),
87 params={'csrf_token': self.csrf_token}, status=302)
88
89 group = Session().query(UserGroup).filter(
90 UserGroup.users_group_name == users_group_name).one()
91 assert group.group_data.get('extern_type') is None
92 assert group.group_data.get('extern_type_set_by') == TEST_USER_ADMIN_LOGIN
93
59 94 def test_delete(self):
60 95 self.log_user()
61 96 users_group_name = TEST_USER_GROUP + 'another'
62 97 response = self.app.post(url('users_groups'), {
63 98 'users_group_name': users_group_name,
64 99 'user_group_description': 'DESC',
65 100 'active': True,
66 101 'csrf_token': self.csrf_token})
67 102
68 103 user_group_link = link_to(
69 104 users_group_name,
70 105 url('edit_users_group',
71 106 user_group_id=UserGroup.get_by_group_name(
72 107 users_group_name).users_group_id))
73 108 assert_session_flash(
74 109 response,
75 110 'Created user group %s' % user_group_link)
76 111
77 112 group = Session().query(UserGroup).filter(
78 113 UserGroup.users_group_name == users_group_name).one()
79 114
80 response = self.app.post(
115 self.app.post(
81 116 url('delete_users_group', user_group_id=group.users_group_id),
82 117 params={'_method': 'delete', 'csrf_token': self.csrf_token})
83 118
84 119 group = Session().query(UserGroup).filter(
85 120 UserGroup.users_group_name == users_group_name).scalar()
86 121
87 122 assert group is None
88 123
89 124 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
90 125 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
91 126 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
92 127 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
93 128 ('', '', '', '', '', '', True, False),
94 129 ])
95 130 def test_global_perms_on_group(
96 131 self, repo_create, repo_create_write, user_group_create,
97 132 repo_group_create, fork_create, expect_error, expect_form_error,
98 133 inherit_default_permissions):
99 134 self.log_user()
100 135 users_group_name = TEST_USER_GROUP + 'another2'
101 136 response = self.app.post(url('users_groups'),
102 137 {'users_group_name': users_group_name,
103 138 'user_group_description': 'DESC',
104 139 'active': True,
105 140 'csrf_token': self.csrf_token})
106 141
107 142 ug = UserGroup.get_by_group_name(users_group_name)
108 143 user_group_link = link_to(
109 144 users_group_name,
110 145 url('edit_users_group', user_group_id=ug.users_group_id))
111 146 assert_session_flash(
112 147 response,
113 148 'Created user group %s' % user_group_link)
114 149 response.follow()
115 150
116 151 # ENABLE REPO CREATE ON A GROUP
117 152 perm_params = {
118 153 'inherit_default_permissions': False,
119 154 'default_repo_create': repo_create,
120 155 'default_repo_create_on_write': repo_create_write,
121 156 'default_user_group_create': user_group_create,
122 157 'default_repo_group_create': repo_group_create,
123 158 'default_fork_create': fork_create,
124 159 'default_inherit_default_permissions': inherit_default_permissions,
125 160
126 161 '_method': 'put',
127 162 'csrf_token': self.csrf_token,
128 163 }
129 164 response = self.app.post(
130 165 url('edit_user_group_global_perms',
131 166 user_group_id=ug.users_group_id),
132 167 params=perm_params)
133 168
134 169 if expect_form_error:
135 170 assert response.status_int == 200
136 171 response.mustcontain('Value must be one of')
137 172 else:
138 173 if expect_error:
139 174 msg = 'An error occurred during permissions saving'
140 175 else:
141 176 msg = 'User Group global permissions updated successfully'
142 177 ug = UserGroup.get_by_group_name(users_group_name)
143 178 del perm_params['_method']
144 179 del perm_params['csrf_token']
145 180 del perm_params['inherit_default_permissions']
146 181 assert perm_params == ug.get_default_perms()
147 182 assert_session_flash(response, msg)
148 183
149 184 fixture.destroy_user_group(users_group_name)
150 185
151 186 def test_edit_autocomplete(self):
152 187 self.log_user()
153 188 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
154 189 response = self.app.get(
155 190 url('edit_users_group', user_group_id=ug.users_group_id))
156 191 fixture.destroy_user_group(TEST_USER_GROUP)
157 192
158 193 def test_edit_user_group_autocomplete_members(self, xhr_header):
159 194 self.log_user()
160 195 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
161 196 response = self.app.get(
162 197 url('edit_user_group_members', user_group_id=ug.users_group_id),
163 198 extra_environ=xhr_header)
164 199
165 200 assert response.body == '{"members": []}'
166 201 fixture.destroy_user_group(TEST_USER_GROUP)
167 202
168 203 def test_usergroup_escape(self):
169 204 user = User.get_by_username('test_admin')
170 205 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
171 206 user.lastname = (
172 207 '<img src="/image2" onload="alert(\'Hello, World!\');">')
173 208 Session().add(user)
174 209 Session().commit()
175 210
176 211 self.log_user()
177 212 users_group_name = 'samplegroup'
178 213 data = {
179 214 'users_group_name': users_group_name,
180 215 'user_group_description': (
181 216 '<strong onload="alert();">DESC</strong>'),
182 217 'active': True,
183 218 'csrf_token': self.csrf_token
184 219 }
185 220
186 221 self.app.post(url('users_groups'), data)
187 222 response = self.app.get(url('users_groups'))
188 223
189 224 response.mustcontain(
190 225 '&lt;strong onload=&#34;alert();&#34;&gt;'
191 226 'DESC&lt;/strong&gt;')
192 227 response.mustcontain(
193 228 '&lt;img src=&#34;/image2&#34; onload=&#34;'
194 229 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
195 230
196 231 def test_update_members_from_user_ids(self, user_regular):
197 232 uid = user_regular.user_id
198 233 username = user_regular.username
199 234 self.log_user()
200 235
201 236 user_group = fixture.create_user_group('test_gr_ids')
202 237 assert user_group.members == []
203 238 assert user_group.user != user_regular
204 239 expected_active_state = not user_group.users_group_active
205 240
206 241 form_data = [
207 242 ('csrf_token', self.csrf_token),
208 243 ('_method', 'put'),
209 244 ('user', username),
210 245 ('users_group_name', 'changed_name'),
211 246 ('users_group_active', expected_active_state),
212 247 ('user_group_description', 'changed_description'),
213 248
214 249 ('__start__', 'user_group_members:sequence'),
215 250 ('__start__', 'member:mapping'),
216 251 ('member_user_id', uid),
217 252 ('type', 'existing'),
218 253 ('__end__', 'member:mapping'),
219 254 ('__end__', 'user_group_members:sequence'),
220 255 ]
221 256 ugid = user_group.users_group_id
222 257 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
223 258
224 259 user_group = UserGroup.get(ugid)
225 260 assert user_group
226 261
227 262 assert user_group.members[0].user_id == uid
228 263 assert user_group.user_id == uid
229 264 assert 'changed_name' in user_group.users_group_name
230 265 assert 'changed_description' in user_group.user_group_description
231 266 assert user_group.users_group_active == expected_active_state
232 267
233 268 fixture.destroy_user_group(user_group)
General Comments 0
You need to be logged in to leave comments. Login now