##// END OF EJS Templates
permissions: properly flush user cache permissions in more cases of permission changes....
marcink -
r3887:85d5bce0 default
parent child Browse files
Show More
@@ -1,2327 +1,2332 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib import audit_logger, rc_cache
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.celerylib.utils import get_task_id
36 36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 39 from rhodecode.lib.vcs import RepositoryError
40 40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 42 from rhodecode.model.comment import CommentsModel
43 43 from rhodecode.model.db import (
44 44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 45 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
46 47 from rhodecode.model.repo import RepoModel
47 48 from rhodecode.model.scm import ScmModel, RepoList
48 49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 50 from rhodecode.model import validation_schema
50 51 from rhodecode.model.validation_schema.schemas import repo_schema
51 52
52 53 log = logging.getLogger(__name__)
53 54
54 55
55 56 @jsonrpc_method()
56 57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 58 """
58 59 Gets an existing repository by its name or repository_id.
59 60
60 61 The members section so the output returns users groups or users
61 62 associated with that repository.
62 63
63 64 This command can only be run using an |authtoken| with admin rights,
64 65 or users with at least read rights to the |repo|.
65 66
66 67 :param apiuser: This is filled automatically from the |authtoken|.
67 68 :type apiuser: AuthUser
68 69 :param repoid: The repository name or repository id.
69 70 :type repoid: str or int
70 71 :param cache: use the cached value for last changeset
71 72 :type: cache: Optional(bool)
72 73
73 74 Example output:
74 75
75 76 .. code-block:: bash
76 77
77 78 {
78 79 "error": null,
79 80 "id": <repo_id>,
80 81 "result": {
81 82 "clone_uri": null,
82 83 "created_on": "timestamp",
83 84 "description": "repo description",
84 85 "enable_downloads": false,
85 86 "enable_locking": false,
86 87 "enable_statistics": false,
87 88 "followers": [
88 89 {
89 90 "active": true,
90 91 "admin": false,
91 92 "api_key": "****************************************",
92 93 "api_keys": [
93 94 "****************************************"
94 95 ],
95 96 "email": "user@example.com",
96 97 "emails": [
97 98 "user@example.com"
98 99 ],
99 100 "extern_name": "rhodecode",
100 101 "extern_type": "rhodecode",
101 102 "firstname": "username",
102 103 "ip_addresses": [],
103 104 "language": null,
104 105 "last_login": "2015-09-16T17:16:35.854",
105 106 "lastname": "surname",
106 107 "user_id": <user_id>,
107 108 "username": "name"
108 109 }
109 110 ],
110 111 "fork_of": "parent-repo",
111 112 "landing_rev": [
112 113 "rev",
113 114 "tip"
114 115 ],
115 116 "last_changeset": {
116 117 "author": "User <user@example.com>",
117 118 "branch": "default",
118 119 "date": "timestamp",
119 120 "message": "last commit message",
120 121 "parents": [
121 122 {
122 123 "raw_id": "commit-id"
123 124 }
124 125 ],
125 126 "raw_id": "commit-id",
126 127 "revision": <revision number>,
127 128 "short_id": "short id"
128 129 },
129 130 "lock_reason": null,
130 131 "locked_by": null,
131 132 "locked_date": null,
132 133 "owner": "owner-name",
133 134 "permissions": [
134 135 {
135 136 "name": "super-admin-name",
136 137 "origin": "super-admin",
137 138 "permission": "repository.admin",
138 139 "type": "user"
139 140 },
140 141 {
141 142 "name": "owner-name",
142 143 "origin": "owner",
143 144 "permission": "repository.admin",
144 145 "type": "user"
145 146 },
146 147 {
147 148 "name": "user-group-name",
148 149 "origin": "permission",
149 150 "permission": "repository.write",
150 151 "type": "user_group"
151 152 }
152 153 ],
153 154 "private": true,
154 155 "repo_id": 676,
155 156 "repo_name": "user-group/repo-name",
156 157 "repo_type": "hg"
157 158 }
158 159 }
159 160 """
160 161
161 162 repo = get_repo_or_error(repoid)
162 163 cache = Optional.extract(cache)
163 164
164 165 include_secrets = False
165 166 if has_superadmin_permission(apiuser):
166 167 include_secrets = True
167 168 else:
168 169 # check if we have at least read permission for this repo !
169 170 _perms = (
170 171 'repository.admin', 'repository.write', 'repository.read',)
171 172 validate_repo_permissions(apiuser, repoid, repo, _perms)
172 173
173 174 permissions = []
174 175 for _user in repo.permissions():
175 176 user_data = {
176 177 'name': _user.username,
177 178 'permission': _user.permission,
178 179 'origin': get_origin(_user),
179 180 'type': "user",
180 181 }
181 182 permissions.append(user_data)
182 183
183 184 for _user_group in repo.permission_user_groups():
184 185 user_group_data = {
185 186 'name': _user_group.users_group_name,
186 187 'permission': _user_group.permission,
187 188 'origin': get_origin(_user_group),
188 189 'type': "user_group",
189 190 }
190 191 permissions.append(user_group_data)
191 192
192 193 following_users = [
193 194 user.user.get_api_data(include_secrets=include_secrets)
194 195 for user in repo.followers]
195 196
196 197 if not cache:
197 198 repo.update_commit_cache()
198 199 data = repo.get_api_data(include_secrets=include_secrets)
199 200 data['permissions'] = permissions
200 201 data['followers'] = following_users
201 202 return data
202 203
203 204
204 205 @jsonrpc_method()
205 206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 207 """
207 208 Lists all existing repositories.
208 209
209 210 This command can only be run using an |authtoken| with admin rights,
210 211 or users with at least read rights to |repos|.
211 212
212 213 :param apiuser: This is filled automatically from the |authtoken|.
213 214 :type apiuser: AuthUser
214 215 :param root: specify root repository group to fetch repositories.
215 216 filters the returned repositories to be members of given root group.
216 217 :type root: Optional(None)
217 218 :param traverse: traverse given root into subrepositories. With this flag
218 219 set to False, it will only return top-level repositories from `root`.
219 220 if root is empty it will return just top-level repositories.
220 221 :type traverse: Optional(True)
221 222
222 223
223 224 Example output:
224 225
225 226 .. code-block:: bash
226 227
227 228 id : <id_given_in_input>
228 229 result: [
229 230 {
230 231 "repo_id" : "<repo_id>",
231 232 "repo_name" : "<reponame>"
232 233 "repo_type" : "<repo_type>",
233 234 "clone_uri" : "<clone_uri>",
234 235 "private": : "<bool>",
235 236 "created_on" : "<datetimecreated>",
236 237 "description" : "<description>",
237 238 "landing_rev": "<landing_rev>",
238 239 "owner": "<repo_owner>",
239 240 "fork_of": "<name_of_fork_parent>",
240 241 "enable_downloads": "<bool>",
241 242 "enable_locking": "<bool>",
242 243 "enable_statistics": "<bool>",
243 244 },
244 245 ...
245 246 ]
246 247 error: null
247 248 """
248 249
249 250 include_secrets = has_superadmin_permission(apiuser)
250 251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 252 extras = {'user': apiuser}
252 253
253 254 root = Optional.extract(root)
254 255 traverse = Optional.extract(traverse, binary=True)
255 256
256 257 if root:
257 258 # verify parent existance, if it's empty return an error
258 259 parent = RepoGroup.get_by_group_name(root)
259 260 if not parent:
260 261 raise JSONRPCError(
261 262 'Root repository group `{}` does not exist'.format(root))
262 263
263 264 if traverse:
264 265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 266 else:
266 267 repos = RepoModel().get_repos_for_root(root=parent)
267 268 else:
268 269 if traverse:
269 270 repos = RepoModel().get_all()
270 271 else:
271 272 # return just top-level
272 273 repos = RepoModel().get_repos_for_root(root=None)
273 274
274 275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 276 return [repo.get_api_data(include_secrets=include_secrets)
276 277 for repo in repo_list]
277 278
278 279
279 280 @jsonrpc_method()
280 281 def get_repo_changeset(request, apiuser, repoid, revision,
281 282 details=Optional('basic')):
282 283 """
283 284 Returns information about a changeset.
284 285
285 286 Additionally parameters define the amount of details returned by
286 287 this function.
287 288
288 289 This command can only be run using an |authtoken| with admin rights,
289 290 or users with at least read rights to the |repo|.
290 291
291 292 :param apiuser: This is filled automatically from the |authtoken|.
292 293 :type apiuser: AuthUser
293 294 :param repoid: The repository name or repository id
294 295 :type repoid: str or int
295 296 :param revision: revision for which listing should be done
296 297 :type revision: str
297 298 :param details: details can be 'basic|extended|full' full gives diff
298 299 info details like the diff itself, and number of changed files etc.
299 300 :type details: Optional(str)
300 301
301 302 """
302 303 repo = get_repo_or_error(repoid)
303 304 if not has_superadmin_permission(apiuser):
304 305 _perms = (
305 306 'repository.admin', 'repository.write', 'repository.read',)
306 307 validate_repo_permissions(apiuser, repoid, repo, _perms)
307 308
308 309 changes_details = Optional.extract(details)
309 310 _changes_details_types = ['basic', 'extended', 'full']
310 311 if changes_details not in _changes_details_types:
311 312 raise JSONRPCError(
312 313 'ret_type must be one of %s' % (
313 314 ','.join(_changes_details_types)))
314 315
315 316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 317 'status', '_commit', '_file_paths']
317 318
318 319 try:
319 320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 321 except TypeError as e:
321 322 raise JSONRPCError(safe_str(e))
322 323 _cs_json = cs.__json__()
323 324 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 325 if changes_details == 'full':
325 326 _cs_json['refs'] = cs._get_refs()
326 327 return _cs_json
327 328
328 329
329 330 @jsonrpc_method()
330 331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 332 details=Optional('basic')):
332 333 """
333 334 Returns a set of commits limited by the number starting
334 335 from the `start_rev` option.
335 336
336 337 Additional parameters define the amount of details returned by this
337 338 function.
338 339
339 340 This command can only be run using an |authtoken| with admin rights,
340 341 or users with at least read rights to |repos|.
341 342
342 343 :param apiuser: This is filled automatically from the |authtoken|.
343 344 :type apiuser: AuthUser
344 345 :param repoid: The repository name or repository ID.
345 346 :type repoid: str or int
346 347 :param start_rev: The starting revision from where to get changesets.
347 348 :type start_rev: str
348 349 :param limit: Limit the number of commits to this amount
349 350 :type limit: str or int
350 351 :param details: Set the level of detail returned. Valid option are:
351 352 ``basic``, ``extended`` and ``full``.
352 353 :type details: Optional(str)
353 354
354 355 .. note::
355 356
356 357 Setting the parameter `details` to the value ``full`` is extensive
357 358 and returns details like the diff itself, and the number
358 359 of changed files.
359 360
360 361 """
361 362 repo = get_repo_or_error(repoid)
362 363 if not has_superadmin_permission(apiuser):
363 364 _perms = (
364 365 'repository.admin', 'repository.write', 'repository.read',)
365 366 validate_repo_permissions(apiuser, repoid, repo, _perms)
366 367
367 368 changes_details = Optional.extract(details)
368 369 _changes_details_types = ['basic', 'extended', 'full']
369 370 if changes_details not in _changes_details_types:
370 371 raise JSONRPCError(
371 372 'ret_type must be one of %s' % (
372 373 ','.join(_changes_details_types)))
373 374
374 375 limit = int(limit)
375 376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 377 'status', '_commit', '_file_paths']
377 378
378 379 vcs_repo = repo.scm_instance()
379 380 # SVN needs a special case to distinguish its index and commit id
380 381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 382 start_rev = vcs_repo.commit_ids[0]
382 383
383 384 try:
384 385 commits = vcs_repo.get_commits(
385 386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 387 except TypeError as e:
387 388 raise JSONRPCError(safe_str(e))
388 389 except Exception:
389 390 log.exception('Fetching of commits failed')
390 391 raise JSONRPCError('Error occurred during commit fetching')
391 392
392 393 ret = []
393 394 for cnt, commit in enumerate(commits):
394 395 if cnt >= limit != -1:
395 396 break
396 397 _cs_json = commit.__json__()
397 398 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 399 if changes_details == 'full':
399 400 _cs_json['refs'] = {
400 401 'branches': [commit.branch],
401 402 'bookmarks': getattr(commit, 'bookmarks', []),
402 403 'tags': commit.tags
403 404 }
404 405 ret.append(_cs_json)
405 406 return ret
406 407
407 408
408 409 @jsonrpc_method()
409 410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 411 ret_type=Optional('all'), details=Optional('basic'),
411 412 max_file_bytes=Optional(None)):
412 413 """
413 414 Returns a list of nodes and children in a flat list for a given
414 415 path at given revision.
415 416
416 417 It's possible to specify ret_type to show only `files` or `dirs`.
417 418
418 419 This command can only be run using an |authtoken| with admin rights,
419 420 or users with at least read rights to |repos|.
420 421
421 422 :param apiuser: This is filled automatically from the |authtoken|.
422 423 :type apiuser: AuthUser
423 424 :param repoid: The repository name or repository ID.
424 425 :type repoid: str or int
425 426 :param revision: The revision for which listing should be done.
426 427 :type revision: str
427 428 :param root_path: The path from which to start displaying.
428 429 :type root_path: str
429 430 :param ret_type: Set the return type. Valid options are
430 431 ``all`` (default), ``files`` and ``dirs``.
431 432 :type ret_type: Optional(str)
432 433 :param details: Returns extended information about nodes, such as
433 434 md5, binary, and or content.
434 435 The valid options are ``basic`` and ``full``.
435 436 :type details: Optional(str)
436 437 :param max_file_bytes: Only return file content under this file size bytes
437 438 :type details: Optional(int)
438 439
439 440 Example output:
440 441
441 442 .. code-block:: bash
442 443
443 444 id : <id_given_in_input>
444 445 result: [
445 446 {
446 447 "binary": false,
447 448 "content": "File line",
448 449 "extension": "md",
449 450 "lines": 2,
450 451 "md5": "059fa5d29b19c0657e384749480f6422",
451 452 "mimetype": "text/x-minidsrc",
452 453 "name": "file.md",
453 454 "size": 580,
454 455 "type": "file"
455 456 },
456 457 ...
457 458 ]
458 459 error: null
459 460 """
460 461
461 462 repo = get_repo_or_error(repoid)
462 463 if not has_superadmin_permission(apiuser):
463 464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 465 validate_repo_permissions(apiuser, repoid, repo, _perms)
465 466
466 467 ret_type = Optional.extract(ret_type)
467 468 details = Optional.extract(details)
468 469 _extended_types = ['basic', 'full']
469 470 if details not in _extended_types:
470 471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 472 extended_info = False
472 473 content = False
473 474 if details == 'basic':
474 475 extended_info = True
475 476
476 477 if details == 'full':
477 478 extended_info = content = True
478 479
479 480 _map = {}
480 481 try:
481 482 # check if repo is not empty by any chance, skip quicker if it is.
482 483 _scm = repo.scm_instance()
483 484 if _scm.is_empty():
484 485 return []
485 486
486 487 _d, _f = ScmModel().get_nodes(
487 488 repo, revision, root_path, flat=False,
488 489 extended_info=extended_info, content=content,
489 490 max_file_bytes=max_file_bytes)
490 491 _map = {
491 492 'all': _d + _f,
492 493 'files': _f,
493 494 'dirs': _d,
494 495 }
495 496 return _map[ret_type]
496 497 except KeyError:
497 498 raise JSONRPCError(
498 499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 500 except Exception:
500 501 log.exception("Exception occurred while trying to get repo nodes")
501 502 raise JSONRPCError(
502 503 'failed to get repo: `%s` nodes' % repo.repo_name
503 504 )
504 505
505 506
506 507 @jsonrpc_method()
507 508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 509 max_file_bytes=Optional(None), details=Optional('basic'),
509 510 cache=Optional(True)):
510 511 """
511 512 Returns a single file from repository at given revision.
512 513
513 514 This command can only be run using an |authtoken| with admin rights,
514 515 or users with at least read rights to |repos|.
515 516
516 517 :param apiuser: This is filled automatically from the |authtoken|.
517 518 :type apiuser: AuthUser
518 519 :param repoid: The repository name or repository ID.
519 520 :type repoid: str or int
520 521 :param commit_id: The revision for which listing should be done.
521 522 :type commit_id: str
522 523 :param file_path: The path from which to start displaying.
523 524 :type file_path: str
524 525 :param details: Returns different set of information about nodes.
525 526 The valid options are ``minimal`` ``basic`` and ``full``.
526 527 :type details: Optional(str)
527 528 :param max_file_bytes: Only return file content under this file size bytes
528 529 :type max_file_bytes: Optional(int)
529 530 :param cache: Use internal caches for fetching files. If disabled fetching
530 531 files is slower but more memory efficient
531 532 :type cache: Optional(bool)
532 533
533 534 Example output:
534 535
535 536 .. code-block:: bash
536 537
537 538 id : <id_given_in_input>
538 539 result: {
539 540 "binary": false,
540 541 "extension": "py",
541 542 "lines": 35,
542 543 "content": "....",
543 544 "md5": "76318336366b0f17ee249e11b0c99c41",
544 545 "mimetype": "text/x-python",
545 546 "name": "python.py",
546 547 "size": 817,
547 548 "type": "file",
548 549 }
549 550 error: null
550 551 """
551 552
552 553 repo = get_repo_or_error(repoid)
553 554 if not has_superadmin_permission(apiuser):
554 555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 556 validate_repo_permissions(apiuser, repoid, repo, _perms)
556 557
557 558 cache = Optional.extract(cache, binary=True)
558 559 details = Optional.extract(details)
559 560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 561 if details not in _extended_types:
561 562 raise JSONRPCError(
562 563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 564 extended_info = False
564 565 content = False
565 566
566 567 if details == 'minimal':
567 568 extended_info = False
568 569
569 570 elif details == 'basic':
570 571 extended_info = True
571 572
572 573 elif details == 'full':
573 574 extended_info = content = True
574 575
575 576 try:
576 577 # check if repo is not empty by any chance, skip quicker if it is.
577 578 _scm = repo.scm_instance()
578 579 if _scm.is_empty():
579 580 return None
580 581
581 582 node = ScmModel().get_node(
582 583 repo, commit_id, file_path, extended_info=extended_info,
583 584 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 585 except NodeDoesNotExistError:
585 586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 587 repo.repo_name, file_path, commit_id))
587 588 except Exception:
588 589 log.exception("Exception occurred while trying to get repo %s file",
589 590 repo.repo_name)
590 591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 592 repo.repo_name, file_path))
592 593
593 594 return node
594 595
595 596
596 597 @jsonrpc_method()
597 598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 599 """
599 600 Returns a list of tree nodes for path at given revision. This api is built
600 601 strictly for usage in full text search building, and shouldn't be consumed
601 602
602 603 This command can only be run using an |authtoken| with admin rights,
603 604 or users with at least read rights to |repos|.
604 605
605 606 """
606 607
607 608 repo = get_repo_or_error(repoid)
608 609 if not has_superadmin_permission(apiuser):
609 610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 611 validate_repo_permissions(apiuser, repoid, repo, _perms)
611 612
612 613 repo_id = repo.repo_id
613 614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 615 cache_on = cache_seconds > 0
615 616
616 617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618 619
619 620 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 622
622 623 try:
623 624 # check if repo is not empty by any chance, skip quicker if it is.
624 625 _scm = repo.scm_instance()
625 626 if _scm.is_empty():
626 627 return []
627 628 except RepositoryError:
628 629 log.exception("Exception occurred while trying to get repo nodes")
629 630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630 631
631 632 try:
632 633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 634 # sending 'master' is a pointer that needs to be translated to current commit.
634 635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 636 log.debug(
636 637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 638 'with caching: %s[TTL: %ss]' % (
638 639 repo_id, commit_id, cache_on, cache_seconds or 0))
639 640
640 641 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
641 642 return tree_files
642 643
643 644 except Exception:
644 645 log.exception("Exception occurred while trying to get repo nodes")
645 646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646 647
647 648
648 649 @jsonrpc_method()
649 650 def get_repo_refs(request, apiuser, repoid):
650 651 """
651 652 Returns a dictionary of current references. It returns
652 653 bookmarks, branches, closed_branches, and tags for given repository
653 654
654 655 It's possible to specify ret_type to show only `files` or `dirs`.
655 656
656 657 This command can only be run using an |authtoken| with admin rights,
657 658 or users with at least read rights to |repos|.
658 659
659 660 :param apiuser: This is filled automatically from the |authtoken|.
660 661 :type apiuser: AuthUser
661 662 :param repoid: The repository name or repository ID.
662 663 :type repoid: str or int
663 664
664 665 Example output:
665 666
666 667 .. code-block:: bash
667 668
668 669 id : <id_given_in_input>
669 670 "result": {
670 671 "bookmarks": {
671 672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 674 },
674 675 "branches": {
675 676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 678 },
678 679 "branches_closed": {},
679 680 "tags": {
680 681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 685 }
685 686 }
686 687 error: null
687 688 """
688 689
689 690 repo = get_repo_or_error(repoid)
690 691 if not has_superadmin_permission(apiuser):
691 692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 693 validate_repo_permissions(apiuser, repoid, repo, _perms)
693 694
694 695 try:
695 696 # check if repo is not empty by any chance, skip quicker if it is.
696 697 vcs_instance = repo.scm_instance()
697 698 refs = vcs_instance.refs()
698 699 return refs
699 700 except Exception:
700 701 log.exception("Exception occurred while trying to get repo refs")
701 702 raise JSONRPCError(
702 703 'failed to get repo: `%s` references' % repo.repo_name
703 704 )
704 705
705 706
706 707 @jsonrpc_method()
707 708 def create_repo(
708 709 request, apiuser, repo_name, repo_type,
709 710 owner=Optional(OAttr('apiuser')),
710 711 description=Optional(''),
711 712 private=Optional(False),
712 713 clone_uri=Optional(None),
713 714 push_uri=Optional(None),
714 715 landing_rev=Optional(None),
715 716 enable_statistics=Optional(False),
716 717 enable_locking=Optional(False),
717 718 enable_downloads=Optional(False),
718 719 copy_permissions=Optional(False)):
719 720 """
720 721 Creates a repository.
721 722
722 723 * If the repository name contains "/", repository will be created inside
723 724 a repository group or nested repository groups
724 725
725 726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 727 group "foo/bar". You have to have permissions to access and write to
727 728 the last repository group ("bar" in this example)
728 729
729 730 This command can only be run using an |authtoken| with at least
730 731 permissions to create repositories, or write permissions to
731 732 parent repository groups.
732 733
733 734 :param apiuser: This is filled automatically from the |authtoken|.
734 735 :type apiuser: AuthUser
735 736 :param repo_name: Set the repository name.
736 737 :type repo_name: str
737 738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 739 :type repo_type: str
739 740 :param owner: user_id or username
740 741 :type owner: Optional(str)
741 742 :param description: Set the repository description.
742 743 :type description: Optional(str)
743 744 :param private: set repository as private
744 745 :type private: bool
745 746 :param clone_uri: set clone_uri
746 747 :type clone_uri: str
747 748 :param push_uri: set push_uri
748 749 :type push_uri: str
749 750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 751 :type landing_rev: str
751 752 :param enable_locking:
752 753 :type enable_locking: bool
753 754 :param enable_downloads:
754 755 :type enable_downloads: bool
755 756 :param enable_statistics:
756 757 :type enable_statistics: bool
757 758 :param copy_permissions: Copy permission from group in which the
758 759 repository is being created.
759 760 :type copy_permissions: bool
760 761
761 762
762 763 Example output:
763 764
764 765 .. code-block:: bash
765 766
766 767 id : <id_given_in_input>
767 768 result: {
768 769 "msg": "Created new repository `<reponame>`",
769 770 "success": true,
770 771 "task": "<celery task id or None if done sync>"
771 772 }
772 773 error: null
773 774
774 775
775 776 Example error output:
776 777
777 778 .. code-block:: bash
778 779
779 780 id : <id_given_in_input>
780 781 result : null
781 782 error : {
782 783 'failed to create repository `<repo_name>`'
783 784 }
784 785
785 786 """
786 787
787 788 owner = validate_set_owner_permissions(apiuser, owner)
788 789
789 790 description = Optional.extract(description)
790 791 copy_permissions = Optional.extract(copy_permissions)
791 792 clone_uri = Optional.extract(clone_uri)
792 793 push_uri = Optional.extract(push_uri)
793 794
794 795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 796 if isinstance(private, Optional):
796 797 private = defs.get('repo_private') or Optional.extract(private)
797 798 if isinstance(repo_type, Optional):
798 799 repo_type = defs.get('repo_type')
799 800 if isinstance(enable_statistics, Optional):
800 801 enable_statistics = defs.get('repo_enable_statistics')
801 802 if isinstance(enable_locking, Optional):
802 803 enable_locking = defs.get('repo_enable_locking')
803 804 if isinstance(enable_downloads, Optional):
804 805 enable_downloads = defs.get('repo_enable_downloads')
805 806
806 807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 809 ref_choices = list(set(ref_choices + [landing_ref]))
809 810
810 811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811 812
812 813 schema = repo_schema.RepoSchema().bind(
813 814 repo_type_options=rhodecode.BACKENDS.keys(),
814 815 repo_ref_options=ref_choices,
815 816 repo_type=repo_type,
816 817 # user caller
817 818 user=apiuser)
818 819
819 820 try:
820 821 schema_data = schema.deserialize(dict(
821 822 repo_name=repo_name,
822 823 repo_type=repo_type,
823 824 repo_owner=owner.username,
824 825 repo_description=description,
825 826 repo_landing_commit_ref=landing_commit_ref,
826 827 repo_clone_uri=clone_uri,
827 828 repo_push_uri=push_uri,
828 829 repo_private=private,
829 830 repo_copy_permissions=copy_permissions,
830 831 repo_enable_statistics=enable_statistics,
831 832 repo_enable_downloads=enable_downloads,
832 833 repo_enable_locking=enable_locking))
833 834 except validation_schema.Invalid as err:
834 835 raise JSONRPCValidationError(colander_exc=err)
835 836
836 837 try:
837 838 data = {
838 839 'owner': owner,
839 840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 841 'repo_name_full': schema_data['repo_name'],
841 842 'repo_group': schema_data['repo_group']['repo_group_id'],
842 843 'repo_type': schema_data['repo_type'],
843 844 'repo_description': schema_data['repo_description'],
844 845 'repo_private': schema_data['repo_private'],
845 846 'clone_uri': schema_data['repo_clone_uri'],
846 847 'push_uri': schema_data['repo_push_uri'],
847 848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 849 'enable_statistics': schema_data['repo_enable_statistics'],
849 850 'enable_locking': schema_data['repo_enable_locking'],
850 851 'enable_downloads': schema_data['repo_enable_downloads'],
851 852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 853 }
853 854
854 855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 856 task_id = get_task_id(task)
856 857 # no commit, it's done in RepoModel, or async via celery
857 858 return {
858 859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 860 'success': True, # cannot return the repo data here since fork
860 861 # can be done async
861 862 'task': task_id
862 863 }
863 864 except Exception:
864 865 log.exception(
865 866 u"Exception while trying to create the repository %s",
866 867 schema_data['repo_name'])
867 868 raise JSONRPCError(
868 869 'failed to create repository `%s`' % (schema_data['repo_name'],))
869 870
870 871
871 872 @jsonrpc_method()
872 873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 874 description=Optional('')):
874 875 """
875 876 Adds an extra field to a repository.
876 877
877 878 This command can only be run using an |authtoken| with at least
878 879 write permissions to the |repo|.
879 880
880 881 :param apiuser: This is filled automatically from the |authtoken|.
881 882 :type apiuser: AuthUser
882 883 :param repoid: Set the repository name or repository id.
883 884 :type repoid: str or int
884 885 :param key: Create a unique field key for this repository.
885 886 :type key: str
886 887 :param label:
887 888 :type label: Optional(str)
888 889 :param description:
889 890 :type description: Optional(str)
890 891 """
891 892 repo = get_repo_or_error(repoid)
892 893 if not has_superadmin_permission(apiuser):
893 894 _perms = ('repository.admin',)
894 895 validate_repo_permissions(apiuser, repoid, repo, _perms)
895 896
896 897 label = Optional.extract(label) or key
897 898 description = Optional.extract(description)
898 899
899 900 field = RepositoryField.get_by_key_name(key, repo)
900 901 if field:
901 902 raise JSONRPCError('Field with key '
902 903 '`%s` exists for repo `%s`' % (key, repoid))
903 904
904 905 try:
905 906 RepoModel().add_repo_field(repo, key, field_label=label,
906 907 field_desc=description)
907 908 Session().commit()
908 909 return {
909 910 'msg': "Added new repository field `%s`" % (key,),
910 911 'success': True,
911 912 }
912 913 except Exception:
913 914 log.exception("Exception occurred while trying to add field to repo")
914 915 raise JSONRPCError(
915 916 'failed to create new field for repository `%s`' % (repoid,))
916 917
917 918
918 919 @jsonrpc_method()
919 920 def remove_field_from_repo(request, apiuser, repoid, key):
920 921 """
921 922 Removes an extra field from a repository.
922 923
923 924 This command can only be run using an |authtoken| with at least
924 925 write permissions to the |repo|.
925 926
926 927 :param apiuser: This is filled automatically from the |authtoken|.
927 928 :type apiuser: AuthUser
928 929 :param repoid: Set the repository name or repository ID.
929 930 :type repoid: str or int
930 931 :param key: Set the unique field key for this repository.
931 932 :type key: str
932 933 """
933 934
934 935 repo = get_repo_or_error(repoid)
935 936 if not has_superadmin_permission(apiuser):
936 937 _perms = ('repository.admin',)
937 938 validate_repo_permissions(apiuser, repoid, repo, _perms)
938 939
939 940 field = RepositoryField.get_by_key_name(key, repo)
940 941 if not field:
941 942 raise JSONRPCError('Field with key `%s` does not '
942 943 'exists for repo `%s`' % (key, repoid))
943 944
944 945 try:
945 946 RepoModel().delete_repo_field(repo, field_key=key)
946 947 Session().commit()
947 948 return {
948 949 'msg': "Deleted repository field `%s`" % (key,),
949 950 'success': True,
950 951 }
951 952 except Exception:
952 953 log.exception(
953 954 "Exception occurred while trying to delete field from repo")
954 955 raise JSONRPCError(
955 956 'failed to delete field for repository `%s`' % (repoid,))
956 957
957 958
958 959 @jsonrpc_method()
959 960 def update_repo(
960 961 request, apiuser, repoid, repo_name=Optional(None),
961 962 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 963 private=Optional(False),
963 964 clone_uri=Optional(None), push_uri=Optional(None),
964 965 landing_rev=Optional(None), fork_of=Optional(None),
965 966 enable_statistics=Optional(False),
966 967 enable_locking=Optional(False),
967 968 enable_downloads=Optional(False), fields=Optional('')):
968 969 """
969 970 Updates a repository with the given information.
970 971
971 972 This command can only be run using an |authtoken| with at least
972 973 admin permissions to the |repo|.
973 974
974 975 * If the repository name contains "/", repository will be updated
975 976 accordingly with a repository group or nested repository groups
976 977
977 978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 979 called "repo-test" and place it inside group "foo/bar".
979 980 You have to have permissions to access and write to the last repository
980 981 group ("bar" in this example)
981 982
982 983 :param apiuser: This is filled automatically from the |authtoken|.
983 984 :type apiuser: AuthUser
984 985 :param repoid: repository name or repository ID.
985 986 :type repoid: str or int
986 987 :param repo_name: Update the |repo| name, including the
987 988 repository group it's in.
988 989 :type repo_name: str
989 990 :param owner: Set the |repo| owner.
990 991 :type owner: str
991 992 :param fork_of: Set the |repo| as fork of another |repo|.
992 993 :type fork_of: str
993 994 :param description: Update the |repo| description.
994 995 :type description: str
995 996 :param private: Set the |repo| as private. (True | False)
996 997 :type private: bool
997 998 :param clone_uri: Update the |repo| clone URI.
998 999 :type clone_uri: str
999 1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 1001 :type landing_rev: str
1001 1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 1003 :type enable_statistics: bool
1003 1004 :param enable_locking: Enable |repo| locking.
1004 1005 :type enable_locking: bool
1005 1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 1007 :type enable_downloads: bool
1007 1008 :param fields: Add extra fields to the |repo|. Use the following
1008 1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 1010 Escape ', ' with \,
1010 1011 :type fields: str
1011 1012 """
1012 1013
1013 1014 repo = get_repo_or_error(repoid)
1014 1015
1015 1016 include_secrets = False
1016 1017 if not has_superadmin_permission(apiuser):
1017 1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 1019 else:
1019 1020 include_secrets = True
1020 1021
1021 1022 updates = dict(
1022 1023 repo_name=repo_name
1023 1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1024 1025
1025 1026 fork_id=fork_of
1026 1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027 1028
1028 1029 user=owner
1029 1030 if not isinstance(owner, Optional) else repo.user.username,
1030 1031
1031 1032 repo_description=description
1032 1033 if not isinstance(description, Optional) else repo.description,
1033 1034
1034 1035 repo_private=private
1035 1036 if not isinstance(private, Optional) else repo.private,
1036 1037
1037 1038 clone_uri=clone_uri
1038 1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039 1040
1040 1041 push_uri=push_uri
1041 1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1042 1043
1043 1044 repo_landing_rev=landing_rev
1044 1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045 1046
1046 1047 repo_enable_statistics=enable_statistics
1047 1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048 1049
1049 1050 repo_enable_locking=enable_locking
1050 1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051 1052
1052 1053 repo_enable_downloads=enable_downloads
1053 1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054 1055
1055 1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 1058 request.translate, repo=repo)
1058 1059 ref_choices = list(set(ref_choices + [landing_ref]))
1059 1060
1060 1061 old_values = repo.get_api_data()
1061 1062 repo_type = repo.repo_type
1062 1063 schema = repo_schema.RepoSchema().bind(
1063 1064 repo_type_options=rhodecode.BACKENDS.keys(),
1064 1065 repo_ref_options=ref_choices,
1065 1066 repo_type=repo_type,
1066 1067 # user caller
1067 1068 user=apiuser,
1068 1069 old_values=old_values)
1069 1070 try:
1070 1071 schema_data = schema.deserialize(dict(
1071 1072 # we save old value, users cannot change type
1072 1073 repo_type=repo_type,
1073 1074
1074 1075 repo_name=updates['repo_name'],
1075 1076 repo_owner=updates['user'],
1076 1077 repo_description=updates['repo_description'],
1077 1078 repo_clone_uri=updates['clone_uri'],
1078 1079 repo_push_uri=updates['push_uri'],
1079 1080 repo_fork_of=updates['fork_id'],
1080 1081 repo_private=updates['repo_private'],
1081 1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 1083 repo_enable_statistics=updates['repo_enable_statistics'],
1083 1084 repo_enable_downloads=updates['repo_enable_downloads'],
1084 1085 repo_enable_locking=updates['repo_enable_locking']))
1085 1086 except validation_schema.Invalid as err:
1086 1087 raise JSONRPCValidationError(colander_exc=err)
1087 1088
1088 1089 # save validated data back into the updates dict
1089 1090 validated_updates = dict(
1090 1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 1092 repo_group=schema_data['repo_group']['repo_group_id'],
1092 1093
1093 1094 user=schema_data['repo_owner'],
1094 1095 repo_description=schema_data['repo_description'],
1095 1096 repo_private=schema_data['repo_private'],
1096 1097 clone_uri=schema_data['repo_clone_uri'],
1097 1098 push_uri=schema_data['repo_push_uri'],
1098 1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 1101 repo_enable_locking=schema_data['repo_enable_locking'],
1101 1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 1103 )
1103 1104
1104 1105 if schema_data['repo_fork_of']:
1105 1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 1107 validated_updates['fork_id'] = fork_repo.repo_id
1107 1108
1108 1109 # extra fields
1109 1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 1111 if fields:
1111 1112 validated_updates.update(fields)
1112 1113
1113 1114 try:
1114 1115 RepoModel().update(repo, **validated_updates)
1115 1116 audit_logger.store_api(
1116 1117 'repo.edit', action_data={'old_data': old_values},
1117 1118 user=apiuser, repo=repo)
1118 1119 Session().commit()
1119 1120 return {
1120 1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 1123 }
1123 1124 except Exception:
1124 1125 log.exception(
1125 1126 u"Exception while trying to update the repository %s",
1126 1127 repoid)
1127 1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128 1129
1129 1130
1130 1131 @jsonrpc_method()
1131 1132 def fork_repo(request, apiuser, repoid, fork_name,
1132 1133 owner=Optional(OAttr('apiuser')),
1133 1134 description=Optional(''),
1134 1135 private=Optional(False),
1135 1136 clone_uri=Optional(None),
1136 1137 landing_rev=Optional(None),
1137 1138 copy_permissions=Optional(False)):
1138 1139 """
1139 1140 Creates a fork of the specified |repo|.
1140 1141
1141 1142 * If the fork_name contains "/", fork will be created inside
1142 1143 a repository group or nested repository groups
1143 1144
1144 1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 1146 inside group "foo/bar". You have to have permissions to access and
1146 1147 write to the last repository group ("bar" in this example)
1147 1148
1148 1149 This command can only be run using an |authtoken| with minimum
1149 1150 read permissions of the forked repo, create fork permissions for an user.
1150 1151
1151 1152 :param apiuser: This is filled automatically from the |authtoken|.
1152 1153 :type apiuser: AuthUser
1153 1154 :param repoid: Set repository name or repository ID.
1154 1155 :type repoid: str or int
1155 1156 :param fork_name: Set the fork name, including it's repository group membership.
1156 1157 :type fork_name: str
1157 1158 :param owner: Set the fork owner.
1158 1159 :type owner: str
1159 1160 :param description: Set the fork description.
1160 1161 :type description: str
1161 1162 :param copy_permissions: Copy permissions from parent |repo|. The
1162 1163 default is False.
1163 1164 :type copy_permissions: bool
1164 1165 :param private: Make the fork private. The default is False.
1165 1166 :type private: bool
1166 1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167 1168
1168 1169 Example output:
1169 1170
1170 1171 .. code-block:: bash
1171 1172
1172 1173 id : <id_for_response>
1173 1174 api_key : "<api_key>"
1174 1175 args: {
1175 1176 "repoid" : "<reponame or repo_id>",
1176 1177 "fork_name": "<forkname>",
1177 1178 "owner": "<username or user_id = Optional(=apiuser)>",
1178 1179 "description": "<description>",
1179 1180 "copy_permissions": "<bool>",
1180 1181 "private": "<bool>",
1181 1182 "landing_rev": "<landing_rev>"
1182 1183 }
1183 1184
1184 1185 Example error output:
1185 1186
1186 1187 .. code-block:: bash
1187 1188
1188 1189 id : <id_given_in_input>
1189 1190 result: {
1190 1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 1192 "success": true,
1192 1193 "task": "<celery task id or None if done sync>"
1193 1194 }
1194 1195 error: null
1195 1196
1196 1197 """
1197 1198
1198 1199 repo = get_repo_or_error(repoid)
1199 1200 repo_name = repo.repo_name
1200 1201
1201 1202 if not has_superadmin_permission(apiuser):
1202 1203 # check if we have at least read permission for
1203 1204 # this repo that we fork !
1204 1205 _perms = (
1205 1206 'repository.admin', 'repository.write', 'repository.read')
1206 1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207 1208
1208 1209 # check if the regular user has at least fork permissions as well
1209 1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 1211 raise JSONRPCForbidden()
1211 1212
1212 1213 # check if user can set owner parameter
1213 1214 owner = validate_set_owner_permissions(apiuser, owner)
1214 1215
1215 1216 description = Optional.extract(description)
1216 1217 copy_permissions = Optional.extract(copy_permissions)
1217 1218 clone_uri = Optional.extract(clone_uri)
1218 1219
1219 1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 1222 ref_choices = list(set(ref_choices + [landing_ref]))
1222 1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223 1224
1224 1225 private = Optional.extract(private)
1225 1226
1226 1227 schema = repo_schema.RepoSchema().bind(
1227 1228 repo_type_options=rhodecode.BACKENDS.keys(),
1228 1229 repo_ref_options=ref_choices,
1229 1230 repo_type=repo.repo_type,
1230 1231 # user caller
1231 1232 user=apiuser)
1232 1233
1233 1234 try:
1234 1235 schema_data = schema.deserialize(dict(
1235 1236 repo_name=fork_name,
1236 1237 repo_type=repo.repo_type,
1237 1238 repo_owner=owner.username,
1238 1239 repo_description=description,
1239 1240 repo_landing_commit_ref=landing_commit_ref,
1240 1241 repo_clone_uri=clone_uri,
1241 1242 repo_private=private,
1242 1243 repo_copy_permissions=copy_permissions))
1243 1244 except validation_schema.Invalid as err:
1244 1245 raise JSONRPCValidationError(colander_exc=err)
1245 1246
1246 1247 try:
1247 1248 data = {
1248 1249 'fork_parent_id': repo.repo_id,
1249 1250
1250 1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 1252 'repo_name_full': schema_data['repo_name'],
1252 1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 1254 'repo_type': schema_data['repo_type'],
1254 1255 'description': schema_data['repo_description'],
1255 1256 'private': schema_data['repo_private'],
1256 1257 'copy_permissions': schema_data['repo_copy_permissions'],
1257 1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 1259 }
1259 1260
1260 1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 1262 # no commit, it's done in RepoModel, or async via celery
1262 1263 task_id = get_task_id(task)
1263 1264
1264 1265 return {
1265 1266 'msg': 'Created fork of `%s` as `%s`' % (
1266 1267 repo.repo_name, schema_data['repo_name']),
1267 1268 'success': True, # cannot return the repo data here since fork
1268 1269 # can be done async
1269 1270 'task': task_id
1270 1271 }
1271 1272 except Exception:
1272 1273 log.exception(
1273 1274 u"Exception while trying to create fork %s",
1274 1275 schema_data['repo_name'])
1275 1276 raise JSONRPCError(
1276 1277 'failed to fork repository `%s` as `%s`' % (
1277 1278 repo_name, schema_data['repo_name']))
1278 1279
1279 1280
1280 1281 @jsonrpc_method()
1281 1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 1283 """
1283 1284 Deletes a repository.
1284 1285
1285 1286 * When the `forks` parameter is set it's possible to detach or delete
1286 1287 forks of deleted repository.
1287 1288
1288 1289 This command can only be run using an |authtoken| with admin
1289 1290 permissions on the |repo|.
1290 1291
1291 1292 :param apiuser: This is filled automatically from the |authtoken|.
1292 1293 :type apiuser: AuthUser
1293 1294 :param repoid: Set the repository name or repository ID.
1294 1295 :type repoid: str or int
1295 1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 1297 :type forks: Optional(str)
1297 1298
1298 1299 Example error output:
1299 1300
1300 1301 .. code-block:: bash
1301 1302
1302 1303 id : <id_given_in_input>
1303 1304 result: {
1304 1305 "msg": "Deleted repository `<reponame>`",
1305 1306 "success": true
1306 1307 }
1307 1308 error: null
1308 1309 """
1309 1310
1310 1311 repo = get_repo_or_error(repoid)
1311 1312 repo_name = repo.repo_name
1312 1313 if not has_superadmin_permission(apiuser):
1313 1314 _perms = ('repository.admin',)
1314 1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 1316
1316 1317 try:
1317 1318 handle_forks = Optional.extract(forks)
1318 1319 _forks_msg = ''
1319 1320 _forks = [f for f in repo.forks]
1320 1321 if handle_forks == 'detach':
1321 1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 1323 elif handle_forks == 'delete':
1323 1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 1325 elif _forks:
1325 1326 raise JSONRPCError(
1326 1327 'Cannot delete `%s` it still contains attached forks' %
1327 1328 (repo.repo_name,)
1328 1329 )
1329 1330 old_data = repo.get_api_data()
1330 1331 RepoModel().delete(repo, forks=forks)
1331 1332
1332 1333 repo = audit_logger.RepoWrap(repo_id=None,
1333 1334 repo_name=repo.repo_name)
1334 1335
1335 1336 audit_logger.store_api(
1336 1337 'repo.delete', action_data={'old_data': old_data},
1337 1338 user=apiuser, repo=repo)
1338 1339
1339 1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 1341 Session().commit()
1341 1342 return {
1342 1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 1344 'success': True
1344 1345 }
1345 1346 except Exception:
1346 1347 log.exception("Exception occurred while trying to delete repo")
1347 1348 raise JSONRPCError(
1348 1349 'failed to delete repository `%s`' % (repo_name,)
1349 1350 )
1350 1351
1351 1352
1352 1353 #TODO: marcink, change name ?
1353 1354 @jsonrpc_method()
1354 1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 1356 """
1356 1357 Invalidates the cache for the specified repository.
1357 1358
1358 1359 This command can only be run using an |authtoken| with admin rights to
1359 1360 the specified repository.
1360 1361
1361 1362 This command takes the following options:
1362 1363
1363 1364 :param apiuser: This is filled automatically from |authtoken|.
1364 1365 :type apiuser: AuthUser
1365 1366 :param repoid: Sets the repository name or repository ID.
1366 1367 :type repoid: str or int
1367 1368 :param delete_keys: This deletes the invalidated keys instead of
1368 1369 just flagging them.
1369 1370 :type delete_keys: Optional(``True`` | ``False``)
1370 1371
1371 1372 Example output:
1372 1373
1373 1374 .. code-block:: bash
1374 1375
1375 1376 id : <id_given_in_input>
1376 1377 result : {
1377 1378 'msg': Cache for repository `<repository name>` was invalidated,
1378 1379 'repository': <repository name>
1379 1380 }
1380 1381 error : null
1381 1382
1382 1383 Example error output:
1383 1384
1384 1385 .. code-block:: bash
1385 1386
1386 1387 id : <id_given_in_input>
1387 1388 result : null
1388 1389 error : {
1389 1390 'Error occurred during cache invalidation action'
1390 1391 }
1391 1392
1392 1393 """
1393 1394
1394 1395 repo = get_repo_or_error(repoid)
1395 1396 if not has_superadmin_permission(apiuser):
1396 1397 _perms = ('repository.admin', 'repository.write',)
1397 1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398 1399
1399 1400 delete = Optional.extract(delete_keys)
1400 1401 try:
1401 1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 1403 return {
1403 1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 1405 'repository': repo.repo_name
1405 1406 }
1406 1407 except Exception:
1407 1408 log.exception(
1408 1409 "Exception occurred while trying to invalidate repo cache")
1409 1410 raise JSONRPCError(
1410 1411 'Error occurred during cache invalidation action'
1411 1412 )
1412 1413
1413 1414
1414 1415 #TODO: marcink, change name ?
1415 1416 @jsonrpc_method()
1416 1417 def lock(request, apiuser, repoid, locked=Optional(None),
1417 1418 userid=Optional(OAttr('apiuser'))):
1418 1419 """
1419 1420 Sets the lock state of the specified |repo| by the given user.
1420 1421 From more information, see :ref:`repo-locking`.
1421 1422
1422 1423 * If the ``userid`` option is not set, the repository is locked to the
1423 1424 user who called the method.
1424 1425 * If the ``locked`` parameter is not set, the current lock state of the
1425 1426 repository is displayed.
1426 1427
1427 1428 This command can only be run using an |authtoken| with admin rights to
1428 1429 the specified repository.
1429 1430
1430 1431 This command takes the following options:
1431 1432
1432 1433 :param apiuser: This is filled automatically from the |authtoken|.
1433 1434 :type apiuser: AuthUser
1434 1435 :param repoid: Sets the repository name or repository ID.
1435 1436 :type repoid: str or int
1436 1437 :param locked: Sets the lock state.
1437 1438 :type locked: Optional(``True`` | ``False``)
1438 1439 :param userid: Set the repository lock to this user.
1439 1440 :type userid: Optional(str or int)
1440 1441
1441 1442 Example error output:
1442 1443
1443 1444 .. code-block:: bash
1444 1445
1445 1446 id : <id_given_in_input>
1446 1447 result : {
1447 1448 'repo': '<reponame>',
1448 1449 'locked': <bool: lock state>,
1449 1450 'locked_since': <int: lock timestamp>,
1450 1451 'locked_by': <username of person who made the lock>,
1451 1452 'lock_reason': <str: reason for locking>,
1452 1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 1455 or
1455 1456 'msg': 'Repo `<repository name>` not locked.'
1456 1457 or
1457 1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 1459 }
1459 1460 error : null
1460 1461
1461 1462 Example error output:
1462 1463
1463 1464 .. code-block:: bash
1464 1465
1465 1466 id : <id_given_in_input>
1466 1467 result : null
1467 1468 error : {
1468 1469 'Error occurred locking repository `<reponame>`'
1469 1470 }
1470 1471 """
1471 1472
1472 1473 repo = get_repo_or_error(repoid)
1473 1474 if not has_superadmin_permission(apiuser):
1474 1475 # check if we have at least write permission for this repo !
1475 1476 _perms = ('repository.admin', 'repository.write',)
1476 1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477 1478
1478 1479 # make sure normal user does not pass someone else userid,
1479 1480 # he is not allowed to do that
1480 1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 1482 raise JSONRPCError('userid is not the same as your user')
1482 1483
1483 1484 if isinstance(userid, Optional):
1484 1485 userid = apiuser.user_id
1485 1486
1486 1487 user = get_user_or_error(userid)
1487 1488
1488 1489 if isinstance(locked, Optional):
1489 1490 lockobj = repo.locked
1490 1491
1491 1492 if lockobj[0] is None:
1492 1493 _d = {
1493 1494 'repo': repo.repo_name,
1494 1495 'locked': False,
1495 1496 'locked_since': None,
1496 1497 'locked_by': None,
1497 1498 'lock_reason': None,
1498 1499 'lock_state_changed': False,
1499 1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 1501 }
1501 1502 return _d
1502 1503 else:
1503 1504 _user_id, _time, _reason = lockobj
1504 1505 lock_user = get_user_or_error(userid)
1505 1506 _d = {
1506 1507 'repo': repo.repo_name,
1507 1508 'locked': True,
1508 1509 'locked_since': _time,
1509 1510 'locked_by': lock_user.username,
1510 1511 'lock_reason': _reason,
1511 1512 'lock_state_changed': False,
1512 1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 1514 % (repo.repo_name, lock_user.username,
1514 1515 json.dumps(time_to_datetime(_time))))
1515 1516 }
1516 1517 return _d
1517 1518
1518 1519 # force locked state through a flag
1519 1520 else:
1520 1521 locked = str2bool(locked)
1521 1522 lock_reason = Repository.LOCK_API
1522 1523 try:
1523 1524 if locked:
1524 1525 lock_time = time.time()
1525 1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 1527 else:
1527 1528 lock_time = None
1528 1529 Repository.unlock(repo)
1529 1530 _d = {
1530 1531 'repo': repo.repo_name,
1531 1532 'locked': locked,
1532 1533 'locked_since': lock_time,
1533 1534 'locked_by': user.username,
1534 1535 'lock_reason': lock_reason,
1535 1536 'lock_state_changed': True,
1536 1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 1538 % (user.username, repo.repo_name, locked))
1538 1539 }
1539 1540 return _d
1540 1541 except Exception:
1541 1542 log.exception(
1542 1543 "Exception occurred while trying to lock repository")
1543 1544 raise JSONRPCError(
1544 1545 'Error occurred locking repository `%s`' % repo.repo_name
1545 1546 )
1546 1547
1547 1548
1548 1549 @jsonrpc_method()
1549 1550 def comment_commit(
1550 1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 1553 resolves_comment_id=Optional(None),
1553 1554 userid=Optional(OAttr('apiuser'))):
1554 1555 """
1555 1556 Set a commit comment, and optionally change the status of the commit.
1556 1557
1557 1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 1559 :type apiuser: AuthUser
1559 1560 :param repoid: Set the repository name or repository ID.
1560 1561 :type repoid: str or int
1561 1562 :param commit_id: Specify the commit_id for which to set a comment.
1562 1563 :type commit_id: str
1563 1564 :param message: The comment text.
1564 1565 :type message: str
1565 1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 1567 'approved', 'rejected', 'under_review'
1567 1568 :type status: str
1568 1569 :param comment_type: Comment type, one of: 'note', 'todo'
1569 1570 :type comment_type: Optional(str), default: 'note'
1570 1571 :param userid: Set the user name of the comment creator.
1571 1572 :type userid: Optional(str or int)
1572 1573
1573 1574 Example error output:
1574 1575
1575 1576 .. code-block:: bash
1576 1577
1577 1578 {
1578 1579 "id" : <id_given_in_input>,
1579 1580 "result" : {
1580 1581 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1581 1582 "status_change": null or <status>,
1582 1583 "success": true
1583 1584 },
1584 1585 "error" : null
1585 1586 }
1586 1587
1587 1588 """
1588 1589 repo = get_repo_or_error(repoid)
1589 1590 if not has_superadmin_permission(apiuser):
1590 1591 _perms = ('repository.read', 'repository.write', 'repository.admin')
1591 1592 validate_repo_permissions(apiuser, repoid, repo, _perms)
1592 1593
1593 1594 try:
1594 1595 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1595 1596 except Exception as e:
1596 1597 log.exception('Failed to fetch commit')
1597 1598 raise JSONRPCError(safe_str(e))
1598 1599
1599 1600 if isinstance(userid, Optional):
1600 1601 userid = apiuser.user_id
1601 1602
1602 1603 user = get_user_or_error(userid)
1603 1604 status = Optional.extract(status)
1604 1605 comment_type = Optional.extract(comment_type)
1605 1606 resolves_comment_id = Optional.extract(resolves_comment_id)
1606 1607
1607 1608 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1608 1609 if status and status not in allowed_statuses:
1609 1610 raise JSONRPCError('Bad status, must be on '
1610 1611 'of %s got %s' % (allowed_statuses, status,))
1611 1612
1612 1613 if resolves_comment_id:
1613 1614 comment = ChangesetComment.get(resolves_comment_id)
1614 1615 if not comment:
1615 1616 raise JSONRPCError(
1616 1617 'Invalid resolves_comment_id `%s` for this commit.'
1617 1618 % resolves_comment_id)
1618 1619 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1619 1620 raise JSONRPCError(
1620 1621 'Comment `%s` is wrong type for setting status to resolved.'
1621 1622 % resolves_comment_id)
1622 1623
1623 1624 try:
1624 1625 rc_config = SettingsModel().get_all_settings()
1625 1626 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1626 1627 status_change_label = ChangesetStatus.get_status_lbl(status)
1627 1628 comment = CommentsModel().create(
1628 1629 message, repo, user, commit_id=commit_id,
1629 1630 status_change=status_change_label,
1630 1631 status_change_type=status,
1631 1632 renderer=renderer,
1632 1633 comment_type=comment_type,
1633 1634 resolves_comment_id=resolves_comment_id,
1634 1635 auth_user=apiuser
1635 1636 )
1636 1637 if status:
1637 1638 # also do a status change
1638 1639 try:
1639 1640 ChangesetStatusModel().set_status(
1640 1641 repo, status, user, comment, revision=commit_id,
1641 1642 dont_allow_on_closed_pull_request=True
1642 1643 )
1643 1644 except StatusChangeOnClosedPullRequestError:
1644 1645 log.exception(
1645 1646 "Exception occurred while trying to change repo commit status")
1646 1647 msg = ('Changing status on a changeset associated with '
1647 1648 'a closed pull request is not allowed')
1648 1649 raise JSONRPCError(msg)
1649 1650
1650 1651 Session().commit()
1651 1652 return {
1652 1653 'msg': (
1653 1654 'Commented on commit `%s` for repository `%s`' % (
1654 1655 comment.revision, repo.repo_name)),
1655 1656 'status_change': status,
1656 1657 'success': True,
1657 1658 }
1658 1659 except JSONRPCError:
1659 1660 # catch any inside errors, and re-raise them to prevent from
1660 1661 # below global catch to silence them
1661 1662 raise
1662 1663 except Exception:
1663 1664 log.exception("Exception occurred while trying to comment on commit")
1664 1665 raise JSONRPCError(
1665 1666 'failed to set comment on repository `%s`' % (repo.repo_name,)
1666 1667 )
1667 1668
1668 1669
1669 1670 @jsonrpc_method()
1670 1671 def get_repo_comments(request, apiuser, repoid,
1671 1672 commit_id=Optional(None), comment_type=Optional(None),
1672 1673 userid=Optional(None)):
1673 1674 """
1674 1675 Get all comments for a repository
1675 1676
1676 1677 :param apiuser: This is filled automatically from the |authtoken|.
1677 1678 :type apiuser: AuthUser
1678 1679 :param repoid: Set the repository name or repository ID.
1679 1680 :type repoid: str or int
1680 1681 :param commit_id: Optionally filter the comments by the commit_id
1681 1682 :type commit_id: Optional(str), default: None
1682 1683 :param comment_type: Optionally filter the comments by the comment_type
1683 1684 one of: 'note', 'todo'
1684 1685 :type comment_type: Optional(str), default: None
1685 1686 :param userid: Optionally filter the comments by the author of comment
1686 1687 :type userid: Optional(str or int), Default: None
1687 1688
1688 1689 Example error output:
1689 1690
1690 1691 .. code-block:: bash
1691 1692
1692 1693 {
1693 1694 "id" : <id_given_in_input>,
1694 1695 "result" : [
1695 1696 {
1696 1697 "comment_author": <USER_DETAILS>,
1697 1698 "comment_created_on": "2017-02-01T14:38:16.309",
1698 1699 "comment_f_path": "file.txt",
1699 1700 "comment_id": 282,
1700 1701 "comment_lineno": "n1",
1701 1702 "comment_resolved_by": null,
1702 1703 "comment_status": [],
1703 1704 "comment_text": "This file needs a header",
1704 1705 "comment_type": "todo"
1705 1706 }
1706 1707 ],
1707 1708 "error" : null
1708 1709 }
1709 1710
1710 1711 """
1711 1712 repo = get_repo_or_error(repoid)
1712 1713 if not has_superadmin_permission(apiuser):
1713 1714 _perms = ('repository.read', 'repository.write', 'repository.admin')
1714 1715 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715 1716
1716 1717 commit_id = Optional.extract(commit_id)
1717 1718
1718 1719 userid = Optional.extract(userid)
1719 1720 if userid:
1720 1721 user = get_user_or_error(userid)
1721 1722 else:
1722 1723 user = None
1723 1724
1724 1725 comment_type = Optional.extract(comment_type)
1725 1726 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1726 1727 raise JSONRPCError(
1727 1728 'comment_type must be one of `{}` got {}'.format(
1728 1729 ChangesetComment.COMMENT_TYPES, comment_type)
1729 1730 )
1730 1731
1731 1732 comments = CommentsModel().get_repository_comments(
1732 1733 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1733 1734 return comments
1734 1735
1735 1736
1736 1737 @jsonrpc_method()
1737 1738 def grant_user_permission(request, apiuser, repoid, userid, perm):
1738 1739 """
1739 1740 Grant permissions for the specified user on the given repository,
1740 1741 or update existing permissions if found.
1741 1742
1742 1743 This command can only be run using an |authtoken| with admin
1743 1744 permissions on the |repo|.
1744 1745
1745 1746 :param apiuser: This is filled automatically from the |authtoken|.
1746 1747 :type apiuser: AuthUser
1747 1748 :param repoid: Set the repository name or repository ID.
1748 1749 :type repoid: str or int
1749 1750 :param userid: Set the user name.
1750 1751 :type userid: str
1751 1752 :param perm: Set the user permissions, using the following format
1752 1753 ``(repository.(none|read|write|admin))``
1753 1754 :type perm: str
1754 1755
1755 1756 Example output:
1756 1757
1757 1758 .. code-block:: bash
1758 1759
1759 1760 id : <id_given_in_input>
1760 1761 result: {
1761 1762 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1762 1763 "success": true
1763 1764 }
1764 1765 error: null
1765 1766 """
1766 1767
1767 1768 repo = get_repo_or_error(repoid)
1768 1769 user = get_user_or_error(userid)
1769 1770 perm = get_perm_or_error(perm)
1770 1771 if not has_superadmin_permission(apiuser):
1771 1772 _perms = ('repository.admin',)
1772 1773 validate_repo_permissions(apiuser, repoid, repo, _perms)
1773 1774
1774 1775 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1775 1776 try:
1776 1777 changes = RepoModel().update_permissions(
1777 1778 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1778 1779
1779 1780 action_data = {
1780 1781 'added': changes['added'],
1781 1782 'updated': changes['updated'],
1782 1783 'deleted': changes['deleted'],
1783 1784 }
1784 1785 audit_logger.store_api(
1785 1786 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1787 Session().commit()
1788 PermissionModel().flush_user_permission_caches(changes)
1786 1789
1787 Session().commit()
1788 1790 return {
1789 1791 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1790 1792 perm.permission_name, user.username, repo.repo_name
1791 1793 ),
1792 1794 'success': True
1793 1795 }
1794 1796 except Exception:
1795 1797 log.exception("Exception occurred while trying edit permissions for repo")
1796 1798 raise JSONRPCError(
1797 1799 'failed to edit permission for user: `%s` in repo: `%s`' % (
1798 1800 userid, repoid
1799 1801 )
1800 1802 )
1801 1803
1802 1804
1803 1805 @jsonrpc_method()
1804 1806 def revoke_user_permission(request, apiuser, repoid, userid):
1805 1807 """
1806 1808 Revoke permission for a user on the specified repository.
1807 1809
1808 1810 This command can only be run using an |authtoken| with admin
1809 1811 permissions on the |repo|.
1810 1812
1811 1813 :param apiuser: This is filled automatically from the |authtoken|.
1812 1814 :type apiuser: AuthUser
1813 1815 :param repoid: Set the repository name or repository ID.
1814 1816 :type repoid: str or int
1815 1817 :param userid: Set the user name of revoked user.
1816 1818 :type userid: str or int
1817 1819
1818 1820 Example error output:
1819 1821
1820 1822 .. code-block:: bash
1821 1823
1822 1824 id : <id_given_in_input>
1823 1825 result: {
1824 1826 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1825 1827 "success": true
1826 1828 }
1827 1829 error: null
1828 1830 """
1829 1831
1830 1832 repo = get_repo_or_error(repoid)
1831 1833 user = get_user_or_error(userid)
1832 1834 if not has_superadmin_permission(apiuser):
1833 1835 _perms = ('repository.admin',)
1834 1836 validate_repo_permissions(apiuser, repoid, repo, _perms)
1835 1837
1836 1838 perm_deletions = [[user.user_id, None, "user"]]
1837 1839 try:
1838 1840 changes = RepoModel().update_permissions(
1839 1841 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1840 1842
1841 1843 action_data = {
1842 1844 'added': changes['added'],
1843 1845 'updated': changes['updated'],
1844 1846 'deleted': changes['deleted'],
1845 1847 }
1846 1848 audit_logger.store_api(
1847 1849 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1850 Session().commit()
1851 PermissionModel().flush_user_permission_caches(changes)
1848 1852
1849 Session().commit()
1850 1853 return {
1851 1854 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1852 1855 user.username, repo.repo_name
1853 1856 ),
1854 1857 'success': True
1855 1858 }
1856 1859 except Exception:
1857 1860 log.exception("Exception occurred while trying revoke permissions to repo")
1858 1861 raise JSONRPCError(
1859 1862 'failed to edit permission for user: `%s` in repo: `%s`' % (
1860 1863 userid, repoid
1861 1864 )
1862 1865 )
1863 1866
1864 1867
1865 1868 @jsonrpc_method()
1866 1869 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1867 1870 """
1868 1871 Grant permission for a user group on the specified repository,
1869 1872 or update existing permissions.
1870 1873
1871 1874 This command can only be run using an |authtoken| with admin
1872 1875 permissions on the |repo|.
1873 1876
1874 1877 :param apiuser: This is filled automatically from the |authtoken|.
1875 1878 :type apiuser: AuthUser
1876 1879 :param repoid: Set the repository name or repository ID.
1877 1880 :type repoid: str or int
1878 1881 :param usergroupid: Specify the ID of the user group.
1879 1882 :type usergroupid: str or int
1880 1883 :param perm: Set the user group permissions using the following
1881 1884 format: (repository.(none|read|write|admin))
1882 1885 :type perm: str
1883 1886
1884 1887 Example output:
1885 1888
1886 1889 .. code-block:: bash
1887 1890
1888 1891 id : <id_given_in_input>
1889 1892 result : {
1890 1893 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1891 1894 "success": true
1892 1895
1893 1896 }
1894 1897 error : null
1895 1898
1896 1899 Example error output:
1897 1900
1898 1901 .. code-block:: bash
1899 1902
1900 1903 id : <id_given_in_input>
1901 1904 result : null
1902 1905 error : {
1903 1906 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1904 1907 }
1905 1908
1906 1909 """
1907 1910
1908 1911 repo = get_repo_or_error(repoid)
1909 1912 perm = get_perm_or_error(perm)
1910 1913 if not has_superadmin_permission(apiuser):
1911 1914 _perms = ('repository.admin',)
1912 1915 validate_repo_permissions(apiuser, repoid, repo, _perms)
1913 1916
1914 1917 user_group = get_user_group_or_error(usergroupid)
1915 1918 if not has_superadmin_permission(apiuser):
1916 1919 # check if we have at least read permission for this user group !
1917 1920 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1918 1921 if not HasUserGroupPermissionAnyApi(*_perms)(
1919 1922 user=apiuser, user_group_name=user_group.users_group_name):
1920 1923 raise JSONRPCError(
1921 1924 'user group `%s` does not exist' % (usergroupid,))
1922 1925
1923 1926 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1924 1927 try:
1925 1928 changes = RepoModel().update_permissions(
1926 1929 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1927 1930 action_data = {
1928 1931 'added': changes['added'],
1929 1932 'updated': changes['updated'],
1930 1933 'deleted': changes['deleted'],
1931 1934 }
1932 1935 audit_logger.store_api(
1933 1936 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1937 Session().commit()
1938 PermissionModel().flush_user_permission_caches(changes)
1934 1939
1935 Session().commit()
1936 1940 return {
1937 1941 'msg': 'Granted perm: `%s` for user group: `%s` in '
1938 1942 'repo: `%s`' % (
1939 1943 perm.permission_name, user_group.users_group_name,
1940 1944 repo.repo_name
1941 1945 ),
1942 1946 'success': True
1943 1947 }
1944 1948 except Exception:
1945 1949 log.exception(
1946 1950 "Exception occurred while trying change permission on repo")
1947 1951 raise JSONRPCError(
1948 1952 'failed to edit permission for user group: `%s` in '
1949 1953 'repo: `%s`' % (
1950 1954 usergroupid, repo.repo_name
1951 1955 )
1952 1956 )
1953 1957
1954 1958
1955 1959 @jsonrpc_method()
1956 1960 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1957 1961 """
1958 1962 Revoke the permissions of a user group on a given repository.
1959 1963
1960 1964 This command can only be run using an |authtoken| with admin
1961 1965 permissions on the |repo|.
1962 1966
1963 1967 :param apiuser: This is filled automatically from the |authtoken|.
1964 1968 :type apiuser: AuthUser
1965 1969 :param repoid: Set the repository name or repository ID.
1966 1970 :type repoid: str or int
1967 1971 :param usergroupid: Specify the user group ID.
1968 1972 :type usergroupid: str or int
1969 1973
1970 1974 Example output:
1971 1975
1972 1976 .. code-block:: bash
1973 1977
1974 1978 id : <id_given_in_input>
1975 1979 result: {
1976 1980 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1977 1981 "success": true
1978 1982 }
1979 1983 error: null
1980 1984 """
1981 1985
1982 1986 repo = get_repo_or_error(repoid)
1983 1987 if not has_superadmin_permission(apiuser):
1984 1988 _perms = ('repository.admin',)
1985 1989 validate_repo_permissions(apiuser, repoid, repo, _perms)
1986 1990
1987 1991 user_group = get_user_group_or_error(usergroupid)
1988 1992 if not has_superadmin_permission(apiuser):
1989 1993 # check if we have at least read permission for this user group !
1990 1994 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1991 1995 if not HasUserGroupPermissionAnyApi(*_perms)(
1992 1996 user=apiuser, user_group_name=user_group.users_group_name):
1993 1997 raise JSONRPCError(
1994 1998 'user group `%s` does not exist' % (usergroupid,))
1995 1999
1996 2000 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1997 2001 try:
1998 2002 changes = RepoModel().update_permissions(
1999 2003 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2000 2004 action_data = {
2001 2005 'added': changes['added'],
2002 2006 'updated': changes['updated'],
2003 2007 'deleted': changes['deleted'],
2004 2008 }
2005 2009 audit_logger.store_api(
2006 2010 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2011 Session().commit()
2012 PermissionModel().flush_user_permission_caches(changes)
2007 2013
2008 Session().commit()
2009 2014 return {
2010 2015 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2011 2016 user_group.users_group_name, repo.repo_name
2012 2017 ),
2013 2018 'success': True
2014 2019 }
2015 2020 except Exception:
2016 2021 log.exception("Exception occurred while trying revoke "
2017 2022 "user group permission on repo")
2018 2023 raise JSONRPCError(
2019 2024 'failed to edit permission for user group: `%s` in '
2020 2025 'repo: `%s`' % (
2021 2026 user_group.users_group_name, repo.repo_name
2022 2027 )
2023 2028 )
2024 2029
2025 2030
2026 2031 @jsonrpc_method()
2027 2032 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2028 2033 """
2029 2034 Triggers a pull on the given repository from a remote location. You
2030 2035 can use this to keep remote repositories up-to-date.
2031 2036
2032 2037 This command can only be run using an |authtoken| with admin
2033 2038 rights to the specified repository. For more information,
2034 2039 see :ref:`config-token-ref`.
2035 2040
2036 2041 This command takes the following options:
2037 2042
2038 2043 :param apiuser: This is filled automatically from the |authtoken|.
2039 2044 :type apiuser: AuthUser
2040 2045 :param repoid: The repository name or repository ID.
2041 2046 :type repoid: str or int
2042 2047 :param remote_uri: Optional remote URI to pass in for pull
2043 2048 :type remote_uri: str
2044 2049
2045 2050 Example output:
2046 2051
2047 2052 .. code-block:: bash
2048 2053
2049 2054 id : <id_given_in_input>
2050 2055 result : {
2051 2056 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2052 2057 "repository": "<repository name>"
2053 2058 }
2054 2059 error : null
2055 2060
2056 2061 Example error output:
2057 2062
2058 2063 .. code-block:: bash
2059 2064
2060 2065 id : <id_given_in_input>
2061 2066 result : null
2062 2067 error : {
2063 2068 "Unable to push changes from `<remote_url>`"
2064 2069 }
2065 2070
2066 2071 """
2067 2072
2068 2073 repo = get_repo_or_error(repoid)
2069 2074 remote_uri = Optional.extract(remote_uri)
2070 2075 remote_uri_display = remote_uri or repo.clone_uri_hidden
2071 2076 if not has_superadmin_permission(apiuser):
2072 2077 _perms = ('repository.admin',)
2073 2078 validate_repo_permissions(apiuser, repoid, repo, _perms)
2074 2079
2075 2080 try:
2076 2081 ScmModel().pull_changes(
2077 2082 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2078 2083 return {
2079 2084 'msg': 'Pulled from url `%s` on repo `%s`' % (
2080 2085 remote_uri_display, repo.repo_name),
2081 2086 'repository': repo.repo_name
2082 2087 }
2083 2088 except Exception:
2084 2089 log.exception("Exception occurred while trying to "
2085 2090 "pull changes from remote location")
2086 2091 raise JSONRPCError(
2087 2092 'Unable to pull changes from `%s`' % remote_uri_display
2088 2093 )
2089 2094
2090 2095
2091 2096 @jsonrpc_method()
2092 2097 def strip(request, apiuser, repoid, revision, branch):
2093 2098 """
2094 2099 Strips the given revision from the specified repository.
2095 2100
2096 2101 * This will remove the revision and all of its decendants.
2097 2102
2098 2103 This command can only be run using an |authtoken| with admin rights to
2099 2104 the specified repository.
2100 2105
2101 2106 This command takes the following options:
2102 2107
2103 2108 :param apiuser: This is filled automatically from the |authtoken|.
2104 2109 :type apiuser: AuthUser
2105 2110 :param repoid: The repository name or repository ID.
2106 2111 :type repoid: str or int
2107 2112 :param revision: The revision you wish to strip.
2108 2113 :type revision: str
2109 2114 :param branch: The branch from which to strip the revision.
2110 2115 :type branch: str
2111 2116
2112 2117 Example output:
2113 2118
2114 2119 .. code-block:: bash
2115 2120
2116 2121 id : <id_given_in_input>
2117 2122 result : {
2118 2123 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2119 2124 "repository": "<repository name>"
2120 2125 }
2121 2126 error : null
2122 2127
2123 2128 Example error output:
2124 2129
2125 2130 .. code-block:: bash
2126 2131
2127 2132 id : <id_given_in_input>
2128 2133 result : null
2129 2134 error : {
2130 2135 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2131 2136 }
2132 2137
2133 2138 """
2134 2139
2135 2140 repo = get_repo_or_error(repoid)
2136 2141 if not has_superadmin_permission(apiuser):
2137 2142 _perms = ('repository.admin',)
2138 2143 validate_repo_permissions(apiuser, repoid, repo, _perms)
2139 2144
2140 2145 try:
2141 2146 ScmModel().strip(repo, revision, branch)
2142 2147 audit_logger.store_api(
2143 2148 'repo.commit.strip', action_data={'commit_id': revision},
2144 2149 repo=repo,
2145 2150 user=apiuser, commit=True)
2146 2151
2147 2152 return {
2148 2153 'msg': 'Stripped commit %s from repo `%s`' % (
2149 2154 revision, repo.repo_name),
2150 2155 'repository': repo.repo_name
2151 2156 }
2152 2157 except Exception:
2153 2158 log.exception("Exception while trying to strip")
2154 2159 raise JSONRPCError(
2155 2160 'Unable to strip commit %s from repo `%s`' % (
2156 2161 revision, repo.repo_name)
2157 2162 )
2158 2163
2159 2164
2160 2165 @jsonrpc_method()
2161 2166 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2162 2167 """
2163 2168 Returns all settings for a repository. If key is given it only returns the
2164 2169 setting identified by the key or null.
2165 2170
2166 2171 :param apiuser: This is filled automatically from the |authtoken|.
2167 2172 :type apiuser: AuthUser
2168 2173 :param repoid: The repository name or repository id.
2169 2174 :type repoid: str or int
2170 2175 :param key: Key of the setting to return.
2171 2176 :type: key: Optional(str)
2172 2177
2173 2178 Example output:
2174 2179
2175 2180 .. code-block:: bash
2176 2181
2177 2182 {
2178 2183 "error": null,
2179 2184 "id": 237,
2180 2185 "result": {
2181 2186 "extensions_largefiles": true,
2182 2187 "extensions_evolve": true,
2183 2188 "hooks_changegroup_push_logger": true,
2184 2189 "hooks_changegroup_repo_size": false,
2185 2190 "hooks_outgoing_pull_logger": true,
2186 2191 "phases_publish": "True",
2187 2192 "rhodecode_hg_use_rebase_for_merging": true,
2188 2193 "rhodecode_pr_merge_enabled": true,
2189 2194 "rhodecode_use_outdated_comments": true
2190 2195 }
2191 2196 }
2192 2197 """
2193 2198
2194 2199 # Restrict access to this api method to admins only.
2195 2200 if not has_superadmin_permission(apiuser):
2196 2201 raise JSONRPCForbidden()
2197 2202
2198 2203 try:
2199 2204 repo = get_repo_or_error(repoid)
2200 2205 settings_model = VcsSettingsModel(repo=repo)
2201 2206 settings = settings_model.get_global_settings()
2202 2207 settings.update(settings_model.get_repo_settings())
2203 2208
2204 2209 # If only a single setting is requested fetch it from all settings.
2205 2210 key = Optional.extract(key)
2206 2211 if key is not None:
2207 2212 settings = settings.get(key, None)
2208 2213 except Exception:
2209 2214 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2210 2215 log.exception(msg)
2211 2216 raise JSONRPCError(msg)
2212 2217
2213 2218 return settings
2214 2219
2215 2220
2216 2221 @jsonrpc_method()
2217 2222 def set_repo_settings(request, apiuser, repoid, settings):
2218 2223 """
2219 2224 Update repository settings. Returns true on success.
2220 2225
2221 2226 :param apiuser: This is filled automatically from the |authtoken|.
2222 2227 :type apiuser: AuthUser
2223 2228 :param repoid: The repository name or repository id.
2224 2229 :type repoid: str or int
2225 2230 :param settings: The new settings for the repository.
2226 2231 :type: settings: dict
2227 2232
2228 2233 Example output:
2229 2234
2230 2235 .. code-block:: bash
2231 2236
2232 2237 {
2233 2238 "error": null,
2234 2239 "id": 237,
2235 2240 "result": true
2236 2241 }
2237 2242 """
2238 2243 # Restrict access to this api method to admins only.
2239 2244 if not has_superadmin_permission(apiuser):
2240 2245 raise JSONRPCForbidden()
2241 2246
2242 2247 if type(settings) is not dict:
2243 2248 raise JSONRPCError('Settings have to be a JSON Object.')
2244 2249
2245 2250 try:
2246 2251 settings_model = VcsSettingsModel(repo=repoid)
2247 2252
2248 2253 # Merge global, repo and incoming settings.
2249 2254 new_settings = settings_model.get_global_settings()
2250 2255 new_settings.update(settings_model.get_repo_settings())
2251 2256 new_settings.update(settings)
2252 2257
2253 2258 # Update the settings.
2254 2259 inherit_global_settings = new_settings.get(
2255 2260 'inherit_global_settings', False)
2256 2261 settings_model.create_or_update_repo_settings(
2257 2262 new_settings, inherit_global_settings=inherit_global_settings)
2258 2263 Session().commit()
2259 2264 except Exception:
2260 2265 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2261 2266 log.exception(msg)
2262 2267 raise JSONRPCError(msg)
2263 2268
2264 2269 # Indicate success.
2265 2270 return True
2266 2271
2267 2272
2268 2273 @jsonrpc_method()
2269 2274 def maintenance(request, apiuser, repoid):
2270 2275 """
2271 2276 Triggers a maintenance on the given repository.
2272 2277
2273 2278 This command can only be run using an |authtoken| with admin
2274 2279 rights to the specified repository. For more information,
2275 2280 see :ref:`config-token-ref`.
2276 2281
2277 2282 This command takes the following options:
2278 2283
2279 2284 :param apiuser: This is filled automatically from the |authtoken|.
2280 2285 :type apiuser: AuthUser
2281 2286 :param repoid: The repository name or repository ID.
2282 2287 :type repoid: str or int
2283 2288
2284 2289 Example output:
2285 2290
2286 2291 .. code-block:: bash
2287 2292
2288 2293 id : <id_given_in_input>
2289 2294 result : {
2290 2295 "msg": "executed maintenance command",
2291 2296 "executed_actions": [
2292 2297 <action_message>, <action_message2>...
2293 2298 ],
2294 2299 "repository": "<repository name>"
2295 2300 }
2296 2301 error : null
2297 2302
2298 2303 Example error output:
2299 2304
2300 2305 .. code-block:: bash
2301 2306
2302 2307 id : <id_given_in_input>
2303 2308 result : null
2304 2309 error : {
2305 2310 "Unable to execute maintenance on `<reponame>`"
2306 2311 }
2307 2312
2308 2313 """
2309 2314
2310 2315 repo = get_repo_or_error(repoid)
2311 2316 if not has_superadmin_permission(apiuser):
2312 2317 _perms = ('repository.admin',)
2313 2318 validate_repo_permissions(apiuser, repoid, repo, _perms)
2314 2319
2315 2320 try:
2316 2321 maintenance = repo_maintenance.RepoMaintenance()
2317 2322 executed_actions = maintenance.execute(repo)
2318 2323
2319 2324 return {
2320 2325 'msg': 'executed maintenance command',
2321 2326 'executed_actions': executed_actions,
2322 2327 'repository': repo.repo_name
2323 2328 }
2324 2329 except Exception:
2325 2330 log.exception("Exception occurred while trying to run maintenance")
2326 2331 raise JSONRPCError(
2327 2332 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,754 +1,759 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from rhodecode.api import JSONRPCValidationError
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 26 from rhodecode.api.utils import (
27 27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import (
32 32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
33 33 from rhodecode.model.db import Session
34 from rhodecode.model.permission import PermissionModel
34 35 from rhodecode.model.repo_group import RepoGroupModel
35 36 from rhodecode.model.scm import RepoGroupList
36 37 from rhodecode.model import validation_schema
37 38 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 39
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 @jsonrpc_method()
44 45 def get_repo_group(request, apiuser, repogroupid):
45 46 """
46 47 Return the specified |repo| group, along with permissions,
47 48 and repositories inside the group
48 49
49 50 :param apiuser: This is filled automatically from the |authtoken|.
50 51 :type apiuser: AuthUser
51 52 :param repogroupid: Specify the name of ID of the repository group.
52 53 :type repogroupid: str or int
53 54
54 55
55 56 Example output:
56 57
57 58 .. code-block:: bash
58 59
59 60 {
60 61 "error": null,
61 62 "id": repo-group-id,
62 63 "result": {
63 64 "group_description": "repo group description",
64 65 "group_id": 14,
65 66 "group_name": "group name",
66 67 "permissions": [
67 68 {
68 69 "name": "super-admin-username",
69 70 "origin": "super-admin",
70 71 "permission": "group.admin",
71 72 "type": "user"
72 73 },
73 74 {
74 75 "name": "owner-name",
75 76 "origin": "owner",
76 77 "permission": "group.admin",
77 78 "type": "user"
78 79 },
79 80 {
80 81 "name": "user-group-name",
81 82 "origin": "permission",
82 83 "permission": "group.write",
83 84 "type": "user_group"
84 85 }
85 86 ],
86 87 "owner": "owner-name",
87 88 "parent_group": null,
88 89 "repositories": [ repo-list ]
89 90 }
90 91 }
91 92 """
92 93
93 94 repo_group = get_repo_group_or_error(repogroupid)
94 95 if not has_superadmin_permission(apiuser):
95 96 # check if we have at least read permission for this repo group !
96 97 _perms = ('group.admin', 'group.write', 'group.read',)
97 98 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 99 user=apiuser, group_name=repo_group.group_name):
99 100 raise JSONRPCError(
100 101 'repository group `%s` does not exist' % (repogroupid,))
101 102
102 103 permissions = []
103 104 for _user in repo_group.permissions():
104 105 user_data = {
105 106 'name': _user.username,
106 107 'permission': _user.permission,
107 108 'origin': get_origin(_user),
108 109 'type': "user",
109 110 }
110 111 permissions.append(user_data)
111 112
112 113 for _user_group in repo_group.permission_user_groups():
113 114 user_group_data = {
114 115 'name': _user_group.users_group_name,
115 116 'permission': _user_group.permission,
116 117 'origin': get_origin(_user_group),
117 118 'type': "user_group",
118 119 }
119 120 permissions.append(user_group_data)
120 121
121 122 data = repo_group.get_api_data()
122 123 data["permissions"] = permissions
123 124 return data
124 125
125 126
126 127 @jsonrpc_method()
127 128 def get_repo_groups(request, apiuser):
128 129 """
129 130 Returns all repository groups.
130 131
131 132 :param apiuser: This is filled automatically from the |authtoken|.
132 133 :type apiuser: AuthUser
133 134 """
134 135
135 136 result = []
136 137 _perms = ('group.read', 'group.write', 'group.admin',)
137 138 extras = {'user': apiuser}
138 139 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 140 perm_set=_perms, extra_kwargs=extras):
140 141 result.append(repo_group.get_api_data())
141 142 return result
142 143
143 144
144 145 @jsonrpc_method()
145 146 def create_repo_group(
146 147 request, apiuser, group_name,
147 148 owner=Optional(OAttr('apiuser')),
148 149 description=Optional(''),
149 150 copy_permissions=Optional(False)):
150 151 """
151 152 Creates a repository group.
152 153
153 154 * If the repository group name contains "/", repository group will be
154 155 created inside a repository group or nested repository groups
155 156
156 157 For example "foo/bar/group1" will create repository group called "group1"
157 158 inside group "foo/bar". You have to have permissions to access and
158 159 write to the last repository group ("bar" in this example)
159 160
160 161 This command can only be run using an |authtoken| with at least
161 162 permissions to create repository groups, or admin permissions to
162 163 parent repository groups.
163 164
164 165 :param apiuser: This is filled automatically from the |authtoken|.
165 166 :type apiuser: AuthUser
166 167 :param group_name: Set the repository group name.
167 168 :type group_name: str
168 169 :param description: Set the |repo| group description.
169 170 :type description: str
170 171 :param owner: Set the |repo| group owner.
171 172 :type owner: str
172 173 :param copy_permissions:
173 174 :type copy_permissions:
174 175
175 176 Example output:
176 177
177 178 .. code-block:: bash
178 179
179 180 id : <id_given_in_input>
180 181 result : {
181 182 "msg": "Created new repo group `<repo_group_name>`"
182 183 "repo_group": <repogroup_object>
183 184 }
184 185 error : null
185 186
186 187
187 188 Example error output:
188 189
189 190 .. code-block:: bash
190 191
191 192 id : <id_given_in_input>
192 193 result : null
193 194 error : {
194 195 failed to create repo group `<repogroupid>`
195 196 }
196 197
197 198 """
198 199
199 200 owner = validate_set_owner_permissions(apiuser, owner)
200 201
201 202 description = Optional.extract(description)
202 203 copy_permissions = Optional.extract(copy_permissions)
203 204
204 205 schema = repo_group_schema.RepoGroupSchema().bind(
205 206 # user caller
206 207 user=apiuser)
207 208
208 209 try:
209 210 schema_data = schema.deserialize(dict(
210 211 repo_group_name=group_name,
211 212 repo_group_owner=owner.username,
212 213 repo_group_description=description,
213 214 repo_group_copy_permissions=copy_permissions,
214 215 ))
215 216 except validation_schema.Invalid as err:
216 217 raise JSONRPCValidationError(colander_exc=err)
217 218
218 219 validated_group_name = schema_data['repo_group_name']
219 220
220 221 try:
221 222 repo_group = RepoGroupModel().create(
222 223 owner=owner,
223 224 group_name=validated_group_name,
224 225 group_description=schema_data['repo_group_description'],
225 226 copy_permissions=schema_data['repo_group_copy_permissions'])
226 227 Session().flush()
227 228
228 229 repo_group_data = repo_group.get_api_data()
229 230 audit_logger.store_api(
230 231 'repo_group.create', action_data={'data': repo_group_data},
231 232 user=apiuser)
232 233
233 234 Session().commit()
234 235 return {
235 236 'msg': 'Created new repo group `%s`' % validated_group_name,
236 237 'repo_group': repo_group.get_api_data()
237 238 }
238 239 except Exception:
239 240 log.exception("Exception occurred while trying create repo group")
240 241 raise JSONRPCError(
241 242 'failed to create repo group `%s`' % (validated_group_name,))
242 243
243 244
244 245 @jsonrpc_method()
245 246 def update_repo_group(
246 247 request, apiuser, repogroupid, group_name=Optional(''),
247 248 description=Optional(''), owner=Optional(OAttr('apiuser')),
248 249 enable_locking=Optional(False)):
249 250 """
250 251 Updates repository group with the details given.
251 252
252 253 This command can only be run using an |authtoken| with admin
253 254 permissions.
254 255
255 256 * If the group_name name contains "/", repository group will be updated
256 257 accordingly with a repository group or nested repository groups
257 258
258 259 For example repogroupid=group-test group_name="foo/bar/group-test"
259 260 will update repository group called "group-test" and place it
260 261 inside group "foo/bar".
261 262 You have to have permissions to access and write to the last repository
262 263 group ("bar" in this example)
263 264
264 265 :param apiuser: This is filled automatically from the |authtoken|.
265 266 :type apiuser: AuthUser
266 267 :param repogroupid: Set the ID of repository group.
267 268 :type repogroupid: str or int
268 269 :param group_name: Set the name of the |repo| group.
269 270 :type group_name: str
270 271 :param description: Set a description for the group.
271 272 :type description: str
272 273 :param owner: Set the |repo| group owner.
273 274 :type owner: str
274 275 :param enable_locking: Enable |repo| locking. The default is false.
275 276 :type enable_locking: bool
276 277 """
277 278
278 279 repo_group = get_repo_group_or_error(repogroupid)
279 280
280 281 if not has_superadmin_permission(apiuser):
281 282 validate_repo_group_permissions(
282 283 apiuser, repogroupid, repo_group, ('group.admin',))
283 284
284 285 updates = dict(
285 286 group_name=group_name
286 287 if not isinstance(group_name, Optional) else repo_group.group_name,
287 288
288 289 group_description=description
289 290 if not isinstance(description, Optional) else repo_group.group_description,
290 291
291 292 user=owner
292 293 if not isinstance(owner, Optional) else repo_group.user.username,
293 294
294 295 enable_locking=enable_locking
295 296 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
296 297 )
297 298
298 299 schema = repo_group_schema.RepoGroupSchema().bind(
299 300 # user caller
300 301 user=apiuser,
301 302 old_values=repo_group.get_api_data())
302 303
303 304 try:
304 305 schema_data = schema.deserialize(dict(
305 306 repo_group_name=updates['group_name'],
306 307 repo_group_owner=updates['user'],
307 308 repo_group_description=updates['group_description'],
308 309 repo_group_enable_locking=updates['enable_locking'],
309 310 ))
310 311 except validation_schema.Invalid as err:
311 312 raise JSONRPCValidationError(colander_exc=err)
312 313
313 314 validated_updates = dict(
314 315 group_name=schema_data['repo_group']['repo_group_name_without_group'],
315 316 group_parent_id=schema_data['repo_group']['repo_group_id'],
316 317 user=schema_data['repo_group_owner'],
317 318 group_description=schema_data['repo_group_description'],
318 319 enable_locking=schema_data['repo_group_enable_locking'],
319 320 )
320 321
321 322 old_data = repo_group.get_api_data()
322 323 try:
323 324 RepoGroupModel().update(repo_group, validated_updates)
324 325 audit_logger.store_api(
325 326 'repo_group.edit', action_data={'old_data': old_data},
326 327 user=apiuser)
327 328
328 329 Session().commit()
329 330 return {
330 331 'msg': 'updated repository group ID:%s %s' % (
331 332 repo_group.group_id, repo_group.group_name),
332 333 'repo_group': repo_group.get_api_data()
333 334 }
334 335 except Exception:
335 336 log.exception(
336 337 u"Exception occurred while trying update repo group %s",
337 338 repogroupid)
338 339 raise JSONRPCError('failed to update repository group `%s`'
339 340 % (repogroupid,))
340 341
341 342
342 343 @jsonrpc_method()
343 344 def delete_repo_group(request, apiuser, repogroupid):
344 345 """
345 346 Deletes a |repo| group.
346 347
347 348 :param apiuser: This is filled automatically from the |authtoken|.
348 349 :type apiuser: AuthUser
349 350 :param repogroupid: Set the name or ID of repository group to be
350 351 deleted.
351 352 :type repogroupid: str or int
352 353
353 354 Example output:
354 355
355 356 .. code-block:: bash
356 357
357 358 id : <id_given_in_input>
358 359 result : {
359 360 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
360 361 'repo_group': null
361 362 }
362 363 error : null
363 364
364 365 Example error output:
365 366
366 367 .. code-block:: bash
367 368
368 369 id : <id_given_in_input>
369 370 result : null
370 371 error : {
371 372 "failed to delete repo group ID:<repogroupid> <repogroupname>"
372 373 }
373 374
374 375 """
375 376
376 377 repo_group = get_repo_group_or_error(repogroupid)
377 378 if not has_superadmin_permission(apiuser):
378 379 validate_repo_group_permissions(
379 380 apiuser, repogroupid, repo_group, ('group.admin',))
380 381
381 382 old_data = repo_group.get_api_data()
382 383 try:
383 384 RepoGroupModel().delete(repo_group)
384 385 audit_logger.store_api(
385 386 'repo_group.delete', action_data={'old_data': old_data},
386 387 user=apiuser)
387 388 Session().commit()
388 389 return {
389 390 'msg': 'deleted repo group ID:%s %s' %
390 391 (repo_group.group_id, repo_group.group_name),
391 392 'repo_group': None
392 393 }
393 394 except Exception:
394 395 log.exception("Exception occurred while trying to delete repo group")
395 396 raise JSONRPCError('failed to delete repo group ID:%s %s' %
396 397 (repo_group.group_id, repo_group.group_name))
397 398
398 399
399 400 @jsonrpc_method()
400 401 def grant_user_permission_to_repo_group(
401 402 request, apiuser, repogroupid, userid, perm,
402 403 apply_to_children=Optional('none')):
403 404 """
404 405 Grant permission for a user on the given repository group, or update
405 406 existing permissions if found.
406 407
407 408 This command can only be run using an |authtoken| with admin
408 409 permissions.
409 410
410 411 :param apiuser: This is filled automatically from the |authtoken|.
411 412 :type apiuser: AuthUser
412 413 :param repogroupid: Set the name or ID of repository group.
413 414 :type repogroupid: str or int
414 415 :param userid: Set the user name.
415 416 :type userid: str
416 417 :param perm: (group.(none|read|write|admin))
417 418 :type perm: str
418 419 :param apply_to_children: 'none', 'repos', 'groups', 'all'
419 420 :type apply_to_children: str
420 421
421 422 Example output:
422 423
423 424 .. code-block:: bash
424 425
425 426 id : <id_given_in_input>
426 427 result: {
427 428 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
428 429 "success": true
429 430 }
430 431 error: null
431 432
432 433 Example error output:
433 434
434 435 .. code-block:: bash
435 436
436 437 id : <id_given_in_input>
437 438 result : null
438 439 error : {
439 440 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
440 441 }
441 442
442 443 """
443 444
444 445 repo_group = get_repo_group_or_error(repogroupid)
445 446
446 447 if not has_superadmin_permission(apiuser):
447 448 validate_repo_group_permissions(
448 449 apiuser, repogroupid, repo_group, ('group.admin',))
449 450
450 451 user = get_user_or_error(userid)
451 452 perm = get_perm_or_error(perm, prefix='group.')
452 453 apply_to_children = Optional.extract(apply_to_children)
453 454
454 455 perm_additions = [[user.user_id, perm.permission_name, "user"]]
455 456 try:
456 457 changes = RepoGroupModel().update_permissions(
457 458 repo_group=repo_group, perm_additions=perm_additions,
458 459 recursive=apply_to_children, cur_user=apiuser)
459 460
460 461 action_data = {
461 462 'added': changes['added'],
462 463 'updated': changes['updated'],
463 464 'deleted': changes['deleted'],
464 465 }
465 466 audit_logger.store_api(
466 467 'repo_group.edit.permissions', action_data=action_data,
467 468 user=apiuser)
469 Session().commit()
470 PermissionModel().flush_user_permission_caches(changes)
468 471
469 Session().commit()
470 472 return {
471 473 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
472 474 '`%s` in repo group: `%s`' % (
473 475 perm.permission_name, apply_to_children, user.username,
474 476 repo_group.name
475 477 ),
476 478 'success': True
477 479 }
478 480 except Exception:
479 481 log.exception("Exception occurred while trying to grant "
480 482 "user permissions to repo group")
481 483 raise JSONRPCError(
482 484 'failed to edit permission for user: '
483 485 '`%s` in repo group: `%s`' % (userid, repo_group.name))
484 486
485 487
486 488 @jsonrpc_method()
487 489 def revoke_user_permission_from_repo_group(
488 490 request, apiuser, repogroupid, userid,
489 491 apply_to_children=Optional('none')):
490 492 """
491 493 Revoke permission for a user in a given repository group.
492 494
493 495 This command can only be run using an |authtoken| with admin
494 496 permissions on the |repo| group.
495 497
496 498 :param apiuser: This is filled automatically from the |authtoken|.
497 499 :type apiuser: AuthUser
498 500 :param repogroupid: Set the name or ID of the repository group.
499 501 :type repogroupid: str or int
500 502 :param userid: Set the user name to revoke.
501 503 :type userid: str
502 504 :param apply_to_children: 'none', 'repos', 'groups', 'all'
503 505 :type apply_to_children: str
504 506
505 507 Example output:
506 508
507 509 .. code-block:: bash
508 510
509 511 id : <id_given_in_input>
510 512 result: {
511 513 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
512 514 "success": true
513 515 }
514 516 error: null
515 517
516 518 Example error output:
517 519
518 520 .. code-block:: bash
519 521
520 522 id : <id_given_in_input>
521 523 result : null
522 524 error : {
523 525 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
524 526 }
525 527
526 528 """
527 529
528 530 repo_group = get_repo_group_or_error(repogroupid)
529 531
530 532 if not has_superadmin_permission(apiuser):
531 533 validate_repo_group_permissions(
532 534 apiuser, repogroupid, repo_group, ('group.admin',))
533 535
534 536 user = get_user_or_error(userid)
535 537 apply_to_children = Optional.extract(apply_to_children)
536 538
537 539 perm_deletions = [[user.user_id, None, "user"]]
538 540 try:
539 541 changes = RepoGroupModel().update_permissions(
540 542 repo_group=repo_group, perm_deletions=perm_deletions,
541 543 recursive=apply_to_children, cur_user=apiuser)
542 544
543 545 action_data = {
544 546 'added': changes['added'],
545 547 'updated': changes['updated'],
546 548 'deleted': changes['deleted'],
547 549 }
548 550 audit_logger.store_api(
549 551 'repo_group.edit.permissions', action_data=action_data,
550 552 user=apiuser)
553 Session().commit()
554 PermissionModel().flush_user_permission_caches(changes)
551 555
552 Session().commit()
553 556 return {
554 557 'msg': 'Revoked perm (recursive:%s) for user: '
555 558 '`%s` in repo group: `%s`' % (
556 559 apply_to_children, user.username, repo_group.name
557 560 ),
558 561 'success': True
559 562 }
560 563 except Exception:
561 564 log.exception("Exception occurred while trying revoke user "
562 565 "permission from repo group")
563 566 raise JSONRPCError(
564 567 'failed to edit permission for user: '
565 568 '`%s` in repo group: `%s`' % (userid, repo_group.name))
566 569
567 570
568 571 @jsonrpc_method()
569 572 def grant_user_group_permission_to_repo_group(
570 573 request, apiuser, repogroupid, usergroupid, perm,
571 574 apply_to_children=Optional('none'), ):
572 575 """
573 576 Grant permission for a user group on given repository group, or update
574 577 existing permissions if found.
575 578
576 579 This command can only be run using an |authtoken| with admin
577 580 permissions on the |repo| group.
578 581
579 582 :param apiuser: This is filled automatically from the |authtoken|.
580 583 :type apiuser: AuthUser
581 584 :param repogroupid: Set the name or id of repository group
582 585 :type repogroupid: str or int
583 586 :param usergroupid: id of usergroup
584 587 :type usergroupid: str or int
585 588 :param perm: (group.(none|read|write|admin))
586 589 :type perm: str
587 590 :param apply_to_children: 'none', 'repos', 'groups', 'all'
588 591 :type apply_to_children: str
589 592
590 593 Example output:
591 594
592 595 .. code-block:: bash
593 596
594 597 id : <id_given_in_input>
595 598 result : {
596 599 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
597 600 "success": true
598 601
599 602 }
600 603 error : null
601 604
602 605 Example error output:
603 606
604 607 .. code-block:: bash
605 608
606 609 id : <id_given_in_input>
607 610 result : null
608 611 error : {
609 612 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
610 613 }
611 614
612 615 """
613 616
614 617 repo_group = get_repo_group_or_error(repogroupid)
615 618 perm = get_perm_or_error(perm, prefix='group.')
616 619 user_group = get_user_group_or_error(usergroupid)
617 620 if not has_superadmin_permission(apiuser):
618 621 validate_repo_group_permissions(
619 622 apiuser, repogroupid, repo_group, ('group.admin',))
620 623
621 624 # check if we have at least read permission for this user group !
622 625 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
623 626 if not HasUserGroupPermissionAnyApi(*_perms)(
624 627 user=apiuser, user_group_name=user_group.users_group_name):
625 628 raise JSONRPCError(
626 629 'user group `%s` does not exist' % (usergroupid,))
627 630
628 631 apply_to_children = Optional.extract(apply_to_children)
629 632
630 633 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
631 634 try:
632 635 changes = RepoGroupModel().update_permissions(
633 636 repo_group=repo_group, perm_additions=perm_additions,
634 637 recursive=apply_to_children, cur_user=apiuser)
635 638
636 639 action_data = {
637 640 'added': changes['added'],
638 641 'updated': changes['updated'],
639 642 'deleted': changes['deleted'],
640 643 }
641 644 audit_logger.store_api(
642 645 'repo_group.edit.permissions', action_data=action_data,
643 646 user=apiuser)
647 Session().commit()
648 PermissionModel().flush_user_permission_caches(changes)
644 649
645 Session().commit()
646 650 return {
647 651 'msg': 'Granted perm: `%s` (recursive:%s) '
648 652 'for user group: `%s` in repo group: `%s`' % (
649 653 perm.permission_name, apply_to_children,
650 654 user_group.users_group_name, repo_group.name
651 655 ),
652 656 'success': True
653 657 }
654 658 except Exception:
655 659 log.exception("Exception occurred while trying to grant user "
656 660 "group permissions to repo group")
657 661 raise JSONRPCError(
658 662 'failed to edit permission for user group: `%s` in '
659 663 'repo group: `%s`' % (
660 664 usergroupid, repo_group.name
661 665 )
662 666 )
663 667
664 668
665 669 @jsonrpc_method()
666 670 def revoke_user_group_permission_from_repo_group(
667 671 request, apiuser, repogroupid, usergroupid,
668 672 apply_to_children=Optional('none')):
669 673 """
670 674 Revoke permission for user group on given repository.
671 675
672 676 This command can only be run using an |authtoken| with admin
673 677 permissions on the |repo| group.
674 678
675 679 :param apiuser: This is filled automatically from the |authtoken|.
676 680 :type apiuser: AuthUser
677 681 :param repogroupid: name or id of repository group
678 682 :type repogroupid: str or int
679 683 :param usergroupid:
680 684 :param apply_to_children: 'none', 'repos', 'groups', 'all'
681 685 :type apply_to_children: str
682 686
683 687 Example output:
684 688
685 689 .. code-block:: bash
686 690
687 691 id : <id_given_in_input>
688 692 result: {
689 693 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
690 694 "success": true
691 695 }
692 696 error: null
693 697
694 698 Example error output:
695 699
696 700 .. code-block:: bash
697 701
698 702 id : <id_given_in_input>
699 703 result : null
700 704 error : {
701 705 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
702 706 }
703 707
704 708
705 709 """
706 710
707 711 repo_group = get_repo_group_or_error(repogroupid)
708 712 user_group = get_user_group_or_error(usergroupid)
709 713 if not has_superadmin_permission(apiuser):
710 714 validate_repo_group_permissions(
711 715 apiuser, repogroupid, repo_group, ('group.admin',))
712 716
713 717 # check if we have at least read permission for this user group !
714 718 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
715 719 if not HasUserGroupPermissionAnyApi(*_perms)(
716 720 user=apiuser, user_group_name=user_group.users_group_name):
717 721 raise JSONRPCError(
718 722 'user group `%s` does not exist' % (usergroupid,))
719 723
720 724 apply_to_children = Optional.extract(apply_to_children)
721 725
722 726 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
723 727 try:
724 728 changes = RepoGroupModel().update_permissions(
725 729 repo_group=repo_group, perm_deletions=perm_deletions,
726 730 recursive=apply_to_children, cur_user=apiuser)
727 731
728 732 action_data = {
729 733 'added': changes['added'],
730 734 'updated': changes['updated'],
731 735 'deleted': changes['deleted'],
732 736 }
733 737 audit_logger.store_api(
734 738 'repo_group.edit.permissions', action_data=action_data,
735 739 user=apiuser)
740 Session().commit()
741 PermissionModel().flush_user_permission_caches(changes)
736 742
737 Session().commit()
738 743 return {
739 744 'msg': 'Revoked perm (recursive:%s) for user group: '
740 745 '`%s` in repo group: `%s`' % (
741 746 apply_to_children, user_group.users_group_name,
742 747 repo_group.name
743 748 ),
744 749 'success': True
745 750 }
746 751 except Exception:
747 752 log.exception("Exception occurred while trying revoke user group "
748 753 "permissions from repo group")
749 754 raise JSONRPCError(
750 755 'failed to edit permission for user group: '
751 756 '`%s` in repo group: `%s`' % (
752 757 user_group.users_group_name, repo_group.name
753 758 )
754 759 )
@@ -1,899 +1,907 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from rhodecode.api import (
24 24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
25 25 from rhodecode.api.utils import (
26 26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
27 27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
30 30 from rhodecode.lib.exceptions import UserGroupAssignedException
31 31 from rhodecode.model.db import Session
32 from rhodecode.model.permission import PermissionModel
32 33 from rhodecode.model.scm import UserGroupList
33 34 from rhodecode.model.user_group import UserGroupModel
34 35 from rhodecode.model import validation_schema
35 36 from rhodecode.model.validation_schema.schemas import user_group_schema
36 37
37 38 log = logging.getLogger(__name__)
38 39
39 40
40 41 @jsonrpc_method()
41 42 def get_user_group(request, apiuser, usergroupid):
42 43 """
43 44 Returns the data of an existing user group.
44 45
45 46 This command can only be run using an |authtoken| with admin rights to
46 47 the specified repository.
47 48
48 49 :param apiuser: This is filled automatically from the |authtoken|.
49 50 :type apiuser: AuthUser
50 51 :param usergroupid: Set the user group from which to return data.
51 52 :type usergroupid: str or int
52 53
53 54 Example error output:
54 55
55 56 .. code-block:: bash
56 57
57 58 {
58 59 "error": null,
59 60 "id": <id>,
60 61 "result": {
61 62 "active": true,
62 63 "group_description": "group description",
63 64 "group_name": "group name",
64 65 "permissions": [
65 66 {
66 67 "name": "owner-name",
67 68 "origin": "owner",
68 69 "permission": "usergroup.admin",
69 70 "type": "user"
70 71 },
71 72 {
72 73 {
73 74 "name": "user name",
74 75 "origin": "permission",
75 76 "permission": "usergroup.admin",
76 77 "type": "user"
77 78 },
78 79 {
79 80 "name": "user group name",
80 81 "origin": "permission",
81 82 "permission": "usergroup.write",
82 83 "type": "user_group"
83 84 }
84 85 ],
85 86 "permissions_summary": {
86 87 "repositories": {
87 88 "aa-root-level-repo-1": "repository.admin"
88 89 },
89 90 "repositories_groups": {}
90 91 },
91 92 "owner": "owner name",
92 93 "users": [],
93 94 "users_group_id": 2
94 95 }
95 96 }
96 97
97 98 """
98 99
99 100 user_group = get_user_group_or_error(usergroupid)
100 101 if not has_superadmin_permission(apiuser):
101 102 # check if we have at least read permission for this user group !
102 103 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
103 104 if not HasUserGroupPermissionAnyApi(*_perms)(
104 105 user=apiuser, user_group_name=user_group.users_group_name):
105 106 raise JSONRPCError('user group `%s` does not exist' % (
106 107 usergroupid,))
107 108
108 109 permissions = []
109 110 for _user in user_group.permissions():
110 111 user_data = {
111 112 'name': _user.username,
112 113 'permission': _user.permission,
113 114 'origin': get_origin(_user),
114 115 'type': "user",
115 116 }
116 117 permissions.append(user_data)
117 118
118 119 for _user_group in user_group.permission_user_groups():
119 120 user_group_data = {
120 121 'name': _user_group.users_group_name,
121 122 'permission': _user_group.permission,
122 123 'origin': get_origin(_user_group),
123 124 'type': "user_group",
124 125 }
125 126 permissions.append(user_group_data)
126 127
127 128 data = user_group.get_api_data()
128 129 data["permissions"] = permissions
129 130 data["permissions_summary"] = UserGroupModel().get_perms_summary(
130 131 user_group.users_group_id)
131 132 return data
132 133
133 134
134 135 @jsonrpc_method()
135 136 def get_user_groups(request, apiuser):
136 137 """
137 138 Lists all the existing user groups within RhodeCode.
138 139
139 140 This command can only be run using an |authtoken| with admin rights to
140 141 the specified repository.
141 142
142 143 This command takes the following options:
143 144
144 145 :param apiuser: This is filled automatically from the |authtoken|.
145 146 :type apiuser: AuthUser
146 147
147 148 Example error output:
148 149
149 150 .. code-block:: bash
150 151
151 152 id : <id_given_in_input>
152 153 result : [<user_group_obj>,...]
153 154 error : null
154 155 """
155 156
156 157 include_secrets = has_superadmin_permission(apiuser)
157 158
158 159 result = []
159 160 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
160 161 extras = {'user': apiuser}
161 162 for user_group in UserGroupList(UserGroupModel().get_all(),
162 163 perm_set=_perms, extra_kwargs=extras):
163 164 result.append(
164 165 user_group.get_api_data(include_secrets=include_secrets))
165 166 return result
166 167
167 168
168 169 @jsonrpc_method()
169 170 def create_user_group(
170 171 request, apiuser, group_name, description=Optional(''),
171 172 owner=Optional(OAttr('apiuser')), active=Optional(True),
172 173 sync=Optional(None)):
173 174 """
174 175 Creates a new user group.
175 176
176 177 This command can only be run using an |authtoken| with admin rights to
177 178 the specified repository.
178 179
179 180 This command takes the following options:
180 181
181 182 :param apiuser: This is filled automatically from the |authtoken|.
182 183 :type apiuser: AuthUser
183 184 :param group_name: Set the name of the new user group.
184 185 :type group_name: str
185 186 :param description: Give a description of the new user group.
186 187 :type description: str
187 188 :param owner: Set the owner of the new user group.
188 189 If not set, the owner is the |authtoken| user.
189 190 :type owner: Optional(str or int)
190 191 :param active: Set this group as active.
191 192 :type active: Optional(``True`` | ``False``)
192 193 :param sync: Set enabled or disabled the automatically sync from
193 194 external authentication types like ldap. If User Group will be named like
194 195 one from e.g ldap and sync flag is enabled members will be synced automatically.
195 196 Sync type when enabled via API is set to `manual_api`
196 197 :type sync: Optional(``True`` | ``False``)
197 198
198 199 Example output:
199 200
200 201 .. code-block:: bash
201 202
202 203 id : <id_given_in_input>
203 204 result: {
204 205 "msg": "created new user group `<groupname>`",
205 206 "user_group": <user_group_object>
206 207 }
207 208 error: null
208 209
209 210 Example error output:
210 211
211 212 .. code-block:: bash
212 213
213 214 id : <id_given_in_input>
214 215 result : null
215 216 error : {
216 217 "user group `<group name>` already exist"
217 218 or
218 219 "failed to create group `<group name>`"
219 220 }
220 221
221 222 """
222 223
223 224 if not has_superadmin_permission(apiuser):
224 225 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
225 226 raise JSONRPCForbidden()
226 227
227 228 if UserGroupModel().get_by_name(group_name):
228 229 raise JSONRPCError("user group `%s` already exist" % (group_name,))
229 230
230 231 if isinstance(owner, Optional):
231 232 owner = apiuser.user_id
232 233
233 234 owner = get_user_or_error(owner)
234 235 active = Optional.extract(active)
235 236 description = Optional.extract(description)
236 237 sync = Optional.extract(sync)
237 238
238 239 # set the sync option based on group_data
239 240 group_data = None
240 241 if sync:
241 242 group_data = {
242 243 'extern_type': 'manual_api',
243 244 'extern_type_set_by': apiuser.username
244 245 }
245 246
246 247 schema = user_group_schema.UserGroupSchema().bind(
247 248 # user caller
248 249 user=apiuser)
249 250 try:
250 251 schema_data = schema.deserialize(dict(
251 252 user_group_name=group_name,
252 253 user_group_description=description,
253 254 user_group_owner=owner.username,
254 255 user_group_active=active,
255 256 ))
256 257 except validation_schema.Invalid as err:
257 258 raise JSONRPCValidationError(colander_exc=err)
258 259
259 260 try:
260 261 user_group = UserGroupModel().create(
261 262 name=schema_data['user_group_name'],
262 263 description=schema_data['user_group_description'],
263 264 owner=owner,
264 265 active=schema_data['user_group_active'], group_data=group_data)
265 266 Session().flush()
266 267 creation_data = user_group.get_api_data()
267 268 audit_logger.store_api(
268 269 'user_group.create', action_data={'data': creation_data},
269 270 user=apiuser)
270 271 Session().commit()
272
273 affected_user_ids = [apiuser.user_id, owner.user_id]
274 PermissionModel().trigger_permission_flush(affected_user_ids)
275
271 276 return {
272 277 'msg': 'created new user group `%s`' % group_name,
273 278 'user_group': creation_data
274 279 }
275 280 except Exception:
276 281 log.exception("Error occurred during creation of user group")
277 282 raise JSONRPCError('failed to create group `%s`' % (group_name,))
278 283
279 284
280 285 @jsonrpc_method()
281 286 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
282 287 description=Optional(''), owner=Optional(None),
283 288 active=Optional(True), sync=Optional(None)):
284 289 """
285 290 Updates the specified `user group` with the details provided.
286 291
287 292 This command can only be run using an |authtoken| with admin rights to
288 293 the specified repository.
289 294
290 295 :param apiuser: This is filled automatically from the |authtoken|.
291 296 :type apiuser: AuthUser
292 297 :param usergroupid: Set the id of the `user group` to update.
293 298 :type usergroupid: str or int
294 299 :param group_name: Set the new name the `user group`
295 300 :type group_name: str
296 301 :param description: Give a description for the `user group`
297 302 :type description: str
298 303 :param owner: Set the owner of the `user group`.
299 304 :type owner: Optional(str or int)
300 305 :param active: Set the group as active.
301 306 :type active: Optional(``True`` | ``False``)
302 307 :param sync: Set enabled or disabled the automatically sync from
303 308 external authentication types like ldap. If User Group will be named like
304 309 one from e.g ldap and sync flag is enabled members will be synced automatically.
305 310 Sync type when enabled via API is set to `manual_api`
306 311 :type sync: Optional(``True`` | ``False``)
307 312
308 313 Example output:
309 314
310 315 .. code-block:: bash
311 316
312 317 id : <id_given_in_input>
313 318 result : {
314 319 "msg": 'updated user group ID:<user group id> <user group name>',
315 320 "user_group": <user_group_object>
316 321 }
317 322 error : null
318 323
319 324 Example error output:
320 325
321 326 .. code-block:: bash
322 327
323 328 id : <id_given_in_input>
324 329 result : null
325 330 error : {
326 331 "failed to update user group `<user group name>`"
327 332 }
328 333
329 334 """
330 335
331 336 user_group = get_user_group_or_error(usergroupid)
332 337 include_secrets = False
333 338 if not has_superadmin_permission(apiuser):
334 339 # check if we have admin permission for this user group !
335 340 _perms = ('usergroup.admin',)
336 341 if not HasUserGroupPermissionAnyApi(*_perms)(
337 342 user=apiuser, user_group_name=user_group.users_group_name):
338 343 raise JSONRPCError(
339 344 'user group `%s` does not exist' % (usergroupid,))
340 345 else:
341 346 include_secrets = True
342 347
343 348 if not isinstance(owner, Optional):
344 349 owner = get_user_or_error(owner)
345 350
346 351 old_data = user_group.get_api_data()
347 352 updates = {}
348 353 store_update(updates, group_name, 'users_group_name')
349 354 store_update(updates, description, 'user_group_description')
350 355 store_update(updates, owner, 'user')
351 356 store_update(updates, active, 'users_group_active')
352 357
353 358 sync = Optional.extract(sync)
354 359 group_data = None
355 360 if sync is True:
356 361 group_data = {
357 362 'extern_type': 'manual_api',
358 363 'extern_type_set_by': apiuser.username
359 364 }
360 365 if sync is False:
361 366 group_data = user_group.group_data
362 367 if group_data and "extern_type" in group_data:
363 368 del group_data["extern_type"]
364 369
365 370 try:
366 371 UserGroupModel().update(user_group, updates, group_data=group_data)
367 372 audit_logger.store_api(
368 373 'user_group.edit', action_data={'old_data': old_data},
369 374 user=apiuser)
370 375 Session().commit()
371 376 return {
372 377 'msg': 'updated user group ID:%s %s' % (
373 378 user_group.users_group_id, user_group.users_group_name),
374 379 'user_group': user_group.get_api_data(
375 380 include_secrets=include_secrets)
376 381 }
377 382 except Exception:
378 383 log.exception("Error occurred during update of user group")
379 384 raise JSONRPCError(
380 385 'failed to update user group `%s`' % (usergroupid,))
381 386
382 387
383 388 @jsonrpc_method()
384 389 def delete_user_group(request, apiuser, usergroupid):
385 390 """
386 391 Deletes the specified `user group`.
387 392
388 393 This command can only be run using an |authtoken| with admin rights to
389 394 the specified repository.
390 395
391 396 This command takes the following options:
392 397
393 398 :param apiuser: filled automatically from apikey
394 399 :type apiuser: AuthUser
395 400 :param usergroupid:
396 401 :type usergroupid: int
397 402
398 403 Example output:
399 404
400 405 .. code-block:: bash
401 406
402 407 id : <id_given_in_input>
403 408 result : {
404 409 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
405 410 }
406 411 error : null
407 412
408 413 Example error output:
409 414
410 415 .. code-block:: bash
411 416
412 417 id : <id_given_in_input>
413 418 result : null
414 419 error : {
415 420 "failed to delete user group ID:<user_group_id> <user_group_name>"
416 421 or
417 422 "RepoGroup assigned to <repo_groups_list>"
418 423 }
419 424
420 425 """
421 426
422 427 user_group = get_user_group_or_error(usergroupid)
423 428 if not has_superadmin_permission(apiuser):
424 429 # check if we have admin permission for this user group !
425 430 _perms = ('usergroup.admin',)
426 431 if not HasUserGroupPermissionAnyApi(*_perms)(
427 432 user=apiuser, user_group_name=user_group.users_group_name):
428 433 raise JSONRPCError(
429 434 'user group `%s` does not exist' % (usergroupid,))
430 435
431 436 old_data = user_group.get_api_data()
432 437 try:
433 438 UserGroupModel().delete(user_group)
434 439 audit_logger.store_api(
435 440 'user_group.delete', action_data={'old_data': old_data},
436 441 user=apiuser)
437 442 Session().commit()
438 443 return {
439 444 'msg': 'deleted user group ID:%s %s' % (
440 445 user_group.users_group_id, user_group.users_group_name),
441 446 'user_group': None
442 447 }
443 448 except UserGroupAssignedException as e:
444 449 log.exception("UserGroupAssigned error")
445 450 raise JSONRPCError(str(e))
446 451 except Exception:
447 452 log.exception("Error occurred during deletion of user group")
448 453 raise JSONRPCError(
449 454 'failed to delete user group ID:%s %s' %(
450 455 user_group.users_group_id, user_group.users_group_name))
451 456
452 457
453 458 @jsonrpc_method()
454 459 def add_user_to_user_group(request, apiuser, usergroupid, userid):
455 460 """
456 461 Adds a user to a `user group`. If the user already exists in the group
457 462 this command will return false.
458 463
459 464 This command can only be run using an |authtoken| with admin rights to
460 465 the specified user group.
461 466
462 467 This command takes the following options:
463 468
464 469 :param apiuser: This is filled automatically from the |authtoken|.
465 470 :type apiuser: AuthUser
466 471 :param usergroupid: Set the name of the `user group` to which a
467 472 user will be added.
468 473 :type usergroupid: int
469 474 :param userid: Set the `user_id` of the user to add to the group.
470 475 :type userid: int
471 476
472 477 Example output:
473 478
474 479 .. code-block:: bash
475 480
476 481 id : <id_given_in_input>
477 482 result : {
478 483 "success": True|False # depends on if member is in group
479 484 "msg": "added member `<username>` to user group `<groupname>` |
480 485 User is already in that group"
481 486
482 487 }
483 488 error : null
484 489
485 490 Example error output:
486 491
487 492 .. code-block:: bash
488 493
489 494 id : <id_given_in_input>
490 495 result : null
491 496 error : {
492 497 "failed to add member to user group `<user_group_name>`"
493 498 }
494 499
495 500 """
496 501
497 502 user = get_user_or_error(userid)
498 503 user_group = get_user_group_or_error(usergroupid)
499 504 if not has_superadmin_permission(apiuser):
500 505 # check if we have admin permission for this user group !
501 506 _perms = ('usergroup.admin',)
502 507 if not HasUserGroupPermissionAnyApi(*_perms)(
503 508 user=apiuser, user_group_name=user_group.users_group_name):
504 509 raise JSONRPCError('user group `%s` does not exist' % (
505 510 usergroupid,))
506 511
507 512 old_values = user_group.get_api_data()
508 513 try:
509 514 ugm = UserGroupModel().add_user_to_group(user_group, user)
510 515 success = True if ugm is not True else False
511 516 msg = 'added member `%s` to user group `%s`' % (
512 517 user.username, user_group.users_group_name
513 518 )
514 519 msg = msg if success else 'User is already in that group'
515 520 if success:
516 521 user_data = user.get_api_data()
517 522 audit_logger.store_api(
518 523 'user_group.edit.member.add',
519 524 action_data={'user': user_data, 'old_data': old_values},
520 525 user=apiuser)
521 526
522 527 Session().commit()
523 528
524 529 return {
525 530 'success': success,
526 531 'msg': msg
527 532 }
528 533 except Exception:
529 534 log.exception("Error occurred during adding a member to user group")
530 535 raise JSONRPCError(
531 536 'failed to add member to user group `%s`' % (
532 537 user_group.users_group_name,
533 538 )
534 539 )
535 540
536 541
537 542 @jsonrpc_method()
538 543 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
539 544 """
540 545 Removes a user from a user group.
541 546
542 547 * If the specified user is not in the group, this command will return
543 548 `false`.
544 549
545 550 This command can only be run using an |authtoken| with admin rights to
546 551 the specified user group.
547 552
548 553 :param apiuser: This is filled automatically from the |authtoken|.
549 554 :type apiuser: AuthUser
550 555 :param usergroupid: Sets the user group name.
551 556 :type usergroupid: str or int
552 557 :param userid: The user you wish to remove from |RCE|.
553 558 :type userid: str or int
554 559
555 560 Example output:
556 561
557 562 .. code-block:: bash
558 563
559 564 id : <id_given_in_input>
560 565 result: {
561 566 "success": True|False, # depends on if member is in group
562 567 "msg": "removed member <username> from user group <groupname> |
563 568 User wasn't in group"
564 569 }
565 570 error: null
566 571
567 572 """
568 573
569 574 user = get_user_or_error(userid)
570 575 user_group = get_user_group_or_error(usergroupid)
571 576 if not has_superadmin_permission(apiuser):
572 577 # check if we have admin permission for this user group !
573 578 _perms = ('usergroup.admin',)
574 579 if not HasUserGroupPermissionAnyApi(*_perms)(
575 580 user=apiuser, user_group_name=user_group.users_group_name):
576 581 raise JSONRPCError(
577 582 'user group `%s` does not exist' % (usergroupid,))
578 583
579 584 old_values = user_group.get_api_data()
580 585 try:
581 586 success = UserGroupModel().remove_user_from_group(user_group, user)
582 587 msg = 'removed member `%s` from user group `%s`' % (
583 588 user.username, user_group.users_group_name
584 589 )
585 590 msg = msg if success else "User wasn't in group"
586 591 if success:
587 592 user_data = user.get_api_data()
588 593 audit_logger.store_api(
589 594 'user_group.edit.member.delete',
590 595 action_data={'user': user_data, 'old_data': old_values},
591 596 user=apiuser)
592 597
593 598 Session().commit()
594 599 return {'success': success, 'msg': msg}
595 600 except Exception:
596 601 log.exception("Error occurred during removing an member from user group")
597 602 raise JSONRPCError(
598 603 'failed to remove member from user group `%s`' % (
599 604 user_group.users_group_name,
600 605 )
601 606 )
602 607
603 608
604 609 @jsonrpc_method()
605 610 def grant_user_permission_to_user_group(
606 611 request, apiuser, usergroupid, userid, perm):
607 612 """
608 613 Set permissions for a user in a user group.
609 614
610 615 :param apiuser: This is filled automatically from the |authtoken|.
611 616 :type apiuser: AuthUser
612 617 :param usergroupid: Set the user group to edit permissions on.
613 618 :type usergroupid: str or int
614 619 :param userid: Set the user from whom you wish to set permissions.
615 620 :type userid: str
616 621 :param perm: (usergroup.(none|read|write|admin))
617 622 :type perm: str
618 623
619 624 Example output:
620 625
621 626 .. code-block:: bash
622 627
623 628 id : <id_given_in_input>
624 629 result : {
625 630 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
626 631 "success": true
627 632 }
628 633 error : null
629 634 """
630 635
631 636 user_group = get_user_group_or_error(usergroupid)
632 637
633 638 if not has_superadmin_permission(apiuser):
634 639 # check if we have admin permission for this user group !
635 640 _perms = ('usergroup.admin',)
636 641 if not HasUserGroupPermissionAnyApi(*_perms)(
637 642 user=apiuser, user_group_name=user_group.users_group_name):
638 643 raise JSONRPCError(
639 644 'user group `%s` does not exist' % (usergroupid,))
640 645
641 646 user = get_user_or_error(userid)
642 647 perm = get_perm_or_error(perm, prefix='usergroup.')
643 648
644 649 try:
645 650 changes = UserGroupModel().grant_user_permission(
646 651 user_group=user_group, user=user, perm=perm)
647 652
648 653 action_data = {
649 654 'added': changes['added'],
650 655 'updated': changes['updated'],
651 656 'deleted': changes['deleted'],
652 657 }
653 658 audit_logger.store_api(
654 659 'user_group.edit.permissions', action_data=action_data,
655 660 user=apiuser)
661 Session().commit()
662 PermissionModel().flush_user_permission_caches(changes)
656 663
657 Session().commit()
658 664 return {
659 665 'msg':
660 666 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
661 667 perm.permission_name, user.username,
662 668 user_group.users_group_name
663 669 ),
664 670 'success': True
665 671 }
666 672 except Exception:
667 673 log.exception("Error occurred during editing permissions "
668 674 "for user in user group")
669 675 raise JSONRPCError(
670 676 'failed to edit permission for user: '
671 677 '`%s` in user group: `%s`' % (
672 678 userid, user_group.users_group_name))
673 679
674 680
675 681 @jsonrpc_method()
676 682 def revoke_user_permission_from_user_group(
677 683 request, apiuser, usergroupid, userid):
678 684 """
679 685 Revoke a users permissions in a user group.
680 686
681 687 :param apiuser: This is filled automatically from the |authtoken|.
682 688 :type apiuser: AuthUser
683 689 :param usergroupid: Set the user group from which to revoke the user
684 690 permissions.
685 691 :type: usergroupid: str or int
686 692 :param userid: Set the userid of the user whose permissions will be
687 693 revoked.
688 694 :type userid: str
689 695
690 696 Example output:
691 697
692 698 .. code-block:: bash
693 699
694 700 id : <id_given_in_input>
695 701 result : {
696 702 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
697 703 "success": true
698 704 }
699 705 error : null
700 706 """
701 707
702 708 user_group = get_user_group_or_error(usergroupid)
703 709
704 710 if not has_superadmin_permission(apiuser):
705 711 # check if we have admin permission for this user group !
706 712 _perms = ('usergroup.admin',)
707 713 if not HasUserGroupPermissionAnyApi(*_perms)(
708 714 user=apiuser, user_group_name=user_group.users_group_name):
709 715 raise JSONRPCError(
710 716 'user group `%s` does not exist' % (usergroupid,))
711 717
712 718 user = get_user_or_error(userid)
713 719
714 720 try:
715 721 changes = UserGroupModel().revoke_user_permission(
716 722 user_group=user_group, user=user)
717 723 action_data = {
718 724 'added': changes['added'],
719 725 'updated': changes['updated'],
720 726 'deleted': changes['deleted'],
721 727 }
722 728 audit_logger.store_api(
723 729 'user_group.edit.permissions', action_data=action_data,
724 730 user=apiuser)
731 Session().commit()
732 PermissionModel().flush_user_permission_caches(changes)
725 733
726 Session().commit()
727 734 return {
728 735 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
729 736 user.username, user_group.users_group_name
730 737 ),
731 738 'success': True
732 739 }
733 740 except Exception:
734 741 log.exception("Error occurred during editing permissions "
735 742 "for user in user group")
736 743 raise JSONRPCError(
737 744 'failed to edit permission for user: `%s` in user group: `%s`'
738 745 % (userid, user_group.users_group_name))
739 746
740 747
741 748 @jsonrpc_method()
742 749 def grant_user_group_permission_to_user_group(
743 750 request, apiuser, usergroupid, sourceusergroupid, perm):
744 751 """
745 752 Give one user group permissions to another user group.
746 753
747 754 :param apiuser: This is filled automatically from the |authtoken|.
748 755 :type apiuser: AuthUser
749 756 :param usergroupid: Set the user group on which to edit permissions.
750 757 :type usergroupid: str or int
751 758 :param sourceusergroupid: Set the source user group to which
752 759 access/permissions will be granted.
753 760 :type sourceusergroupid: str or int
754 761 :param perm: (usergroup.(none|read|write|admin))
755 762 :type perm: str
756 763
757 764 Example output:
758 765
759 766 .. code-block:: bash
760 767
761 768 id : <id_given_in_input>
762 769 result : {
763 770 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
764 771 "success": true
765 772 }
766 773 error : null
767 774 """
768 775
769 776 user_group = get_user_group_or_error(sourceusergroupid)
770 777 target_user_group = get_user_group_or_error(usergroupid)
771 778 perm = get_perm_or_error(perm, prefix='usergroup.')
772 779
773 780 if not has_superadmin_permission(apiuser):
774 781 # check if we have admin permission for this user group !
775 782 _perms = ('usergroup.admin',)
776 783 if not HasUserGroupPermissionAnyApi(*_perms)(
777 784 user=apiuser,
778 785 user_group_name=target_user_group.users_group_name):
779 786 raise JSONRPCError(
780 787 'to user group `%s` does not exist' % (usergroupid,))
781 788
782 789 # check if we have at least read permission for source user group !
783 790 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
784 791 if not HasUserGroupPermissionAnyApi(*_perms)(
785 792 user=apiuser, user_group_name=user_group.users_group_name):
786 793 raise JSONRPCError(
787 794 'user group `%s` does not exist' % (sourceusergroupid,))
788 795
789 796 try:
790 797 changes = UserGroupModel().grant_user_group_permission(
791 798 target_user_group=target_user_group,
792 799 user_group=user_group, perm=perm)
793 800
794 801 action_data = {
795 802 'added': changes['added'],
796 803 'updated': changes['updated'],
797 804 'deleted': changes['deleted'],
798 805 }
799 806 audit_logger.store_api(
800 807 'user_group.edit.permissions', action_data=action_data,
801 808 user=apiuser)
809 Session().commit()
810 PermissionModel().flush_user_permission_caches(changes)
802 811
803 Session().commit()
804 812 return {
805 813 'msg': 'Granted perm: `%s` for user group: `%s` '
806 814 'in user group: `%s`' % (
807 815 perm.permission_name, user_group.users_group_name,
808 816 target_user_group.users_group_name
809 817 ),
810 818 'success': True
811 819 }
812 820 except Exception:
813 821 log.exception("Error occurred during editing permissions "
814 822 "for user group in user group")
815 823 raise JSONRPCError(
816 824 'failed to edit permission for user group: `%s` in '
817 825 'user group: `%s`' % (
818 826 sourceusergroupid, target_user_group.users_group_name
819 827 )
820 828 )
821 829
822 830
823 831 @jsonrpc_method()
824 832 def revoke_user_group_permission_from_user_group(
825 833 request, apiuser, usergroupid, sourceusergroupid):
826 834 """
827 835 Revoke the permissions that one user group has to another.
828 836
829 837 :param apiuser: This is filled automatically from the |authtoken|.
830 838 :type apiuser: AuthUser
831 839 :param usergroupid: Set the user group on which to edit permissions.
832 840 :type usergroupid: str or int
833 841 :param sourceusergroupid: Set the user group from which permissions
834 842 are revoked.
835 843 :type sourceusergroupid: str or int
836 844
837 845 Example output:
838 846
839 847 .. code-block:: bash
840 848
841 849 id : <id_given_in_input>
842 850 result : {
843 851 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
844 852 "success": true
845 853 }
846 854 error : null
847 855 """
848 856
849 857 user_group = get_user_group_or_error(sourceusergroupid)
850 858 target_user_group = get_user_group_or_error(usergroupid)
851 859
852 860 if not has_superadmin_permission(apiuser):
853 861 # check if we have admin permission for this user group !
854 862 _perms = ('usergroup.admin',)
855 863 if not HasUserGroupPermissionAnyApi(*_perms)(
856 864 user=apiuser,
857 865 user_group_name=target_user_group.users_group_name):
858 866 raise JSONRPCError(
859 867 'to user group `%s` does not exist' % (usergroupid,))
860 868
861 869 # check if we have at least read permission
862 870 # for the source user group !
863 871 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
864 872 if not HasUserGroupPermissionAnyApi(*_perms)(
865 873 user=apiuser, user_group_name=user_group.users_group_name):
866 874 raise JSONRPCError(
867 875 'user group `%s` does not exist' % (sourceusergroupid,))
868 876
869 877 try:
870 878 changes = UserGroupModel().revoke_user_group_permission(
871 879 target_user_group=target_user_group, user_group=user_group)
872 880 action_data = {
873 881 'added': changes['added'],
874 882 'updated': changes['updated'],
875 883 'deleted': changes['deleted'],
876 884 }
877 885 audit_logger.store_api(
878 886 'user_group.edit.permissions', action_data=action_data,
879 887 user=apiuser)
880
881 888 Session().commit()
889 PermissionModel().flush_user_permission_caches(changes)
882 890
883 891 return {
884 892 'msg': 'Revoked perm for user group: '
885 893 '`%s` in user group: `%s`' % (
886 894 user_group.users_group_name,
887 895 target_user_group.users_group_name
888 896 ),
889 897 'success': True
890 898 }
891 899 except Exception:
892 900 log.exception("Error occurred during editing permissions "
893 901 "for user group in user group")
894 902 raise JSONRPCError(
895 903 'failed to edit permission for user group: '
896 904 '`%s` in user group: `%s`' % (
897 905 sourceusergroupid, target_user_group.users_group_name
898 906 )
899 907 )
@@ -1,519 +1,519 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 re
22 22 import logging
23 23 import formencode
24 24 import formencode.htmlfill
25 25 import datetime
26 26 from pyramid.interfaces import IRoutesMapper
27 27
28 28 from pyramid.view import view_config
29 29 from pyramid.httpexceptions import HTTPFound
30 30 from pyramid.renderers import render
31 31 from pyramid.response import Response
32 32
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 35 from rhodecode import events
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 40 from rhodecode.lib.utils2 import aslist, safe_unicode
41 41 from rhodecode.model.db import (
42 42 or_, coalesce, User, UserIpMap, UserSshKeys)
43 43 from rhodecode.model.forms import (
44 44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.permission import PermissionModel
47 47 from rhodecode.model.settings import SettingsModel
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 54 def load_default_context(self):
55 55 c = self._get_local_tmpl_context()
56 56 PermissionModel().set_global_permission_choices(
57 57 c, gettext_translator=self.request.translate)
58 58 return c
59 59
60 60 @LoginRequired()
61 61 @HasPermissionAllDecorator('hg.admin')
62 62 @view_config(
63 63 route_name='admin_permissions_application', request_method='GET',
64 64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
65 65 def permissions_application(self):
66 66 c = self.load_default_context()
67 67 c.active = 'application'
68 68
69 69 c.user = User.get_default_user(refresh=True)
70 70
71 71 app_settings = c.rc_config
72 72
73 73 defaults = {
74 74 'anonymous': c.user.active,
75 75 'default_register_message': app_settings.get(
76 76 'rhodecode_register_message')
77 77 }
78 78 defaults.update(c.user.get_default_perms())
79 79
80 80 data = render('rhodecode:templates/admin/permissions/permissions.mako',
81 81 self._get_template_context(c), self.request)
82 82 html = formencode.htmlfill.render(
83 83 data,
84 84 defaults=defaults,
85 85 encoding="UTF-8",
86 86 force_defaults=False
87 87 )
88 88 return Response(html)
89 89
90 90 @LoginRequired()
91 91 @HasPermissionAllDecorator('hg.admin')
92 92 @CSRFRequired()
93 93 @view_config(
94 94 route_name='admin_permissions_application_update', request_method='POST',
95 95 renderer='rhodecode:templates/admin/permissions/permissions.mako')
96 96 def permissions_application_update(self):
97 97 _ = self.request.translate
98 98 c = self.load_default_context()
99 99 c.active = 'application'
100 100
101 101 _form = ApplicationPermissionsForm(
102 102 self.request.translate,
103 103 [x[0] for x in c.register_choices],
104 104 [x[0] for x in c.password_reset_choices],
105 105 [x[0] for x in c.extern_activate_choices])()
106 106
107 107 try:
108 108 form_result = _form.to_python(dict(self.request.POST))
109 109 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 110 PermissionModel().update_application_permissions(form_result)
111 111
112 112 settings = [
113 113 ('register_message', 'default_register_message'),
114 114 ]
115 115 for setting, form_key in settings:
116 116 sett = SettingsModel().create_or_update_setting(
117 117 setting, form_result[form_key])
118 118 Session().add(sett)
119 119
120 120 Session().commit()
121 121 h.flash(_('Application permissions updated successfully'),
122 122 category='success')
123 123
124 124 except formencode.Invalid as errors:
125 125 defaults = errors.value
126 126
127 127 data = render(
128 128 'rhodecode:templates/admin/permissions/permissions.mako',
129 129 self._get_template_context(c), self.request)
130 130 html = formencode.htmlfill.render(
131 131 data,
132 132 defaults=defaults,
133 133 errors=errors.error_dict or {},
134 134 prefix_error=False,
135 135 encoding="UTF-8",
136 136 force_defaults=False
137 137 )
138 138 return Response(html)
139 139
140 140 except Exception:
141 141 log.exception("Exception during update of permissions")
142 142 h.flash(_('Error occurred during update of permissions'),
143 143 category='error')
144 144
145 145 affected_user_ids = [User.get_default_user().user_id]
146 events.trigger(events.UserPermissionsChange(affected_user_ids))
146 PermissionModel().trigger_permission_flush(affected_user_ids)
147 147
148 148 raise HTTPFound(h.route_path('admin_permissions_application'))
149 149
150 150 @LoginRequired()
151 151 @HasPermissionAllDecorator('hg.admin')
152 152 @view_config(
153 153 route_name='admin_permissions_object', request_method='GET',
154 154 renderer='rhodecode:templates/admin/permissions/permissions.mako')
155 155 def permissions_objects(self):
156 156 c = self.load_default_context()
157 157 c.active = 'objects'
158 158
159 159 c.user = User.get_default_user(refresh=True)
160 160 defaults = {}
161 161 defaults.update(c.user.get_default_perms())
162 162
163 163 data = render(
164 164 'rhodecode:templates/admin/permissions/permissions.mako',
165 165 self._get_template_context(c), self.request)
166 166 html = formencode.htmlfill.render(
167 167 data,
168 168 defaults=defaults,
169 169 encoding="UTF-8",
170 170 force_defaults=False
171 171 )
172 172 return Response(html)
173 173
174 174 @LoginRequired()
175 175 @HasPermissionAllDecorator('hg.admin')
176 176 @CSRFRequired()
177 177 @view_config(
178 178 route_name='admin_permissions_object_update', request_method='POST',
179 179 renderer='rhodecode:templates/admin/permissions/permissions.mako')
180 180 def permissions_objects_update(self):
181 181 _ = self.request.translate
182 182 c = self.load_default_context()
183 183 c.active = 'objects'
184 184
185 185 _form = ObjectPermissionsForm(
186 186 self.request.translate,
187 187 [x[0] for x in c.repo_perms_choices],
188 188 [x[0] for x in c.group_perms_choices],
189 189 [x[0] for x in c.user_group_perms_choices],
190 190 )()
191 191
192 192 try:
193 193 form_result = _form.to_python(dict(self.request.POST))
194 194 form_result.update({'perm_user_name': User.DEFAULT_USER})
195 195 PermissionModel().update_object_permissions(form_result)
196 196
197 197 Session().commit()
198 198 h.flash(_('Object permissions updated successfully'),
199 199 category='success')
200 200
201 201 except formencode.Invalid as errors:
202 202 defaults = errors.value
203 203
204 204 data = render(
205 205 'rhodecode:templates/admin/permissions/permissions.mako',
206 206 self._get_template_context(c), self.request)
207 207 html = formencode.htmlfill.render(
208 208 data,
209 209 defaults=defaults,
210 210 errors=errors.error_dict or {},
211 211 prefix_error=False,
212 212 encoding="UTF-8",
213 213 force_defaults=False
214 214 )
215 215 return Response(html)
216 216 except Exception:
217 217 log.exception("Exception during update of permissions")
218 218 h.flash(_('Error occurred during update of permissions'),
219 219 category='error')
220 220
221 221 affected_user_ids = [User.get_default_user().user_id]
222 events.trigger(events.UserPermissionsChange(affected_user_ids))
222 PermissionModel().trigger_permission_flush(affected_user_ids)
223 223
224 224 raise HTTPFound(h.route_path('admin_permissions_object'))
225 225
226 226 @LoginRequired()
227 227 @HasPermissionAllDecorator('hg.admin')
228 228 @view_config(
229 229 route_name='admin_permissions_branch', request_method='GET',
230 230 renderer='rhodecode:templates/admin/permissions/permissions.mako')
231 231 def permissions_branch(self):
232 232 c = self.load_default_context()
233 233 c.active = 'branch'
234 234
235 235 c.user = User.get_default_user(refresh=True)
236 236 defaults = {}
237 237 defaults.update(c.user.get_default_perms())
238 238
239 239 data = render(
240 240 'rhodecode:templates/admin/permissions/permissions.mako',
241 241 self._get_template_context(c), self.request)
242 242 html = formencode.htmlfill.render(
243 243 data,
244 244 defaults=defaults,
245 245 encoding="UTF-8",
246 246 force_defaults=False
247 247 )
248 248 return Response(html)
249 249
250 250 @LoginRequired()
251 251 @HasPermissionAllDecorator('hg.admin')
252 252 @view_config(
253 253 route_name='admin_permissions_global', request_method='GET',
254 254 renderer='rhodecode:templates/admin/permissions/permissions.mako')
255 255 def permissions_global(self):
256 256 c = self.load_default_context()
257 257 c.active = 'global'
258 258
259 259 c.user = User.get_default_user(refresh=True)
260 260 defaults = {}
261 261 defaults.update(c.user.get_default_perms())
262 262
263 263 data = render(
264 264 'rhodecode:templates/admin/permissions/permissions.mako',
265 265 self._get_template_context(c), self.request)
266 266 html = formencode.htmlfill.render(
267 267 data,
268 268 defaults=defaults,
269 269 encoding="UTF-8",
270 270 force_defaults=False
271 271 )
272 272 return Response(html)
273 273
274 274 @LoginRequired()
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 @CSRFRequired()
277 277 @view_config(
278 278 route_name='admin_permissions_global_update', request_method='POST',
279 279 renderer='rhodecode:templates/admin/permissions/permissions.mako')
280 280 def permissions_global_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283 c.active = 'global'
284 284
285 285 _form = UserPermissionsForm(
286 286 self.request.translate,
287 287 [x[0] for x in c.repo_create_choices],
288 288 [x[0] for x in c.repo_create_on_write_choices],
289 289 [x[0] for x in c.repo_group_create_choices],
290 290 [x[0] for x in c.user_group_create_choices],
291 291 [x[0] for x in c.fork_choices],
292 292 [x[0] for x in c.inherit_default_permission_choices])()
293 293
294 294 try:
295 295 form_result = _form.to_python(dict(self.request.POST))
296 296 form_result.update({'perm_user_name': User.DEFAULT_USER})
297 297 PermissionModel().update_user_permissions(form_result)
298 298
299 299 Session().commit()
300 300 h.flash(_('Global permissions updated successfully'),
301 301 category='success')
302 302
303 303 except formencode.Invalid as errors:
304 304 defaults = errors.value
305 305
306 306 data = render(
307 307 'rhodecode:templates/admin/permissions/permissions.mako',
308 308 self._get_template_context(c), self.request)
309 309 html = formencode.htmlfill.render(
310 310 data,
311 311 defaults=defaults,
312 312 errors=errors.error_dict or {},
313 313 prefix_error=False,
314 314 encoding="UTF-8",
315 315 force_defaults=False
316 316 )
317 317 return Response(html)
318 318 except Exception:
319 319 log.exception("Exception during update of permissions")
320 320 h.flash(_('Error occurred during update of permissions'),
321 321 category='error')
322 322
323 323 affected_user_ids = [User.get_default_user().user_id]
324 events.trigger(events.UserPermissionsChange(affected_user_ids))
324 PermissionModel().trigger_permission_flush(affected_user_ids)
325 325
326 326 raise HTTPFound(h.route_path('admin_permissions_global'))
327 327
328 328 @LoginRequired()
329 329 @HasPermissionAllDecorator('hg.admin')
330 330 @view_config(
331 331 route_name='admin_permissions_ips', request_method='GET',
332 332 renderer='rhodecode:templates/admin/permissions/permissions.mako')
333 333 def permissions_ips(self):
334 334 c = self.load_default_context()
335 335 c.active = 'ips'
336 336
337 337 c.user = User.get_default_user(refresh=True)
338 338 c.user_ip_map = (
339 339 UserIpMap.query().filter(UserIpMap.user == c.user).all())
340 340
341 341 return self._get_template_context(c)
342 342
343 343 @LoginRequired()
344 344 @HasPermissionAllDecorator('hg.admin')
345 345 @view_config(
346 346 route_name='admin_permissions_overview', request_method='GET',
347 347 renderer='rhodecode:templates/admin/permissions/permissions.mako')
348 348 def permissions_overview(self):
349 349 c = self.load_default_context()
350 350 c.active = 'perms'
351 351
352 352 c.user = User.get_default_user(refresh=True)
353 353 c.perm_user = c.user.AuthUser()
354 354 return self._get_template_context(c)
355 355
356 356 @LoginRequired()
357 357 @HasPermissionAllDecorator('hg.admin')
358 358 @view_config(
359 359 route_name='admin_permissions_auth_token_access', request_method='GET',
360 360 renderer='rhodecode:templates/admin/permissions/permissions.mako')
361 361 def auth_token_access(self):
362 362 from rhodecode import CONFIG
363 363
364 364 c = self.load_default_context()
365 365 c.active = 'auth_token_access'
366 366
367 367 c.user = User.get_default_user(refresh=True)
368 368 c.perm_user = c.user.AuthUser()
369 369
370 370 mapper = self.request.registry.queryUtility(IRoutesMapper)
371 371 c.view_data = []
372 372
373 373 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
374 374 introspector = self.request.registry.introspector
375 375
376 376 view_intr = {}
377 377 for view_data in introspector.get_category('views'):
378 378 intr = view_data['introspectable']
379 379
380 380 if 'route_name' in intr and intr['attr']:
381 381 view_intr[intr['route_name']] = '{}:{}'.format(
382 382 str(intr['derived_callable'].func_name), intr['attr']
383 383 )
384 384
385 385 c.whitelist_key = 'api_access_controllers_whitelist'
386 386 c.whitelist_file = CONFIG.get('__file__')
387 387 whitelist_views = aslist(
388 388 CONFIG.get(c.whitelist_key), sep=',')
389 389
390 390 for route_info in mapper.get_routes():
391 391 if not route_info.name.startswith('__'):
392 392 routepath = route_info.pattern
393 393
394 394 def replace(matchobj):
395 395 if matchobj.group(1):
396 396 return "{%s}" % matchobj.group(1).split(':')[0]
397 397 else:
398 398 return "{%s}" % matchobj.group(2)
399 399
400 400 routepath = _argument_prog.sub(replace, routepath)
401 401
402 402 if not routepath.startswith('/'):
403 403 routepath = '/' + routepath
404 404
405 405 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
406 406 active = view_fqn in whitelist_views
407 407 c.view_data.append((route_info.name, view_fqn, routepath, active))
408 408
409 409 c.whitelist_views = whitelist_views
410 410 return self._get_template_context(c)
411 411
412 412 def ssh_enabled(self):
413 413 return self.request.registry.settings.get(
414 414 'ssh.generate_authorized_keyfile')
415 415
416 416 @LoginRequired()
417 417 @HasPermissionAllDecorator('hg.admin')
418 418 @view_config(
419 419 route_name='admin_permissions_ssh_keys', request_method='GET',
420 420 renderer='rhodecode:templates/admin/permissions/permissions.mako')
421 421 def ssh_keys(self):
422 422 c = self.load_default_context()
423 423 c.active = 'ssh_keys'
424 424 c.ssh_enabled = self.ssh_enabled()
425 425 return self._get_template_context(c)
426 426
427 427 @LoginRequired()
428 428 @HasPermissionAllDecorator('hg.admin')
429 429 @view_config(
430 430 route_name='admin_permissions_ssh_keys_data', request_method='GET',
431 431 renderer='json_ext', xhr=True)
432 432 def ssh_keys_data(self):
433 433 _ = self.request.translate
434 434 self.load_default_context()
435 435 column_map = {
436 436 'fingerprint': 'ssh_key_fingerprint',
437 437 'username': User.username
438 438 }
439 439 draw, start, limit = self._extract_chunk(self.request)
440 440 search_q, order_by, order_dir = self._extract_ordering(
441 441 self.request, column_map=column_map)
442 442
443 443 ssh_keys_data_total_count = UserSshKeys.query()\
444 444 .count()
445 445
446 446 # json generate
447 447 base_q = UserSshKeys.query().join(UserSshKeys.user)
448 448
449 449 if search_q:
450 450 like_expression = u'%{}%'.format(safe_unicode(search_q))
451 451 base_q = base_q.filter(or_(
452 452 User.username.ilike(like_expression),
453 453 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
454 454 ))
455 455
456 456 users_data_total_filtered_count = base_q.count()
457 457
458 458 sort_col = self._get_order_col(order_by, UserSshKeys)
459 459 if sort_col:
460 460 if order_dir == 'asc':
461 461 # handle null values properly to order by NULL last
462 462 if order_by in ['created_on']:
463 463 sort_col = coalesce(sort_col, datetime.date.max)
464 464 sort_col = sort_col.asc()
465 465 else:
466 466 # handle null values properly to order by NULL last
467 467 if order_by in ['created_on']:
468 468 sort_col = coalesce(sort_col, datetime.date.min)
469 469 sort_col = sort_col.desc()
470 470
471 471 base_q = base_q.order_by(sort_col)
472 472 base_q = base_q.offset(start).limit(limit)
473 473
474 474 ssh_keys = base_q.all()
475 475
476 476 ssh_keys_data = []
477 477 for ssh_key in ssh_keys:
478 478 ssh_keys_data.append({
479 479 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
480 480 "fingerprint": ssh_key.ssh_key_fingerprint,
481 481 "description": ssh_key.description,
482 482 "created_on": h.format_date(ssh_key.created_on),
483 483 "accessed_on": h.format_date(ssh_key.accessed_on),
484 484 "action": h.link_to(
485 485 _('Edit'), h.route_path('edit_user_ssh_keys',
486 486 user_id=ssh_key.user.user_id))
487 487 })
488 488
489 489 data = ({
490 490 'draw': draw,
491 491 'data': ssh_keys_data,
492 492 'recordsTotal': ssh_keys_data_total_count,
493 493 'recordsFiltered': users_data_total_filtered_count,
494 494 })
495 495
496 496 return data
497 497
498 498 @LoginRequired()
499 499 @HasPermissionAllDecorator('hg.admin')
500 500 @CSRFRequired()
501 501 @view_config(
502 502 route_name='admin_permissions_ssh_keys_update', request_method='POST',
503 503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
504 504 def ssh_keys_update(self):
505 505 _ = self.request.translate
506 506 self.load_default_context()
507 507
508 508 ssh_enabled = self.ssh_enabled()
509 509 key_file = self.request.registry.settings.get(
510 510 'ssh.authorized_keys_file_path')
511 511 if ssh_enabled:
512 512 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
513 513 h.flash(_('Updated SSH keys file: {}').format(key_file),
514 514 category='success')
515 515 else:
516 516 h.flash(_('SSH key support is disabled in .ini file'),
517 517 category='warning')
518 518
519 519 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,361 +1,362 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 import datetime
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, CSRFRequired, NotAnonymous,
35 35 HasPermissionAny, HasRepoGroupPermissionAny)
36 36 from rhodecode.lib import helpers as h, audit_logger
37 37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
38 38 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.permission import PermissionModel
39 40 from rhodecode.model.repo_group import RepoGroupModel
40 41 from rhodecode.model.scm import RepoGroupList
41 42 from rhodecode.model.db import (
42 43 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43 44
44 45 log = logging.getLogger(__name__)
45 46
46 47
47 48 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
48 49
49 50 def load_default_context(self):
50 51 c = self._get_local_tmpl_context()
51 52
52 53 return c
53 54
54 55 def _load_form_data(self, c):
55 56 allow_empty_group = False
56 57
57 58 if self._can_create_repo_group():
58 59 # we're global admin, we're ok and we can create TOP level groups
59 60 allow_empty_group = True
60 61
61 62 # override the choices for this form, we need to filter choices
62 63 # and display only those we have ADMIN right
63 64 groups_with_admin_rights = RepoGroupList(
64 65 RepoGroup.query().all(),
65 66 perm_set=['group.admin'])
66 67 c.repo_groups = RepoGroup.groups_choices(
67 68 groups=groups_with_admin_rights,
68 69 show_empty_group=allow_empty_group)
69 70
70 71 def _can_create_repo_group(self, parent_group_id=None):
71 72 is_admin = HasPermissionAny('hg.admin')('group create controller')
72 73 create_repo_group = HasPermissionAny(
73 74 'hg.repogroup.create.true')('group create controller')
74 75 if is_admin or (create_repo_group and not parent_group_id):
75 76 # we're global admin, or we have global repo group create
76 77 # permission
77 78 # we're ok and we can create TOP level groups
78 79 return True
79 80 elif parent_group_id:
80 81 # we check the permission if we can write to parent group
81 82 group = RepoGroup.get(parent_group_id)
82 83 group_name = group.group_name if group else None
83 84 if HasRepoGroupPermissionAny('group.admin')(
84 85 group_name, 'check if user is an admin of group'):
85 86 # we're an admin of passed in group, we're ok.
86 87 return True
87 88 else:
88 89 return False
89 90 return False
90 91
91 92 # permission check in data loading of
92 93 # `repo_group_list_data` via RepoGroupList
93 94 @LoginRequired()
94 95 @NotAnonymous()
95 96 @view_config(
96 97 route_name='repo_groups', request_method='GET',
97 98 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
98 99 def repo_group_list(self):
99 100 c = self.load_default_context()
100 101 return self._get_template_context(c)
101 102
102 103 # permission check inside
103 104 @LoginRequired()
104 105 @NotAnonymous()
105 106 @view_config(
106 107 route_name='repo_groups_data', request_method='GET',
107 108 renderer='json_ext', xhr=True)
108 109 def repo_group_list_data(self):
109 110 self.load_default_context()
110 111 column_map = {
111 112 'name_raw': 'group_name_hash',
112 113 'desc': 'group_description',
113 114 'last_change_raw': 'updated_on',
114 115 'top_level_repos': 'repos_total',
115 116 'owner': 'user_username',
116 117 }
117 118 draw, start, limit = self._extract_chunk(self.request)
118 119 search_q, order_by, order_dir = self._extract_ordering(
119 120 self.request, column_map=column_map)
120 121
121 122 _render = self.request.get_partial_renderer(
122 123 'rhodecode:templates/data_table/_dt_elements.mako')
123 124 c = _render.get_call_context()
124 125
125 126 def quick_menu(repo_group_name):
126 127 return _render('quick_repo_group_menu', repo_group_name)
127 128
128 129 def repo_group_lnk(repo_group_name):
129 130 return _render('repo_group_name', repo_group_name)
130 131
131 132 def last_change(last_change):
132 133 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 134 delta = datetime.timedelta(
134 135 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 136 last_change = last_change + delta
136 137 return _render("last_change", last_change)
137 138
138 139 def desc(desc, personal):
139 140 return _render(
140 141 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141 142
142 143 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 144 return _render(
144 145 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145 146
146 147 def user_profile(username):
147 148 return _render('user_profile', username)
148 149
149 150 auth_repo_group_list = RepoGroupList(
150 151 RepoGroup.query().all(), perm_set=['group.admin'])
151 152
152 153 allowed_ids = [-1]
153 154 for repo_group in auth_repo_group_list:
154 155 allowed_ids.append(repo_group.group_id)
155 156
156 157 repo_groups_data_total_count = RepoGroup.query()\
157 158 .filter(or_(
158 159 # generate multiple IN to fix limitation problems
159 160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 161 )) \
161 162 .count()
162 163
163 164 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 165 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 166 .count()
166 167
167 168 repo_count = count(Repository.repo_id)
168 169 base_q = Session.query(
169 170 RepoGroup.group_name,
170 171 RepoGroup.group_name_hash,
171 172 RepoGroup.group_description,
172 173 RepoGroup.group_id,
173 174 RepoGroup.personal,
174 175 RepoGroup.updated_on,
175 176 User,
176 177 repo_count.label('repos_count')
177 178 ) \
178 179 .filter(or_(
179 180 # generate multiple IN to fix limitation problems
180 181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 182 )) \
182 183 .outerjoin(Repository) \
183 184 .join(User, User.user_id == RepoGroup.user_id) \
184 185 .group_by(RepoGroup, User)
185 186
186 187 if search_q:
187 188 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 189 base_q = base_q.filter(or_(
189 190 RepoGroup.group_name.ilike(like_expression),
190 191 ))
191 192
192 193 repo_groups_data_total_filtered_count = base_q.count()
193 194 # the inactive isn't really used, but we still make it same as other data grids
194 195 # which use inactive (users,user groups)
195 196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 197
197 198 sort_defined = False
198 199 if order_by == 'group_name':
199 200 sort_col = func.lower(RepoGroup.group_name)
200 201 sort_defined = True
201 202 elif order_by == 'repos_total':
202 203 sort_col = repo_count
203 204 sort_defined = True
204 205 elif order_by == 'user_username':
205 206 sort_col = User.username
206 207 else:
207 208 sort_col = getattr(RepoGroup, order_by, None)
208 209
209 210 if sort_defined or sort_col:
210 211 if order_dir == 'asc':
211 212 sort_col = sort_col.asc()
212 213 else:
213 214 sort_col = sort_col.desc()
214 215
215 216 base_q = base_q.order_by(sort_col)
216 217 base_q = base_q.offset(start).limit(limit)
217 218
218 219 # authenticated access to user groups
219 220 auth_repo_group_list = base_q.all()
220 221
221 222 repo_groups_data = []
222 223 for repo_gr in auth_repo_group_list:
223 224 row = {
224 225 "menu": quick_menu(repo_gr.group_name),
225 226 "name": repo_group_lnk(repo_gr.group_name),
226 227 "name_raw": repo_gr.group_name,
227 228 "last_change": last_change(repo_gr.updated_on),
228 229 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229 230
230 231 "last_changeset": "",
231 232 "last_changeset_raw": "",
232 233
233 234 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 235 "owner": user_profile(repo_gr.User.username),
235 236 "top_level_repos": repo_gr.repos_count,
236 237 "action": repo_group_actions(
237 238 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238 239
239 240 }
240 241
241 242 repo_groups_data.append(row)
242 243
243 244 data = ({
244 245 'draw': draw,
245 246 'data': repo_groups_data,
246 247 'recordsTotal': repo_groups_data_total_count,
247 248 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 249 'recordsFiltered': repo_groups_data_total_filtered_count,
249 250 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 251 })
251 252
252 253 return data
253 254
254 255 @LoginRequired()
255 256 @NotAnonymous()
256 257 # perm checks inside
257 258 @view_config(
258 259 route_name='repo_group_new', request_method='GET',
259 260 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 261 def repo_group_new(self):
261 262 c = self.load_default_context()
262 263
263 264 # perm check for admin, create_group perm or admin of parent_group
264 265 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 266 if not self._can_create_repo_group(parent_group_id):
266 267 raise HTTPForbidden()
267 268
268 269 self._load_form_data(c)
269 270
270 271 defaults = {} # Future proof for default of repo group
271 272 data = render(
272 273 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
273 274 self._get_template_context(c), self.request)
274 275 html = formencode.htmlfill.render(
275 276 data,
276 277 defaults=defaults,
277 278 encoding="UTF-8",
278 279 force_defaults=False
279 280 )
280 281 return Response(html)
281 282
282 283 @LoginRequired()
283 284 @NotAnonymous()
284 285 @CSRFRequired()
285 286 # perm checks inside
286 287 @view_config(
287 288 route_name='repo_group_create', request_method='POST',
288 289 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
289 290 def repo_group_create(self):
290 291 c = self.load_default_context()
291 292 _ = self.request.translate
292 293
293 294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 295 can_create = self._can_create_repo_group(parent_group_id)
295 296
296 297 self._load_form_data(c)
297 298 # permissions for can create group based on parent_id are checked
298 299 # here in the Form
299 300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 301 repo_group_form = RepoGroupForm(
301 302 self.request.translate, available_groups=available_groups,
302 303 can_create_in_root=can_create)()
303 304
304 305 repo_group_name = self.request.POST.get('group_name')
305 306 try:
306 307 owner = self._rhodecode_user
307 308 form_result = repo_group_form.to_python(dict(self.request.POST))
308 309 copy_permissions = form_result.get('group_copy_permissions')
309 310 repo_group = RepoGroupModel().create(
310 311 group_name=form_result['group_name_full'],
311 312 group_description=form_result['group_description'],
312 313 owner=owner.user_id,
313 314 copy_permissions=form_result['group_copy_permissions']
314 315 )
315 316 Session().flush()
316 317
317 318 repo_group_data = repo_group.get_api_data()
318 319 audit_logger.store_web(
319 320 'repo_group.create', action_data={'data': repo_group_data},
320 321 user=self._rhodecode_user)
321 322
322 323 Session().commit()
323 324
324 325 _new_group_name = form_result['group_name_full']
325 326
326 327 repo_group_url = h.link_to(
327 328 _new_group_name,
328 329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 330 h.flash(h.literal(_('Created repository group %s')
330 331 % repo_group_url), category='success')
331 332
332 333 except formencode.Invalid as errors:
333 334 data = render(
334 335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 336 self._get_template_context(c), self.request)
336 337 html = formencode.htmlfill.render(
337 338 data,
338 339 defaults=errors.value,
339 340 errors=errors.error_dict or {},
340 341 prefix_error=False,
341 342 encoding="UTF-8",
342 343 force_defaults=False
343 344 )
344 345 return Response(html)
345 346 except Exception:
346 347 log.exception("Exception during creation of repository group")
347 348 h.flash(_('Error occurred during creation of repository group %s')
348 349 % repo_group_name, category='error')
349 350 raise HTTPFound(h.route_path('home'))
350 351
351 352 affected_user_ids = [self._rhodecode_user.user_id]
352 353 if copy_permissions:
353 354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
354 355 copy_perms = [perm['user_id'] for perm in user_group_perms]
355 356 # also include those newly created by copy
356 357 affected_user_ids.extend(copy_perms)
357 events.trigger(events.UserPermissionsChange(affected_user_ids))
358 PermissionModel().trigger_permission_flush(affected_user_ids)
358 359
359 360 raise HTTPFound(
360 361 h.route_path('repo_group_home',
361 362 repo_group_name=form_result['group_name_full']))
@@ -1,186 +1,187 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.utils import repo_name_slug
40 40 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 41 from rhodecode.model.forms import RepoForm
42 from rhodecode.model.permission import PermissionModel
42 43 from rhodecode.model.repo import RepoModel
43 44 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 45 from rhodecode.model.settings import SettingsModel
45 46 from rhodecode.model.db import Repository, RepoGroup
46 47
47 48 log = logging.getLogger(__name__)
48 49
49 50
50 51 class AdminReposView(BaseAppView, DataGridAppView):
51 52
52 53 def load_default_context(self):
53 54 c = self._get_local_tmpl_context()
54 55
55 56 return c
56 57
57 58 def _load_form_data(self, c):
58 59 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 60 perm_set=['group.write', 'group.admin'])
60 61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63 64
64 65 @LoginRequired()
65 66 @NotAnonymous()
66 67 # perms check inside
67 68 @view_config(
68 69 route_name='repos', request_method='GET',
69 70 renderer='rhodecode:templates/admin/repos/repos.mako')
70 71 def repository_list(self):
71 72 c = self.load_default_context()
72 73
73 74 repo_list = Repository.get_all_repos()
74 75 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
75 76 repos_data = RepoModel().get_repos_as_dict(
76 77 repo_list=c.repo_list, admin=True, super_user_actions=True)
77 78 # json used to render the grid
78 79 c.data = json.dumps(repos_data)
79 80
80 81 return self._get_template_context(c)
81 82
82 83 @LoginRequired()
83 84 @NotAnonymous()
84 85 # perms check inside
85 86 @view_config(
86 87 route_name='repo_new', request_method='GET',
87 88 renderer='rhodecode:templates/admin/repos/repo_add.mako')
88 89 def repository_new(self):
89 90 c = self.load_default_context()
90 91
91 92 new_repo = self.request.GET.get('repo', '')
92 93 parent_group = safe_int(self.request.GET.get('parent_group'))
93 94 _gr = RepoGroup.get(parent_group)
94 95
95 96 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
96 97 # you're not super admin nor have global create permissions,
97 98 # but maybe you have at least write permission to a parent group ?
98 99
99 100 gr_name = _gr.group_name if _gr else None
100 101 # create repositories with write permission on group is set to true
101 102 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
102 103 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
103 104 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
104 105 if not (group_admin or (group_write and create_on_write)):
105 106 raise HTTPForbidden()
106 107
107 108 self._load_form_data(c)
108 109 c.new_repo = repo_name_slug(new_repo)
109 110
110 111 # apply the defaults from defaults page
111 112 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
112 113 # set checkbox to autochecked
113 114 defaults['repo_copy_permissions'] = True
114 115
115 116 parent_group_choice = '-1'
116 117 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
117 118 parent_group_choice = self._rhodecode_user.personal_repo_group
118 119
119 120 if parent_group and _gr:
120 121 if parent_group in [x[0] for x in c.repo_groups]:
121 122 parent_group_choice = safe_unicode(parent_group)
122 123
123 124 defaults.update({'repo_group': parent_group_choice})
124 125
125 126 data = render('rhodecode:templates/admin/repos/repo_add.mako',
126 127 self._get_template_context(c), self.request)
127 128 html = formencode.htmlfill.render(
128 129 data,
129 130 defaults=defaults,
130 131 encoding="UTF-8",
131 132 force_defaults=False
132 133 )
133 134 return Response(html)
134 135
135 136 @LoginRequired()
136 137 @NotAnonymous()
137 138 @CSRFRequired()
138 139 # perms check inside
139 140 @view_config(
140 141 route_name='repo_create', request_method='POST',
141 142 renderer='rhodecode:templates/admin/repos/repos.mako')
142 143 def repository_create(self):
143 144 c = self.load_default_context()
144 145
145 146 form_result = {}
146 147 self._load_form_data(c)
147 148
148 149 try:
149 150 # CanWriteToGroup validators checks permissions of this POST
150 151 form = RepoForm(
151 152 self.request.translate, repo_groups=c.repo_groups_choices)()
152 153 form_result = form.to_python(dict(self.request.POST))
153 154 copy_permissions = form_result.get('repo_copy_permissions')
154 155 # create is done sometimes async on celery, db transaction
155 156 # management is handled there.
156 157 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
157 158 task_id = get_task_id(task)
158 159 except formencode.Invalid as errors:
159 160 data = render('rhodecode:templates/admin/repos/repo_add.mako',
160 161 self._get_template_context(c), self.request)
161 162 html = formencode.htmlfill.render(
162 163 data,
163 164 defaults=errors.value,
164 165 errors=errors.error_dict or {},
165 166 prefix_error=False,
166 167 encoding="UTF-8",
167 168 force_defaults=False
168 169 )
169 170 return Response(html)
170 171
171 172 except Exception as e:
172 173 msg = self._log_creation_exception(e, form_result.get('repo_name'))
173 174 h.flash(msg, category='error')
174 175 raise HTTPFound(h.route_path('home'))
175 176
176 177 repo_name = form_result.get('repo_name_full')
177 178
178 179 affected_user_ids = [self._rhodecode_user.user_id]
179 180 if copy_permissions:
180 181 # permission flush is done in repo creating
181 182 pass
182 events.trigger(events.UserPermissionsChange(affected_user_ids))
183 PermissionModel().trigger_permission_flush(affected_user_ids)
183 184
184 185 raise HTTPFound(
185 186 h.route_path('repo_creating', repo_name=repo_name,
186 187 _query=dict(task_id=task_id)))
@@ -1,271 +1,273 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 35 from rhodecode.lib import helpers as h, audit_logger
36 36 from rhodecode.lib.utils2 import safe_unicode
37 37
38 38 from rhodecode.model.forms import UserGroupForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.scm import UserGroupList
41 41 from rhodecode.model.db import (
42 42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.user_group import UserGroupModel
45 45 from rhodecode.model.db import true
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 PermissionModel().set_global_permission_choices(
56 56 c, gettext_translator=self.request.translate)
57 57
58 58 return c
59 59
60 60 # permission check in data loading of
61 61 # `user_groups_list_data` via UserGroupList
62 62 @LoginRequired()
63 63 @NotAnonymous()
64 64 @view_config(
65 65 route_name='user_groups', request_method='GET',
66 66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 67 def user_groups_list(self):
68 68 c = self.load_default_context()
69 69 return self._get_template_context(c)
70 70
71 71 # permission check inside
72 72 @LoginRequired()
73 73 @NotAnonymous()
74 74 @view_config(
75 75 route_name='user_groups_data', request_method='GET',
76 76 renderer='json_ext', xhr=True)
77 77 def user_groups_list_data(self):
78 78 self.load_default_context()
79 79 column_map = {
80 80 'active': 'users_group_active',
81 81 'description': 'user_group_description',
82 82 'members': 'members_total',
83 83 'owner': 'user_username',
84 84 'sync': 'group_data'
85 85 }
86 86 draw, start, limit = self._extract_chunk(self.request)
87 87 search_q, order_by, order_dir = self._extract_ordering(
88 88 self.request, column_map=column_map)
89 89
90 90 _render = self.request.get_partial_renderer(
91 91 'rhodecode:templates/data_table/_dt_elements.mako')
92 92
93 93 def user_group_name(user_group_name):
94 94 return _render("user_group_name", user_group_name)
95 95
96 96 def user_group_actions(user_group_id, user_group_name):
97 97 return _render("user_group_actions", user_group_id, user_group_name)
98 98
99 99 def user_profile(username):
100 100 return _render('user_profile', username)
101 101
102 102 auth_user_group_list = UserGroupList(
103 103 UserGroup.query().all(), perm_set=['usergroup.admin'])
104 104
105 105 allowed_ids = [-1]
106 106 for user_group in auth_user_group_list:
107 107 allowed_ids.append(user_group.users_group_id)
108 108
109 109 user_groups_data_total_count = UserGroup.query()\
110 110 .filter(or_(
111 111 # generate multiple IN to fix limitation problems
112 112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
113 113 ))\
114 114 .count()
115 115
116 116 user_groups_data_total_inactive_count = UserGroup.query()\
117 117 .filter(or_(
118 118 # generate multiple IN to fix limitation problems
119 119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
120 120 ))\
121 121 .filter(UserGroup.users_group_active != true()).count()
122 122
123 123 member_count = count(UserGroupMember.user_id)
124 124 base_q = Session.query(
125 125 UserGroup.users_group_name,
126 126 UserGroup.user_group_description,
127 127 UserGroup.users_group_active,
128 128 UserGroup.users_group_id,
129 129 UserGroup.group_data,
130 130 User,
131 131 member_count.label('member_count')
132 132 ) \
133 133 .filter(or_(
134 134 # generate multiple IN to fix limitation problems
135 135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
136 136 )) \
137 137 .outerjoin(UserGroupMember) \
138 138 .join(User, User.user_id == UserGroup.user_id) \
139 139 .group_by(UserGroup, User)
140 140
141 141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
142 142
143 143 if search_q:
144 144 like_expression = u'%{}%'.format(safe_unicode(search_q))
145 145 base_q = base_q.filter(or_(
146 146 UserGroup.users_group_name.ilike(like_expression),
147 147 ))
148 148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
149 149
150 150 user_groups_data_total_filtered_count = base_q.count()
151 151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
152 152
153 153 sort_defined = False
154 154 if order_by == 'members_total':
155 155 sort_col = member_count
156 156 sort_defined = True
157 157 elif order_by == 'user_username':
158 158 sort_col = User.username
159 159 else:
160 160 sort_col = getattr(UserGroup, order_by, None)
161 161
162 162 if sort_defined or sort_col:
163 163 if order_dir == 'asc':
164 164 sort_col = sort_col.asc()
165 165 else:
166 166 sort_col = sort_col.desc()
167 167
168 168 base_q = base_q.order_by(sort_col)
169 169 base_q = base_q.offset(start).limit(limit)
170 170
171 171 # authenticated access to user groups
172 172 auth_user_group_list = base_q.all()
173 173
174 174 user_groups_data = []
175 175 for user_gr in auth_user_group_list:
176 176 row = {
177 177 "users_group_name": user_group_name(user_gr.users_group_name),
178 178 "name_raw": h.escape(user_gr.users_group_name),
179 179 "description": h.escape(user_gr.user_group_description),
180 180 "members": user_gr.member_count,
181 181 # NOTE(marcink): because of advanced query we
182 182 # need to load it like that
183 183 "sync": UserGroup._load_sync(
184 184 UserGroup._load_group_data(user_gr.group_data)),
185 185 "active": h.bool2icon(user_gr.users_group_active),
186 186 "owner": user_profile(user_gr.User.username),
187 187 "action": user_group_actions(
188 188 user_gr.users_group_id, user_gr.users_group_name)
189 189 }
190 190 user_groups_data.append(row)
191 191
192 192 data = ({
193 193 'draw': draw,
194 194 'data': user_groups_data,
195 195 'recordsTotal': user_groups_data_total_count,
196 196 'recordsTotalInactive': user_groups_data_total_inactive_count,
197 197 'recordsFiltered': user_groups_data_total_filtered_count,
198 198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
199 199 })
200 200
201 201 return data
202 202
203 203 @LoginRequired()
204 204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
205 205 @view_config(
206 206 route_name='user_groups_new', request_method='GET',
207 207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
208 208 def user_groups_new(self):
209 209 c = self.load_default_context()
210 210 return self._get_template_context(c)
211 211
212 212 @LoginRequired()
213 213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
214 214 @CSRFRequired()
215 215 @view_config(
216 216 route_name='user_groups_create', request_method='POST',
217 217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
218 218 def user_groups_create(self):
219 219 _ = self.request.translate
220 220 c = self.load_default_context()
221 221 users_group_form = UserGroupForm(self.request.translate)()
222 222
223 223 user_group_name = self.request.POST.get('users_group_name')
224 224 try:
225 225 form_result = users_group_form.to_python(dict(self.request.POST))
226 226 user_group = UserGroupModel().create(
227 227 name=form_result['users_group_name'],
228 228 description=form_result['user_group_description'],
229 229 owner=self._rhodecode_user.user_id,
230 230 active=form_result['users_group_active'])
231 231 Session().flush()
232 232 creation_data = user_group.get_api_data()
233 233 user_group_name = form_result['users_group_name']
234 234
235 235 audit_logger.store_web(
236 236 'user_group.create', action_data={'data': creation_data},
237 237 user=self._rhodecode_user)
238 238
239 239 user_group_link = h.link_to(
240 240 h.escape(user_group_name),
241 241 h.route_path(
242 242 'edit_user_group', user_group_id=user_group.users_group_id))
243 243 h.flash(h.literal(_('Created user group %(user_group_link)s')
244 244 % {'user_group_link': user_group_link}),
245 245 category='success')
246 246 Session().commit()
247 247 user_group_id = user_group.users_group_id
248 248 except formencode.Invalid as errors:
249 249
250 250 data = render(
251 251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
252 252 self._get_template_context(c), self.request)
253 253 html = formencode.htmlfill.render(
254 254 data,
255 255 defaults=errors.value,
256 256 errors=errors.error_dict or {},
257 257 prefix_error=False,
258 258 encoding="UTF-8",
259 259 force_defaults=False
260 260 )
261 261 return Response(html)
262 262
263 263 except Exception:
264 264 log.exception("Exception creating user group")
265 265 h.flash(_('Error occurred during creation of user group %s') \
266 266 % user_group_name, category='error')
267 267 raise HTTPFound(h.route_path('user_groups_new'))
268 268
269 events.trigger(events.UserPermissionsChange([self._rhodecode_user.user_id]))
269 affected_user_ids = [self._rhodecode_user.user_id]
270 PermissionModel().trigger_permission_flush(affected_user_ids)
271
270 272 raise HTTPFound(
271 273 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1265 +1,1265 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 34 from rhodecode.authentication.plugins import auth_rhodecode
35 35 from rhodecode.events import trigger
36 36 from rhodecode.model.db import true
37 37
38 38 from rhodecode.lib import audit_logger, rc_cache
39 39 from rhodecode.lib.exceptions import (
40 40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 41 UserOwnsUserGroupsException, DefaultUserException)
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.auth import (
44 44 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 45 from rhodecode.lib import helpers as h
46 46 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
47 47 from rhodecode.model.auth_token import AuthTokenModel
48 48 from rhodecode.model.forms import (
49 49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
50 50 UserExtraEmailForm, UserExtraIpForm)
51 51 from rhodecode.model.permission import PermissionModel
52 52 from rhodecode.model.repo_group import RepoGroupModel
53 53 from rhodecode.model.ssh_key import SshKeyModel
54 54 from rhodecode.model.user import UserModel
55 55 from rhodecode.model.user_group import UserGroupModel
56 56 from rhodecode.model.db import (
57 57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
58 58 UserApiKeys, UserSshKeys, RepoGroup)
59 59 from rhodecode.model.meta import Session
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63
64 64 class AdminUsersView(BaseAppView, DataGridAppView):
65 65
66 66 def load_default_context(self):
67 67 c = self._get_local_tmpl_context()
68 68 return c
69 69
70 70 @LoginRequired()
71 71 @HasPermissionAllDecorator('hg.admin')
72 72 @view_config(
73 73 route_name='users', request_method='GET',
74 74 renderer='rhodecode:templates/admin/users/users.mako')
75 75 def users_list(self):
76 76 c = self.load_default_context()
77 77 return self._get_template_context(c)
78 78
79 79 @LoginRequired()
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 @view_config(
82 82 # renderer defined below
83 83 route_name='users_data', request_method='GET',
84 84 renderer='json_ext', xhr=True)
85 85 def users_list_data(self):
86 86 self.load_default_context()
87 87 column_map = {
88 88 'first_name': 'name',
89 89 'last_name': 'lastname',
90 90 }
91 91 draw, start, limit = self._extract_chunk(self.request)
92 92 search_q, order_by, order_dir = self._extract_ordering(
93 93 self.request, column_map=column_map)
94 94 _render = self.request.get_partial_renderer(
95 95 'rhodecode:templates/data_table/_dt_elements.mako')
96 96
97 97 def user_actions(user_id, username):
98 98 return _render("user_actions", user_id, username)
99 99
100 100 users_data_total_count = User.query()\
101 101 .filter(User.username != User.DEFAULT_USER) \
102 102 .count()
103 103
104 104 users_data_total_inactive_count = User.query()\
105 105 .filter(User.username != User.DEFAULT_USER) \
106 106 .filter(User.active != true())\
107 107 .count()
108 108
109 109 # json generate
110 110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111 111 base_inactive_q = base_q.filter(User.active != true())
112 112
113 113 if search_q:
114 114 like_expression = u'%{}%'.format(safe_unicode(search_q))
115 115 base_q = base_q.filter(or_(
116 116 User.username.ilike(like_expression),
117 117 User._email.ilike(like_expression),
118 118 User.name.ilike(like_expression),
119 119 User.lastname.ilike(like_expression),
120 120 ))
121 121 base_inactive_q = base_q.filter(User.active != true())
122 122
123 123 users_data_total_filtered_count = base_q.count()
124 124 users_data_total_filtered_inactive_count = base_inactive_q.count()
125 125
126 126 sort_col = getattr(User, order_by, None)
127 127 if sort_col:
128 128 if order_dir == 'asc':
129 129 # handle null values properly to order by NULL last
130 130 if order_by in ['last_activity']:
131 131 sort_col = coalesce(sort_col, datetime.date.max)
132 132 sort_col = sort_col.asc()
133 133 else:
134 134 # handle null values properly to order by NULL last
135 135 if order_by in ['last_activity']:
136 136 sort_col = coalesce(sort_col, datetime.date.min)
137 137 sort_col = sort_col.desc()
138 138
139 139 base_q = base_q.order_by(sort_col)
140 140 base_q = base_q.offset(start).limit(limit)
141 141
142 142 users_list = base_q.all()
143 143
144 144 users_data = []
145 145 for user in users_list:
146 146 users_data.append({
147 147 "username": h.gravatar_with_user(self.request, user.username),
148 148 "email": user.email,
149 149 "first_name": user.first_name,
150 150 "last_name": user.last_name,
151 151 "last_login": h.format_date(user.last_login),
152 152 "last_activity": h.format_date(user.last_activity),
153 153 "active": h.bool2icon(user.active),
154 154 "active_raw": user.active,
155 155 "admin": h.bool2icon(user.admin),
156 156 "extern_type": user.extern_type,
157 157 "extern_name": user.extern_name,
158 158 "action": user_actions(user.user_id, user.username),
159 159 })
160 160 data = ({
161 161 'draw': draw,
162 162 'data': users_data,
163 163 'recordsTotal': users_data_total_count,
164 164 'recordsFiltered': users_data_total_filtered_count,
165 165 'recordsTotalInactive': users_data_total_inactive_count,
166 166 'recordsFilteredInactive': users_data_total_filtered_inactive_count
167 167 })
168 168
169 169 return data
170 170
171 171 def _set_personal_repo_group_template_vars(self, c_obj):
172 172 DummyUser = AttributeDict({
173 173 'username': '${username}',
174 174 'user_id': '${user_id}',
175 175 })
176 176 c_obj.default_create_repo_group = RepoGroupModel() \
177 177 .get_default_create_personal_repo_group()
178 178 c_obj.personal_repo_group_name = RepoGroupModel() \
179 179 .get_personal_group_name(DummyUser)
180 180
181 181 @LoginRequired()
182 182 @HasPermissionAllDecorator('hg.admin')
183 183 @view_config(
184 184 route_name='users_new', request_method='GET',
185 185 renderer='rhodecode:templates/admin/users/user_add.mako')
186 186 def users_new(self):
187 187 _ = self.request.translate
188 188 c = self.load_default_context()
189 189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
190 190 self._set_personal_repo_group_template_vars(c)
191 191 return self._get_template_context(c)
192 192
193 193 @LoginRequired()
194 194 @HasPermissionAllDecorator('hg.admin')
195 195 @CSRFRequired()
196 196 @view_config(
197 197 route_name='users_create', request_method='POST',
198 198 renderer='rhodecode:templates/admin/users/user_add.mako')
199 199 def users_create(self):
200 200 _ = self.request.translate
201 201 c = self.load_default_context()
202 202 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
203 203 user_model = UserModel()
204 204 user_form = UserForm(self.request.translate)()
205 205 try:
206 206 form_result = user_form.to_python(dict(self.request.POST))
207 207 user = user_model.create(form_result)
208 208 Session().flush()
209 209 creation_data = user.get_api_data()
210 210 username = form_result['username']
211 211
212 212 audit_logger.store_web(
213 213 'user.create', action_data={'data': creation_data},
214 214 user=c.rhodecode_user)
215 215
216 216 user_link = h.link_to(
217 217 h.escape(username),
218 218 h.route_path('user_edit', user_id=user.user_id))
219 219 h.flash(h.literal(_('Created user %(user_link)s')
220 220 % {'user_link': user_link}), category='success')
221 221 Session().commit()
222 222 except formencode.Invalid as errors:
223 223 self._set_personal_repo_group_template_vars(c)
224 224 data = render(
225 225 'rhodecode:templates/admin/users/user_add.mako',
226 226 self._get_template_context(c), self.request)
227 227 html = formencode.htmlfill.render(
228 228 data,
229 229 defaults=errors.value,
230 230 errors=errors.error_dict or {},
231 231 prefix_error=False,
232 232 encoding="UTF-8",
233 233 force_defaults=False
234 234 )
235 235 return Response(html)
236 236 except UserCreationError as e:
237 237 h.flash(e, 'error')
238 238 except Exception:
239 239 log.exception("Exception creation of user")
240 240 h.flash(_('Error occurred during creation of user %s')
241 241 % self.request.POST.get('username'), category='error')
242 242 raise HTTPFound(h.route_path('users'))
243 243
244 244
245 245 class UsersView(UserAppView):
246 246 ALLOW_SCOPED_TOKENS = False
247 247 """
248 248 This view has alternative version inside EE, if modified please take a look
249 249 in there as well.
250 250 """
251 251
252 252 def load_default_context(self):
253 253 c = self._get_local_tmpl_context()
254 254 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
255 255 c.allowed_languages = [
256 256 ('en', 'English (en)'),
257 257 ('de', 'German (de)'),
258 258 ('fr', 'French (fr)'),
259 259 ('it', 'Italian (it)'),
260 260 ('ja', 'Japanese (ja)'),
261 261 ('pl', 'Polish (pl)'),
262 262 ('pt', 'Portuguese (pt)'),
263 263 ('ru', 'Russian (ru)'),
264 264 ('zh', 'Chinese (zh)'),
265 265 ]
266 266 req = self.request
267 267
268 268 c.available_permissions = req.registry.settings['available_permissions']
269 269 PermissionModel().set_global_permission_choices(
270 270 c, gettext_translator=req.translate)
271 271
272 272 return c
273 273
274 274 @LoginRequired()
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 @CSRFRequired()
277 277 @view_config(
278 278 route_name='user_update', request_method='POST',
279 279 renderer='rhodecode:templates/admin/users/user_edit.mako')
280 280 def user_update(self):
281 281 _ = self.request.translate
282 282 c = self.load_default_context()
283 283
284 284 user_id = self.db_user_id
285 285 c.user = self.db_user
286 286
287 287 c.active = 'profile'
288 288 c.extern_type = c.user.extern_type
289 289 c.extern_name = c.user.extern_name
290 290 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
291 291 available_languages = [x[0] for x in c.allowed_languages]
292 292 _form = UserForm(self.request.translate, edit=True,
293 293 available_languages=available_languages,
294 294 old_data={'user_id': user_id,
295 295 'email': c.user.email})()
296 296 form_result = {}
297 297 old_values = c.user.get_api_data()
298 298 try:
299 299 form_result = _form.to_python(dict(self.request.POST))
300 300 skip_attrs = ['extern_type', 'extern_name']
301 301 # TODO: plugin should define if username can be updated
302 302 if c.extern_type != "rhodecode":
303 303 # forbid updating username for external accounts
304 304 skip_attrs.append('username')
305 305
306 306 UserModel().update_user(
307 307 user_id, skip_attrs=skip_attrs, **form_result)
308 308
309 309 audit_logger.store_web(
310 310 'user.edit', action_data={'old_data': old_values},
311 311 user=c.rhodecode_user)
312 312
313 313 Session().commit()
314 314 h.flash(_('User updated successfully'), category='success')
315 315 except formencode.Invalid as errors:
316 316 data = render(
317 317 'rhodecode:templates/admin/users/user_edit.mako',
318 318 self._get_template_context(c), self.request)
319 319 html = formencode.htmlfill.render(
320 320 data,
321 321 defaults=errors.value,
322 322 errors=errors.error_dict or {},
323 323 prefix_error=False,
324 324 encoding="UTF-8",
325 325 force_defaults=False
326 326 )
327 327 return Response(html)
328 328 except UserCreationError as e:
329 329 h.flash(e, 'error')
330 330 except Exception:
331 331 log.exception("Exception updating user")
332 332 h.flash(_('Error occurred during update of user %s')
333 333 % form_result.get('username'), category='error')
334 334 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
335 335
336 336 @LoginRequired()
337 337 @HasPermissionAllDecorator('hg.admin')
338 338 @CSRFRequired()
339 339 @view_config(
340 340 route_name='user_delete', request_method='POST',
341 341 renderer='rhodecode:templates/admin/users/user_edit.mako')
342 342 def user_delete(self):
343 343 _ = self.request.translate
344 344 c = self.load_default_context()
345 345 c.user = self.db_user
346 346
347 347 _repos = c.user.repositories
348 348 _repo_groups = c.user.repository_groups
349 349 _user_groups = c.user.user_groups
350 350
351 351 handle_repos = None
352 352 handle_repo_groups = None
353 353 handle_user_groups = None
354 354 # dummy call for flash of handle
355 355 set_handle_flash_repos = lambda: None
356 356 set_handle_flash_repo_groups = lambda: None
357 357 set_handle_flash_user_groups = lambda: None
358 358
359 359 if _repos and self.request.POST.get('user_repos'):
360 360 do = self.request.POST['user_repos']
361 361 if do == 'detach':
362 362 handle_repos = 'detach'
363 363 set_handle_flash_repos = lambda: h.flash(
364 364 _('Detached %s repositories') % len(_repos),
365 365 category='success')
366 366 elif do == 'delete':
367 367 handle_repos = 'delete'
368 368 set_handle_flash_repos = lambda: h.flash(
369 369 _('Deleted %s repositories') % len(_repos),
370 370 category='success')
371 371
372 372 if _repo_groups and self.request.POST.get('user_repo_groups'):
373 373 do = self.request.POST['user_repo_groups']
374 374 if do == 'detach':
375 375 handle_repo_groups = 'detach'
376 376 set_handle_flash_repo_groups = lambda: h.flash(
377 377 _('Detached %s repository groups') % len(_repo_groups),
378 378 category='success')
379 379 elif do == 'delete':
380 380 handle_repo_groups = 'delete'
381 381 set_handle_flash_repo_groups = lambda: h.flash(
382 382 _('Deleted %s repository groups') % len(_repo_groups),
383 383 category='success')
384 384
385 385 if _user_groups and self.request.POST.get('user_user_groups'):
386 386 do = self.request.POST['user_user_groups']
387 387 if do == 'detach':
388 388 handle_user_groups = 'detach'
389 389 set_handle_flash_user_groups = lambda: h.flash(
390 390 _('Detached %s user groups') % len(_user_groups),
391 391 category='success')
392 392 elif do == 'delete':
393 393 handle_user_groups = 'delete'
394 394 set_handle_flash_user_groups = lambda: h.flash(
395 395 _('Deleted %s user groups') % len(_user_groups),
396 396 category='success')
397 397
398 398 old_values = c.user.get_api_data()
399 399 try:
400 400 UserModel().delete(c.user, handle_repos=handle_repos,
401 401 handle_repo_groups=handle_repo_groups,
402 402 handle_user_groups=handle_user_groups)
403 403
404 404 audit_logger.store_web(
405 405 'user.delete', action_data={'old_data': old_values},
406 406 user=c.rhodecode_user)
407 407
408 408 Session().commit()
409 409 set_handle_flash_repos()
410 410 set_handle_flash_repo_groups()
411 411 set_handle_flash_user_groups()
412 412 h.flash(_('Successfully deleted user'), category='success')
413 413 except (UserOwnsReposException, UserOwnsRepoGroupsException,
414 414 UserOwnsUserGroupsException, DefaultUserException) as e:
415 415 h.flash(e, category='warning')
416 416 except Exception:
417 417 log.exception("Exception during deletion of user")
418 418 h.flash(_('An error occurred during deletion of user'),
419 419 category='error')
420 420 raise HTTPFound(h.route_path('users'))
421 421
422 422 @LoginRequired()
423 423 @HasPermissionAllDecorator('hg.admin')
424 424 @view_config(
425 425 route_name='user_edit', request_method='GET',
426 426 renderer='rhodecode:templates/admin/users/user_edit.mako')
427 427 def user_edit(self):
428 428 _ = self.request.translate
429 429 c = self.load_default_context()
430 430 c.user = self.db_user
431 431
432 432 c.active = 'profile'
433 433 c.extern_type = c.user.extern_type
434 434 c.extern_name = c.user.extern_name
435 435 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
436 436
437 437 defaults = c.user.get_dict()
438 438 defaults.update({'language': c.user.user_data.get('language')})
439 439
440 440 data = render(
441 441 'rhodecode:templates/admin/users/user_edit.mako',
442 442 self._get_template_context(c), self.request)
443 443 html = formencode.htmlfill.render(
444 444 data,
445 445 defaults=defaults,
446 446 encoding="UTF-8",
447 447 force_defaults=False
448 448 )
449 449 return Response(html)
450 450
451 451 @LoginRequired()
452 452 @HasPermissionAllDecorator('hg.admin')
453 453 @view_config(
454 454 route_name='user_edit_advanced', request_method='GET',
455 455 renderer='rhodecode:templates/admin/users/user_edit.mako')
456 456 def user_edit_advanced(self):
457 457 _ = self.request.translate
458 458 c = self.load_default_context()
459 459
460 460 user_id = self.db_user_id
461 461 c.user = self.db_user
462 462
463 463 c.active = 'advanced'
464 464 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
465 465 c.personal_repo_group_name = RepoGroupModel()\
466 466 .get_personal_group_name(c.user)
467 467
468 468 c.user_to_review_rules = sorted(
469 469 (x.user for x in c.user.user_review_rules),
470 470 key=lambda u: u.username.lower())
471 471
472 472 c.first_admin = User.get_first_super_admin()
473 473 defaults = c.user.get_dict()
474 474
475 475 # Interim workaround if the user participated on any pull requests as a
476 476 # reviewer.
477 477 has_review = len(c.user.reviewer_pull_requests)
478 478 c.can_delete_user = not has_review
479 479 c.can_delete_user_message = ''
480 480 inactive_link = h.link_to(
481 481 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
482 482 if has_review == 1:
483 483 c.can_delete_user_message = h.literal(_(
484 484 'The user participates as reviewer in {} pull request and '
485 485 'cannot be deleted. \nYou can set the user to '
486 486 '"{}" instead of deleting it.').format(
487 487 has_review, inactive_link))
488 488 elif has_review:
489 489 c.can_delete_user_message = h.literal(_(
490 490 'The user participates as reviewer in {} pull requests and '
491 491 'cannot be deleted. \nYou can set the user to '
492 492 '"{}" instead of deleting it.').format(
493 493 has_review, inactive_link))
494 494
495 495 data = render(
496 496 'rhodecode:templates/admin/users/user_edit.mako',
497 497 self._get_template_context(c), self.request)
498 498 html = formencode.htmlfill.render(
499 499 data,
500 500 defaults=defaults,
501 501 encoding="UTF-8",
502 502 force_defaults=False
503 503 )
504 504 return Response(html)
505 505
506 506 @LoginRequired()
507 507 @HasPermissionAllDecorator('hg.admin')
508 508 @view_config(
509 509 route_name='user_edit_global_perms', request_method='GET',
510 510 renderer='rhodecode:templates/admin/users/user_edit.mako')
511 511 def user_edit_global_perms(self):
512 512 _ = self.request.translate
513 513 c = self.load_default_context()
514 514 c.user = self.db_user
515 515
516 516 c.active = 'global_perms'
517 517
518 518 c.default_user = User.get_default_user()
519 519 defaults = c.user.get_dict()
520 520 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
521 521 defaults.update(c.default_user.get_default_perms())
522 522 defaults.update(c.user.get_default_perms())
523 523
524 524 data = render(
525 525 'rhodecode:templates/admin/users/user_edit.mako',
526 526 self._get_template_context(c), self.request)
527 527 html = formencode.htmlfill.render(
528 528 data,
529 529 defaults=defaults,
530 530 encoding="UTF-8",
531 531 force_defaults=False
532 532 )
533 533 return Response(html)
534 534
535 535 @LoginRequired()
536 536 @HasPermissionAllDecorator('hg.admin')
537 537 @CSRFRequired()
538 538 @view_config(
539 539 route_name='user_edit_global_perms_update', request_method='POST',
540 540 renderer='rhodecode:templates/admin/users/user_edit.mako')
541 541 def user_edit_global_perms_update(self):
542 542 _ = self.request.translate
543 543 c = self.load_default_context()
544 544
545 545 user_id = self.db_user_id
546 546 c.user = self.db_user
547 547
548 548 c.active = 'global_perms'
549 549 try:
550 550 # first stage that verifies the checkbox
551 551 _form = UserIndividualPermissionsForm(self.request.translate)
552 552 form_result = _form.to_python(dict(self.request.POST))
553 553 inherit_perms = form_result['inherit_default_permissions']
554 554 c.user.inherit_default_permissions = inherit_perms
555 555 Session().add(c.user)
556 556
557 557 if not inherit_perms:
558 558 # only update the individual ones if we un check the flag
559 559 _form = UserPermissionsForm(
560 560 self.request.translate,
561 561 [x[0] for x in c.repo_create_choices],
562 562 [x[0] for x in c.repo_create_on_write_choices],
563 563 [x[0] for x in c.repo_group_create_choices],
564 564 [x[0] for x in c.user_group_create_choices],
565 565 [x[0] for x in c.fork_choices],
566 566 [x[0] for x in c.inherit_default_permission_choices])()
567 567
568 568 form_result = _form.to_python(dict(self.request.POST))
569 569 form_result.update({'perm_user_id': c.user.user_id})
570 570
571 571 PermissionModel().update_user_permissions(form_result)
572 572
573 573 # TODO(marcink): implement global permissions
574 574 # audit_log.store_web('user.edit.permissions')
575 575
576 576 Session().commit()
577 577
578 578 h.flash(_('User global permissions updated successfully'),
579 579 category='success')
580 580
581 581 except formencode.Invalid as errors:
582 582 data = render(
583 583 'rhodecode:templates/admin/users/user_edit.mako',
584 584 self._get_template_context(c), self.request)
585 585 html = formencode.htmlfill.render(
586 586 data,
587 587 defaults=errors.value,
588 588 errors=errors.error_dict or {},
589 589 prefix_error=False,
590 590 encoding="UTF-8",
591 591 force_defaults=False
592 592 )
593 593 return Response(html)
594 594 except Exception:
595 595 log.exception("Exception during permissions saving")
596 596 h.flash(_('An error occurred during permissions saving'),
597 597 category='error')
598 598
599 599 affected_user_ids = [user_id]
600 events.trigger(events.UserPermissionsChange(affected_user_ids))
600 PermissionModel().trigger_permission_flush(affected_user_ids)
601 601 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
602 602
603 603 @LoginRequired()
604 604 @HasPermissionAllDecorator('hg.admin')
605 605 @CSRFRequired()
606 606 @view_config(
607 607 route_name='user_enable_force_password_reset', request_method='POST',
608 608 renderer='rhodecode:templates/admin/users/user_edit.mako')
609 609 def user_enable_force_password_reset(self):
610 610 _ = self.request.translate
611 611 c = self.load_default_context()
612 612
613 613 user_id = self.db_user_id
614 614 c.user = self.db_user
615 615
616 616 try:
617 617 c.user.update_userdata(force_password_change=True)
618 618
619 619 msg = _('Force password change enabled for user')
620 620 audit_logger.store_web('user.edit.password_reset.enabled',
621 621 user=c.rhodecode_user)
622 622
623 623 Session().commit()
624 624 h.flash(msg, category='success')
625 625 except Exception:
626 626 log.exception("Exception during password reset for user")
627 627 h.flash(_('An error occurred during password reset for user'),
628 628 category='error')
629 629
630 630 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
631 631
632 632 @LoginRequired()
633 633 @HasPermissionAllDecorator('hg.admin')
634 634 @CSRFRequired()
635 635 @view_config(
636 636 route_name='user_disable_force_password_reset', request_method='POST',
637 637 renderer='rhodecode:templates/admin/users/user_edit.mako')
638 638 def user_disable_force_password_reset(self):
639 639 _ = self.request.translate
640 640 c = self.load_default_context()
641 641
642 642 user_id = self.db_user_id
643 643 c.user = self.db_user
644 644
645 645 try:
646 646 c.user.update_userdata(force_password_change=False)
647 647
648 648 msg = _('Force password change disabled for user')
649 649 audit_logger.store_web(
650 650 'user.edit.password_reset.disabled',
651 651 user=c.rhodecode_user)
652 652
653 653 Session().commit()
654 654 h.flash(msg, category='success')
655 655 except Exception:
656 656 log.exception("Exception during password reset for user")
657 657 h.flash(_('An error occurred during password reset for user'),
658 658 category='error')
659 659
660 660 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
661 661
662 662 @LoginRequired()
663 663 @HasPermissionAllDecorator('hg.admin')
664 664 @CSRFRequired()
665 665 @view_config(
666 666 route_name='user_create_personal_repo_group', request_method='POST',
667 667 renderer='rhodecode:templates/admin/users/user_edit.mako')
668 668 def user_create_personal_repo_group(self):
669 669 """
670 670 Create personal repository group for this user
671 671 """
672 672 from rhodecode.model.repo_group import RepoGroupModel
673 673
674 674 _ = self.request.translate
675 675 c = self.load_default_context()
676 676
677 677 user_id = self.db_user_id
678 678 c.user = self.db_user
679 679
680 680 personal_repo_group = RepoGroup.get_user_personal_repo_group(
681 681 c.user.user_id)
682 682 if personal_repo_group:
683 683 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
684 684
685 685 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
686 686 named_personal_group = RepoGroup.get_by_group_name(
687 687 personal_repo_group_name)
688 688 try:
689 689
690 690 if named_personal_group and named_personal_group.user_id == c.user.user_id:
691 691 # migrate the same named group, and mark it as personal
692 692 named_personal_group.personal = True
693 693 Session().add(named_personal_group)
694 694 Session().commit()
695 695 msg = _('Linked repository group `%s` as personal' % (
696 696 personal_repo_group_name,))
697 697 h.flash(msg, category='success')
698 698 elif not named_personal_group:
699 699 RepoGroupModel().create_personal_repo_group(c.user)
700 700
701 701 msg = _('Created repository group `%s`' % (
702 702 personal_repo_group_name,))
703 703 h.flash(msg, category='success')
704 704 else:
705 705 msg = _('Repository group `%s` is already taken' % (
706 706 personal_repo_group_name,))
707 707 h.flash(msg, category='warning')
708 708 except Exception:
709 709 log.exception("Exception during repository group creation")
710 710 msg = _(
711 711 'An error occurred during repository group creation for user')
712 712 h.flash(msg, category='error')
713 713 Session().rollback()
714 714
715 715 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
716 716
717 717 @LoginRequired()
718 718 @HasPermissionAllDecorator('hg.admin')
719 719 @view_config(
720 720 route_name='edit_user_auth_tokens', request_method='GET',
721 721 renderer='rhodecode:templates/admin/users/user_edit.mako')
722 722 def auth_tokens(self):
723 723 _ = self.request.translate
724 724 c = self.load_default_context()
725 725 c.user = self.db_user
726 726
727 727 c.active = 'auth_tokens'
728 728
729 729 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
730 730 c.role_values = [
731 731 (x, AuthTokenModel.cls._get_role_name(x))
732 732 for x in AuthTokenModel.cls.ROLES]
733 733 c.role_options = [(c.role_values, _("Role"))]
734 734 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
735 735 c.user.user_id, show_expired=True)
736 736 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
737 737 return self._get_template_context(c)
738 738
739 739 def maybe_attach_token_scope(self, token):
740 740 # implemented in EE edition
741 741 pass
742 742
743 743 @LoginRequired()
744 744 @HasPermissionAllDecorator('hg.admin')
745 745 @CSRFRequired()
746 746 @view_config(
747 747 route_name='edit_user_auth_tokens_add', request_method='POST')
748 748 def auth_tokens_add(self):
749 749 _ = self.request.translate
750 750 c = self.load_default_context()
751 751
752 752 user_id = self.db_user_id
753 753 c.user = self.db_user
754 754
755 755 user_data = c.user.get_api_data()
756 756 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
757 757 description = self.request.POST.get('description')
758 758 role = self.request.POST.get('role')
759 759
760 760 token = UserModel().add_auth_token(
761 761 user=c.user.user_id,
762 762 lifetime_minutes=lifetime, role=role, description=description,
763 763 scope_callback=self.maybe_attach_token_scope)
764 764 token_data = token.get_api_data()
765 765
766 766 audit_logger.store_web(
767 767 'user.edit.token.add', action_data={
768 768 'data': {'token': token_data, 'user': user_data}},
769 769 user=self._rhodecode_user, )
770 770 Session().commit()
771 771
772 772 h.flash(_("Auth token successfully created"), category='success')
773 773 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
774 774
775 775 @LoginRequired()
776 776 @HasPermissionAllDecorator('hg.admin')
777 777 @CSRFRequired()
778 778 @view_config(
779 779 route_name='edit_user_auth_tokens_delete', request_method='POST')
780 780 def auth_tokens_delete(self):
781 781 _ = self.request.translate
782 782 c = self.load_default_context()
783 783
784 784 user_id = self.db_user_id
785 785 c.user = self.db_user
786 786
787 787 user_data = c.user.get_api_data()
788 788
789 789 del_auth_token = self.request.POST.get('del_auth_token')
790 790
791 791 if del_auth_token:
792 792 token = UserApiKeys.get_or_404(del_auth_token)
793 793 token_data = token.get_api_data()
794 794
795 795 AuthTokenModel().delete(del_auth_token, c.user.user_id)
796 796 audit_logger.store_web(
797 797 'user.edit.token.delete', action_data={
798 798 'data': {'token': token_data, 'user': user_data}},
799 799 user=self._rhodecode_user,)
800 800 Session().commit()
801 801 h.flash(_("Auth token successfully deleted"), category='success')
802 802
803 803 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
804 804
805 805 @LoginRequired()
806 806 @HasPermissionAllDecorator('hg.admin')
807 807 @view_config(
808 808 route_name='edit_user_ssh_keys', request_method='GET',
809 809 renderer='rhodecode:templates/admin/users/user_edit.mako')
810 810 def ssh_keys(self):
811 811 _ = self.request.translate
812 812 c = self.load_default_context()
813 813 c.user = self.db_user
814 814
815 815 c.active = 'ssh_keys'
816 816 c.default_key = self.request.GET.get('default_key')
817 817 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
818 818 return self._get_template_context(c)
819 819
820 820 @LoginRequired()
821 821 @HasPermissionAllDecorator('hg.admin')
822 822 @view_config(
823 823 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
824 824 renderer='rhodecode:templates/admin/users/user_edit.mako')
825 825 def ssh_keys_generate_keypair(self):
826 826 _ = self.request.translate
827 827 c = self.load_default_context()
828 828
829 829 c.user = self.db_user
830 830
831 831 c.active = 'ssh_keys_generate'
832 832 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
833 833 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
834 834
835 835 return self._get_template_context(c)
836 836
837 837 @LoginRequired()
838 838 @HasPermissionAllDecorator('hg.admin')
839 839 @CSRFRequired()
840 840 @view_config(
841 841 route_name='edit_user_ssh_keys_add', request_method='POST')
842 842 def ssh_keys_add(self):
843 843 _ = self.request.translate
844 844 c = self.load_default_context()
845 845
846 846 user_id = self.db_user_id
847 847 c.user = self.db_user
848 848
849 849 user_data = c.user.get_api_data()
850 850 key_data = self.request.POST.get('key_data')
851 851 description = self.request.POST.get('description')
852 852
853 853 fingerprint = 'unknown'
854 854 try:
855 855 if not key_data:
856 856 raise ValueError('Please add a valid public key')
857 857
858 858 key = SshKeyModel().parse_key(key_data.strip())
859 859 fingerprint = key.hash_md5()
860 860
861 861 ssh_key = SshKeyModel().create(
862 862 c.user.user_id, fingerprint, key.keydata, description)
863 863 ssh_key_data = ssh_key.get_api_data()
864 864
865 865 audit_logger.store_web(
866 866 'user.edit.ssh_key.add', action_data={
867 867 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
868 868 user=self._rhodecode_user, )
869 869 Session().commit()
870 870
871 871 # Trigger an event on change of keys.
872 872 trigger(SshKeyFileChangeEvent(), self.request.registry)
873 873
874 874 h.flash(_("Ssh Key successfully created"), category='success')
875 875
876 876 except IntegrityError:
877 877 log.exception("Exception during ssh key saving")
878 878 err = 'Such key with fingerprint `{}` already exists, ' \
879 879 'please use a different one'.format(fingerprint)
880 880 h.flash(_('An error occurred during ssh key saving: {}').format(err),
881 881 category='error')
882 882 except Exception as e:
883 883 log.exception("Exception during ssh key saving")
884 884 h.flash(_('An error occurred during ssh key saving: {}').format(e),
885 885 category='error')
886 886
887 887 return HTTPFound(
888 888 h.route_path('edit_user_ssh_keys', user_id=user_id))
889 889
890 890 @LoginRequired()
891 891 @HasPermissionAllDecorator('hg.admin')
892 892 @CSRFRequired()
893 893 @view_config(
894 894 route_name='edit_user_ssh_keys_delete', request_method='POST')
895 895 def ssh_keys_delete(self):
896 896 _ = self.request.translate
897 897 c = self.load_default_context()
898 898
899 899 user_id = self.db_user_id
900 900 c.user = self.db_user
901 901
902 902 user_data = c.user.get_api_data()
903 903
904 904 del_ssh_key = self.request.POST.get('del_ssh_key')
905 905
906 906 if del_ssh_key:
907 907 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
908 908 ssh_key_data = ssh_key.get_api_data()
909 909
910 910 SshKeyModel().delete(del_ssh_key, c.user.user_id)
911 911 audit_logger.store_web(
912 912 'user.edit.ssh_key.delete', action_data={
913 913 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
914 914 user=self._rhodecode_user,)
915 915 Session().commit()
916 916 # Trigger an event on change of keys.
917 917 trigger(SshKeyFileChangeEvent(), self.request.registry)
918 918 h.flash(_("Ssh key successfully deleted"), category='success')
919 919
920 920 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
921 921
922 922 @LoginRequired()
923 923 @HasPermissionAllDecorator('hg.admin')
924 924 @view_config(
925 925 route_name='edit_user_emails', request_method='GET',
926 926 renderer='rhodecode:templates/admin/users/user_edit.mako')
927 927 def emails(self):
928 928 _ = self.request.translate
929 929 c = self.load_default_context()
930 930 c.user = self.db_user
931 931
932 932 c.active = 'emails'
933 933 c.user_email_map = UserEmailMap.query() \
934 934 .filter(UserEmailMap.user == c.user).all()
935 935
936 936 return self._get_template_context(c)
937 937
938 938 @LoginRequired()
939 939 @HasPermissionAllDecorator('hg.admin')
940 940 @CSRFRequired()
941 941 @view_config(
942 942 route_name='edit_user_emails_add', request_method='POST')
943 943 def emails_add(self):
944 944 _ = self.request.translate
945 945 c = self.load_default_context()
946 946
947 947 user_id = self.db_user_id
948 948 c.user = self.db_user
949 949
950 950 email = self.request.POST.get('new_email')
951 951 user_data = c.user.get_api_data()
952 952 try:
953 953
954 954 form = UserExtraEmailForm(self.request.translate)()
955 955 data = form.to_python({'email': email})
956 956 email = data['email']
957 957
958 958 UserModel().add_extra_email(c.user.user_id, email)
959 959 audit_logger.store_web(
960 960 'user.edit.email.add',
961 961 action_data={'email': email, 'user': user_data},
962 962 user=self._rhodecode_user)
963 963 Session().commit()
964 964 h.flash(_("Added new email address `%s` for user account") % email,
965 965 category='success')
966 966 except formencode.Invalid as error:
967 967 h.flash(h.escape(error.error_dict['email']), category='error')
968 968 except IntegrityError:
969 969 log.warning("Email %s already exists", email)
970 970 h.flash(_('Email `{}` is already registered for another user.').format(email),
971 971 category='error')
972 972 except Exception:
973 973 log.exception("Exception during email saving")
974 974 h.flash(_('An error occurred during email saving'),
975 975 category='error')
976 976 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
977 977
978 978 @LoginRequired()
979 979 @HasPermissionAllDecorator('hg.admin')
980 980 @CSRFRequired()
981 981 @view_config(
982 982 route_name='edit_user_emails_delete', request_method='POST')
983 983 def emails_delete(self):
984 984 _ = self.request.translate
985 985 c = self.load_default_context()
986 986
987 987 user_id = self.db_user_id
988 988 c.user = self.db_user
989 989
990 990 email_id = self.request.POST.get('del_email_id')
991 991 user_model = UserModel()
992 992
993 993 email = UserEmailMap.query().get(email_id).email
994 994 user_data = c.user.get_api_data()
995 995 user_model.delete_extra_email(c.user.user_id, email_id)
996 996 audit_logger.store_web(
997 997 'user.edit.email.delete',
998 998 action_data={'email': email, 'user': user_data},
999 999 user=self._rhodecode_user)
1000 1000 Session().commit()
1001 1001 h.flash(_("Removed email address from user account"),
1002 1002 category='success')
1003 1003 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1004 1004
1005 1005 @LoginRequired()
1006 1006 @HasPermissionAllDecorator('hg.admin')
1007 1007 @view_config(
1008 1008 route_name='edit_user_ips', request_method='GET',
1009 1009 renderer='rhodecode:templates/admin/users/user_edit.mako')
1010 1010 def ips(self):
1011 1011 _ = self.request.translate
1012 1012 c = self.load_default_context()
1013 1013 c.user = self.db_user
1014 1014
1015 1015 c.active = 'ips'
1016 1016 c.user_ip_map = UserIpMap.query() \
1017 1017 .filter(UserIpMap.user == c.user).all()
1018 1018
1019 1019 c.inherit_default_ips = c.user.inherit_default_permissions
1020 1020 c.default_user_ip_map = UserIpMap.query() \
1021 1021 .filter(UserIpMap.user == User.get_default_user()).all()
1022 1022
1023 1023 return self._get_template_context(c)
1024 1024
1025 1025 @LoginRequired()
1026 1026 @HasPermissionAllDecorator('hg.admin')
1027 1027 @CSRFRequired()
1028 1028 @view_config(
1029 1029 route_name='edit_user_ips_add', request_method='POST')
1030 1030 # NOTE(marcink): this view is allowed for default users, as we can
1031 1031 # edit their IP white list
1032 1032 def ips_add(self):
1033 1033 _ = self.request.translate
1034 1034 c = self.load_default_context()
1035 1035
1036 1036 user_id = self.db_user_id
1037 1037 c.user = self.db_user
1038 1038
1039 1039 user_model = UserModel()
1040 1040 desc = self.request.POST.get('description')
1041 1041 try:
1042 1042 ip_list = user_model.parse_ip_range(
1043 1043 self.request.POST.get('new_ip'))
1044 1044 except Exception as e:
1045 1045 ip_list = []
1046 1046 log.exception("Exception during ip saving")
1047 1047 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1048 1048 category='error')
1049 1049 added = []
1050 1050 user_data = c.user.get_api_data()
1051 1051 for ip in ip_list:
1052 1052 try:
1053 1053 form = UserExtraIpForm(self.request.translate)()
1054 1054 data = form.to_python({'ip': ip})
1055 1055 ip = data['ip']
1056 1056
1057 1057 user_model.add_extra_ip(c.user.user_id, ip, desc)
1058 1058 audit_logger.store_web(
1059 1059 'user.edit.ip.add',
1060 1060 action_data={'ip': ip, 'user': user_data},
1061 1061 user=self._rhodecode_user)
1062 1062 Session().commit()
1063 1063 added.append(ip)
1064 1064 except formencode.Invalid as error:
1065 1065 msg = error.error_dict['ip']
1066 1066 h.flash(msg, category='error')
1067 1067 except Exception:
1068 1068 log.exception("Exception during ip saving")
1069 1069 h.flash(_('An error occurred during ip saving'),
1070 1070 category='error')
1071 1071 if added:
1072 1072 h.flash(
1073 1073 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1074 1074 category='success')
1075 1075 if 'default_user' in self.request.POST:
1076 1076 # case for editing global IP list we do it for 'DEFAULT' user
1077 1077 raise HTTPFound(h.route_path('admin_permissions_ips'))
1078 1078 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1079 1079
1080 1080 @LoginRequired()
1081 1081 @HasPermissionAllDecorator('hg.admin')
1082 1082 @CSRFRequired()
1083 1083 @view_config(
1084 1084 route_name='edit_user_ips_delete', request_method='POST')
1085 1085 # NOTE(marcink): this view is allowed for default users, as we can
1086 1086 # edit their IP white list
1087 1087 def ips_delete(self):
1088 1088 _ = self.request.translate
1089 1089 c = self.load_default_context()
1090 1090
1091 1091 user_id = self.db_user_id
1092 1092 c.user = self.db_user
1093 1093
1094 1094 ip_id = self.request.POST.get('del_ip_id')
1095 1095 user_model = UserModel()
1096 1096 user_data = c.user.get_api_data()
1097 1097 ip = UserIpMap.query().get(ip_id).ip_addr
1098 1098 user_model.delete_extra_ip(c.user.user_id, ip_id)
1099 1099 audit_logger.store_web(
1100 1100 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1101 1101 user=self._rhodecode_user)
1102 1102 Session().commit()
1103 1103 h.flash(_("Removed ip address from user whitelist"), category='success')
1104 1104
1105 1105 if 'default_user' in self.request.POST:
1106 1106 # case for editing global IP list we do it for 'DEFAULT' user
1107 1107 raise HTTPFound(h.route_path('admin_permissions_ips'))
1108 1108 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1109 1109
1110 1110 @LoginRequired()
1111 1111 @HasPermissionAllDecorator('hg.admin')
1112 1112 @view_config(
1113 1113 route_name='edit_user_groups_management', request_method='GET',
1114 1114 renderer='rhodecode:templates/admin/users/user_edit.mako')
1115 1115 def groups_management(self):
1116 1116 c = self.load_default_context()
1117 1117 c.user = self.db_user
1118 1118 c.data = c.user.group_member
1119 1119
1120 1120 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1121 1121 for group in c.user.group_member]
1122 1122 c.groups = json.dumps(groups)
1123 1123 c.active = 'groups'
1124 1124
1125 1125 return self._get_template_context(c)
1126 1126
1127 1127 @LoginRequired()
1128 1128 @HasPermissionAllDecorator('hg.admin')
1129 1129 @CSRFRequired()
1130 1130 @view_config(
1131 1131 route_name='edit_user_groups_management_updates', request_method='POST')
1132 1132 def groups_management_updates(self):
1133 1133 _ = self.request.translate
1134 1134 c = self.load_default_context()
1135 1135
1136 1136 user_id = self.db_user_id
1137 1137 c.user = self.db_user
1138 1138
1139 1139 user_groups = set(self.request.POST.getall('users_group_id'))
1140 1140 user_groups_objects = []
1141 1141
1142 1142 for ugid in user_groups:
1143 1143 user_groups_objects.append(
1144 1144 UserGroupModel().get_group(safe_int(ugid)))
1145 1145 user_group_model = UserGroupModel()
1146 1146 added_to_groups, removed_from_groups = \
1147 1147 user_group_model.change_groups(c.user, user_groups_objects)
1148 1148
1149 1149 user_data = c.user.get_api_data()
1150 1150 for user_group_id in added_to_groups:
1151 1151 user_group = UserGroup.get(user_group_id)
1152 1152 old_values = user_group.get_api_data()
1153 1153 audit_logger.store_web(
1154 1154 'user_group.edit.member.add',
1155 1155 action_data={'user': user_data, 'old_data': old_values},
1156 1156 user=self._rhodecode_user)
1157 1157
1158 1158 for user_group_id in removed_from_groups:
1159 1159 user_group = UserGroup.get(user_group_id)
1160 1160 old_values = user_group.get_api_data()
1161 1161 audit_logger.store_web(
1162 1162 'user_group.edit.member.delete',
1163 1163 action_data={'user': user_data, 'old_data': old_values},
1164 1164 user=self._rhodecode_user)
1165 1165
1166 1166 Session().commit()
1167 1167 c.active = 'user_groups_management'
1168 1168 h.flash(_("Groups successfully changed"), category='success')
1169 1169
1170 1170 return HTTPFound(h.route_path(
1171 1171 'edit_user_groups_management', user_id=user_id))
1172 1172
1173 1173 @LoginRequired()
1174 1174 @HasPermissionAllDecorator('hg.admin')
1175 1175 @view_config(
1176 1176 route_name='edit_user_audit_logs', request_method='GET',
1177 1177 renderer='rhodecode:templates/admin/users/user_edit.mako')
1178 1178 def user_audit_logs(self):
1179 1179 _ = self.request.translate
1180 1180 c = self.load_default_context()
1181 1181 c.user = self.db_user
1182 1182
1183 1183 c.active = 'audit'
1184 1184
1185 1185 p = safe_int(self.request.GET.get('page', 1), 1)
1186 1186
1187 1187 filter_term = self.request.GET.get('filter')
1188 1188 user_log = UserModel().get_user_log(c.user, filter_term)
1189 1189
1190 1190 def url_generator(**kw):
1191 1191 if filter_term:
1192 1192 kw['filter'] = filter_term
1193 1193 return self.request.current_route_path(_query=kw)
1194 1194
1195 1195 c.audit_logs = h.Page(
1196 1196 user_log, page=p, items_per_page=10, url=url_generator)
1197 1197 c.filter_term = filter_term
1198 1198 return self._get_template_context(c)
1199 1199
1200 1200 @LoginRequired()
1201 1201 @HasPermissionAllDecorator('hg.admin')
1202 1202 @view_config(
1203 1203 route_name='edit_user_perms_summary', request_method='GET',
1204 1204 renderer='rhodecode:templates/admin/users/user_edit.mako')
1205 1205 def user_perms_summary(self):
1206 1206 _ = self.request.translate
1207 1207 c = self.load_default_context()
1208 1208 c.user = self.db_user
1209 1209
1210 1210 c.active = 'perms_summary'
1211 1211 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1212 1212
1213 1213 return self._get_template_context(c)
1214 1214
1215 1215 @LoginRequired()
1216 1216 @HasPermissionAllDecorator('hg.admin')
1217 1217 @view_config(
1218 1218 route_name='edit_user_perms_summary_json', request_method='GET',
1219 1219 renderer='json_ext')
1220 1220 def user_perms_summary_json(self):
1221 1221 self.load_default_context()
1222 1222 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1223 1223
1224 1224 return perm_user.permissions
1225 1225
1226 1226 @LoginRequired()
1227 1227 @HasPermissionAllDecorator('hg.admin')
1228 1228 @view_config(
1229 1229 route_name='edit_user_caches', request_method='GET',
1230 1230 renderer='rhodecode:templates/admin/users/user_edit.mako')
1231 1231 def user_caches(self):
1232 1232 _ = self.request.translate
1233 1233 c = self.load_default_context()
1234 1234 c.user = self.db_user
1235 1235
1236 1236 c.active = 'caches'
1237 1237 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1238 1238
1239 1239 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1240 1240 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1241 1241 c.backend = c.region.backend
1242 1242 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1243 1243
1244 1244 return self._get_template_context(c)
1245 1245
1246 1246 @LoginRequired()
1247 1247 @HasPermissionAllDecorator('hg.admin')
1248 1248 @CSRFRequired()
1249 1249 @view_config(
1250 1250 route_name='edit_user_caches_update', request_method='POST')
1251 1251 def user_caches_update(self):
1252 1252 _ = self.request.translate
1253 1253 c = self.load_default_context()
1254 1254 c.user = self.db_user
1255 1255
1256 1256 c.active = 'caches'
1257 1257 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1258 1258
1259 1259 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1260 1260 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1261 1261
1262 1262 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1263 1263
1264 1264 return HTTPFound(h.route_path(
1265 1265 'edit_user_caches', user_id=c.user.user_id))
@@ -1,116 +1,103 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 from rhodecode import events
27 26 from rhodecode.apps._base import RepoGroupAppView
28 27 from rhodecode.lib import helpers as h
29 28 from rhodecode.lib import audit_logger
30 29 from rhodecode.lib.auth import (
31 30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import UserGroup
31 from rhodecode.model.permission import PermissionModel
34 32 from rhodecode.model.repo_group import RepoGroupModel
35 33 from rhodecode.model.forms import RepoGroupPermsForm
36 34 from rhodecode.model.meta import Session
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38
41 39 class RepoGroupPermissionsView(RepoGroupAppView):
42 40 def load_default_context(self):
43 41 c = self._get_local_tmpl_context()
44 42
45 43 return c
46 44
47 45 @LoginRequired()
48 46 @HasRepoGroupPermissionAnyDecorator('group.admin')
49 47 @view_config(
50 48 route_name='edit_repo_group_perms', request_method='GET',
51 49 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
52 50 def edit_repo_group_permissions(self):
53 51 c = self.load_default_context()
54 52 c.active = 'permissions'
55 53 c.repo_group = self.db_repo_group
56 54 return self._get_template_context(c)
57 55
58 56 @LoginRequired()
59 57 @HasRepoGroupPermissionAnyDecorator('group.admin')
60 58 @CSRFRequired()
61 59 @view_config(
62 60 route_name='edit_repo_group_perms_update', request_method='POST',
63 61 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
64 62 def edit_repo_groups_permissions_update(self):
65 63 _ = self.request.translate
66 64 c = self.load_default_context()
67 65 c.active = 'perms'
68 66 c.repo_group = self.db_repo_group
69 67
70 68 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
71 69 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
72 70 .to_python(self.request.POST)
73 71
74 72 if not c.rhodecode_user.is_admin:
75 73 if self._revoke_perms_on_yourself(form):
76 74 msg = _('Cannot change permission for yourself as admin')
77 75 h.flash(msg, category='warning')
78 76 raise HTTPFound(
79 77 h.route_path('edit_repo_group_perms',
80 78 repo_group_name=self.db_repo_group_name))
81 79
82 80 # iterate over all members(if in recursive mode) of this groups and
83 81 # set the permissions !
84 82 # this can be potentially heavy operation
85 83 changes = RepoGroupModel().update_permissions(
86 84 c.repo_group,
87 85 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
88 86 form['recursive'])
89 87
90 88 action_data = {
91 89 'added': changes['added'],
92 90 'updated': changes['updated'],
93 91 'deleted': changes['deleted'],
94 92 }
95 93 audit_logger.store_web(
96 94 'repo_group.edit.permissions', action_data=action_data,
97 95 user=c.rhodecode_user)
98 96
99 97 Session().commit()
100 98 h.flash(_('Repository Group permissions updated'), category='success')
101
102 affected_user_ids = []
103 for change in changes['added'] + changes['updated'] + changes['deleted']:
104 if change['type'] == 'user':
105 affected_user_ids.append(change['id'])
106 if change['type'] == 'user_group':
107 user_group = UserGroup.get(safe_int(change['id']))
108 if user_group:
109 group_members_ids = [x.user_id for x in user_group.members]
110 affected_user_ids.extend(group_members_ids)
111
112 events.trigger(events.UserPermissionsChange(affected_user_ids))
99 PermissionModel().flush_user_permission_caches(changes)
113 100
114 101 raise HTTPFound(
115 102 h.route_path('edit_repo_group_perms',
116 103 repo_group_name=self.db_repo_group_name))
@@ -1,193 +1,194 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import deform
23 23
24 24 from pyramid.view import view_config
25 25 from pyramid.httpexceptions import HTTPFound
26 26
27 27 from rhodecode import events
28 28 from rhodecode.apps._base import RepoGroupAppView
29 29 from rhodecode.forms import RcForm
30 30 from rhodecode.lib import helpers as h
31 31 from rhodecode.lib import audit_logger
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasPermissionAll,
34 34 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
35 35 from rhodecode.model.db import Session, RepoGroup, User
36 from rhodecode.model.permission import PermissionModel
36 37 from rhodecode.model.scm import RepoGroupList
37 38 from rhodecode.model.repo_group import RepoGroupModel
38 39 from rhodecode.model.validation_schema.schemas import repo_group_schema
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 class RepoGroupSettingsView(RepoGroupAppView):
44 45 def load_default_context(self):
45 46 c = self._get_local_tmpl_context()
46 47 c.repo_group = self.db_repo_group
47 48 no_parrent = not c.repo_group.parent_group
48 49 can_create_in_root = self._can_create_repo_group()
49 50
50 51 show_root_location = False
51 52 if no_parrent or can_create_in_root:
52 53 # we're global admin, we're ok and we can create TOP level groups
53 54 # or in case this group is already at top-level we also allow
54 55 # creation in root
55 56 show_root_location = True
56 57
57 58 acl_groups = RepoGroupList(
58 59 RepoGroup.query().all(),
59 60 perm_set=['group.admin'])
60 61 c.repo_groups = RepoGroup.groups_choices(
61 62 groups=acl_groups,
62 63 show_empty_group=show_root_location)
63 64 # filter out current repo group
64 65 exclude_group_ids = [c.repo_group.group_id]
65 66 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
66 67 c.repo_groups)
67 68 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
68 69
69 70 parent_group = c.repo_group.parent_group
70 71
71 72 add_parent_group = (parent_group and (
72 73 parent_group.group_id not in c.repo_groups_choices))
73 74 if add_parent_group:
74 75 c.repo_groups_choices.append(parent_group.group_id)
75 76 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
76 77 return c
77 78
78 79 def _can_create_repo_group(self, parent_group_id=None):
79 80 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 81 create_repo_group = HasPermissionAll(
81 82 'hg.repogroup.create.true')('group create controller')
82 83 if is_admin or (create_repo_group and not parent_group_id):
83 84 # we're global admin, or we have global repo group create
84 85 # permission
85 86 # we're ok and we can create TOP level groups
86 87 return True
87 88 elif parent_group_id:
88 89 # we check the permission if we can write to parent group
89 90 group = RepoGroup.get(parent_group_id)
90 91 group_name = group.group_name if group else None
91 92 if HasRepoGroupPermissionAny('group.admin')(
92 93 group_name, 'check if user is an admin of group'):
93 94 # we're an admin of passed in group, we're ok.
94 95 return True
95 96 else:
96 97 return False
97 98 return False
98 99
99 100 def _get_schema(self, c, old_values=None):
100 101 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 102 repo_group_repo_group_options=c.repo_groups_choices,
102 103 repo_group_repo_group_items=c.repo_groups,
103 104
104 105 # user caller
105 106 user=self._rhodecode_user,
106 107 old_values=old_values
107 108 )
108 109
109 110 @LoginRequired()
110 111 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 112 @view_config(
112 113 route_name='edit_repo_group', request_method='GET',
113 114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
114 115 def edit_settings(self):
115 116 c = self.load_default_context()
116 117 c.active = 'settings'
117 118
118 119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
119 120 defaults['repo_group_owner'] = defaults['user']
120 121
121 122 schema = self._get_schema(c)
122 123 c.form = RcForm(schema, appstruct=defaults)
123 124 return self._get_template_context(c)
124 125
125 126 @LoginRequired()
126 127 @HasRepoGroupPermissionAnyDecorator('group.admin')
127 128 @CSRFRequired()
128 129 @view_config(
129 130 route_name='edit_repo_group', request_method='POST',
130 131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
131 132 def edit_settings_update(self):
132 133 _ = self.request.translate
133 134 c = self.load_default_context()
134 135 c.active = 'settings'
135 136
136 137 old_repo_group_name = self.db_repo_group_name
137 138 new_repo_group_name = old_repo_group_name
138 139
139 140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
140 141 schema = self._get_schema(c, old_values=old_values)
141 142
142 143 c.form = RcForm(schema)
143 144 pstruct = self.request.POST.items()
144 145
145 146 try:
146 147 schema_data = c.form.validate(pstruct)
147 148 except deform.ValidationFailure as err_form:
148 149 return self._get_template_context(c)
149 150
150 151 # data is now VALID, proceed with updates
151 152 # save validated data back into the updates dict
152 153 validated_updates = dict(
153 154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
154 155 group_parent_id=schema_data['repo_group']['repo_group_id'],
155 156 user=schema_data['repo_group_owner'],
156 157 group_description=schema_data['repo_group_description'],
157 158 enable_locking=schema_data['repo_group_enable_locking'],
158 159 )
159 160
160 161 try:
161 162 RepoGroupModel().update(self.db_repo_group, validated_updates)
162 163
163 164 audit_logger.store_web(
164 165 'repo_group.edit', action_data={'old_data': old_values},
165 166 user=c.rhodecode_user)
166 167
167 168 Session().commit()
168 169
169 170 # use the new full name for redirect once we know we updated
170 171 # the name on filesystem and in DB
171 172 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
172 173
173 174 h.flash(_('Repository Group `{}` updated successfully').format(
174 175 old_repo_group_name), category='success')
175 176
176 177 except Exception:
177 178 log.exception("Exception during update or repository group")
178 179 h.flash(_('Error occurred during update of repository group %s')
179 180 % old_repo_group_name, category='error')
180 181
181 182 name_changed = old_repo_group_name != new_repo_group_name
182 183 if name_changed:
183 184 current_perms = self.db_repo_group.permissions(expand_from_user_groups=True)
184 185 affected_user_ids = [perm['user_id'] for perm in current_perms]
185 186
186 187 # NOTE(marcink): also add owner maybe it has changed
187 188 owner = User.get_by_username(schema_data['repo_group_owner'])
188 189 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
189 190 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
190 events.trigger(events.UserPermissionsChange(affected_user_ids))
191 PermissionModel().trigger_permission_flush(affected_user_ids)
191 192
192 193 raise HTTPFound(
193 194 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
@@ -1,125 +1,126 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25 25
26 26 from rhodecode import events
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
30 30 from rhodecode.model.db import Repository
31 from rhodecode.model.permission import PermissionModel
31 32 from rhodecode.model.validation_schema.types import RepoNameType
32 33
33 34 log = logging.getLogger(__name__)
34 35
35 36
36 37 class RepoChecksView(BaseAppView):
37 38 def load_default_context(self):
38 39 c = self._get_local_tmpl_context()
39 40
40 41 return c
41 42
42 43 @NotAnonymous()
43 44 @view_config(
44 45 route_name='repo_creating', request_method='GET',
45 46 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
46 47 def repo_creating(self):
47 48 c = self.load_default_context()
48 49 repo_name = self.request.matchdict['repo_name']
49 50 repo_name = RepoNameType().deserialize(None, repo_name)
50 51 db_repo = Repository.get_by_repo_name(repo_name)
51 52
52 53 # check if maybe repo is already created
53 54 if db_repo and db_repo.repo_state in [Repository.STATE_CREATED]:
54 55 self.flush_permissions_on_creation(db_repo)
55 56
56 57 # re-check permissions before redirecting to prevent resource
57 58 # discovery by checking the 302 code
58 59 perm_set = ['repository.read', 'repository.write', 'repository.admin']
59 60 has_perm = HasRepoPermissionAny(*perm_set)(
60 61 db_repo.repo_name, 'Repo Creating check')
61 62 if not has_perm:
62 63 raise HTTPNotFound()
63 64
64 65 raise HTTPFound(h.route_path(
65 66 'repo_summary', repo_name=db_repo.repo_name))
66 67
67 68 c.task_id = self.request.GET.get('task_id')
68 69 c.repo_name = repo_name
69 70
70 71 return self._get_template_context(c)
71 72
72 73 @NotAnonymous()
73 74 @view_config(
74 75 route_name='repo_creating_check', request_method='GET',
75 76 renderer='json_ext')
76 77 def repo_creating_check(self):
77 78 _ = self.request.translate
78 79 task_id = self.request.GET.get('task_id')
79 80 self.load_default_context()
80 81
81 82 repo_name = self.request.matchdict['repo_name']
82 83
83 84 if task_id and task_id not in ['None']:
84 85 import rhodecode
85 86 from rhodecode.lib.celerylib.loader import celery_app, exceptions
86 87 if rhodecode.CELERY_ENABLED:
87 88 log.debug('celery: checking result for task:%s', task_id)
88 89 task = celery_app.AsyncResult(task_id)
89 90 try:
90 91 task.get(timeout=10)
91 92 except exceptions.TimeoutError:
92 93 task = None
93 94 if task and task.failed():
94 95 msg = self._log_creation_exception(task.result, repo_name)
95 96 h.flash(msg, category='error')
96 97 raise HTTPFound(h.route_path('home'), code=501)
97 98
98 99 db_repo = Repository.get_by_repo_name(repo_name)
99 100 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
100 101 if db_repo.clone_uri:
101 102 clone_uri = db_repo.clone_uri_hidden
102 103 h.flash(_('Created repository %s from %s')
103 104 % (db_repo.repo_name, clone_uri), category='success')
104 105 else:
105 106 repo_url = h.link_to(
106 107 db_repo.repo_name,
107 108 h.route_path('repo_summary', repo_name=db_repo.repo_name))
108 109 fork = db_repo.fork
109 110 if fork:
110 111 fork_name = fork.repo_name
111 112 h.flash(h.literal(_('Forked repository %s as %s')
112 113 % (fork_name, repo_url)), category='success')
113 114 else:
114 115 h.flash(h.literal(_('Created repository %s') % repo_url),
115 116 category='success')
116 117 self.flush_permissions_on_creation(db_repo)
117 118
118 119 return {'result': True}
119 120 return {'result': False}
120 121
121 122 def flush_permissions_on_creation(self, db_repo):
122 123 # repo is finished and created, we flush the permissions now
123 124 user_group_perms = db_repo.permissions(expand_from_user_groups=True)
124 125 affected_user_ids = [perm['user_id'] for perm in user_group_perms]
125 events.trigger(events.UserPermissionsChange(affected_user_ids))
126 PermissionModel().trigger_permission_flush(affected_user_ids)
@@ -1,264 +1,265 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 36 import rhodecode.lib.helpers as h
37 37 from rhodecode.lib.celerylib.utils import get_task_id
38 38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
39 from rhodecode.model.permission import PermissionModel
39 40 from rhodecode.model.repo import RepoModel
40 41 from rhodecode.model.forms import RepoForkForm
41 42 from rhodecode.model.scm import ScmModel, RepoGroupList
42 43 from rhodecode.lib.utils2 import safe_int, safe_unicode
43 44
44 45 log = logging.getLogger(__name__)
45 46
46 47
47 48 class RepoForksView(RepoAppView, DataGridAppView):
48 49
49 50 def load_default_context(self):
50 51 c = self._get_local_tmpl_context(include_app_defaults=True)
51 52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 53
53 54 acl_groups = RepoGroupList(
54 55 RepoGroup.query().all(),
55 56 perm_set=['group.write', 'group.admin'])
56 57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
58 59
59 60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
60 61
61 62 return c
62 63
63 64 @LoginRequired()
64 65 @HasRepoPermissionAnyDecorator(
65 66 'repository.read', 'repository.write', 'repository.admin')
66 67 @view_config(
67 68 route_name='repo_forks_show_all', request_method='GET',
68 69 renderer='rhodecode:templates/forks/forks.mako')
69 70 def repo_forks_show_all(self):
70 71 c = self.load_default_context()
71 72 return self._get_template_context(c)
72 73
73 74 @LoginRequired()
74 75 @HasRepoPermissionAnyDecorator(
75 76 'repository.read', 'repository.write', 'repository.admin')
76 77 @view_config(
77 78 route_name='repo_forks_data', request_method='GET',
78 79 renderer='json_ext', xhr=True)
79 80 def repo_forks_data(self):
80 81 _ = self.request.translate
81 82 self.load_default_context()
82 83 column_map = {
83 84 'fork_name': 'repo_name',
84 85 'fork_date': 'created_on',
85 86 'last_activity': 'updated_on'
86 87 }
87 88 draw, start, limit = self._extract_chunk(self.request)
88 89 search_q, order_by, order_dir = self._extract_ordering(
89 90 self.request, column_map=column_map)
90 91
91 92 acl_check = HasRepoPermissionAny(
92 93 'repository.read', 'repository.write', 'repository.admin')
93 94 repo_id = self.db_repo.repo_id
94 95 allowed_ids = [-1]
95 96 for f in Repository.query().filter(Repository.fork_id == repo_id):
96 97 if acl_check(f.repo_name, 'get forks check'):
97 98 allowed_ids.append(f.repo_id)
98 99
99 100 forks_data_total_count = Repository.query()\
100 101 .filter(Repository.fork_id == repo_id)\
101 102 .filter(Repository.repo_id.in_(allowed_ids))\
102 103 .count()
103 104
104 105 # json generate
105 106 base_q = Repository.query()\
106 107 .filter(Repository.fork_id == repo_id)\
107 108 .filter(Repository.repo_id.in_(allowed_ids))\
108 109
109 110 if search_q:
110 111 like_expression = u'%{}%'.format(safe_unicode(search_q))
111 112 base_q = base_q.filter(or_(
112 113 Repository.repo_name.ilike(like_expression),
113 114 Repository.description.ilike(like_expression),
114 115 ))
115 116
116 117 forks_data_total_filtered_count = base_q.count()
117 118
118 119 sort_col = getattr(Repository, order_by, None)
119 120 if sort_col:
120 121 if order_dir == 'asc':
121 122 # handle null values properly to order by NULL last
122 123 if order_by in ['last_activity']:
123 124 sort_col = coalesce(sort_col, datetime.date.max)
124 125 sort_col = sort_col.asc()
125 126 else:
126 127 # handle null values properly to order by NULL last
127 128 if order_by in ['last_activity']:
128 129 sort_col = coalesce(sort_col, datetime.date.min)
129 130 sort_col = sort_col.desc()
130 131
131 132 base_q = base_q.order_by(sort_col)
132 133 base_q = base_q.offset(start).limit(limit)
133 134
134 135 fork_list = base_q.all()
135 136
136 137 def fork_actions(fork):
137 138 url_link = h.route_path(
138 139 'repo_compare',
139 140 repo_name=fork.repo_name,
140 141 source_ref_type=self.db_repo.landing_rev[0],
141 142 source_ref=self.db_repo.landing_rev[1],
142 143 target_ref_type=self.db_repo.landing_rev[0],
143 144 target_ref=self.db_repo.landing_rev[1],
144 145 _query=dict(merge=1, target_repo=f.repo_name))
145 146 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
146 147
147 148 def fork_name(fork):
148 149 return h.link_to(fork.repo_name,
149 150 h.route_path('repo_summary', repo_name=fork.repo_name))
150 151
151 152 forks_data = []
152 153 for fork in fork_list:
153 154 forks_data.append({
154 155 "username": h.gravatar_with_user(self.request, fork.user.username),
155 156 "fork_name": fork_name(fork),
156 157 "description": fork.description_safe,
157 158 "fork_date": h.age_component(fork.created_on, time_is_local=True),
158 159 "last_activity": h.format_date(fork.updated_on),
159 160 "action": fork_actions(fork),
160 161 })
161 162
162 163 data = ({
163 164 'draw': draw,
164 165 'data': forks_data,
165 166 'recordsTotal': forks_data_total_count,
166 167 'recordsFiltered': forks_data_total_filtered_count,
167 168 })
168 169
169 170 return data
170 171
171 172 @LoginRequired()
172 173 @NotAnonymous()
173 174 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
174 175 @HasRepoPermissionAnyDecorator(
175 176 'repository.read', 'repository.write', 'repository.admin')
176 177 @view_config(
177 178 route_name='repo_fork_new', request_method='GET',
178 179 renderer='rhodecode:templates/forks/forks.mako')
179 180 def repo_fork_new(self):
180 181 c = self.load_default_context()
181 182
182 183 defaults = RepoModel()._get_defaults(self.db_repo_name)
183 184 # alter the description to indicate a fork
184 185 defaults['description'] = (
185 186 'fork of repository: %s \n%s' % (
186 187 defaults['repo_name'], defaults['description']))
187 188 # add suffix to fork
188 189 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
189 190
190 191 data = render('rhodecode:templates/forks/fork.mako',
191 192 self._get_template_context(c), self.request)
192 193 html = formencode.htmlfill.render(
193 194 data,
194 195 defaults=defaults,
195 196 encoding="UTF-8",
196 197 force_defaults=False
197 198 )
198 199 return Response(html)
199 200
200 201 @LoginRequired()
201 202 @NotAnonymous()
202 203 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
203 204 @HasRepoPermissionAnyDecorator(
204 205 'repository.read', 'repository.write', 'repository.admin')
205 206 @CSRFRequired()
206 207 @view_config(
207 208 route_name='repo_fork_create', request_method='POST',
208 209 renderer='rhodecode:templates/forks/fork.mako')
209 210 def repo_fork_create(self):
210 211 _ = self.request.translate
211 212 c = self.load_default_context()
212 213
213 214 _form = RepoForkForm(self.request.translate,
214 215 old_data={'repo_type': self.db_repo.repo_type},
215 216 repo_groups=c.repo_groups_choices)()
216 217 post_data = dict(self.request.POST)
217 218
218 219 # forbid injecting other repo by forging a request
219 220 post_data['fork_parent_id'] = self.db_repo.repo_id
220 221
221 222 form_result = {}
222 223 task_id = None
223 224 try:
224 225 form_result = _form.to_python(post_data)
225 226 copy_permissions = form_result.get('copy_permissions')
226 227 # create fork is done sometimes async on celery, db transaction
227 228 # management is handled there.
228 229 task = RepoModel().create_fork(
229 230 form_result, c.rhodecode_user.user_id)
230 231
231 232 task_id = get_task_id(task)
232 233 except formencode.Invalid as errors:
233 234 c.rhodecode_db_repo = self.db_repo
234 235
235 236 data = render('rhodecode:templates/forks/fork.mako',
236 237 self._get_template_context(c), self.request)
237 238 html = formencode.htmlfill.render(
238 239 data,
239 240 defaults=errors.value,
240 241 errors=errors.error_dict or {},
241 242 prefix_error=False,
242 243 encoding="UTF-8",
243 244 force_defaults=False
244 245 )
245 246 return Response(html)
246 247 except Exception:
247 248 log.exception(
248 249 u'Exception while trying to fork the repository %s', self.db_repo_name)
249 250 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
250 251 h.flash(msg, category='error')
251 252 raise HTTPFound(h.route_path('home'))
252 253
253 254 repo_name = form_result.get('repo_name_full', self.db_repo_name)
254 255
255 256 affected_user_ids = [self._rhodecode_user.user_id]
256 257 if copy_permissions:
257 258 # permission flush is done in repo creating
258 259 pass
259 260
260 events.trigger(events.UserPermissionsChange(affected_user_ids))
261 PermissionModel().trigger_permission_flush(affected_user_ids)
261 262
262 263 raise HTTPFound(
263 264 h.route_path('repo_creating', repo_name=repo_name,
264 265 _query=dict(task_id=task_id)))
@@ -1,134 +1,122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 from rhodecode import events
27 26 from rhodecode.apps._base import RepoAppView
28 27 from rhodecode.lib import helpers as h
29 28 from rhodecode.lib import audit_logger
30 29 from rhodecode.lib.auth import (
31 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import UserGroup
34 31 from rhodecode.model.forms import RepoPermsForm
35 32 from rhodecode.model.meta import Session
33 from rhodecode.model.permission import PermissionModel
36 34 from rhodecode.model.repo import RepoModel
37 35
38 36 log = logging.getLogger(__name__)
39 37
40 38
41 39 class RepoSettingsPermissionsView(RepoAppView):
42 40
43 41 def load_default_context(self):
44 42 c = self._get_local_tmpl_context()
45 43 return c
46 44
47 45 @LoginRequired()
48 46 @HasRepoPermissionAnyDecorator('repository.admin')
49 47 @view_config(
50 48 route_name='edit_repo_perms', request_method='GET',
51 49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
52 50 def edit_permissions(self):
53 51 _ = self.request.translate
54 52 c = self.load_default_context()
55 53 c.active = 'permissions'
56 54 if self.request.GET.get('branch_permissions'):
57 55 h.flash(_('Explicitly add user or user group with write+ '
58 56 'permission to modify their branch permissions.'),
59 57 category='notice')
60 58 return self._get_template_context(c)
61 59
62 60 @LoginRequired()
63 61 @HasRepoPermissionAnyDecorator('repository.admin')
64 62 @CSRFRequired()
65 63 @view_config(
66 64 route_name='edit_repo_perms', request_method='POST',
67 65 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
68 66 def edit_permissions_update(self):
69 67 _ = self.request.translate
70 68 c = self.load_default_context()
71 69 c.active = 'permissions'
72 70 data = self.request.POST
73 71 # store private flag outside of HTML to verify if we can modify
74 72 # default user permissions, prevents submission of FAKE post data
75 73 # into the form for private repos
76 74 data['repo_private'] = self.db_repo.private
77 75 form = RepoPermsForm(self.request.translate)().to_python(data)
78 76 changes = RepoModel().update_permissions(
79 77 self.db_repo_name, form['perm_additions'], form['perm_updates'],
80 78 form['perm_deletions'])
81 79
82 80 action_data = {
83 81 'added': changes['added'],
84 82 'updated': changes['updated'],
85 83 'deleted': changes['deleted'],
86 84 }
87 85 audit_logger.store_web(
88 86 'repo.edit.permissions', action_data=action_data,
89 87 user=self._rhodecode_user, repo=self.db_repo)
90 88
91 89 Session().commit()
92 90 h.flash(_('Repository permissions updated'), category='success')
93 91
94 affected_user_ids = []
95 for change in changes['added'] + changes['updated'] + changes['deleted']:
96 if change['type'] == 'user':
97 affected_user_ids.append(change['id'])
98 if change['type'] == 'user_group':
99 user_group = UserGroup.get(safe_int(change['id']))
100 if user_group:
101 group_members_ids = [x.user_id for x in user_group.members]
102 affected_user_ids.extend(group_members_ids)
103
104 events.trigger(events.UserPermissionsChange(affected_user_ids))
92 PermissionModel().flush_user_permission_caches(changes)
105 93
106 94 raise HTTPFound(
107 95 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
108 96
109 97 @LoginRequired()
110 98 @HasRepoPermissionAnyDecorator('repository.admin')
111 99 @CSRFRequired()
112 100 @view_config(
113 101 route_name='edit_repo_perms_set_private', request_method='POST',
114 102 renderer='json_ext')
115 103 def edit_permissions_set_private_repo(self):
116 104 _ = self.request.translate
117 105 self.load_default_context()
118 106
119 107 try:
120 108 RepoModel().update(
121 109 self.db_repo, **{'repo_private': True, 'repo_name': self.db_repo_name})
122 110 Session().commit()
123 111
124 112 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
125 113 category='success')
126 114 except Exception:
127 115 log.exception("Exception during update of repository")
128 116 h.flash(_('Error occurred during update of repository {}').format(
129 117 self.db_repo_name), category='error')
130 118
131 119 return {
132 120 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
133 121 'private': True
134 122 }
@@ -1,266 +1,267 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import deform
24 24 from pyramid.httpexceptions import HTTPFound
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode import events
28 28 from rhodecode.apps._base import RepoAppView
29 29 from rhodecode.forms import RcForm
30 30 from rhodecode.lib import helpers as h
31 31 from rhodecode.lib import audit_logger
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 34 from rhodecode.model.db import RepositoryField, RepoGroup, Repository, User
35 35 from rhodecode.model.meta import Session
36 from rhodecode.model.permission import PermissionModel
36 37 from rhodecode.model.repo import RepoModel
37 38 from rhodecode.model.scm import RepoGroupList, ScmModel
38 39 from rhodecode.model.validation_schema.schemas import repo_schema
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 class RepoSettingsView(RepoAppView):
44 45
45 46 def load_default_context(self):
46 47 c = self._get_local_tmpl_context()
47 48
48 49 acl_groups = RepoGroupList(
49 50 RepoGroup.query().all(),
50 51 perm_set=['group.write', 'group.admin'])
51 52 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
52 53 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
53 54
54 55 # in case someone no longer have a group.write access to a repository
55 56 # pre fill the list with this entry, we don't care if this is the same
56 57 # but it will allow saving repo data properly.
57 58 repo_group = self.db_repo.group
58 59 if repo_group and repo_group.group_id not in c.repo_groups_choices:
59 60 c.repo_groups_choices.append(repo_group.group_id)
60 61 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
61 62
62 63 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
63 64 # we might be in missing requirement state, so we load things
64 65 # without touching scm_instance()
65 66 c.landing_revs_choices, c.landing_revs = \
66 67 ScmModel().get_repo_landing_revs(self.request.translate)
67 68 else:
68 69 c.landing_revs_choices, c.landing_revs = \
69 70 ScmModel().get_repo_landing_revs(
70 71 self.request.translate, self.db_repo)
71 72
72 73 c.personal_repo_group = c.auth_user.personal_repo_group
73 74 c.repo_fields = RepositoryField.query()\
74 75 .filter(RepositoryField.repository == self.db_repo).all()
75 76 return c
76 77
77 78 def _get_schema(self, c, old_values=None):
78 79 return repo_schema.RepoSettingsSchema().bind(
79 80 repo_type=self.db_repo.repo_type,
80 81 repo_type_options=[self.db_repo.repo_type],
81 82 repo_ref_options=c.landing_revs_choices,
82 83 repo_ref_items=c.landing_revs,
83 84 repo_repo_group_options=c.repo_groups_choices,
84 85 repo_repo_group_items=c.repo_groups,
85 86 # user caller
86 87 user=self._rhodecode_user,
87 88 old_values=old_values
88 89 )
89 90
90 91 @LoginRequired()
91 92 @HasRepoPermissionAnyDecorator('repository.admin')
92 93 @view_config(
93 94 route_name='edit_repo', request_method='GET',
94 95 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
95 96 def edit_settings(self):
96 97 c = self.load_default_context()
97 98 c.active = 'settings'
98 99
99 100 defaults = RepoModel()._get_defaults(self.db_repo_name)
100 101 defaults['repo_owner'] = defaults['user']
101 102 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
102 103
103 104 schema = self._get_schema(c)
104 105 c.form = RcForm(schema, appstruct=defaults)
105 106 return self._get_template_context(c)
106 107
107 108 @LoginRequired()
108 109 @HasRepoPermissionAnyDecorator('repository.admin')
109 110 @CSRFRequired()
110 111 @view_config(
111 112 route_name='edit_repo', request_method='POST',
112 113 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 114 def edit_settings_update(self):
114 115 _ = self.request.translate
115 116 c = self.load_default_context()
116 117 c.active = 'settings'
117 118 old_repo_name = self.db_repo_name
118 119
119 120 old_values = self.db_repo.get_api_data()
120 121 schema = self._get_schema(c, old_values=old_values)
121 122
122 123 c.form = RcForm(schema)
123 124 pstruct = self.request.POST.items()
124 125 pstruct.append(('repo_type', self.db_repo.repo_type))
125 126 try:
126 127 schema_data = c.form.validate(pstruct)
127 128 except deform.ValidationFailure as err_form:
128 129 return self._get_template_context(c)
129 130
130 131 # data is now VALID, proceed with updates
131 132 # save validated data back into the updates dict
132 133 validated_updates = dict(
133 134 repo_name=schema_data['repo_group']['repo_name_without_group'],
134 135 repo_group=schema_data['repo_group']['repo_group_id'],
135 136
136 137 user=schema_data['repo_owner'],
137 138 repo_description=schema_data['repo_description'],
138 139 repo_private=schema_data['repo_private'],
139 140 clone_uri=schema_data['repo_clone_uri'],
140 141 push_uri=schema_data['repo_push_uri'],
141 142 repo_landing_rev=schema_data['repo_landing_commit_ref'],
142 143 repo_enable_statistics=schema_data['repo_enable_statistics'],
143 144 repo_enable_locking=schema_data['repo_enable_locking'],
144 145 repo_enable_downloads=schema_data['repo_enable_downloads'],
145 146 )
146 147 # detect if SYNC URI changed, if we get OLD means we keep old values
147 148 if schema_data['repo_clone_uri_change'] == 'OLD':
148 149 validated_updates['clone_uri'] = self.db_repo.clone_uri
149 150
150 151 if schema_data['repo_push_uri_change'] == 'OLD':
151 152 validated_updates['push_uri'] = self.db_repo.push_uri
152 153
153 154 # use the new full name for redirect
154 155 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155 156
156 157 # save extra fields into our validated data
157 158 for key, value in pstruct:
158 159 if key.startswith(RepositoryField.PREFIX):
159 160 validated_updates[key] = value
160 161
161 162 try:
162 163 RepoModel().update(self.db_repo, **validated_updates)
163 164 ScmModel().mark_for_invalidation(new_repo_name)
164 165
165 166 audit_logger.store_web(
166 167 'repo.edit', action_data={'old_data': old_values},
167 168 user=self._rhodecode_user, repo=self.db_repo)
168 169
169 170 Session().commit()
170 171
171 172 h.flash(_('Repository `{}` updated successfully').format(old_repo_name),
172 173 category='success')
173 174 except Exception:
174 175 log.exception("Exception during update of repository")
175 176 h.flash(_('Error occurred during update of repository {}').format(
176 177 old_repo_name), category='error')
177 178
178 179 name_changed = old_repo_name != new_repo_name
179 180 if name_changed:
180 181 current_perms = self.db_repo.permissions(expand_from_user_groups=True)
181 182 affected_user_ids = [perm['user_id'] for perm in current_perms]
182 183
183 184 # NOTE(marcink): also add owner maybe it has changed
184 185 owner = User.get_by_username(schema_data['repo_owner'])
185 186 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
186 187 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
187 events.trigger(events.UserPermissionsChange(affected_user_ids))
188 PermissionModel().trigger_permission_flush(affected_user_ids)
188 189
189 190 raise HTTPFound(
190 191 h.route_path('edit_repo', repo_name=new_repo_name))
191 192
192 193 @LoginRequired()
193 194 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
194 195 @view_config(
195 196 route_name='repo_edit_toggle_locking', request_method='GET',
196 197 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
197 198 def toggle_locking(self):
198 199 """
199 200 Toggle locking of repository by simple GET call to url
200 201 """
201 202 _ = self.request.translate
202 203 repo = self.db_repo
203 204
204 205 try:
205 206 if repo.enable_locking:
206 207 if repo.locked[0]:
207 208 Repository.unlock(repo)
208 209 action = _('Unlocked')
209 210 else:
210 211 Repository.lock(
211 212 repo, self._rhodecode_user.user_id,
212 213 lock_reason=Repository.LOCK_WEB)
213 214 action = _('Locked')
214 215
215 216 h.flash(_('Repository has been %s') % action,
216 217 category='success')
217 218 except Exception:
218 219 log.exception("Exception during unlocking")
219 220 h.flash(_('An error occurred during unlocking'),
220 221 category='error')
221 222 raise HTTPFound(
222 223 h.route_path('repo_summary', repo_name=self.db_repo_name))
223 224
224 225 @LoginRequired()
225 226 @HasRepoPermissionAnyDecorator('repository.admin')
226 227 @view_config(
227 228 route_name='edit_repo_statistics', request_method='GET',
228 229 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
229 230 def edit_statistics_form(self):
230 231 c = self.load_default_context()
231 232
232 233 if self.db_repo.stats:
233 234 # this is on what revision we ended up so we add +1 for count
234 235 last_rev = self.db_repo.stats.stat_on_revision + 1
235 236 else:
236 237 last_rev = 0
237 238
238 239 c.active = 'statistics'
239 240 c.stats_revision = last_rev
240 241 c.repo_last_rev = self.rhodecode_vcs_repo.count()
241 242
242 243 if last_rev == 0 or c.repo_last_rev == 0:
243 244 c.stats_percentage = 0
244 245 else:
245 246 c.stats_percentage = '%.2f' % (
246 247 (float((last_rev)) / c.repo_last_rev) * 100)
247 248 return self._get_template_context(c)
248 249
249 250 @LoginRequired()
250 251 @HasRepoPermissionAnyDecorator('repository.admin')
251 252 @CSRFRequired()
252 253 @view_config(
253 254 route_name='edit_repo_statistics_reset', request_method='POST',
254 255 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
255 256 def repo_statistics_reset(self):
256 257 _ = self.request.translate
257 258
258 259 try:
259 260 RepoModel().delete_stats(self.db_repo_name)
260 261 Session().commit()
261 262 except Exception:
262 263 log.exception('Edit statistics failure')
263 264 h.flash(_('An error occurred during deletion of repository stats'),
264 265 category='error')
265 266 raise HTTPFound(
266 267 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,310 +1,311 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode import events
27 27 from rhodecode.apps._base import RepoAppView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib import audit_logger
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired,
32 32 HasRepoPermissionAny)
33 33 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
34 34 from rhodecode.lib.utils2 import safe_int
35 35 from rhodecode.lib.vcs import RepositoryError
36 36 from rhodecode.model.db import Session, UserFollowing, User, Repository
37 from rhodecode.model.permission import PermissionModel
37 38 from rhodecode.model.repo import RepoModel
38 39 from rhodecode.model.scm import ScmModel
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 class RepoSettingsView(RepoAppView):
44 45
45 46 def load_default_context(self):
46 47 c = self._get_local_tmpl_context()
47 48 return c
48 49
49 50 def _get_users_with_permissions(self):
50 51 user_permissions = {}
51 52 for perm in self.db_repo.permissions():
52 53 user_permissions[perm.user_id] = perm
53 54
54 55 return user_permissions
55 56
56 57 @LoginRequired()
57 58 @HasRepoPermissionAnyDecorator('repository.admin')
58 59 @view_config(
59 60 route_name='edit_repo_advanced', request_method='GET',
60 61 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
61 62 def edit_advanced(self):
62 63 c = self.load_default_context()
63 64 c.active = 'advanced'
64 65
65 66 c.default_user_id = User.get_default_user().user_id
66 67 c.in_public_journal = UserFollowing.query() \
67 68 .filter(UserFollowing.user_id == c.default_user_id) \
68 69 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
69 70
70 71 c.ver_info_dict = self.rhodecode_vcs_repo.get_hooks_info()
71 72
72 73 return self._get_template_context(c)
73 74
74 75 @LoginRequired()
75 76 @HasRepoPermissionAnyDecorator('repository.admin')
76 77 @CSRFRequired()
77 78 @view_config(
78 79 route_name='edit_repo_advanced_archive', request_method='POST',
79 80 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 81 def edit_advanced_archive(self):
81 82 """
82 83 Archives the repository. It will become read-only, and not visible in search
83 84 or other queries. But still visible for super-admins.
84 85 """
85 86
86 87 _ = self.request.translate
87 88
88 89 try:
89 90 old_data = self.db_repo.get_api_data()
90 91 RepoModel().archive(self.db_repo)
91 92
92 93 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
93 94 audit_logger.store_web(
94 95 'repo.archive', action_data={'old_data': old_data},
95 96 user=self._rhodecode_user, repo=repo)
96 97
97 98 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
98 99 h.flash(
99 100 _('Archived repository `%s`') % self.db_repo_name,
100 101 category='success')
101 102 Session().commit()
102 103 except Exception:
103 104 log.exception("Exception during archiving of repository")
104 105 h.flash(_('An error occurred during archiving of `%s`')
105 106 % self.db_repo_name, category='error')
106 107 # redirect to advanced for more deletion options
107 108 raise HTTPFound(
108 109 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
109 110 _anchor='advanced-archive'))
110 111
111 112 # flush permissions for all users defined in permissions
112 113 affected_user_ids = self._get_users_with_permissions().keys()
113 events.trigger(events.UserPermissionsChange(affected_user_ids))
114 PermissionModel().trigger_permission_flush(affected_user_ids)
114 115
115 116 raise HTTPFound(h.route_path('home'))
116 117
117 118 @LoginRequired()
118 119 @HasRepoPermissionAnyDecorator('repository.admin')
119 120 @CSRFRequired()
120 121 @view_config(
121 122 route_name='edit_repo_advanced_delete', request_method='POST',
122 123 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
123 124 def edit_advanced_delete(self):
124 125 """
125 126 Deletes the repository, or shows warnings if deletion is not possible
126 127 because of attached forks or other errors.
127 128 """
128 129 _ = self.request.translate
129 130 handle_forks = self.request.POST.get('forks', None)
130 131 if handle_forks == 'detach_forks':
131 132 handle_forks = 'detach'
132 133 elif handle_forks == 'delete_forks':
133 134 handle_forks = 'delete'
134 135
135 136 try:
136 137 old_data = self.db_repo.get_api_data()
137 138 RepoModel().delete(self.db_repo, forks=handle_forks)
138 139
139 140 _forks = self.db_repo.forks.count()
140 141 if _forks and handle_forks:
141 142 if handle_forks == 'detach_forks':
142 143 h.flash(_('Detached %s forks') % _forks, category='success')
143 144 elif handle_forks == 'delete_forks':
144 145 h.flash(_('Deleted %s forks') % _forks, category='success')
145 146
146 147 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
147 148 audit_logger.store_web(
148 149 'repo.delete', action_data={'old_data': old_data},
149 150 user=self._rhodecode_user, repo=repo)
150 151
151 152 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
152 153 h.flash(
153 154 _('Deleted repository `%s`') % self.db_repo_name,
154 155 category='success')
155 156 Session().commit()
156 157 except AttachedForksError:
157 158 repo_advanced_url = h.route_path(
158 159 'edit_repo_advanced', repo_name=self.db_repo_name,
159 160 _anchor='advanced-delete')
160 161 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
161 162 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
162 163 'Try using {delete_or_detach} option.')
163 164 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
164 165 category='warning')
165 166
166 167 # redirect to advanced for forks handle action ?
167 168 raise HTTPFound(repo_advanced_url)
168 169
169 170 except AttachedPullRequestsError:
170 171 repo_advanced_url = h.route_path(
171 172 'edit_repo_advanced', repo_name=self.db_repo_name,
172 173 _anchor='advanced-delete')
173 174 attached_prs = len(self.db_repo.pull_requests_source +
174 175 self.db_repo.pull_requests_target)
175 176 h.flash(
176 177 _('Cannot delete `{repo}` it still contains {num} attached pull requests. '
177 178 'Consider archiving the repository instead.').format(
178 179 repo=self.db_repo_name, num=attached_prs), category='warning')
179 180
180 181 # redirect to advanced for forks handle action ?
181 182 raise HTTPFound(repo_advanced_url)
182 183
183 184 except Exception:
184 185 log.exception("Exception during deletion of repository")
185 186 h.flash(_('An error occurred during deletion of `%s`')
186 187 % self.db_repo_name, category='error')
187 188 # redirect to advanced for more deletion options
188 189 raise HTTPFound(
189 190 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
190 191 _anchor='advanced-delete'))
191 192
192 193 raise HTTPFound(h.route_path('home'))
193 194
194 195 @LoginRequired()
195 196 @HasRepoPermissionAnyDecorator('repository.admin')
196 197 @CSRFRequired()
197 198 @view_config(
198 199 route_name='edit_repo_advanced_journal', request_method='POST',
199 200 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
200 201 def edit_advanced_journal(self):
201 202 """
202 203 Set's this repository to be visible in public journal,
203 204 in other words making default user to follow this repo
204 205 """
205 206 _ = self.request.translate
206 207
207 208 try:
208 209 user_id = User.get_default_user().user_id
209 210 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
210 211 h.flash(_('Updated repository visibility in public journal'),
211 212 category='success')
212 213 Session().commit()
213 214 except Exception:
214 215 h.flash(_('An error occurred during setting this '
215 216 'repository in public journal'),
216 217 category='error')
217 218
218 219 raise HTTPFound(
219 220 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
220 221
221 222 @LoginRequired()
222 223 @HasRepoPermissionAnyDecorator('repository.admin')
223 224 @CSRFRequired()
224 225 @view_config(
225 226 route_name='edit_repo_advanced_fork', request_method='POST',
226 227 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
227 228 def edit_advanced_fork(self):
228 229 """
229 230 Mark given repository as a fork of another
230 231 """
231 232 _ = self.request.translate
232 233
233 234 new_fork_id = safe_int(self.request.POST.get('id_fork_of'))
234 235
235 236 # valid repo, re-check permissions
236 237 if new_fork_id:
237 238 repo = Repository.get(new_fork_id)
238 239 # ensure we have at least read access to the repo we mark
239 240 perm_check = HasRepoPermissionAny(
240 241 'repository.read', 'repository.write', 'repository.admin')
241 242
242 243 if repo and perm_check(repo_name=repo.repo_name):
243 244 new_fork_id = repo.repo_id
244 245 else:
245 246 new_fork_id = None
246 247
247 248 try:
248 249 repo = ScmModel().mark_as_fork(
249 250 self.db_repo_name, new_fork_id, self._rhodecode_user.user_id)
250 251 fork = repo.fork.repo_name if repo.fork else _('Nothing')
251 252 Session().commit()
252 253 h.flash(
253 254 _('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
254 255 category='success')
255 256 except RepositoryError as e:
256 257 log.exception("Repository Error occurred")
257 258 h.flash(str(e), category='error')
258 259 except Exception:
259 260 log.exception("Exception while editing fork")
260 261 h.flash(_('An error occurred during this operation'),
261 262 category='error')
262 263
263 264 raise HTTPFound(
264 265 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
265 266
266 267 @LoginRequired()
267 268 @HasRepoPermissionAnyDecorator('repository.admin')
268 269 @CSRFRequired()
269 270 @view_config(
270 271 route_name='edit_repo_advanced_locking', request_method='POST',
271 272 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
272 273 def edit_advanced_locking(self):
273 274 """
274 275 Toggle locking of repository
275 276 """
276 277 _ = self.request.translate
277 278 set_lock = self.request.POST.get('set_lock')
278 279 set_unlock = self.request.POST.get('set_unlock')
279 280
280 281 try:
281 282 if set_lock:
282 283 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
283 284 lock_reason=Repository.LOCK_WEB)
284 285 h.flash(_('Locked repository'), category='success')
285 286 elif set_unlock:
286 287 Repository.unlock(self.db_repo)
287 288 h.flash(_('Unlocked repository'), category='success')
288 289 except Exception as e:
289 290 log.exception("Exception during unlocking")
290 291 h.flash(_('An error occurred during unlocking'), category='error')
291 292
292 293 raise HTTPFound(
293 294 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
294 295
295 296 @LoginRequired()
296 297 @HasRepoPermissionAnyDecorator('repository.admin')
297 298 @view_config(
298 299 route_name='edit_repo_advanced_hooks', request_method='GET',
299 300 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
300 301 def edit_advanced_install_hooks(self):
301 302 """
302 303 Install Hooks for repository
303 304 """
304 305 _ = self.request.translate
305 306 self.load_default_context()
306 307 self.rhodecode_vcs_repo.install_hooks(force=True)
307 308 h.flash(_('installed updated hooks into this repository'),
308 309 category='success')
309 310 raise HTTPFound(
310 311 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,550 +1,550 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import peppercorn
24 24 import formencode
25 25 import formencode.htmlfill
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.lib.exceptions import (
33 33 RepoGroupAssignmentError, UserGroupAssignedException)
34 34 from rhodecode.model.forms import (
35 35 UserGroupPermsForm, UserGroupForm, UserIndividualPermissionsForm,
36 36 UserPermissionsForm)
37 37 from rhodecode.model.permission import PermissionModel
38 38
39 39 from rhodecode.apps._base import UserGroupAppView
40 40 from rhodecode.lib.auth import (
41 41 LoginRequired, HasUserGroupPermissionAnyDecorator, CSRFRequired)
42 42 from rhodecode.lib import helpers as h, audit_logger
43 43 from rhodecode.lib.utils2 import str2bool, safe_int
44 44 from rhodecode.model.db import User, UserGroup
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.user_group import UserGroupModel
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class UserGroupsView(UserGroupAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55
56 56 PermissionModel().set_global_permission_choices(
57 57 c, gettext_translator=self.request.translate)
58 58
59 59 return c
60 60
61 61 @LoginRequired()
62 62 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
63 63 @view_config(
64 64 route_name='user_group_members_data', request_method='GET',
65 65 renderer='json_ext', xhr=True)
66 66 def user_group_members(self):
67 67 """
68 68 Return members of given user group
69 69 """
70 70 self.load_default_context()
71 71 user_group = self.db_user_group
72 72 group_members_obj = sorted((x.user for x in user_group.members),
73 73 key=lambda u: u.username.lower())
74 74
75 75 group_members = [
76 76 {
77 77 'id': user.user_id,
78 78 'first_name': user.first_name,
79 79 'last_name': user.last_name,
80 80 'username': user.username,
81 81 'icon_link': h.gravatar_url(user.email, 30),
82 82 'value_display': h.person(user.email),
83 83 'value': user.username,
84 84 'value_type': 'user',
85 85 'active': user.active,
86 86 }
87 87 for user in group_members_obj
88 88 ]
89 89
90 90 return {
91 91 'members': group_members
92 92 }
93 93
94 94 @LoginRequired()
95 95 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
96 96 @view_config(
97 97 route_name='edit_user_group_perms_summary', request_method='GET',
98 98 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
99 99 def user_group_perms_summary(self):
100 100 c = self.load_default_context()
101 101 c.user_group = self.db_user_group
102 102 c.active = 'perms_summary'
103 103 c.permissions = UserGroupModel().get_perms_summary(
104 104 c.user_group.users_group_id)
105 105 return self._get_template_context(c)
106 106
107 107 @LoginRequired()
108 108 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
109 109 @view_config(
110 110 route_name='edit_user_group_perms_summary_json', request_method='GET',
111 111 renderer='json_ext')
112 112 def user_group_perms_summary_json(self):
113 113 self.load_default_context()
114 114 user_group = self.db_user_group
115 115 return UserGroupModel().get_perms_summary(user_group.users_group_id)
116 116
117 117 def _revoke_perms_on_yourself(self, form_result):
118 118 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
119 119 form_result['perm_updates'])
120 120 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
121 121 form_result['perm_additions'])
122 122 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
123 123 form_result['perm_deletions'])
124 124 admin_perm = 'usergroup.admin'
125 125 if _updates and _updates[0][1] != admin_perm or \
126 126 _additions and _additions[0][1] != admin_perm or \
127 127 _deletions and _deletions[0][1] != admin_perm:
128 128 return True
129 129 return False
130 130
131 131 @LoginRequired()
132 132 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
133 133 @CSRFRequired()
134 134 @view_config(
135 135 route_name='user_groups_update', request_method='POST',
136 136 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
137 137 def user_group_update(self):
138 138 _ = self.request.translate
139 139
140 140 user_group = self.db_user_group
141 141 user_group_id = user_group.users_group_id
142 142
143 143 old_user_group_name = self.db_user_group_name
144 144 new_user_group_name = old_user_group_name
145 145
146 146 c = self.load_default_context()
147 147 c.user_group = user_group
148 148 c.group_members_obj = [x.user for x in c.user_group.members]
149 149 c.group_members_obj.sort(key=lambda u: u.username.lower())
150 150 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
151 151 c.active = 'settings'
152 152
153 153 users_group_form = UserGroupForm(
154 154 self.request.translate, edit=True,
155 155 old_data=c.user_group.get_dict(), allow_disabled=True)()
156 156
157 157 old_values = c.user_group.get_api_data()
158 158
159 159 try:
160 160 form_result = users_group_form.to_python(self.request.POST)
161 161 pstruct = peppercorn.parse(self.request.POST.items())
162 162 form_result['users_group_members'] = pstruct['user_group_members']
163 163
164 164 user_group, added_members, removed_members = \
165 165 UserGroupModel().update(c.user_group, form_result)
166 166 new_user_group_name = form_result['users_group_name']
167 167
168 168 for user_id in added_members:
169 169 user = User.get(user_id)
170 170 user_data = user.get_api_data()
171 171 audit_logger.store_web(
172 172 'user_group.edit.member.add',
173 173 action_data={'user': user_data, 'old_data': old_values},
174 174 user=self._rhodecode_user)
175 175
176 176 for user_id in removed_members:
177 177 user = User.get(user_id)
178 178 user_data = user.get_api_data()
179 179 audit_logger.store_web(
180 180 'user_group.edit.member.delete',
181 181 action_data={'user': user_data, 'old_data': old_values},
182 182 user=self._rhodecode_user)
183 183
184 184 audit_logger.store_web(
185 185 'user_group.edit', action_data={'old_data': old_values},
186 186 user=self._rhodecode_user)
187 187
188 188 h.flash(_('Updated user group %s') % new_user_group_name,
189 189 category='success')
190 190
191 191 affected_user_ids = []
192 192 for user_id in added_members + removed_members:
193 193 affected_user_ids.append(user_id)
194 194
195 195 name_changed = old_user_group_name != new_user_group_name
196 196 if name_changed:
197 197 owner = User.get_by_username(form_result['user'])
198 198 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
199 199 affected_user_ids.append(self._rhodecode_user.user_id)
200 200 affected_user_ids.append(owner_id)
201 201
202 events.trigger(events.UserPermissionsChange(affected_user_ids))
202 PermissionModel().trigger_permission_flush(affected_user_ids)
203 203
204 204 Session().commit()
205 205 except formencode.Invalid as errors:
206 206 defaults = errors.value
207 207 e = errors.error_dict or {}
208 208
209 209 data = render(
210 210 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
211 211 self._get_template_context(c), self.request)
212 212 html = formencode.htmlfill.render(
213 213 data,
214 214 defaults=defaults,
215 215 errors=e,
216 216 prefix_error=False,
217 217 encoding="UTF-8",
218 218 force_defaults=False
219 219 )
220 220 return Response(html)
221 221
222 222 except Exception:
223 223 log.exception("Exception during update of user group")
224 224 h.flash(_('Error occurred during update of user group %s')
225 225 % new_user_group_name, category='error')
226 226
227 227 raise HTTPFound(
228 228 h.route_path('edit_user_group', user_group_id=user_group_id))
229 229
230 230 @LoginRequired()
231 231 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
232 232 @CSRFRequired()
233 233 @view_config(
234 234 route_name='user_groups_delete', request_method='POST',
235 235 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
236 236 def user_group_delete(self):
237 237 _ = self.request.translate
238 238 user_group = self.db_user_group
239 239
240 240 self.load_default_context()
241 241 force = str2bool(self.request.POST.get('force'))
242 242
243 243 old_values = user_group.get_api_data()
244 244 try:
245 245 UserGroupModel().delete(user_group, force=force)
246 246 audit_logger.store_web(
247 247 'user.delete', action_data={'old_data': old_values},
248 248 user=self._rhodecode_user)
249 249 Session().commit()
250 250 h.flash(_('Successfully deleted user group'), category='success')
251 251 except UserGroupAssignedException as e:
252 252 h.flash(str(e), category='error')
253 253 except Exception:
254 254 log.exception("Exception during deletion of user group")
255 255 h.flash(_('An error occurred during deletion of user group'),
256 256 category='error')
257 257 raise HTTPFound(h.route_path('user_groups'))
258 258
259 259 @LoginRequired()
260 260 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
261 261 @view_config(
262 262 route_name='edit_user_group', request_method='GET',
263 263 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
264 264 def user_group_edit(self):
265 265 user_group = self.db_user_group
266 266
267 267 c = self.load_default_context()
268 268 c.user_group = user_group
269 269 c.group_members_obj = [x.user for x in c.user_group.members]
270 270 c.group_members_obj.sort(key=lambda u: u.username.lower())
271 271 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
272 272
273 273 c.active = 'settings'
274 274
275 275 defaults = user_group.get_dict()
276 276 # fill owner
277 277 if user_group.user:
278 278 defaults.update({'user': user_group.user.username})
279 279 else:
280 280 replacement_user = User.get_first_super_admin().username
281 281 defaults.update({'user': replacement_user})
282 282
283 283 data = render(
284 284 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
285 285 self._get_template_context(c), self.request)
286 286 html = formencode.htmlfill.render(
287 287 data,
288 288 defaults=defaults,
289 289 encoding="UTF-8",
290 290 force_defaults=False
291 291 )
292 292 return Response(html)
293 293
294 294 @LoginRequired()
295 295 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
296 296 @view_config(
297 297 route_name='edit_user_group_perms', request_method='GET',
298 298 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
299 299 def user_group_edit_perms(self):
300 300 user_group = self.db_user_group
301 301 c = self.load_default_context()
302 302 c.user_group = user_group
303 303 c.active = 'perms'
304 304
305 305 defaults = {}
306 306 # fill user group users
307 307 for p in c.user_group.user_user_group_to_perm:
308 308 defaults.update({'u_perm_%s' % p.user.user_id:
309 309 p.permission.permission_name})
310 310
311 311 for p in c.user_group.user_group_user_group_to_perm:
312 312 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
313 313 p.permission.permission_name})
314 314
315 315 data = render(
316 316 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
317 317 self._get_template_context(c), self.request)
318 318 html = formencode.htmlfill.render(
319 319 data,
320 320 defaults=defaults,
321 321 encoding="UTF-8",
322 322 force_defaults=False
323 323 )
324 324 return Response(html)
325 325
326 326 @LoginRequired()
327 327 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
328 328 @CSRFRequired()
329 329 @view_config(
330 330 route_name='edit_user_group_perms_update', request_method='POST',
331 331 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
332 332 def user_group_update_perms(self):
333 333 """
334 334 grant permission for given user group
335 335 """
336 336 _ = self.request.translate
337 337
338 338 user_group = self.db_user_group
339 339 user_group_id = user_group.users_group_id
340 340 c = self.load_default_context()
341 341 c.user_group = user_group
342 342 form = UserGroupPermsForm(self.request.translate)().to_python(self.request.POST)
343 343
344 344 if not self._rhodecode_user.is_admin:
345 345 if self._revoke_perms_on_yourself(form):
346 346 msg = _('Cannot change permission for yourself as admin')
347 347 h.flash(msg, category='warning')
348 348 raise HTTPFound(
349 349 h.route_path('edit_user_group_perms',
350 350 user_group_id=user_group_id))
351 351
352 352 try:
353 353 changes = UserGroupModel().update_permissions(
354 354 user_group,
355 355 form['perm_additions'], form['perm_updates'],
356 356 form['perm_deletions'])
357 357
358 358 except RepoGroupAssignmentError:
359 359 h.flash(_('Target group cannot be the same'), category='error')
360 360 raise HTTPFound(
361 361 h.route_path('edit_user_group_perms',
362 362 user_group_id=user_group_id))
363 363
364 364 action_data = {
365 365 'added': changes['added'],
366 366 'updated': changes['updated'],
367 367 'deleted': changes['deleted'],
368 368 }
369 369 audit_logger.store_web(
370 370 'user_group.edit.permissions', action_data=action_data,
371 371 user=self._rhodecode_user)
372 372
373 373 Session().commit()
374 374 h.flash(_('User Group permissions updated'), category='success')
375 375
376 376 affected_user_ids = []
377 377 for change in changes['added'] + changes['updated'] + changes['deleted']:
378 378 if change['type'] == 'user':
379 379 affected_user_ids.append(change['id'])
380 380 if change['type'] == 'user_group':
381 381 user_group = UserGroup.get(safe_int(change['id']))
382 382 if user_group:
383 383 group_members_ids = [x.user_id for x in user_group.members]
384 384 affected_user_ids.extend(group_members_ids)
385 385
386 events.trigger(events.UserPermissionsChange(affected_user_ids))
386 PermissionModel().trigger_permission_flush(affected_user_ids)
387 387
388 388 raise HTTPFound(
389 389 h.route_path('edit_user_group_perms', user_group_id=user_group_id))
390 390
391 391 @LoginRequired()
392 392 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
393 393 @view_config(
394 394 route_name='edit_user_group_global_perms', request_method='GET',
395 395 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
396 396 def user_group_global_perms_edit(self):
397 397 user_group = self.db_user_group
398 398 c = self.load_default_context()
399 399 c.user_group = user_group
400 400 c.active = 'global_perms'
401 401
402 402 c.default_user = User.get_default_user()
403 403 defaults = c.user_group.get_dict()
404 404 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
405 405 defaults.update(c.user_group.get_default_perms())
406 406
407 407 data = render(
408 408 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
409 409 self._get_template_context(c), self.request)
410 410 html = formencode.htmlfill.render(
411 411 data,
412 412 defaults=defaults,
413 413 encoding="UTF-8",
414 414 force_defaults=False
415 415 )
416 416 return Response(html)
417 417
418 418 @LoginRequired()
419 419 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
420 420 @CSRFRequired()
421 421 @view_config(
422 422 route_name='edit_user_group_global_perms_update', request_method='POST',
423 423 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
424 424 def user_group_global_perms_update(self):
425 425 _ = self.request.translate
426 426 user_group = self.db_user_group
427 427 user_group_id = self.db_user_group.users_group_id
428 428
429 429 c = self.load_default_context()
430 430 c.user_group = user_group
431 431 c.active = 'global_perms'
432 432
433 433 try:
434 434 # first stage that verifies the checkbox
435 435 _form = UserIndividualPermissionsForm(self.request.translate)
436 436 form_result = _form.to_python(dict(self.request.POST))
437 437 inherit_perms = form_result['inherit_default_permissions']
438 438 user_group.inherit_default_permissions = inherit_perms
439 439 Session().add(user_group)
440 440
441 441 if not inherit_perms:
442 442 # only update the individual ones if we un check the flag
443 443 _form = UserPermissionsForm(
444 444 self.request.translate,
445 445 [x[0] for x in c.repo_create_choices],
446 446 [x[0] for x in c.repo_create_on_write_choices],
447 447 [x[0] for x in c.repo_group_create_choices],
448 448 [x[0] for x in c.user_group_create_choices],
449 449 [x[0] for x in c.fork_choices],
450 450 [x[0] for x in c.inherit_default_permission_choices])()
451 451
452 452 form_result = _form.to_python(dict(self.request.POST))
453 453 form_result.update(
454 454 {'perm_user_group_id': user_group.users_group_id})
455 455
456 456 PermissionModel().update_user_group_permissions(form_result)
457 457
458 458 Session().commit()
459 459 h.flash(_('User Group global permissions updated successfully'),
460 460 category='success')
461 461
462 462 except formencode.Invalid as errors:
463 463 defaults = errors.value
464 464
465 465 data = render(
466 466 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
467 467 self._get_template_context(c), self.request)
468 468 html = formencode.htmlfill.render(
469 469 data,
470 470 defaults=defaults,
471 471 errors=errors.error_dict or {},
472 472 prefix_error=False,
473 473 encoding="UTF-8",
474 474 force_defaults=False
475 475 )
476 476 return Response(html)
477 477 except Exception:
478 478 log.exception("Exception during permissions saving")
479 479 h.flash(_('An error occurred during permissions saving'),
480 480 category='error')
481 481
482 482 raise HTTPFound(
483 483 h.route_path('edit_user_group_global_perms',
484 484 user_group_id=user_group_id))
485 485
486 486 @LoginRequired()
487 487 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
488 488 @view_config(
489 489 route_name='edit_user_group_advanced', request_method='GET',
490 490 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
491 491 def user_group_edit_advanced(self):
492 492 user_group = self.db_user_group
493 493
494 494 c = self.load_default_context()
495 495 c.user_group = user_group
496 496 c.active = 'advanced'
497 497 c.group_members_obj = sorted(
498 498 (x.user for x in c.user_group.members),
499 499 key=lambda u: u.username.lower())
500 500
501 501 c.group_to_repos = sorted(
502 502 (x.repository for x in c.user_group.users_group_repo_to_perm),
503 503 key=lambda u: u.repo_name.lower())
504 504
505 505 c.group_to_repo_groups = sorted(
506 506 (x.group for x in c.user_group.users_group_repo_group_to_perm),
507 507 key=lambda u: u.group_name.lower())
508 508
509 509 c.group_to_review_rules = sorted(
510 510 (x.users_group for x in c.user_group.user_group_review_rules),
511 511 key=lambda u: u.users_group_name.lower())
512 512
513 513 return self._get_template_context(c)
514 514
515 515 @LoginRequired()
516 516 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
517 517 @CSRFRequired()
518 518 @view_config(
519 519 route_name='edit_user_group_advanced_sync', request_method='POST',
520 520 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
521 521 def user_group_edit_advanced_set_synchronization(self):
522 522 _ = self.request.translate
523 523 user_group = self.db_user_group
524 524 user_group_id = user_group.users_group_id
525 525
526 526 existing = user_group.group_data.get('extern_type')
527 527
528 528 if existing:
529 529 new_state = user_group.group_data
530 530 new_state['extern_type'] = None
531 531 else:
532 532 new_state = user_group.group_data
533 533 new_state['extern_type'] = 'manual'
534 534 new_state['extern_type_set_by'] = self._rhodecode_user.username
535 535
536 536 try:
537 537 user_group.group_data = new_state
538 538 Session().add(user_group)
539 539 Session().commit()
540 540
541 541 h.flash(_('User Group synchronization updated successfully'),
542 542 category='success')
543 543 except Exception:
544 544 log.exception("Exception during sync settings saving")
545 545 h.flash(_('An error occurred during synchronization update'),
546 546 category='error')
547 547
548 548 raise HTTPFound(
549 549 h.route_path('edit_user_group_advanced',
550 550 user_group_id=user_group_id))
@@ -1,558 +1,577 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 permissions model for RhodeCode
23 23 """
24 24
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from sqlalchemy.exc import DatabaseError
30 30
31 from rhodecode import events
31 32 from rhodecode.model import BaseModel
32 33 from rhodecode.model.db import (
33 34 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 35 UserUserGroupToPerm, UserGroup, UserGroupToPerm, UserToRepoBranchPermission)
35 36 from rhodecode.lib.utils2 import str2bool, safe_int
36 37
37 38 log = logging.getLogger(__name__)
38 39
39 40
40 41 class PermissionModel(BaseModel):
41 42 """
42 43 Permissions model for RhodeCode
43 44 """
44 45
45 46 cls = Permission
46 47 global_perms = {
47 48 'default_repo_create': None,
48 49 # special case for create repos on write access to group
49 50 'default_repo_create_on_write': None,
50 51 'default_repo_group_create': None,
51 52 'default_user_group_create': None,
52 53 'default_fork_create': None,
53 54 'default_inherit_default_permissions': None,
54 55 'default_register': None,
55 56 'default_password_reset': None,
56 57 'default_extern_activate': None,
57 58
58 59 # object permissions below
59 60 'default_repo_perm': None,
60 61 'default_group_perm': None,
61 62 'default_user_group_perm': None,
62 63
63 64 # branch
64 65 'default_branch_perm': None,
65 66 }
66 67
67 68 def set_global_permission_choices(self, c_obj, gettext_translator):
68 69 _ = gettext_translator
69 70
70 71 c_obj.repo_perms_choices = [
71 72 ('repository.none', _('None'),),
72 73 ('repository.read', _('Read'),),
73 74 ('repository.write', _('Write'),),
74 75 ('repository.admin', _('Admin'),)]
75 76
76 77 c_obj.group_perms_choices = [
77 78 ('group.none', _('None'),),
78 79 ('group.read', _('Read'),),
79 80 ('group.write', _('Write'),),
80 81 ('group.admin', _('Admin'),)]
81 82
82 83 c_obj.user_group_perms_choices = [
83 84 ('usergroup.none', _('None'),),
84 85 ('usergroup.read', _('Read'),),
85 86 ('usergroup.write', _('Write'),),
86 87 ('usergroup.admin', _('Admin'),)]
87 88
88 89 c_obj.branch_perms_choices = [
89 90 ('branch.none', _('Protected/No Access'),),
90 91 ('branch.merge', _('Web merge'),),
91 92 ('branch.push', _('Push'),),
92 93 ('branch.push_force', _('Force Push'),)]
93 94
94 95 c_obj.register_choices = [
95 96 ('hg.register.none', _('Disabled')),
96 97 ('hg.register.manual_activate', _('Allowed with manual account activation')),
97 98 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
98 99
99 100 c_obj.password_reset_choices = [
100 101 ('hg.password_reset.enabled', _('Allow password recovery')),
101 102 ('hg.password_reset.hidden', _('Hide password recovery link')),
102 103 ('hg.password_reset.disabled', _('Disable password recovery')),]
103 104
104 105 c_obj.extern_activate_choices = [
105 106 ('hg.extern_activate.manual', _('Manual activation of external account')),
106 107 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
107 108
108 109 c_obj.repo_create_choices = [
109 110 ('hg.create.none', _('Disabled')),
110 111 ('hg.create.repository', _('Enabled'))]
111 112
112 113 c_obj.repo_create_on_write_choices = [
113 114 ('hg.create.write_on_repogroup.false', _('Disabled')),
114 115 ('hg.create.write_on_repogroup.true', _('Enabled'))]
115 116
116 117 c_obj.user_group_create_choices = [
117 118 ('hg.usergroup.create.false', _('Disabled')),
118 119 ('hg.usergroup.create.true', _('Enabled'))]
119 120
120 121 c_obj.repo_group_create_choices = [
121 122 ('hg.repogroup.create.false', _('Disabled')),
122 123 ('hg.repogroup.create.true', _('Enabled'))]
123 124
124 125 c_obj.fork_choices = [
125 126 ('hg.fork.none', _('Disabled')),
126 127 ('hg.fork.repository', _('Enabled'))]
127 128
128 129 c_obj.inherit_default_permission_choices = [
129 130 ('hg.inherit_default_perms.false', _('Disabled')),
130 131 ('hg.inherit_default_perms.true', _('Enabled'))]
131 132
132 133 def get_default_perms(self, object_perms, suffix):
133 134 defaults = {}
134 135 for perm in object_perms:
135 136 # perms
136 137 if perm.permission.permission_name.startswith('repository.'):
137 138 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
138 139
139 140 if perm.permission.permission_name.startswith('group.'):
140 141 defaults['default_group_perm' + suffix] = perm.permission.permission_name
141 142
142 143 if perm.permission.permission_name.startswith('usergroup.'):
143 144 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
144 145
145 146 # branch
146 147 if perm.permission.permission_name.startswith('branch.'):
147 148 defaults['default_branch_perm' + suffix] = perm.permission.permission_name
148 149
149 150 # creation of objects
150 151 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
151 152 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
152 153
153 154 elif perm.permission.permission_name.startswith('hg.create.'):
154 155 defaults['default_repo_create' + suffix] = perm.permission.permission_name
155 156
156 157 if perm.permission.permission_name.startswith('hg.fork.'):
157 158 defaults['default_fork_create' + suffix] = perm.permission.permission_name
158 159
159 160 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
160 161 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
161 162
162 163 if perm.permission.permission_name.startswith('hg.repogroup.'):
163 164 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
164 165
165 166 if perm.permission.permission_name.startswith('hg.usergroup.'):
166 167 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
167 168
168 169 # registration and external account activation
169 170 if perm.permission.permission_name.startswith('hg.register.'):
170 171 defaults['default_register' + suffix] = perm.permission.permission_name
171 172
172 173 if perm.permission.permission_name.startswith('hg.password_reset.'):
173 174 defaults['default_password_reset' + suffix] = perm.permission.permission_name
174 175
175 176 if perm.permission.permission_name.startswith('hg.extern_activate.'):
176 177 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
177 178
178 179 return defaults
179 180
180 181 def _make_new_user_perm(self, user, perm_name):
181 182 log.debug('Creating new user permission:%s', perm_name)
182 183 new = UserToPerm()
183 184 new.user = user
184 185 new.permission = Permission.get_by_key(perm_name)
185 186 return new
186 187
187 188 def _make_new_user_group_perm(self, user_group, perm_name):
188 189 log.debug('Creating new user group permission:%s', perm_name)
189 190 new = UserGroupToPerm()
190 191 new.users_group = user_group
191 192 new.permission = Permission.get_by_key(perm_name)
192 193 return new
193 194
194 195 def _keep_perm(self, perm_name, keep_fields):
195 196 def get_pat(field_name):
196 197 return {
197 198 # global perms
198 199 'default_repo_create': 'hg.create.',
199 200 # special case for create repos on write access to group
200 201 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
201 202 'default_repo_group_create': 'hg.repogroup.create.',
202 203 'default_user_group_create': 'hg.usergroup.create.',
203 204 'default_fork_create': 'hg.fork.',
204 205 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
205 206
206 207 # application perms
207 208 'default_register': 'hg.register.',
208 209 'default_password_reset': 'hg.password_reset.',
209 210 'default_extern_activate': 'hg.extern_activate.',
210 211
211 212 # object permissions below
212 213 'default_repo_perm': 'repository.',
213 214 'default_group_perm': 'group.',
214 215 'default_user_group_perm': 'usergroup.',
215 216 # branch
216 217 'default_branch_perm': 'branch.',
217 218
218 219 }[field_name]
219 220 for field in keep_fields:
220 221 pat = get_pat(field)
221 222 if perm_name.startswith(pat):
222 223 return True
223 224 return False
224 225
225 226 def _clear_object_perm(self, object_perms, preserve=None):
226 227 preserve = preserve or []
227 228 _deleted = []
228 229 for perm in object_perms:
229 230 perm_name = perm.permission.permission_name
230 231 if not self._keep_perm(perm_name, keep_fields=preserve):
231 232 _deleted.append(perm_name)
232 233 self.sa.delete(perm)
233 234 return _deleted
234 235
235 236 def _clear_user_perms(self, user_id, preserve=None):
236 237 perms = self.sa.query(UserToPerm)\
237 238 .filter(UserToPerm.user_id == user_id)\
238 239 .all()
239 240 return self._clear_object_perm(perms, preserve=preserve)
240 241
241 242 def _clear_user_group_perms(self, user_group_id, preserve=None):
242 243 perms = self.sa.query(UserGroupToPerm)\
243 244 .filter(UserGroupToPerm.users_group_id == user_group_id)\
244 245 .all()
245 246 return self._clear_object_perm(perms, preserve=preserve)
246 247
247 248 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
248 249 # clear current entries, to make this function idempotent
249 250 # it will fix even if we define more permissions or permissions
250 251 # are somehow missing
251 252 preserve = preserve or []
252 253 _global_perms = self.global_perms.copy()
253 254 if obj_type not in ['user', 'user_group']:
254 255 raise ValueError("obj_type must be on of 'user' or 'user_group'")
255 256 global_perms = len(_global_perms)
256 257 default_user_perms = len(Permission.DEFAULT_USER_PERMISSIONS)
257 258 if global_perms != default_user_perms:
258 259 raise Exception(
259 260 'Inconsistent permissions definition. Got {} vs {}'.format(
260 261 global_perms, default_user_perms))
261 262
262 263 if obj_type == 'user':
263 264 self._clear_user_perms(object.user_id, preserve)
264 265 if obj_type == 'user_group':
265 266 self._clear_user_group_perms(object.users_group_id, preserve)
266 267
267 268 # now kill the keys that we want to preserve from the form.
268 269 for key in preserve:
269 270 del _global_perms[key]
270 271
271 272 for k in _global_perms.copy():
272 273 _global_perms[k] = form_result[k]
273 274
274 275 # at that stage we validate all are passed inside form_result
275 276 for _perm_key, perm_value in _global_perms.items():
276 277 if perm_value is None:
277 278 raise ValueError('Missing permission for %s' % (_perm_key,))
278 279
279 280 if obj_type == 'user':
280 281 p = self._make_new_user_perm(object, perm_value)
281 282 self.sa.add(p)
282 283 if obj_type == 'user_group':
283 284 p = self._make_new_user_group_perm(object, perm_value)
284 285 self.sa.add(p)
285 286
286 287 def _set_new_user_perms(self, user, form_result, preserve=None):
287 288 return self._set_new_object_perms(
288 289 'user', user, form_result, preserve)
289 290
290 291 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
291 292 return self._set_new_object_perms(
292 293 'user_group', user_group, form_result, preserve)
293 294
294 295 def set_new_user_perms(self, user, form_result):
295 296 # calculate what to preserve from what is given in form_result
296 297 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
297 298 return self._set_new_user_perms(user, form_result, preserve)
298 299
299 300 def set_new_user_group_perms(self, user_group, form_result):
300 301 # calculate what to preserve from what is given in form_result
301 302 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
302 303 return self._set_new_user_group_perms(user_group, form_result, preserve)
303 304
304 305 def create_permissions(self):
305 306 """
306 307 Create permissions for whole system
307 308 """
308 309 for p in Permission.PERMS:
309 310 if not Permission.get_by_key(p[0]):
310 311 new_perm = Permission()
311 312 new_perm.permission_name = p[0]
312 313 new_perm.permission_longname = p[0] # translation err with p[1]
313 314 self.sa.add(new_perm)
314 315
315 316 def _create_default_object_permission(self, obj_type, obj, obj_perms,
316 317 force=False):
317 318 if obj_type not in ['user', 'user_group']:
318 319 raise ValueError("obj_type must be on of 'user' or 'user_group'")
319 320
320 321 def _get_group(perm_name):
321 322 return '.'.join(perm_name.split('.')[:1])
322 323
323 324 defined_perms_groups = map(
324 325 _get_group, (x.permission.permission_name for x in obj_perms))
325 326 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
326 327
327 328 if force:
328 329 self._clear_object_perm(obj_perms)
329 330 self.sa.commit()
330 331 defined_perms_groups = []
331 332 # for every default permission that needs to be created, we check if
332 333 # it's group is already defined, if it's not we create default perm
333 334 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
334 335 gr = _get_group(perm_name)
335 336 if gr not in defined_perms_groups:
336 337 log.debug('GR:%s not found, creating permission %s',
337 338 gr, perm_name)
338 339 if obj_type == 'user':
339 340 new_perm = self._make_new_user_perm(obj, perm_name)
340 341 self.sa.add(new_perm)
341 342 if obj_type == 'user_group':
342 343 new_perm = self._make_new_user_group_perm(obj, perm_name)
343 344 self.sa.add(new_perm)
344 345
345 346 def create_default_user_permissions(self, user, force=False):
346 347 """
347 348 Creates only missing default permissions for user, if force is set it
348 349 resets the default permissions for that user
349 350
350 351 :param user:
351 352 :param force:
352 353 """
353 354 user = self._get_user(user)
354 355 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
355 356 return self._create_default_object_permission(
356 357 'user', user, obj_perms, force)
357 358
358 359 def create_default_user_group_permissions(self, user_group, force=False):
359 360 """
360 361 Creates only missing default permissions for user group, if force is
361 362 set it resets the default permissions for that user group
362 363
363 364 :param user_group:
364 365 :param force:
365 366 """
366 367 user_group = self._get_user_group(user_group)
367 368 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
368 369 return self._create_default_object_permission(
369 370 'user_group', user_group, obj_perms, force)
370 371
371 372 def update_application_permissions(self, form_result):
372 373 if 'perm_user_id' in form_result:
373 374 perm_user = User.get(safe_int(form_result['perm_user_id']))
374 375 else:
375 376 # used mostly to do lookup for default user
376 377 perm_user = User.get_by_username(form_result['perm_user_name'])
377 378
378 379 try:
379 380 # stage 1 set anonymous access
380 381 if perm_user.username == User.DEFAULT_USER:
381 382 perm_user.active = str2bool(form_result['anonymous'])
382 383 self.sa.add(perm_user)
383 384
384 385 # stage 2 reset defaults and set them from form data
385 386 self._set_new_user_perms(perm_user, form_result, preserve=[
386 387 'default_repo_perm',
387 388 'default_group_perm',
388 389 'default_user_group_perm',
389 390 'default_branch_perm',
390 391
391 392 'default_repo_group_create',
392 393 'default_user_group_create',
393 394 'default_repo_create_on_write',
394 395 'default_repo_create',
395 396 'default_fork_create',
396 397 'default_inherit_default_permissions',])
397 398
398 399 self.sa.commit()
399 400 except (DatabaseError,):
400 401 log.error(traceback.format_exc())
401 402 self.sa.rollback()
402 403 raise
403 404
404 405 def update_user_permissions(self, form_result):
405 406 if 'perm_user_id' in form_result:
406 407 perm_user = User.get(safe_int(form_result['perm_user_id']))
407 408 else:
408 409 # used mostly to do lookup for default user
409 410 perm_user = User.get_by_username(form_result['perm_user_name'])
410 411 try:
411 412 # stage 2 reset defaults and set them from form data
412 413 self._set_new_user_perms(perm_user, form_result, preserve=[
413 414 'default_repo_perm',
414 415 'default_group_perm',
415 416 'default_user_group_perm',
416 417 'default_branch_perm',
417 418
418 419 'default_register',
419 420 'default_password_reset',
420 421 'default_extern_activate'])
421 422 self.sa.commit()
422 423 except (DatabaseError,):
423 424 log.error(traceback.format_exc())
424 425 self.sa.rollback()
425 426 raise
426 427
427 428 def update_user_group_permissions(self, form_result):
428 429 if 'perm_user_group_id' in form_result:
429 430 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
430 431 else:
431 432 # used mostly to do lookup for default user
432 433 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
433 434 try:
434 435 # stage 2 reset defaults and set them from form data
435 436 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
436 437 'default_repo_perm',
437 438 'default_group_perm',
438 439 'default_user_group_perm',
439 440 'default_branch_perm',
440 441
441 442 'default_register',
442 443 'default_password_reset',
443 444 'default_extern_activate'])
444 445 self.sa.commit()
445 446 except (DatabaseError,):
446 447 log.error(traceback.format_exc())
447 448 self.sa.rollback()
448 449 raise
449 450
450 451 def update_object_permissions(self, form_result):
451 452 if 'perm_user_id' in form_result:
452 453 perm_user = User.get(safe_int(form_result['perm_user_id']))
453 454 else:
454 455 # used mostly to do lookup for default user
455 456 perm_user = User.get_by_username(form_result['perm_user_name'])
456 457 try:
457 458
458 459 # stage 2 reset defaults and set them from form data
459 460 self._set_new_user_perms(perm_user, form_result, preserve=[
460 461 'default_repo_group_create',
461 462 'default_user_group_create',
462 463 'default_repo_create_on_write',
463 464 'default_repo_create',
464 465 'default_fork_create',
465 466 'default_inherit_default_permissions',
466 467 'default_branch_perm',
467 468
468 469 'default_register',
469 470 'default_password_reset',
470 471 'default_extern_activate'])
471 472
472 473 # overwrite default repo permissions
473 474 if form_result['overwrite_default_repo']:
474 475 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
475 476 _def = Permission.get_by_key('repository.' + _def_name)
476 477 for r2p in self.sa.query(UserRepoToPerm)\
477 478 .filter(UserRepoToPerm.user == perm_user)\
478 479 .all():
479 480 # don't reset PRIVATE repositories
480 481 if not r2p.repository.private:
481 482 r2p.permission = _def
482 483 self.sa.add(r2p)
483 484
484 485 # overwrite default repo group permissions
485 486 if form_result['overwrite_default_group']:
486 487 _def_name = form_result['default_group_perm'].split('group.')[-1]
487 488 _def = Permission.get_by_key('group.' + _def_name)
488 489 for g2p in self.sa.query(UserRepoGroupToPerm)\
489 490 .filter(UserRepoGroupToPerm.user == perm_user)\
490 491 .all():
491 492 g2p.permission = _def
492 493 self.sa.add(g2p)
493 494
494 495 # overwrite default user group permissions
495 496 if form_result['overwrite_default_user_group']:
496 497 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
497 498 # user groups
498 499 _def = Permission.get_by_key('usergroup.' + _def_name)
499 500 for g2p in self.sa.query(UserUserGroupToPerm)\
500 501 .filter(UserUserGroupToPerm.user == perm_user)\
501 502 .all():
502 503 g2p.permission = _def
503 504 self.sa.add(g2p)
504 505
505 506 # COMMIT
506 507 self.sa.commit()
507 508 except (DatabaseError,):
508 509 log.exception('Failed to set default object permissions')
509 510 self.sa.rollback()
510 511 raise
511 512
512 513 def update_branch_permissions(self, form_result):
513 514 if 'perm_user_id' in form_result:
514 515 perm_user = User.get(safe_int(form_result['perm_user_id']))
515 516 else:
516 517 # used mostly to do lookup for default user
517 518 perm_user = User.get_by_username(form_result['perm_user_name'])
518 519 try:
519 520
520 521 # stage 2 reset defaults and set them from form data
521 522 self._set_new_user_perms(perm_user, form_result, preserve=[
522 523 'default_repo_perm',
523 524 'default_group_perm',
524 525 'default_user_group_perm',
525 526
526 527 'default_repo_group_create',
527 528 'default_user_group_create',
528 529 'default_repo_create_on_write',
529 530 'default_repo_create',
530 531 'default_fork_create',
531 532 'default_inherit_default_permissions',
532 533
533 534 'default_register',
534 535 'default_password_reset',
535 536 'default_extern_activate'])
536 537
537 538 # overwrite default branch permissions
538 539 if form_result['overwrite_default_branch']:
539 540 _def_name = \
540 541 form_result['default_branch_perm'].split('branch.')[-1]
541 542
542 543 _def = Permission.get_by_key('branch.' + _def_name)
543 544
544 545 user_perms = UserToRepoBranchPermission.query()\
545 546 .join(UserToRepoBranchPermission.user_repo_to_perm)\
546 547 .filter(UserRepoToPerm.user == perm_user).all()
547 548
548 549 for g2p in user_perms:
549 550 g2p.permission = _def
550 551 self.sa.add(g2p)
551 552
552 553 # COMMIT
553 554 self.sa.commit()
554 555 except (DatabaseError,):
555 556 log.exception('Failed to set default branch permissions')
556 557 self.sa.rollback()
557 558 raise
558 559
560 def trigger_permission_flush(self, affected_user_ids):
561 events.trigger(events.UserPermissionsChange(affected_user_ids))
562
563 def flush_user_permission_caches(self, changes, affected_user_ids=None):
564 affected_user_ids = affected_user_ids or []
565
566 for change in changes['added'] + changes['updated'] + changes['deleted']:
567 if change['type'] == 'user':
568 affected_user_ids.append(change['id'])
569 if change['type'] == 'user_group':
570 user_group = UserGroup.get(safe_int(change['id']))
571 if user_group:
572 group_members_ids = [x.user_id for x in user_group.members]
573 affected_user_ids.extend(group_members_ids)
574
575 self.trigger_permission_flush(affected_user_ids)
576
577 return affected_user_ids
General Comments 0
You need to be logged in to leave comments. Login now