##// END OF EJS Templates
permissions: properly flush user cache permissions in more cases of permission changes....
marcink -
r3824:49be3910 stable
parent child Browse files
Show More
@@ -1,2315 +1,2320 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 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
620 621 condition=cache_on)
621 622 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
622 623 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
623 624
624 625 try:
625 626 # check if repo is not empty by any chance, skip quicker if it is.
626 627 _scm = repo.scm_instance()
627 628 if _scm.is_empty():
628 629 return []
629 630 except RepositoryError:
630 631 log.exception("Exception occurred while trying to get repo nodes")
631 632 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
632 633
633 634 try:
634 635 # we need to resolve commit_id to a FULL sha for cache to work correctly.
635 636 # sending 'master' is a pointer that needs to be translated to current commit.
636 637 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
637 638 log.debug(
638 639 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
639 640 'with caching: %s[TTL: %ss]' % (
640 641 repo_id, commit_id, cache_on, cache_seconds or 0))
641 642
642 643 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
643 644 return tree_files
644 645
645 646 except Exception:
646 647 log.exception("Exception occurred while trying to get repo nodes")
647 648 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
648 649
649 650
650 651 @jsonrpc_method()
651 652 def get_repo_refs(request, apiuser, repoid):
652 653 """
653 654 Returns a dictionary of current references. It returns
654 655 bookmarks, branches, closed_branches, and tags for given repository
655 656
656 657 It's possible to specify ret_type to show only `files` or `dirs`.
657 658
658 659 This command can only be run using an |authtoken| with admin rights,
659 660 or users with at least read rights to |repos|.
660 661
661 662 :param apiuser: This is filled automatically from the |authtoken|.
662 663 :type apiuser: AuthUser
663 664 :param repoid: The repository name or repository ID.
664 665 :type repoid: str or int
665 666
666 667 Example output:
667 668
668 669 .. code-block:: bash
669 670
670 671 id : <id_given_in_input>
671 672 "result": {
672 673 "bookmarks": {
673 674 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
674 675 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
675 676 },
676 677 "branches": {
677 678 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 679 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 680 },
680 681 "branches_closed": {},
681 682 "tags": {
682 683 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 684 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
684 685 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
685 686 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
686 687 }
687 688 }
688 689 error: null
689 690 """
690 691
691 692 repo = get_repo_or_error(repoid)
692 693 if not has_superadmin_permission(apiuser):
693 694 _perms = ('repository.admin', 'repository.write', 'repository.read',)
694 695 validate_repo_permissions(apiuser, repoid, repo, _perms)
695 696
696 697 try:
697 698 # check if repo is not empty by any chance, skip quicker if it is.
698 699 vcs_instance = repo.scm_instance()
699 700 refs = vcs_instance.refs()
700 701 return refs
701 702 except Exception:
702 703 log.exception("Exception occurred while trying to get repo refs")
703 704 raise JSONRPCError(
704 705 'failed to get repo: `%s` references' % repo.repo_name
705 706 )
706 707
707 708
708 709 @jsonrpc_method()
709 710 def create_repo(
710 711 request, apiuser, repo_name, repo_type,
711 712 owner=Optional(OAttr('apiuser')),
712 713 description=Optional(''),
713 714 private=Optional(False),
714 715 clone_uri=Optional(None),
715 716 push_uri=Optional(None),
716 717 landing_rev=Optional('rev:tip'),
717 718 enable_statistics=Optional(False),
718 719 enable_locking=Optional(False),
719 720 enable_downloads=Optional(False),
720 721 copy_permissions=Optional(False)):
721 722 """
722 723 Creates a repository.
723 724
724 725 * If the repository name contains "/", repository will be created inside
725 726 a repository group or nested repository groups
726 727
727 728 For example "foo/bar/repo1" will create |repo| called "repo1" inside
728 729 group "foo/bar". You have to have permissions to access and write to
729 730 the last repository group ("bar" in this example)
730 731
731 732 This command can only be run using an |authtoken| with at least
732 733 permissions to create repositories, or write permissions to
733 734 parent repository groups.
734 735
735 736 :param apiuser: This is filled automatically from the |authtoken|.
736 737 :type apiuser: AuthUser
737 738 :param repo_name: Set the repository name.
738 739 :type repo_name: str
739 740 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
740 741 :type repo_type: str
741 742 :param owner: user_id or username
742 743 :type owner: Optional(str)
743 744 :param description: Set the repository description.
744 745 :type description: Optional(str)
745 746 :param private: set repository as private
746 747 :type private: bool
747 748 :param clone_uri: set clone_uri
748 749 :type clone_uri: str
749 750 :param push_uri: set push_uri
750 751 :type push_uri: str
751 752 :param landing_rev: <rev_type>:<rev>
752 753 :type landing_rev: str
753 754 :param enable_locking:
754 755 :type enable_locking: bool
755 756 :param enable_downloads:
756 757 :type enable_downloads: bool
757 758 :param enable_statistics:
758 759 :type enable_statistics: bool
759 760 :param copy_permissions: Copy permission from group in which the
760 761 repository is being created.
761 762 :type copy_permissions: bool
762 763
763 764
764 765 Example output:
765 766
766 767 .. code-block:: bash
767 768
768 769 id : <id_given_in_input>
769 770 result: {
770 771 "msg": "Created new repository `<reponame>`",
771 772 "success": true,
772 773 "task": "<celery task id or None if done sync>"
773 774 }
774 775 error: null
775 776
776 777
777 778 Example error output:
778 779
779 780 .. code-block:: bash
780 781
781 782 id : <id_given_in_input>
782 783 result : null
783 784 error : {
784 785 'failed to create repository `<repo_name>`'
785 786 }
786 787
787 788 """
788 789
789 790 owner = validate_set_owner_permissions(apiuser, owner)
790 791
791 792 description = Optional.extract(description)
792 793 copy_permissions = Optional.extract(copy_permissions)
793 794 clone_uri = Optional.extract(clone_uri)
794 795 push_uri = Optional.extract(push_uri)
795 796 landing_commit_ref = Optional.extract(landing_rev)
796 797
797 798 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
798 799 if isinstance(private, Optional):
799 800 private = defs.get('repo_private') or Optional.extract(private)
800 801 if isinstance(repo_type, Optional):
801 802 repo_type = defs.get('repo_type')
802 803 if isinstance(enable_statistics, Optional):
803 804 enable_statistics = defs.get('repo_enable_statistics')
804 805 if isinstance(enable_locking, Optional):
805 806 enable_locking = defs.get('repo_enable_locking')
806 807 if isinstance(enable_downloads, Optional):
807 808 enable_downloads = defs.get('repo_enable_downloads')
808 809
809 810 schema = repo_schema.RepoSchema().bind(
810 811 repo_type_options=rhodecode.BACKENDS.keys(),
811 812 repo_type=repo_type,
812 813 # user caller
813 814 user=apiuser)
814 815
815 816 try:
816 817 schema_data = schema.deserialize(dict(
817 818 repo_name=repo_name,
818 819 repo_type=repo_type,
819 820 repo_owner=owner.username,
820 821 repo_description=description,
821 822 repo_landing_commit_ref=landing_commit_ref,
822 823 repo_clone_uri=clone_uri,
823 824 repo_push_uri=push_uri,
824 825 repo_private=private,
825 826 repo_copy_permissions=copy_permissions,
826 827 repo_enable_statistics=enable_statistics,
827 828 repo_enable_downloads=enable_downloads,
828 829 repo_enable_locking=enable_locking))
829 830 except validation_schema.Invalid as err:
830 831 raise JSONRPCValidationError(colander_exc=err)
831 832
832 833 try:
833 834 data = {
834 835 'owner': owner,
835 836 'repo_name': schema_data['repo_group']['repo_name_without_group'],
836 837 'repo_name_full': schema_data['repo_name'],
837 838 'repo_group': schema_data['repo_group']['repo_group_id'],
838 839 'repo_type': schema_data['repo_type'],
839 840 'repo_description': schema_data['repo_description'],
840 841 'repo_private': schema_data['repo_private'],
841 842 'clone_uri': schema_data['repo_clone_uri'],
842 843 'push_uri': schema_data['repo_push_uri'],
843 844 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
844 845 'enable_statistics': schema_data['repo_enable_statistics'],
845 846 'enable_locking': schema_data['repo_enable_locking'],
846 847 'enable_downloads': schema_data['repo_enable_downloads'],
847 848 'repo_copy_permissions': schema_data['repo_copy_permissions'],
848 849 }
849 850
850 851 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
851 852 task_id = get_task_id(task)
852 853 # no commit, it's done in RepoModel, or async via celery
853 854 return {
854 855 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
855 856 'success': True, # cannot return the repo data here since fork
856 857 # can be done async
857 858 'task': task_id
858 859 }
859 860 except Exception:
860 861 log.exception(
861 862 u"Exception while trying to create the repository %s",
862 863 schema_data['repo_name'])
863 864 raise JSONRPCError(
864 865 'failed to create repository `%s`' % (schema_data['repo_name'],))
865 866
866 867
867 868 @jsonrpc_method()
868 869 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
869 870 description=Optional('')):
870 871 """
871 872 Adds an extra field to a repository.
872 873
873 874 This command can only be run using an |authtoken| with at least
874 875 write permissions to the |repo|.
875 876
876 877 :param apiuser: This is filled automatically from the |authtoken|.
877 878 :type apiuser: AuthUser
878 879 :param repoid: Set the repository name or repository id.
879 880 :type repoid: str or int
880 881 :param key: Create a unique field key for this repository.
881 882 :type key: str
882 883 :param label:
883 884 :type label: Optional(str)
884 885 :param description:
885 886 :type description: Optional(str)
886 887 """
887 888 repo = get_repo_or_error(repoid)
888 889 if not has_superadmin_permission(apiuser):
889 890 _perms = ('repository.admin',)
890 891 validate_repo_permissions(apiuser, repoid, repo, _perms)
891 892
892 893 label = Optional.extract(label) or key
893 894 description = Optional.extract(description)
894 895
895 896 field = RepositoryField.get_by_key_name(key, repo)
896 897 if field:
897 898 raise JSONRPCError('Field with key '
898 899 '`%s` exists for repo `%s`' % (key, repoid))
899 900
900 901 try:
901 902 RepoModel().add_repo_field(repo, key, field_label=label,
902 903 field_desc=description)
903 904 Session().commit()
904 905 return {
905 906 'msg': "Added new repository field `%s`" % (key,),
906 907 'success': True,
907 908 }
908 909 except Exception:
909 910 log.exception("Exception occurred while trying to add field to repo")
910 911 raise JSONRPCError(
911 912 'failed to create new field for repository `%s`' % (repoid,))
912 913
913 914
914 915 @jsonrpc_method()
915 916 def remove_field_from_repo(request, apiuser, repoid, key):
916 917 """
917 918 Removes an extra field from a repository.
918 919
919 920 This command can only be run using an |authtoken| with at least
920 921 write permissions to the |repo|.
921 922
922 923 :param apiuser: This is filled automatically from the |authtoken|.
923 924 :type apiuser: AuthUser
924 925 :param repoid: Set the repository name or repository ID.
925 926 :type repoid: str or int
926 927 :param key: Set the unique field key for this repository.
927 928 :type key: str
928 929 """
929 930
930 931 repo = get_repo_or_error(repoid)
931 932 if not has_superadmin_permission(apiuser):
932 933 _perms = ('repository.admin',)
933 934 validate_repo_permissions(apiuser, repoid, repo, _perms)
934 935
935 936 field = RepositoryField.get_by_key_name(key, repo)
936 937 if not field:
937 938 raise JSONRPCError('Field with key `%s` does not '
938 939 'exists for repo `%s`' % (key, repoid))
939 940
940 941 try:
941 942 RepoModel().delete_repo_field(repo, field_key=key)
942 943 Session().commit()
943 944 return {
944 945 'msg': "Deleted repository field `%s`" % (key,),
945 946 'success': True,
946 947 }
947 948 except Exception:
948 949 log.exception(
949 950 "Exception occurred while trying to delete field from repo")
950 951 raise JSONRPCError(
951 952 'failed to delete field for repository `%s`' % (repoid,))
952 953
953 954
954 955 @jsonrpc_method()
955 956 def update_repo(
956 957 request, apiuser, repoid, repo_name=Optional(None),
957 958 owner=Optional(OAttr('apiuser')), description=Optional(''),
958 959 private=Optional(False),
959 960 clone_uri=Optional(None), push_uri=Optional(None),
960 961 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
961 962 enable_statistics=Optional(False),
962 963 enable_locking=Optional(False),
963 964 enable_downloads=Optional(False), fields=Optional('')):
964 965 """
965 966 Updates a repository with the given information.
966 967
967 968 This command can only be run using an |authtoken| with at least
968 969 admin permissions to the |repo|.
969 970
970 971 * If the repository name contains "/", repository will be updated
971 972 accordingly with a repository group or nested repository groups
972 973
973 974 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
974 975 called "repo-test" and place it inside group "foo/bar".
975 976 You have to have permissions to access and write to the last repository
976 977 group ("bar" in this example)
977 978
978 979 :param apiuser: This is filled automatically from the |authtoken|.
979 980 :type apiuser: AuthUser
980 981 :param repoid: repository name or repository ID.
981 982 :type repoid: str or int
982 983 :param repo_name: Update the |repo| name, including the
983 984 repository group it's in.
984 985 :type repo_name: str
985 986 :param owner: Set the |repo| owner.
986 987 :type owner: str
987 988 :param fork_of: Set the |repo| as fork of another |repo|.
988 989 :type fork_of: str
989 990 :param description: Update the |repo| description.
990 991 :type description: str
991 992 :param private: Set the |repo| as private. (True | False)
992 993 :type private: bool
993 994 :param clone_uri: Update the |repo| clone URI.
994 995 :type clone_uri: str
995 996 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
996 997 :type landing_rev: str
997 998 :param enable_statistics: Enable statistics on the |repo|, (True | False).
998 999 :type enable_statistics: bool
999 1000 :param enable_locking: Enable |repo| locking.
1000 1001 :type enable_locking: bool
1001 1002 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1002 1003 :type enable_downloads: bool
1003 1004 :param fields: Add extra fields to the |repo|. Use the following
1004 1005 example format: ``field_key=field_val,field_key2=fieldval2``.
1005 1006 Escape ', ' with \,
1006 1007 :type fields: str
1007 1008 """
1008 1009
1009 1010 repo = get_repo_or_error(repoid)
1010 1011
1011 1012 include_secrets = False
1012 1013 if not has_superadmin_permission(apiuser):
1013 1014 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1014 1015 else:
1015 1016 include_secrets = True
1016 1017
1017 1018 updates = dict(
1018 1019 repo_name=repo_name
1019 1020 if not isinstance(repo_name, Optional) else repo.repo_name,
1020 1021
1021 1022 fork_id=fork_of
1022 1023 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1023 1024
1024 1025 user=owner
1025 1026 if not isinstance(owner, Optional) else repo.user.username,
1026 1027
1027 1028 repo_description=description
1028 1029 if not isinstance(description, Optional) else repo.description,
1029 1030
1030 1031 repo_private=private
1031 1032 if not isinstance(private, Optional) else repo.private,
1032 1033
1033 1034 clone_uri=clone_uri
1034 1035 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1035 1036
1036 1037 push_uri=push_uri
1037 1038 if not isinstance(push_uri, Optional) else repo.push_uri,
1038 1039
1039 1040 repo_landing_rev=landing_rev
1040 1041 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1041 1042
1042 1043 repo_enable_statistics=enable_statistics
1043 1044 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1044 1045
1045 1046 repo_enable_locking=enable_locking
1046 1047 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1047 1048
1048 1049 repo_enable_downloads=enable_downloads
1049 1050 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1050 1051
1051 1052 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1052 1053 request.translate, repo=repo)
1053 1054
1054 1055 old_values = repo.get_api_data()
1055 1056 repo_type = repo.repo_type
1056 1057 schema = repo_schema.RepoSchema().bind(
1057 1058 repo_type_options=rhodecode.BACKENDS.keys(),
1058 1059 repo_ref_options=ref_choices,
1059 1060 repo_type=repo_type,
1060 1061 # user caller
1061 1062 user=apiuser,
1062 1063 old_values=old_values)
1063 1064 try:
1064 1065 schema_data = schema.deserialize(dict(
1065 1066 # we save old value, users cannot change type
1066 1067 repo_type=repo_type,
1067 1068
1068 1069 repo_name=updates['repo_name'],
1069 1070 repo_owner=updates['user'],
1070 1071 repo_description=updates['repo_description'],
1071 1072 repo_clone_uri=updates['clone_uri'],
1072 1073 repo_push_uri=updates['push_uri'],
1073 1074 repo_fork_of=updates['fork_id'],
1074 1075 repo_private=updates['repo_private'],
1075 1076 repo_landing_commit_ref=updates['repo_landing_rev'],
1076 1077 repo_enable_statistics=updates['repo_enable_statistics'],
1077 1078 repo_enable_downloads=updates['repo_enable_downloads'],
1078 1079 repo_enable_locking=updates['repo_enable_locking']))
1079 1080 except validation_schema.Invalid as err:
1080 1081 raise JSONRPCValidationError(colander_exc=err)
1081 1082
1082 1083 # save validated data back into the updates dict
1083 1084 validated_updates = dict(
1084 1085 repo_name=schema_data['repo_group']['repo_name_without_group'],
1085 1086 repo_group=schema_data['repo_group']['repo_group_id'],
1086 1087
1087 1088 user=schema_data['repo_owner'],
1088 1089 repo_description=schema_data['repo_description'],
1089 1090 repo_private=schema_data['repo_private'],
1090 1091 clone_uri=schema_data['repo_clone_uri'],
1091 1092 push_uri=schema_data['repo_push_uri'],
1092 1093 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1093 1094 repo_enable_statistics=schema_data['repo_enable_statistics'],
1094 1095 repo_enable_locking=schema_data['repo_enable_locking'],
1095 1096 repo_enable_downloads=schema_data['repo_enable_downloads'],
1096 1097 )
1097 1098
1098 1099 if schema_data['repo_fork_of']:
1099 1100 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1100 1101 validated_updates['fork_id'] = fork_repo.repo_id
1101 1102
1102 1103 # extra fields
1103 1104 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1104 1105 if fields:
1105 1106 validated_updates.update(fields)
1106 1107
1107 1108 try:
1108 1109 RepoModel().update(repo, **validated_updates)
1109 1110 audit_logger.store_api(
1110 1111 'repo.edit', action_data={'old_data': old_values},
1111 1112 user=apiuser, repo=repo)
1112 1113 Session().commit()
1113 1114 return {
1114 1115 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1115 1116 'repository': repo.get_api_data(include_secrets=include_secrets)
1116 1117 }
1117 1118 except Exception:
1118 1119 log.exception(
1119 1120 u"Exception while trying to update the repository %s",
1120 1121 repoid)
1121 1122 raise JSONRPCError('failed to update repo `%s`' % repoid)
1122 1123
1123 1124
1124 1125 @jsonrpc_method()
1125 1126 def fork_repo(request, apiuser, repoid, fork_name,
1126 1127 owner=Optional(OAttr('apiuser')),
1127 1128 description=Optional(''),
1128 1129 private=Optional(False),
1129 1130 clone_uri=Optional(None),
1130 1131 landing_rev=Optional('rev:tip'),
1131 1132 copy_permissions=Optional(False)):
1132 1133 """
1133 1134 Creates a fork of the specified |repo|.
1134 1135
1135 1136 * If the fork_name contains "/", fork will be created inside
1136 1137 a repository group or nested repository groups
1137 1138
1138 1139 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1139 1140 inside group "foo/bar". You have to have permissions to access and
1140 1141 write to the last repository group ("bar" in this example)
1141 1142
1142 1143 This command can only be run using an |authtoken| with minimum
1143 1144 read permissions of the forked repo, create fork permissions for an user.
1144 1145
1145 1146 :param apiuser: This is filled automatically from the |authtoken|.
1146 1147 :type apiuser: AuthUser
1147 1148 :param repoid: Set repository name or repository ID.
1148 1149 :type repoid: str or int
1149 1150 :param fork_name: Set the fork name, including it's repository group membership.
1150 1151 :type fork_name: str
1151 1152 :param owner: Set the fork owner.
1152 1153 :type owner: str
1153 1154 :param description: Set the fork description.
1154 1155 :type description: str
1155 1156 :param copy_permissions: Copy permissions from parent |repo|. The
1156 1157 default is False.
1157 1158 :type copy_permissions: bool
1158 1159 :param private: Make the fork private. The default is False.
1159 1160 :type private: bool
1160 1161 :param landing_rev: Set the landing revision. The default is tip.
1161 1162
1162 1163 Example output:
1163 1164
1164 1165 .. code-block:: bash
1165 1166
1166 1167 id : <id_for_response>
1167 1168 api_key : "<api_key>"
1168 1169 args: {
1169 1170 "repoid" : "<reponame or repo_id>",
1170 1171 "fork_name": "<forkname>",
1171 1172 "owner": "<username or user_id = Optional(=apiuser)>",
1172 1173 "description": "<description>",
1173 1174 "copy_permissions": "<bool>",
1174 1175 "private": "<bool>",
1175 1176 "landing_rev": "<landing_rev>"
1176 1177 }
1177 1178
1178 1179 Example error output:
1179 1180
1180 1181 .. code-block:: bash
1181 1182
1182 1183 id : <id_given_in_input>
1183 1184 result: {
1184 1185 "msg": "Created fork of `<reponame>` as `<forkname>`",
1185 1186 "success": true,
1186 1187 "task": "<celery task id or None if done sync>"
1187 1188 }
1188 1189 error: null
1189 1190
1190 1191 """
1191 1192
1192 1193 repo = get_repo_or_error(repoid)
1193 1194 repo_name = repo.repo_name
1194 1195
1195 1196 if not has_superadmin_permission(apiuser):
1196 1197 # check if we have at least read permission for
1197 1198 # this repo that we fork !
1198 1199 _perms = (
1199 1200 'repository.admin', 'repository.write', 'repository.read')
1200 1201 validate_repo_permissions(apiuser, repoid, repo, _perms)
1201 1202
1202 1203 # check if the regular user has at least fork permissions as well
1203 1204 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1204 1205 raise JSONRPCForbidden()
1205 1206
1206 1207 # check if user can set owner parameter
1207 1208 owner = validate_set_owner_permissions(apiuser, owner)
1208 1209
1209 1210 description = Optional.extract(description)
1210 1211 copy_permissions = Optional.extract(copy_permissions)
1211 1212 clone_uri = Optional.extract(clone_uri)
1212 1213 landing_commit_ref = Optional.extract(landing_rev)
1213 1214 private = Optional.extract(private)
1214 1215
1215 1216 schema = repo_schema.RepoSchema().bind(
1216 1217 repo_type_options=rhodecode.BACKENDS.keys(),
1217 1218 repo_type=repo.repo_type,
1218 1219 # user caller
1219 1220 user=apiuser)
1220 1221
1221 1222 try:
1222 1223 schema_data = schema.deserialize(dict(
1223 1224 repo_name=fork_name,
1224 1225 repo_type=repo.repo_type,
1225 1226 repo_owner=owner.username,
1226 1227 repo_description=description,
1227 1228 repo_landing_commit_ref=landing_commit_ref,
1228 1229 repo_clone_uri=clone_uri,
1229 1230 repo_private=private,
1230 1231 repo_copy_permissions=copy_permissions))
1231 1232 except validation_schema.Invalid as err:
1232 1233 raise JSONRPCValidationError(colander_exc=err)
1233 1234
1234 1235 try:
1235 1236 data = {
1236 1237 'fork_parent_id': repo.repo_id,
1237 1238
1238 1239 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1239 1240 'repo_name_full': schema_data['repo_name'],
1240 1241 'repo_group': schema_data['repo_group']['repo_group_id'],
1241 1242 'repo_type': schema_data['repo_type'],
1242 1243 'description': schema_data['repo_description'],
1243 1244 'private': schema_data['repo_private'],
1244 1245 'copy_permissions': schema_data['repo_copy_permissions'],
1245 1246 'landing_rev': schema_data['repo_landing_commit_ref'],
1246 1247 }
1247 1248
1248 1249 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1249 1250 # no commit, it's done in RepoModel, or async via celery
1250 1251 task_id = get_task_id(task)
1251 1252
1252 1253 return {
1253 1254 'msg': 'Created fork of `%s` as `%s`' % (
1254 1255 repo.repo_name, schema_data['repo_name']),
1255 1256 'success': True, # cannot return the repo data here since fork
1256 1257 # can be done async
1257 1258 'task': task_id
1258 1259 }
1259 1260 except Exception:
1260 1261 log.exception(
1261 1262 u"Exception while trying to create fork %s",
1262 1263 schema_data['repo_name'])
1263 1264 raise JSONRPCError(
1264 1265 'failed to fork repository `%s` as `%s`' % (
1265 1266 repo_name, schema_data['repo_name']))
1266 1267
1267 1268
1268 1269 @jsonrpc_method()
1269 1270 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1270 1271 """
1271 1272 Deletes a repository.
1272 1273
1273 1274 * When the `forks` parameter is set it's possible to detach or delete
1274 1275 forks of deleted repository.
1275 1276
1276 1277 This command can only be run using an |authtoken| with admin
1277 1278 permissions on the |repo|.
1278 1279
1279 1280 :param apiuser: This is filled automatically from the |authtoken|.
1280 1281 :type apiuser: AuthUser
1281 1282 :param repoid: Set the repository name or repository ID.
1282 1283 :type repoid: str or int
1283 1284 :param forks: Set to `detach` or `delete` forks from the |repo|.
1284 1285 :type forks: Optional(str)
1285 1286
1286 1287 Example error output:
1287 1288
1288 1289 .. code-block:: bash
1289 1290
1290 1291 id : <id_given_in_input>
1291 1292 result: {
1292 1293 "msg": "Deleted repository `<reponame>`",
1293 1294 "success": true
1294 1295 }
1295 1296 error: null
1296 1297 """
1297 1298
1298 1299 repo = get_repo_or_error(repoid)
1299 1300 repo_name = repo.repo_name
1300 1301 if not has_superadmin_permission(apiuser):
1301 1302 _perms = ('repository.admin',)
1302 1303 validate_repo_permissions(apiuser, repoid, repo, _perms)
1303 1304
1304 1305 try:
1305 1306 handle_forks = Optional.extract(forks)
1306 1307 _forks_msg = ''
1307 1308 _forks = [f for f in repo.forks]
1308 1309 if handle_forks == 'detach':
1309 1310 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1310 1311 elif handle_forks == 'delete':
1311 1312 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1312 1313 elif _forks:
1313 1314 raise JSONRPCError(
1314 1315 'Cannot delete `%s` it still contains attached forks' %
1315 1316 (repo.repo_name,)
1316 1317 )
1317 1318 old_data = repo.get_api_data()
1318 1319 RepoModel().delete(repo, forks=forks)
1319 1320
1320 1321 repo = audit_logger.RepoWrap(repo_id=None,
1321 1322 repo_name=repo.repo_name)
1322 1323
1323 1324 audit_logger.store_api(
1324 1325 'repo.delete', action_data={'old_data': old_data},
1325 1326 user=apiuser, repo=repo)
1326 1327
1327 1328 ScmModel().mark_for_invalidation(repo_name, delete=True)
1328 1329 Session().commit()
1329 1330 return {
1330 1331 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1331 1332 'success': True
1332 1333 }
1333 1334 except Exception:
1334 1335 log.exception("Exception occurred while trying to delete repo")
1335 1336 raise JSONRPCError(
1336 1337 'failed to delete repository `%s`' % (repo_name,)
1337 1338 )
1338 1339
1339 1340
1340 1341 #TODO: marcink, change name ?
1341 1342 @jsonrpc_method()
1342 1343 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1343 1344 """
1344 1345 Invalidates the cache for the specified repository.
1345 1346
1346 1347 This command can only be run using an |authtoken| with admin rights to
1347 1348 the specified repository.
1348 1349
1349 1350 This command takes the following options:
1350 1351
1351 1352 :param apiuser: This is filled automatically from |authtoken|.
1352 1353 :type apiuser: AuthUser
1353 1354 :param repoid: Sets the repository name or repository ID.
1354 1355 :type repoid: str or int
1355 1356 :param delete_keys: This deletes the invalidated keys instead of
1356 1357 just flagging them.
1357 1358 :type delete_keys: Optional(``True`` | ``False``)
1358 1359
1359 1360 Example output:
1360 1361
1361 1362 .. code-block:: bash
1362 1363
1363 1364 id : <id_given_in_input>
1364 1365 result : {
1365 1366 'msg': Cache for repository `<repository name>` was invalidated,
1366 1367 'repository': <repository name>
1367 1368 }
1368 1369 error : null
1369 1370
1370 1371 Example error output:
1371 1372
1372 1373 .. code-block:: bash
1373 1374
1374 1375 id : <id_given_in_input>
1375 1376 result : null
1376 1377 error : {
1377 1378 'Error occurred during cache invalidation action'
1378 1379 }
1379 1380
1380 1381 """
1381 1382
1382 1383 repo = get_repo_or_error(repoid)
1383 1384 if not has_superadmin_permission(apiuser):
1384 1385 _perms = ('repository.admin', 'repository.write',)
1385 1386 validate_repo_permissions(apiuser, repoid, repo, _perms)
1386 1387
1387 1388 delete = Optional.extract(delete_keys)
1388 1389 try:
1389 1390 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1390 1391 return {
1391 1392 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1392 1393 'repository': repo.repo_name
1393 1394 }
1394 1395 except Exception:
1395 1396 log.exception(
1396 1397 "Exception occurred while trying to invalidate repo cache")
1397 1398 raise JSONRPCError(
1398 1399 'Error occurred during cache invalidation action'
1399 1400 )
1400 1401
1401 1402
1402 1403 #TODO: marcink, change name ?
1403 1404 @jsonrpc_method()
1404 1405 def lock(request, apiuser, repoid, locked=Optional(None),
1405 1406 userid=Optional(OAttr('apiuser'))):
1406 1407 """
1407 1408 Sets the lock state of the specified |repo| by the given user.
1408 1409 From more information, see :ref:`repo-locking`.
1409 1410
1410 1411 * If the ``userid`` option is not set, the repository is locked to the
1411 1412 user who called the method.
1412 1413 * If the ``locked`` parameter is not set, the current lock state of the
1413 1414 repository is displayed.
1414 1415
1415 1416 This command can only be run using an |authtoken| with admin rights to
1416 1417 the specified repository.
1417 1418
1418 1419 This command takes the following options:
1419 1420
1420 1421 :param apiuser: This is filled automatically from the |authtoken|.
1421 1422 :type apiuser: AuthUser
1422 1423 :param repoid: Sets the repository name or repository ID.
1423 1424 :type repoid: str or int
1424 1425 :param locked: Sets the lock state.
1425 1426 :type locked: Optional(``True`` | ``False``)
1426 1427 :param userid: Set the repository lock to this user.
1427 1428 :type userid: Optional(str or int)
1428 1429
1429 1430 Example error output:
1430 1431
1431 1432 .. code-block:: bash
1432 1433
1433 1434 id : <id_given_in_input>
1434 1435 result : {
1435 1436 'repo': '<reponame>',
1436 1437 'locked': <bool: lock state>,
1437 1438 'locked_since': <int: lock timestamp>,
1438 1439 'locked_by': <username of person who made the lock>,
1439 1440 'lock_reason': <str: reason for locking>,
1440 1441 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1441 1442 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1442 1443 or
1443 1444 'msg': 'Repo `<repository name>` not locked.'
1444 1445 or
1445 1446 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1446 1447 }
1447 1448 error : null
1448 1449
1449 1450 Example error output:
1450 1451
1451 1452 .. code-block:: bash
1452 1453
1453 1454 id : <id_given_in_input>
1454 1455 result : null
1455 1456 error : {
1456 1457 'Error occurred locking repository `<reponame>`'
1457 1458 }
1458 1459 """
1459 1460
1460 1461 repo = get_repo_or_error(repoid)
1461 1462 if not has_superadmin_permission(apiuser):
1462 1463 # check if we have at least write permission for this repo !
1463 1464 _perms = ('repository.admin', 'repository.write',)
1464 1465 validate_repo_permissions(apiuser, repoid, repo, _perms)
1465 1466
1466 1467 # make sure normal user does not pass someone else userid,
1467 1468 # he is not allowed to do that
1468 1469 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1469 1470 raise JSONRPCError('userid is not the same as your user')
1470 1471
1471 1472 if isinstance(userid, Optional):
1472 1473 userid = apiuser.user_id
1473 1474
1474 1475 user = get_user_or_error(userid)
1475 1476
1476 1477 if isinstance(locked, Optional):
1477 1478 lockobj = repo.locked
1478 1479
1479 1480 if lockobj[0] is None:
1480 1481 _d = {
1481 1482 'repo': repo.repo_name,
1482 1483 'locked': False,
1483 1484 'locked_since': None,
1484 1485 'locked_by': None,
1485 1486 'lock_reason': None,
1486 1487 'lock_state_changed': False,
1487 1488 'msg': 'Repo `%s` not locked.' % repo.repo_name
1488 1489 }
1489 1490 return _d
1490 1491 else:
1491 1492 _user_id, _time, _reason = lockobj
1492 1493 lock_user = get_user_or_error(userid)
1493 1494 _d = {
1494 1495 'repo': repo.repo_name,
1495 1496 'locked': True,
1496 1497 'locked_since': _time,
1497 1498 'locked_by': lock_user.username,
1498 1499 'lock_reason': _reason,
1499 1500 'lock_state_changed': False,
1500 1501 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1501 1502 % (repo.repo_name, lock_user.username,
1502 1503 json.dumps(time_to_datetime(_time))))
1503 1504 }
1504 1505 return _d
1505 1506
1506 1507 # force locked state through a flag
1507 1508 else:
1508 1509 locked = str2bool(locked)
1509 1510 lock_reason = Repository.LOCK_API
1510 1511 try:
1511 1512 if locked:
1512 1513 lock_time = time.time()
1513 1514 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1514 1515 else:
1515 1516 lock_time = None
1516 1517 Repository.unlock(repo)
1517 1518 _d = {
1518 1519 'repo': repo.repo_name,
1519 1520 'locked': locked,
1520 1521 'locked_since': lock_time,
1521 1522 'locked_by': user.username,
1522 1523 'lock_reason': lock_reason,
1523 1524 'lock_state_changed': True,
1524 1525 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1525 1526 % (user.username, repo.repo_name, locked))
1526 1527 }
1527 1528 return _d
1528 1529 except Exception:
1529 1530 log.exception(
1530 1531 "Exception occurred while trying to lock repository")
1531 1532 raise JSONRPCError(
1532 1533 'Error occurred locking repository `%s`' % repo.repo_name
1533 1534 )
1534 1535
1535 1536
1536 1537 @jsonrpc_method()
1537 1538 def comment_commit(
1538 1539 request, apiuser, repoid, commit_id, message, status=Optional(None),
1539 1540 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1540 1541 resolves_comment_id=Optional(None),
1541 1542 userid=Optional(OAttr('apiuser'))):
1542 1543 """
1543 1544 Set a commit comment, and optionally change the status of the commit.
1544 1545
1545 1546 :param apiuser: This is filled automatically from the |authtoken|.
1546 1547 :type apiuser: AuthUser
1547 1548 :param repoid: Set the repository name or repository ID.
1548 1549 :type repoid: str or int
1549 1550 :param commit_id: Specify the commit_id for which to set a comment.
1550 1551 :type commit_id: str
1551 1552 :param message: The comment text.
1552 1553 :type message: str
1553 1554 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1554 1555 'approved', 'rejected', 'under_review'
1555 1556 :type status: str
1556 1557 :param comment_type: Comment type, one of: 'note', 'todo'
1557 1558 :type comment_type: Optional(str), default: 'note'
1558 1559 :param userid: Set the user name of the comment creator.
1559 1560 :type userid: Optional(str or int)
1560 1561
1561 1562 Example error output:
1562 1563
1563 1564 .. code-block:: bash
1564 1565
1565 1566 {
1566 1567 "id" : <id_given_in_input>,
1567 1568 "result" : {
1568 1569 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1569 1570 "status_change": null or <status>,
1570 1571 "success": true
1571 1572 },
1572 1573 "error" : null
1573 1574 }
1574 1575
1575 1576 """
1576 1577 repo = get_repo_or_error(repoid)
1577 1578 if not has_superadmin_permission(apiuser):
1578 1579 _perms = ('repository.read', 'repository.write', 'repository.admin')
1579 1580 validate_repo_permissions(apiuser, repoid, repo, _perms)
1580 1581
1581 1582 try:
1582 1583 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1583 1584 except Exception as e:
1584 1585 log.exception('Failed to fetch commit')
1585 1586 raise JSONRPCError(safe_str(e))
1586 1587
1587 1588 if isinstance(userid, Optional):
1588 1589 userid = apiuser.user_id
1589 1590
1590 1591 user = get_user_or_error(userid)
1591 1592 status = Optional.extract(status)
1592 1593 comment_type = Optional.extract(comment_type)
1593 1594 resolves_comment_id = Optional.extract(resolves_comment_id)
1594 1595
1595 1596 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1596 1597 if status and status not in allowed_statuses:
1597 1598 raise JSONRPCError('Bad status, must be on '
1598 1599 'of %s got %s' % (allowed_statuses, status,))
1599 1600
1600 1601 if resolves_comment_id:
1601 1602 comment = ChangesetComment.get(resolves_comment_id)
1602 1603 if not comment:
1603 1604 raise JSONRPCError(
1604 1605 'Invalid resolves_comment_id `%s` for this commit.'
1605 1606 % resolves_comment_id)
1606 1607 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1607 1608 raise JSONRPCError(
1608 1609 'Comment `%s` is wrong type for setting status to resolved.'
1609 1610 % resolves_comment_id)
1610 1611
1611 1612 try:
1612 1613 rc_config = SettingsModel().get_all_settings()
1613 1614 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1614 1615 status_change_label = ChangesetStatus.get_status_lbl(status)
1615 1616 comment = CommentsModel().create(
1616 1617 message, repo, user, commit_id=commit_id,
1617 1618 status_change=status_change_label,
1618 1619 status_change_type=status,
1619 1620 renderer=renderer,
1620 1621 comment_type=comment_type,
1621 1622 resolves_comment_id=resolves_comment_id,
1622 1623 auth_user=apiuser
1623 1624 )
1624 1625 if status:
1625 1626 # also do a status change
1626 1627 try:
1627 1628 ChangesetStatusModel().set_status(
1628 1629 repo, status, user, comment, revision=commit_id,
1629 1630 dont_allow_on_closed_pull_request=True
1630 1631 )
1631 1632 except StatusChangeOnClosedPullRequestError:
1632 1633 log.exception(
1633 1634 "Exception occurred while trying to change repo commit status")
1634 1635 msg = ('Changing status on a changeset associated with '
1635 1636 'a closed pull request is not allowed')
1636 1637 raise JSONRPCError(msg)
1637 1638
1638 1639 Session().commit()
1639 1640 return {
1640 1641 'msg': (
1641 1642 'Commented on commit `%s` for repository `%s`' % (
1642 1643 comment.revision, repo.repo_name)),
1643 1644 'status_change': status,
1644 1645 'success': True,
1645 1646 }
1646 1647 except JSONRPCError:
1647 1648 # catch any inside errors, and re-raise them to prevent from
1648 1649 # below global catch to silence them
1649 1650 raise
1650 1651 except Exception:
1651 1652 log.exception("Exception occurred while trying to comment on commit")
1652 1653 raise JSONRPCError(
1653 1654 'failed to set comment on repository `%s`' % (repo.repo_name,)
1654 1655 )
1655 1656
1656 1657
1657 1658 @jsonrpc_method()
1658 1659 def get_repo_comments(request, apiuser, repoid,
1659 1660 commit_id=Optional(None), comment_type=Optional(None),
1660 1661 userid=Optional(None)):
1661 1662 """
1662 1663 Get all comments for a repository
1663 1664
1664 1665 :param apiuser: This is filled automatically from the |authtoken|.
1665 1666 :type apiuser: AuthUser
1666 1667 :param repoid: Set the repository name or repository ID.
1667 1668 :type repoid: str or int
1668 1669 :param commit_id: Optionally filter the comments by the commit_id
1669 1670 :type commit_id: Optional(str), default: None
1670 1671 :param comment_type: Optionally filter the comments by the comment_type
1671 1672 one of: 'note', 'todo'
1672 1673 :type comment_type: Optional(str), default: None
1673 1674 :param userid: Optionally filter the comments by the author of comment
1674 1675 :type userid: Optional(str or int), Default: None
1675 1676
1676 1677 Example error output:
1677 1678
1678 1679 .. code-block:: bash
1679 1680
1680 1681 {
1681 1682 "id" : <id_given_in_input>,
1682 1683 "result" : [
1683 1684 {
1684 1685 "comment_author": <USER_DETAILS>,
1685 1686 "comment_created_on": "2017-02-01T14:38:16.309",
1686 1687 "comment_f_path": "file.txt",
1687 1688 "comment_id": 282,
1688 1689 "comment_lineno": "n1",
1689 1690 "comment_resolved_by": null,
1690 1691 "comment_status": [],
1691 1692 "comment_text": "This file needs a header",
1692 1693 "comment_type": "todo"
1693 1694 }
1694 1695 ],
1695 1696 "error" : null
1696 1697 }
1697 1698
1698 1699 """
1699 1700 repo = get_repo_or_error(repoid)
1700 1701 if not has_superadmin_permission(apiuser):
1701 1702 _perms = ('repository.read', 'repository.write', 'repository.admin')
1702 1703 validate_repo_permissions(apiuser, repoid, repo, _perms)
1703 1704
1704 1705 commit_id = Optional.extract(commit_id)
1705 1706
1706 1707 userid = Optional.extract(userid)
1707 1708 if userid:
1708 1709 user = get_user_or_error(userid)
1709 1710 else:
1710 1711 user = None
1711 1712
1712 1713 comment_type = Optional.extract(comment_type)
1713 1714 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1714 1715 raise JSONRPCError(
1715 1716 'comment_type must be one of `{}` got {}'.format(
1716 1717 ChangesetComment.COMMENT_TYPES, comment_type)
1717 1718 )
1718 1719
1719 1720 comments = CommentsModel().get_repository_comments(
1720 1721 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1721 1722 return comments
1722 1723
1723 1724
1724 1725 @jsonrpc_method()
1725 1726 def grant_user_permission(request, apiuser, repoid, userid, perm):
1726 1727 """
1727 1728 Grant permissions for the specified user on the given repository,
1728 1729 or update existing permissions if found.
1729 1730
1730 1731 This command can only be run using an |authtoken| with admin
1731 1732 permissions on the |repo|.
1732 1733
1733 1734 :param apiuser: This is filled automatically from the |authtoken|.
1734 1735 :type apiuser: AuthUser
1735 1736 :param repoid: Set the repository name or repository ID.
1736 1737 :type repoid: str or int
1737 1738 :param userid: Set the user name.
1738 1739 :type userid: str
1739 1740 :param perm: Set the user permissions, using the following format
1740 1741 ``(repository.(none|read|write|admin))``
1741 1742 :type perm: str
1742 1743
1743 1744 Example output:
1744 1745
1745 1746 .. code-block:: bash
1746 1747
1747 1748 id : <id_given_in_input>
1748 1749 result: {
1749 1750 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1750 1751 "success": true
1751 1752 }
1752 1753 error: null
1753 1754 """
1754 1755
1755 1756 repo = get_repo_or_error(repoid)
1756 1757 user = get_user_or_error(userid)
1757 1758 perm = get_perm_or_error(perm)
1758 1759 if not has_superadmin_permission(apiuser):
1759 1760 _perms = ('repository.admin',)
1760 1761 validate_repo_permissions(apiuser, repoid, repo, _perms)
1761 1762
1762 1763 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1763 1764 try:
1764 1765 changes = RepoModel().update_permissions(
1765 1766 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1766 1767
1767 1768 action_data = {
1768 1769 'added': changes['added'],
1769 1770 'updated': changes['updated'],
1770 1771 'deleted': changes['deleted'],
1771 1772 }
1772 1773 audit_logger.store_api(
1773 1774 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1775 Session().commit()
1776 PermissionModel().flush_user_permission_caches(changes)
1774 1777
1775 Session().commit()
1776 1778 return {
1777 1779 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1778 1780 perm.permission_name, user.username, repo.repo_name
1779 1781 ),
1780 1782 'success': True
1781 1783 }
1782 1784 except Exception:
1783 1785 log.exception("Exception occurred while trying edit permissions for repo")
1784 1786 raise JSONRPCError(
1785 1787 'failed to edit permission for user: `%s` in repo: `%s`' % (
1786 1788 userid, repoid
1787 1789 )
1788 1790 )
1789 1791
1790 1792
1791 1793 @jsonrpc_method()
1792 1794 def revoke_user_permission(request, apiuser, repoid, userid):
1793 1795 """
1794 1796 Revoke permission for a user on the specified repository.
1795 1797
1796 1798 This command can only be run using an |authtoken| with admin
1797 1799 permissions on the |repo|.
1798 1800
1799 1801 :param apiuser: This is filled automatically from the |authtoken|.
1800 1802 :type apiuser: AuthUser
1801 1803 :param repoid: Set the repository name or repository ID.
1802 1804 :type repoid: str or int
1803 1805 :param userid: Set the user name of revoked user.
1804 1806 :type userid: str or int
1805 1807
1806 1808 Example error output:
1807 1809
1808 1810 .. code-block:: bash
1809 1811
1810 1812 id : <id_given_in_input>
1811 1813 result: {
1812 1814 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1813 1815 "success": true
1814 1816 }
1815 1817 error: null
1816 1818 """
1817 1819
1818 1820 repo = get_repo_or_error(repoid)
1819 1821 user = get_user_or_error(userid)
1820 1822 if not has_superadmin_permission(apiuser):
1821 1823 _perms = ('repository.admin',)
1822 1824 validate_repo_permissions(apiuser, repoid, repo, _perms)
1823 1825
1824 1826 perm_deletions = [[user.user_id, None, "user"]]
1825 1827 try:
1826 1828 changes = RepoModel().update_permissions(
1827 1829 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1828 1830
1829 1831 action_data = {
1830 1832 'added': changes['added'],
1831 1833 'updated': changes['updated'],
1832 1834 'deleted': changes['deleted'],
1833 1835 }
1834 1836 audit_logger.store_api(
1835 1837 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1838 Session().commit()
1839 PermissionModel().flush_user_permission_caches(changes)
1836 1840
1837 Session().commit()
1838 1841 return {
1839 1842 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1840 1843 user.username, repo.repo_name
1841 1844 ),
1842 1845 'success': True
1843 1846 }
1844 1847 except Exception:
1845 1848 log.exception("Exception occurred while trying revoke permissions to repo")
1846 1849 raise JSONRPCError(
1847 1850 'failed to edit permission for user: `%s` in repo: `%s`' % (
1848 1851 userid, repoid
1849 1852 )
1850 1853 )
1851 1854
1852 1855
1853 1856 @jsonrpc_method()
1854 1857 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1855 1858 """
1856 1859 Grant permission for a user group on the specified repository,
1857 1860 or update existing permissions.
1858 1861
1859 1862 This command can only be run using an |authtoken| with admin
1860 1863 permissions on the |repo|.
1861 1864
1862 1865 :param apiuser: This is filled automatically from the |authtoken|.
1863 1866 :type apiuser: AuthUser
1864 1867 :param repoid: Set the repository name or repository ID.
1865 1868 :type repoid: str or int
1866 1869 :param usergroupid: Specify the ID of the user group.
1867 1870 :type usergroupid: str or int
1868 1871 :param perm: Set the user group permissions using the following
1869 1872 format: (repository.(none|read|write|admin))
1870 1873 :type perm: str
1871 1874
1872 1875 Example output:
1873 1876
1874 1877 .. code-block:: bash
1875 1878
1876 1879 id : <id_given_in_input>
1877 1880 result : {
1878 1881 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1879 1882 "success": true
1880 1883
1881 1884 }
1882 1885 error : null
1883 1886
1884 1887 Example error output:
1885 1888
1886 1889 .. code-block:: bash
1887 1890
1888 1891 id : <id_given_in_input>
1889 1892 result : null
1890 1893 error : {
1891 1894 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1892 1895 }
1893 1896
1894 1897 """
1895 1898
1896 1899 repo = get_repo_or_error(repoid)
1897 1900 perm = get_perm_or_error(perm)
1898 1901 if not has_superadmin_permission(apiuser):
1899 1902 _perms = ('repository.admin',)
1900 1903 validate_repo_permissions(apiuser, repoid, repo, _perms)
1901 1904
1902 1905 user_group = get_user_group_or_error(usergroupid)
1903 1906 if not has_superadmin_permission(apiuser):
1904 1907 # check if we have at least read permission for this user group !
1905 1908 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1906 1909 if not HasUserGroupPermissionAnyApi(*_perms)(
1907 1910 user=apiuser, user_group_name=user_group.users_group_name):
1908 1911 raise JSONRPCError(
1909 1912 'user group `%s` does not exist' % (usergroupid,))
1910 1913
1911 1914 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1912 1915 try:
1913 1916 changes = RepoModel().update_permissions(
1914 1917 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1915 1918 action_data = {
1916 1919 'added': changes['added'],
1917 1920 'updated': changes['updated'],
1918 1921 'deleted': changes['deleted'],
1919 1922 }
1920 1923 audit_logger.store_api(
1921 1924 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1925 Session().commit()
1926 PermissionModel().flush_user_permission_caches(changes)
1922 1927
1923 Session().commit()
1924 1928 return {
1925 1929 'msg': 'Granted perm: `%s` for user group: `%s` in '
1926 1930 'repo: `%s`' % (
1927 1931 perm.permission_name, user_group.users_group_name,
1928 1932 repo.repo_name
1929 1933 ),
1930 1934 'success': True
1931 1935 }
1932 1936 except Exception:
1933 1937 log.exception(
1934 1938 "Exception occurred while trying change permission on repo")
1935 1939 raise JSONRPCError(
1936 1940 'failed to edit permission for user group: `%s` in '
1937 1941 'repo: `%s`' % (
1938 1942 usergroupid, repo.repo_name
1939 1943 )
1940 1944 )
1941 1945
1942 1946
1943 1947 @jsonrpc_method()
1944 1948 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1945 1949 """
1946 1950 Revoke the permissions of a user group on a given repository.
1947 1951
1948 1952 This command can only be run using an |authtoken| with admin
1949 1953 permissions on the |repo|.
1950 1954
1951 1955 :param apiuser: This is filled automatically from the |authtoken|.
1952 1956 :type apiuser: AuthUser
1953 1957 :param repoid: Set the repository name or repository ID.
1954 1958 :type repoid: str or int
1955 1959 :param usergroupid: Specify the user group ID.
1956 1960 :type usergroupid: str or int
1957 1961
1958 1962 Example output:
1959 1963
1960 1964 .. code-block:: bash
1961 1965
1962 1966 id : <id_given_in_input>
1963 1967 result: {
1964 1968 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1965 1969 "success": true
1966 1970 }
1967 1971 error: null
1968 1972 """
1969 1973
1970 1974 repo = get_repo_or_error(repoid)
1971 1975 if not has_superadmin_permission(apiuser):
1972 1976 _perms = ('repository.admin',)
1973 1977 validate_repo_permissions(apiuser, repoid, repo, _perms)
1974 1978
1975 1979 user_group = get_user_group_or_error(usergroupid)
1976 1980 if not has_superadmin_permission(apiuser):
1977 1981 # check if we have at least read permission for this user group !
1978 1982 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1979 1983 if not HasUserGroupPermissionAnyApi(*_perms)(
1980 1984 user=apiuser, user_group_name=user_group.users_group_name):
1981 1985 raise JSONRPCError(
1982 1986 'user group `%s` does not exist' % (usergroupid,))
1983 1987
1984 1988 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1985 1989 try:
1986 1990 changes = RepoModel().update_permissions(
1987 1991 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1988 1992 action_data = {
1989 1993 'added': changes['added'],
1990 1994 'updated': changes['updated'],
1991 1995 'deleted': changes['deleted'],
1992 1996 }
1993 1997 audit_logger.store_api(
1994 1998 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1999 Session().commit()
2000 PermissionModel().flush_user_permission_caches(changes)
1995 2001
1996 Session().commit()
1997 2002 return {
1998 2003 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1999 2004 user_group.users_group_name, repo.repo_name
2000 2005 ),
2001 2006 'success': True
2002 2007 }
2003 2008 except Exception:
2004 2009 log.exception("Exception occurred while trying revoke "
2005 2010 "user group permission on repo")
2006 2011 raise JSONRPCError(
2007 2012 'failed to edit permission for user group: `%s` in '
2008 2013 'repo: `%s`' % (
2009 2014 user_group.users_group_name, repo.repo_name
2010 2015 )
2011 2016 )
2012 2017
2013 2018
2014 2019 @jsonrpc_method()
2015 2020 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2016 2021 """
2017 2022 Triggers a pull on the given repository from a remote location. You
2018 2023 can use this to keep remote repositories up-to-date.
2019 2024
2020 2025 This command can only be run using an |authtoken| with admin
2021 2026 rights to the specified repository. For more information,
2022 2027 see :ref:`config-token-ref`.
2023 2028
2024 2029 This command takes the following options:
2025 2030
2026 2031 :param apiuser: This is filled automatically from the |authtoken|.
2027 2032 :type apiuser: AuthUser
2028 2033 :param repoid: The repository name or repository ID.
2029 2034 :type repoid: str or int
2030 2035 :param remote_uri: Optional remote URI to pass in for pull
2031 2036 :type remote_uri: str
2032 2037
2033 2038 Example output:
2034 2039
2035 2040 .. code-block:: bash
2036 2041
2037 2042 id : <id_given_in_input>
2038 2043 result : {
2039 2044 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2040 2045 "repository": "<repository name>"
2041 2046 }
2042 2047 error : null
2043 2048
2044 2049 Example error output:
2045 2050
2046 2051 .. code-block:: bash
2047 2052
2048 2053 id : <id_given_in_input>
2049 2054 result : null
2050 2055 error : {
2051 2056 "Unable to push changes from `<remote_url>`"
2052 2057 }
2053 2058
2054 2059 """
2055 2060
2056 2061 repo = get_repo_or_error(repoid)
2057 2062 remote_uri = Optional.extract(remote_uri)
2058 2063 remote_uri_display = remote_uri or repo.clone_uri_hidden
2059 2064 if not has_superadmin_permission(apiuser):
2060 2065 _perms = ('repository.admin',)
2061 2066 validate_repo_permissions(apiuser, repoid, repo, _perms)
2062 2067
2063 2068 try:
2064 2069 ScmModel().pull_changes(
2065 2070 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2066 2071 return {
2067 2072 'msg': 'Pulled from url `%s` on repo `%s`' % (
2068 2073 remote_uri_display, repo.repo_name),
2069 2074 'repository': repo.repo_name
2070 2075 }
2071 2076 except Exception:
2072 2077 log.exception("Exception occurred while trying to "
2073 2078 "pull changes from remote location")
2074 2079 raise JSONRPCError(
2075 2080 'Unable to pull changes from `%s`' % remote_uri_display
2076 2081 )
2077 2082
2078 2083
2079 2084 @jsonrpc_method()
2080 2085 def strip(request, apiuser, repoid, revision, branch):
2081 2086 """
2082 2087 Strips the given revision from the specified repository.
2083 2088
2084 2089 * This will remove the revision and all of its decendants.
2085 2090
2086 2091 This command can only be run using an |authtoken| with admin rights to
2087 2092 the specified repository.
2088 2093
2089 2094 This command takes the following options:
2090 2095
2091 2096 :param apiuser: This is filled automatically from the |authtoken|.
2092 2097 :type apiuser: AuthUser
2093 2098 :param repoid: The repository name or repository ID.
2094 2099 :type repoid: str or int
2095 2100 :param revision: The revision you wish to strip.
2096 2101 :type revision: str
2097 2102 :param branch: The branch from which to strip the revision.
2098 2103 :type branch: str
2099 2104
2100 2105 Example output:
2101 2106
2102 2107 .. code-block:: bash
2103 2108
2104 2109 id : <id_given_in_input>
2105 2110 result : {
2106 2111 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2107 2112 "repository": "<repository name>"
2108 2113 }
2109 2114 error : null
2110 2115
2111 2116 Example error output:
2112 2117
2113 2118 .. code-block:: bash
2114 2119
2115 2120 id : <id_given_in_input>
2116 2121 result : null
2117 2122 error : {
2118 2123 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2119 2124 }
2120 2125
2121 2126 """
2122 2127
2123 2128 repo = get_repo_or_error(repoid)
2124 2129 if not has_superadmin_permission(apiuser):
2125 2130 _perms = ('repository.admin',)
2126 2131 validate_repo_permissions(apiuser, repoid, repo, _perms)
2127 2132
2128 2133 try:
2129 2134 ScmModel().strip(repo, revision, branch)
2130 2135 audit_logger.store_api(
2131 2136 'repo.commit.strip', action_data={'commit_id': revision},
2132 2137 repo=repo,
2133 2138 user=apiuser, commit=True)
2134 2139
2135 2140 return {
2136 2141 'msg': 'Stripped commit %s from repo `%s`' % (
2137 2142 revision, repo.repo_name),
2138 2143 'repository': repo.repo_name
2139 2144 }
2140 2145 except Exception:
2141 2146 log.exception("Exception while trying to strip")
2142 2147 raise JSONRPCError(
2143 2148 'Unable to strip commit %s from repo `%s`' % (
2144 2149 revision, repo.repo_name)
2145 2150 )
2146 2151
2147 2152
2148 2153 @jsonrpc_method()
2149 2154 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2150 2155 """
2151 2156 Returns all settings for a repository. If key is given it only returns the
2152 2157 setting identified by the key or null.
2153 2158
2154 2159 :param apiuser: This is filled automatically from the |authtoken|.
2155 2160 :type apiuser: AuthUser
2156 2161 :param repoid: The repository name or repository id.
2157 2162 :type repoid: str or int
2158 2163 :param key: Key of the setting to return.
2159 2164 :type: key: Optional(str)
2160 2165
2161 2166 Example output:
2162 2167
2163 2168 .. code-block:: bash
2164 2169
2165 2170 {
2166 2171 "error": null,
2167 2172 "id": 237,
2168 2173 "result": {
2169 2174 "extensions_largefiles": true,
2170 2175 "extensions_evolve": true,
2171 2176 "hooks_changegroup_push_logger": true,
2172 2177 "hooks_changegroup_repo_size": false,
2173 2178 "hooks_outgoing_pull_logger": true,
2174 2179 "phases_publish": "True",
2175 2180 "rhodecode_hg_use_rebase_for_merging": true,
2176 2181 "rhodecode_pr_merge_enabled": true,
2177 2182 "rhodecode_use_outdated_comments": true
2178 2183 }
2179 2184 }
2180 2185 """
2181 2186
2182 2187 # Restrict access to this api method to admins only.
2183 2188 if not has_superadmin_permission(apiuser):
2184 2189 raise JSONRPCForbidden()
2185 2190
2186 2191 try:
2187 2192 repo = get_repo_or_error(repoid)
2188 2193 settings_model = VcsSettingsModel(repo=repo)
2189 2194 settings = settings_model.get_global_settings()
2190 2195 settings.update(settings_model.get_repo_settings())
2191 2196
2192 2197 # If only a single setting is requested fetch it from all settings.
2193 2198 key = Optional.extract(key)
2194 2199 if key is not None:
2195 2200 settings = settings.get(key, None)
2196 2201 except Exception:
2197 2202 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2198 2203 log.exception(msg)
2199 2204 raise JSONRPCError(msg)
2200 2205
2201 2206 return settings
2202 2207
2203 2208
2204 2209 @jsonrpc_method()
2205 2210 def set_repo_settings(request, apiuser, repoid, settings):
2206 2211 """
2207 2212 Update repository settings. Returns true on success.
2208 2213
2209 2214 :param apiuser: This is filled automatically from the |authtoken|.
2210 2215 :type apiuser: AuthUser
2211 2216 :param repoid: The repository name or repository id.
2212 2217 :type repoid: str or int
2213 2218 :param settings: The new settings for the repository.
2214 2219 :type: settings: dict
2215 2220
2216 2221 Example output:
2217 2222
2218 2223 .. code-block:: bash
2219 2224
2220 2225 {
2221 2226 "error": null,
2222 2227 "id": 237,
2223 2228 "result": true
2224 2229 }
2225 2230 """
2226 2231 # Restrict access to this api method to admins only.
2227 2232 if not has_superadmin_permission(apiuser):
2228 2233 raise JSONRPCForbidden()
2229 2234
2230 2235 if type(settings) is not dict:
2231 2236 raise JSONRPCError('Settings have to be a JSON Object.')
2232 2237
2233 2238 try:
2234 2239 settings_model = VcsSettingsModel(repo=repoid)
2235 2240
2236 2241 # Merge global, repo and incoming settings.
2237 2242 new_settings = settings_model.get_global_settings()
2238 2243 new_settings.update(settings_model.get_repo_settings())
2239 2244 new_settings.update(settings)
2240 2245
2241 2246 # Update the settings.
2242 2247 inherit_global_settings = new_settings.get(
2243 2248 'inherit_global_settings', False)
2244 2249 settings_model.create_or_update_repo_settings(
2245 2250 new_settings, inherit_global_settings=inherit_global_settings)
2246 2251 Session().commit()
2247 2252 except Exception:
2248 2253 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2249 2254 log.exception(msg)
2250 2255 raise JSONRPCError(msg)
2251 2256
2252 2257 # Indicate success.
2253 2258 return True
2254 2259
2255 2260
2256 2261 @jsonrpc_method()
2257 2262 def maintenance(request, apiuser, repoid):
2258 2263 """
2259 2264 Triggers a maintenance on the given repository.
2260 2265
2261 2266 This command can only be run using an |authtoken| with admin
2262 2267 rights to the specified repository. For more information,
2263 2268 see :ref:`config-token-ref`.
2264 2269
2265 2270 This command takes the following options:
2266 2271
2267 2272 :param apiuser: This is filled automatically from the |authtoken|.
2268 2273 :type apiuser: AuthUser
2269 2274 :param repoid: The repository name or repository ID.
2270 2275 :type repoid: str or int
2271 2276
2272 2277 Example output:
2273 2278
2274 2279 .. code-block:: bash
2275 2280
2276 2281 id : <id_given_in_input>
2277 2282 result : {
2278 2283 "msg": "executed maintenance command",
2279 2284 "executed_actions": [
2280 2285 <action_message>, <action_message2>...
2281 2286 ],
2282 2287 "repository": "<repository name>"
2283 2288 }
2284 2289 error : null
2285 2290
2286 2291 Example error output:
2287 2292
2288 2293 .. code-block:: bash
2289 2294
2290 2295 id : <id_given_in_input>
2291 2296 result : null
2292 2297 error : {
2293 2298 "Unable to execute maintenance on `<reponame>`"
2294 2299 }
2295 2300
2296 2301 """
2297 2302
2298 2303 repo = get_repo_or_error(repoid)
2299 2304 if not has_superadmin_permission(apiuser):
2300 2305 _perms = ('repository.admin',)
2301 2306 validate_repo_permissions(apiuser, repoid, repo, _perms)
2302 2307
2303 2308 try:
2304 2309 maintenance = repo_maintenance.RepoMaintenance()
2305 2310 executed_actions = maintenance.execute(repo)
2306 2311
2307 2312 return {
2308 2313 'msg': 'executed maintenance command',
2309 2314 'executed_actions': executed_actions,
2310 2315 'repository': repo.repo_name
2311 2316 }
2312 2317 except Exception:
2313 2318 log.exception("Exception occurred while trying to run maintenance")
2314 2319 raise JSONRPCError(
2315 2320 '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,895 +1,903 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.
194 195 :type sync: Optional(``True`` | ``False``)
195 196
196 197 Example output:
197 198
198 199 .. code-block:: bash
199 200
200 201 id : <id_given_in_input>
201 202 result: {
202 203 "msg": "created new user group `<groupname>`",
203 204 "user_group": <user_group_object>
204 205 }
205 206 error: null
206 207
207 208 Example error output:
208 209
209 210 .. code-block:: bash
210 211
211 212 id : <id_given_in_input>
212 213 result : null
213 214 error : {
214 215 "user group `<group name>` already exist"
215 216 or
216 217 "failed to create group `<group name>`"
217 218 }
218 219
219 220 """
220 221
221 222 if not has_superadmin_permission(apiuser):
222 223 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
223 224 raise JSONRPCForbidden()
224 225
225 226 if UserGroupModel().get_by_name(group_name):
226 227 raise JSONRPCError("user group `%s` already exist" % (group_name,))
227 228
228 229 if isinstance(owner, Optional):
229 230 owner = apiuser.user_id
230 231
231 232 owner = get_user_or_error(owner)
232 233 active = Optional.extract(active)
233 234 description = Optional.extract(description)
234 235 sync = Optional.extract(sync)
235 236
236 237 # set the sync option based on group_data
237 238 group_data = None
238 239 if sync:
239 240 group_data = {
240 241 'extern_type': 'manual_api',
241 242 'extern_type_set_by': apiuser.username
242 243 }
243 244
244 245 schema = user_group_schema.UserGroupSchema().bind(
245 246 # user caller
246 247 user=apiuser)
247 248 try:
248 249 schema_data = schema.deserialize(dict(
249 250 user_group_name=group_name,
250 251 user_group_description=description,
251 252 user_group_owner=owner.username,
252 253 user_group_active=active,
253 254 ))
254 255 except validation_schema.Invalid as err:
255 256 raise JSONRPCValidationError(colander_exc=err)
256 257
257 258 try:
258 259 user_group = UserGroupModel().create(
259 260 name=schema_data['user_group_name'],
260 261 description=schema_data['user_group_description'],
261 262 owner=owner,
262 263 active=schema_data['user_group_active'], group_data=group_data)
263 264 Session().flush()
264 265 creation_data = user_group.get_api_data()
265 266 audit_logger.store_api(
266 267 'user_group.create', action_data={'data': creation_data},
267 268 user=apiuser)
268 269 Session().commit()
270
271 affected_user_ids = [apiuser.user_id, owner.user_id]
272 PermissionModel().trigger_permission_flush(affected_user_ids)
273
269 274 return {
270 275 'msg': 'created new user group `%s`' % group_name,
271 276 'user_group': creation_data
272 277 }
273 278 except Exception:
274 279 log.exception("Error occurred during creation of user group")
275 280 raise JSONRPCError('failed to create group `%s`' % (group_name,))
276 281
277 282
278 283 @jsonrpc_method()
279 284 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
280 285 description=Optional(''), owner=Optional(None),
281 286 active=Optional(True), sync=Optional(None)):
282 287 """
283 288 Updates the specified `user group` with the details provided.
284 289
285 290 This command can only be run using an |authtoken| with admin rights to
286 291 the specified repository.
287 292
288 293 :param apiuser: This is filled automatically from the |authtoken|.
289 294 :type apiuser: AuthUser
290 295 :param usergroupid: Set the id of the `user group` to update.
291 296 :type usergroupid: str or int
292 297 :param group_name: Set the new name the `user group`
293 298 :type group_name: str
294 299 :param description: Give a description for the `user group`
295 300 :type description: str
296 301 :param owner: Set the owner of the `user group`.
297 302 :type owner: Optional(str or int)
298 303 :param active: Set the group as active.
299 304 :type active: Optional(``True`` | ``False``)
300 305 :param sync: Set enabled or disabled the automatically sync from
301 306 external authentication types like ldap.
302 307 :type sync: Optional(``True`` | ``False``)
303 308
304 309 Example output:
305 310
306 311 .. code-block:: bash
307 312
308 313 id : <id_given_in_input>
309 314 result : {
310 315 "msg": 'updated user group ID:<user group id> <user group name>',
311 316 "user_group": <user_group_object>
312 317 }
313 318 error : null
314 319
315 320 Example error output:
316 321
317 322 .. code-block:: bash
318 323
319 324 id : <id_given_in_input>
320 325 result : null
321 326 error : {
322 327 "failed to update user group `<user group name>`"
323 328 }
324 329
325 330 """
326 331
327 332 user_group = get_user_group_or_error(usergroupid)
328 333 include_secrets = False
329 334 if not has_superadmin_permission(apiuser):
330 335 # check if we have admin permission for this user group !
331 336 _perms = ('usergroup.admin',)
332 337 if not HasUserGroupPermissionAnyApi(*_perms)(
333 338 user=apiuser, user_group_name=user_group.users_group_name):
334 339 raise JSONRPCError(
335 340 'user group `%s` does not exist' % (usergroupid,))
336 341 else:
337 342 include_secrets = True
338 343
339 344 if not isinstance(owner, Optional):
340 345 owner = get_user_or_error(owner)
341 346
342 347 old_data = user_group.get_api_data()
343 348 updates = {}
344 349 store_update(updates, group_name, 'users_group_name')
345 350 store_update(updates, description, 'user_group_description')
346 351 store_update(updates, owner, 'user')
347 352 store_update(updates, active, 'users_group_active')
348 353
349 354 sync = Optional.extract(sync)
350 355 group_data = None
351 356 if sync is True:
352 357 group_data = {
353 358 'extern_type': 'manual_api',
354 359 'extern_type_set_by': apiuser.username
355 360 }
356 361 if sync is False:
357 362 group_data = user_group.group_data
358 363 if group_data and "extern_type" in group_data:
359 364 del group_data["extern_type"]
360 365
361 366 try:
362 367 UserGroupModel().update(user_group, updates, group_data=group_data)
363 368 audit_logger.store_api(
364 369 'user_group.edit', action_data={'old_data': old_data},
365 370 user=apiuser)
366 371 Session().commit()
367 372 return {
368 373 'msg': 'updated user group ID:%s %s' % (
369 374 user_group.users_group_id, user_group.users_group_name),
370 375 'user_group': user_group.get_api_data(
371 376 include_secrets=include_secrets)
372 377 }
373 378 except Exception:
374 379 log.exception("Error occurred during update of user group")
375 380 raise JSONRPCError(
376 381 'failed to update user group `%s`' % (usergroupid,))
377 382
378 383
379 384 @jsonrpc_method()
380 385 def delete_user_group(request, apiuser, usergroupid):
381 386 """
382 387 Deletes the specified `user group`.
383 388
384 389 This command can only be run using an |authtoken| with admin rights to
385 390 the specified repository.
386 391
387 392 This command takes the following options:
388 393
389 394 :param apiuser: filled automatically from apikey
390 395 :type apiuser: AuthUser
391 396 :param usergroupid:
392 397 :type usergroupid: int
393 398
394 399 Example output:
395 400
396 401 .. code-block:: bash
397 402
398 403 id : <id_given_in_input>
399 404 result : {
400 405 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
401 406 }
402 407 error : null
403 408
404 409 Example error output:
405 410
406 411 .. code-block:: bash
407 412
408 413 id : <id_given_in_input>
409 414 result : null
410 415 error : {
411 416 "failed to delete user group ID:<user_group_id> <user_group_name>"
412 417 or
413 418 "RepoGroup assigned to <repo_groups_list>"
414 419 }
415 420
416 421 """
417 422
418 423 user_group = get_user_group_or_error(usergroupid)
419 424 if not has_superadmin_permission(apiuser):
420 425 # check if we have admin permission for this user group !
421 426 _perms = ('usergroup.admin',)
422 427 if not HasUserGroupPermissionAnyApi(*_perms)(
423 428 user=apiuser, user_group_name=user_group.users_group_name):
424 429 raise JSONRPCError(
425 430 'user group `%s` does not exist' % (usergroupid,))
426 431
427 432 old_data = user_group.get_api_data()
428 433 try:
429 434 UserGroupModel().delete(user_group)
430 435 audit_logger.store_api(
431 436 'user_group.delete', action_data={'old_data': old_data},
432 437 user=apiuser)
433 438 Session().commit()
434 439 return {
435 440 'msg': 'deleted user group ID:%s %s' % (
436 441 user_group.users_group_id, user_group.users_group_name),
437 442 'user_group': None
438 443 }
439 444 except UserGroupAssignedException as e:
440 445 log.exception("UserGroupAssigned error")
441 446 raise JSONRPCError(str(e))
442 447 except Exception:
443 448 log.exception("Error occurred during deletion of user group")
444 449 raise JSONRPCError(
445 450 'failed to delete user group ID:%s %s' %(
446 451 user_group.users_group_id, user_group.users_group_name))
447 452
448 453
449 454 @jsonrpc_method()
450 455 def add_user_to_user_group(request, apiuser, usergroupid, userid):
451 456 """
452 457 Adds a user to a `user group`. If the user already exists in the group
453 458 this command will return false.
454 459
455 460 This command can only be run using an |authtoken| with admin rights to
456 461 the specified user group.
457 462
458 463 This command takes the following options:
459 464
460 465 :param apiuser: This is filled automatically from the |authtoken|.
461 466 :type apiuser: AuthUser
462 467 :param usergroupid: Set the name of the `user group` to which a
463 468 user will be added.
464 469 :type usergroupid: int
465 470 :param userid: Set the `user_id` of the user to add to the group.
466 471 :type userid: int
467 472
468 473 Example output:
469 474
470 475 .. code-block:: bash
471 476
472 477 id : <id_given_in_input>
473 478 result : {
474 479 "success": True|False # depends on if member is in group
475 480 "msg": "added member `<username>` to user group `<groupname>` |
476 481 User is already in that group"
477 482
478 483 }
479 484 error : null
480 485
481 486 Example error output:
482 487
483 488 .. code-block:: bash
484 489
485 490 id : <id_given_in_input>
486 491 result : null
487 492 error : {
488 493 "failed to add member to user group `<user_group_name>`"
489 494 }
490 495
491 496 """
492 497
493 498 user = get_user_or_error(userid)
494 499 user_group = get_user_group_or_error(usergroupid)
495 500 if not has_superadmin_permission(apiuser):
496 501 # check if we have admin permission for this user group !
497 502 _perms = ('usergroup.admin',)
498 503 if not HasUserGroupPermissionAnyApi(*_perms)(
499 504 user=apiuser, user_group_name=user_group.users_group_name):
500 505 raise JSONRPCError('user group `%s` does not exist' % (
501 506 usergroupid,))
502 507
503 508 old_values = user_group.get_api_data()
504 509 try:
505 510 ugm = UserGroupModel().add_user_to_group(user_group, user)
506 511 success = True if ugm is not True else False
507 512 msg = 'added member `%s` to user group `%s`' % (
508 513 user.username, user_group.users_group_name
509 514 )
510 515 msg = msg if success else 'User is already in that group'
511 516 if success:
512 517 user_data = user.get_api_data()
513 518 audit_logger.store_api(
514 519 'user_group.edit.member.add',
515 520 action_data={'user': user_data, 'old_data': old_values},
516 521 user=apiuser)
517 522
518 523 Session().commit()
519 524
520 525 return {
521 526 'success': success,
522 527 'msg': msg
523 528 }
524 529 except Exception:
525 530 log.exception("Error occurred during adding a member to user group")
526 531 raise JSONRPCError(
527 532 'failed to add member to user group `%s`' % (
528 533 user_group.users_group_name,
529 534 )
530 535 )
531 536
532 537
533 538 @jsonrpc_method()
534 539 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
535 540 """
536 541 Removes a user from a user group.
537 542
538 543 * If the specified user is not in the group, this command will return
539 544 `false`.
540 545
541 546 This command can only be run using an |authtoken| with admin rights to
542 547 the specified user group.
543 548
544 549 :param apiuser: This is filled automatically from the |authtoken|.
545 550 :type apiuser: AuthUser
546 551 :param usergroupid: Sets the user group name.
547 552 :type usergroupid: str or int
548 553 :param userid: The user you wish to remove from |RCE|.
549 554 :type userid: str or int
550 555
551 556 Example output:
552 557
553 558 .. code-block:: bash
554 559
555 560 id : <id_given_in_input>
556 561 result: {
557 562 "success": True|False, # depends on if member is in group
558 563 "msg": "removed member <username> from user group <groupname> |
559 564 User wasn't in group"
560 565 }
561 566 error: null
562 567
563 568 """
564 569
565 570 user = get_user_or_error(userid)
566 571 user_group = get_user_group_or_error(usergroupid)
567 572 if not has_superadmin_permission(apiuser):
568 573 # check if we have admin permission for this user group !
569 574 _perms = ('usergroup.admin',)
570 575 if not HasUserGroupPermissionAnyApi(*_perms)(
571 576 user=apiuser, user_group_name=user_group.users_group_name):
572 577 raise JSONRPCError(
573 578 'user group `%s` does not exist' % (usergroupid,))
574 579
575 580 old_values = user_group.get_api_data()
576 581 try:
577 582 success = UserGroupModel().remove_user_from_group(user_group, user)
578 583 msg = 'removed member `%s` from user group `%s`' % (
579 584 user.username, user_group.users_group_name
580 585 )
581 586 msg = msg if success else "User wasn't in group"
582 587 if success:
583 588 user_data = user.get_api_data()
584 589 audit_logger.store_api(
585 590 'user_group.edit.member.delete',
586 591 action_data={'user': user_data, 'old_data': old_values},
587 592 user=apiuser)
588 593
589 594 Session().commit()
590 595 return {'success': success, 'msg': msg}
591 596 except Exception:
592 597 log.exception("Error occurred during removing an member from user group")
593 598 raise JSONRPCError(
594 599 'failed to remove member from user group `%s`' % (
595 600 user_group.users_group_name,
596 601 )
597 602 )
598 603
599 604
600 605 @jsonrpc_method()
601 606 def grant_user_permission_to_user_group(
602 607 request, apiuser, usergroupid, userid, perm):
603 608 """
604 609 Set permissions for a user in a user group.
605 610
606 611 :param apiuser: This is filled automatically from the |authtoken|.
607 612 :type apiuser: AuthUser
608 613 :param usergroupid: Set the user group to edit permissions on.
609 614 :type usergroupid: str or int
610 615 :param userid: Set the user from whom you wish to set permissions.
611 616 :type userid: str
612 617 :param perm: (usergroup.(none|read|write|admin))
613 618 :type perm: str
614 619
615 620 Example output:
616 621
617 622 .. code-block:: bash
618 623
619 624 id : <id_given_in_input>
620 625 result : {
621 626 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
622 627 "success": true
623 628 }
624 629 error : null
625 630 """
626 631
627 632 user_group = get_user_group_or_error(usergroupid)
628 633
629 634 if not has_superadmin_permission(apiuser):
630 635 # check if we have admin permission for this user group !
631 636 _perms = ('usergroup.admin',)
632 637 if not HasUserGroupPermissionAnyApi(*_perms)(
633 638 user=apiuser, user_group_name=user_group.users_group_name):
634 639 raise JSONRPCError(
635 640 'user group `%s` does not exist' % (usergroupid,))
636 641
637 642 user = get_user_or_error(userid)
638 643 perm = get_perm_or_error(perm, prefix='usergroup.')
639 644
640 645 try:
641 646 changes = UserGroupModel().grant_user_permission(
642 647 user_group=user_group, user=user, perm=perm)
643 648
644 649 action_data = {
645 650 'added': changes['added'],
646 651 'updated': changes['updated'],
647 652 'deleted': changes['deleted'],
648 653 }
649 654 audit_logger.store_api(
650 655 'user_group.edit.permissions', action_data=action_data,
651 656 user=apiuser)
657 Session().commit()
658 PermissionModel().flush_user_permission_caches(changes)
652 659
653 Session().commit()
654 660 return {
655 661 'msg':
656 662 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
657 663 perm.permission_name, user.username,
658 664 user_group.users_group_name
659 665 ),
660 666 'success': True
661 667 }
662 668 except Exception:
663 669 log.exception("Error occurred during editing permissions "
664 670 "for user in user group")
665 671 raise JSONRPCError(
666 672 'failed to edit permission for user: '
667 673 '`%s` in user group: `%s`' % (
668 674 userid, user_group.users_group_name))
669 675
670 676
671 677 @jsonrpc_method()
672 678 def revoke_user_permission_from_user_group(
673 679 request, apiuser, usergroupid, userid):
674 680 """
675 681 Revoke a users permissions in a user group.
676 682
677 683 :param apiuser: This is filled automatically from the |authtoken|.
678 684 :type apiuser: AuthUser
679 685 :param usergroupid: Set the user group from which to revoke the user
680 686 permissions.
681 687 :type: usergroupid: str or int
682 688 :param userid: Set the userid of the user whose permissions will be
683 689 revoked.
684 690 :type userid: str
685 691
686 692 Example output:
687 693
688 694 .. code-block:: bash
689 695
690 696 id : <id_given_in_input>
691 697 result : {
692 698 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
693 699 "success": true
694 700 }
695 701 error : null
696 702 """
697 703
698 704 user_group = get_user_group_or_error(usergroupid)
699 705
700 706 if not has_superadmin_permission(apiuser):
701 707 # check if we have admin permission for this user group !
702 708 _perms = ('usergroup.admin',)
703 709 if not HasUserGroupPermissionAnyApi(*_perms)(
704 710 user=apiuser, user_group_name=user_group.users_group_name):
705 711 raise JSONRPCError(
706 712 'user group `%s` does not exist' % (usergroupid,))
707 713
708 714 user = get_user_or_error(userid)
709 715
710 716 try:
711 717 changes = UserGroupModel().revoke_user_permission(
712 718 user_group=user_group, user=user)
713 719 action_data = {
714 720 'added': changes['added'],
715 721 'updated': changes['updated'],
716 722 'deleted': changes['deleted'],
717 723 }
718 724 audit_logger.store_api(
719 725 'user_group.edit.permissions', action_data=action_data,
720 726 user=apiuser)
727 Session().commit()
728 PermissionModel().flush_user_permission_caches(changes)
721 729
722 Session().commit()
723 730 return {
724 731 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
725 732 user.username, user_group.users_group_name
726 733 ),
727 734 'success': True
728 735 }
729 736 except Exception:
730 737 log.exception("Error occurred during editing permissions "
731 738 "for user in user group")
732 739 raise JSONRPCError(
733 740 'failed to edit permission for user: `%s` in user group: `%s`'
734 741 % (userid, user_group.users_group_name))
735 742
736 743
737 744 @jsonrpc_method()
738 745 def grant_user_group_permission_to_user_group(
739 746 request, apiuser, usergroupid, sourceusergroupid, perm):
740 747 """
741 748 Give one user group permissions to another user group.
742 749
743 750 :param apiuser: This is filled automatically from the |authtoken|.
744 751 :type apiuser: AuthUser
745 752 :param usergroupid: Set the user group on which to edit permissions.
746 753 :type usergroupid: str or int
747 754 :param sourceusergroupid: Set the source user group to which
748 755 access/permissions will be granted.
749 756 :type sourceusergroupid: str or int
750 757 :param perm: (usergroup.(none|read|write|admin))
751 758 :type perm: str
752 759
753 760 Example output:
754 761
755 762 .. code-block:: bash
756 763
757 764 id : <id_given_in_input>
758 765 result : {
759 766 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
760 767 "success": true
761 768 }
762 769 error : null
763 770 """
764 771
765 772 user_group = get_user_group_or_error(sourceusergroupid)
766 773 target_user_group = get_user_group_or_error(usergroupid)
767 774 perm = get_perm_or_error(perm, prefix='usergroup.')
768 775
769 776 if not has_superadmin_permission(apiuser):
770 777 # check if we have admin permission for this user group !
771 778 _perms = ('usergroup.admin',)
772 779 if not HasUserGroupPermissionAnyApi(*_perms)(
773 780 user=apiuser,
774 781 user_group_name=target_user_group.users_group_name):
775 782 raise JSONRPCError(
776 783 'to user group `%s` does not exist' % (usergroupid,))
777 784
778 785 # check if we have at least read permission for source user group !
779 786 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
780 787 if not HasUserGroupPermissionAnyApi(*_perms)(
781 788 user=apiuser, user_group_name=user_group.users_group_name):
782 789 raise JSONRPCError(
783 790 'user group `%s` does not exist' % (sourceusergroupid,))
784 791
785 792 try:
786 793 changes = UserGroupModel().grant_user_group_permission(
787 794 target_user_group=target_user_group,
788 795 user_group=user_group, perm=perm)
789 796
790 797 action_data = {
791 798 'added': changes['added'],
792 799 'updated': changes['updated'],
793 800 'deleted': changes['deleted'],
794 801 }
795 802 audit_logger.store_api(
796 803 'user_group.edit.permissions', action_data=action_data,
797 804 user=apiuser)
805 Session().commit()
806 PermissionModel().flush_user_permission_caches(changes)
798 807
799 Session().commit()
800 808 return {
801 809 'msg': 'Granted perm: `%s` for user group: `%s` '
802 810 'in user group: `%s`' % (
803 811 perm.permission_name, user_group.users_group_name,
804 812 target_user_group.users_group_name
805 813 ),
806 814 'success': True
807 815 }
808 816 except Exception:
809 817 log.exception("Error occurred during editing permissions "
810 818 "for user group in user group")
811 819 raise JSONRPCError(
812 820 'failed to edit permission for user group: `%s` in '
813 821 'user group: `%s`' % (
814 822 sourceusergroupid, target_user_group.users_group_name
815 823 )
816 824 )
817 825
818 826
819 827 @jsonrpc_method()
820 828 def revoke_user_group_permission_from_user_group(
821 829 request, apiuser, usergroupid, sourceusergroupid):
822 830 """
823 831 Revoke the permissions that one user group has to another.
824 832
825 833 :param apiuser: This is filled automatically from the |authtoken|.
826 834 :type apiuser: AuthUser
827 835 :param usergroupid: Set the user group on which to edit permissions.
828 836 :type usergroupid: str or int
829 837 :param sourceusergroupid: Set the user group from which permissions
830 838 are revoked.
831 839 :type sourceusergroupid: str or int
832 840
833 841 Example output:
834 842
835 843 .. code-block:: bash
836 844
837 845 id : <id_given_in_input>
838 846 result : {
839 847 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
840 848 "success": true
841 849 }
842 850 error : null
843 851 """
844 852
845 853 user_group = get_user_group_or_error(sourceusergroupid)
846 854 target_user_group = get_user_group_or_error(usergroupid)
847 855
848 856 if not has_superadmin_permission(apiuser):
849 857 # check if we have admin permission for this user group !
850 858 _perms = ('usergroup.admin',)
851 859 if not HasUserGroupPermissionAnyApi(*_perms)(
852 860 user=apiuser,
853 861 user_group_name=target_user_group.users_group_name):
854 862 raise JSONRPCError(
855 863 'to user group `%s` does not exist' % (usergroupid,))
856 864
857 865 # check if we have at least read permission
858 866 # for the source user group !
859 867 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
860 868 if not HasUserGroupPermissionAnyApi(*_perms)(
861 869 user=apiuser, user_group_name=user_group.users_group_name):
862 870 raise JSONRPCError(
863 871 'user group `%s` does not exist' % (sourceusergroupid,))
864 872
865 873 try:
866 874 changes = UserGroupModel().revoke_user_group_permission(
867 875 target_user_group=target_user_group, user_group=user_group)
868 876 action_data = {
869 877 'added': changes['added'],
870 878 'updated': changes['updated'],
871 879 'deleted': changes['deleted'],
872 880 }
873 881 audit_logger.store_api(
874 882 'user_group.edit.permissions', action_data=action_data,
875 883 user=apiuser)
876
877 884 Session().commit()
885 PermissionModel().flush_user_permission_caches(changes)
878 886
879 887 return {
880 888 'msg': 'Revoked perm for user group: '
881 889 '`%s` in user group: `%s`' % (
882 890 user_group.users_group_name,
883 891 target_user_group.users_group_name
884 892 ),
885 893 'success': True
886 894 }
887 895 except Exception:
888 896 log.exception("Error occurred during editing permissions "
889 897 "for user group in user group")
890 898 raise JSONRPCError(
891 899 'failed to edit permission for user group: '
892 900 '`%s` in user group: `%s`' % (
893 901 sourceusergroupid, target_user_group.users_group_name
894 902 )
895 903 )
@@ -1,518 +1,518 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 = SettingsModel().get_all_settings()
72 72 defaults = {
73 73 'anonymous': c.user.active,
74 74 'default_register_message': app_settings.get(
75 75 'rhodecode_register_message')
76 76 }
77 77 defaults.update(c.user.get_default_perms())
78 78
79 79 data = render('rhodecode:templates/admin/permissions/permissions.mako',
80 80 self._get_template_context(c), self.request)
81 81 html = formencode.htmlfill.render(
82 82 data,
83 83 defaults=defaults,
84 84 encoding="UTF-8",
85 85 force_defaults=False
86 86 )
87 87 return Response(html)
88 88
89 89 @LoginRequired()
90 90 @HasPermissionAllDecorator('hg.admin')
91 91 @CSRFRequired()
92 92 @view_config(
93 93 route_name='admin_permissions_application_update', request_method='POST',
94 94 renderer='rhodecode:templates/admin/permissions/permissions.mako')
95 95 def permissions_application_update(self):
96 96 _ = self.request.translate
97 97 c = self.load_default_context()
98 98 c.active = 'application'
99 99
100 100 _form = ApplicationPermissionsForm(
101 101 self.request.translate,
102 102 [x[0] for x in c.register_choices],
103 103 [x[0] for x in c.password_reset_choices],
104 104 [x[0] for x in c.extern_activate_choices])()
105 105
106 106 try:
107 107 form_result = _form.to_python(dict(self.request.POST))
108 108 form_result.update({'perm_user_name': User.DEFAULT_USER})
109 109 PermissionModel().update_application_permissions(form_result)
110 110
111 111 settings = [
112 112 ('register_message', 'default_register_message'),
113 113 ]
114 114 for setting, form_key in settings:
115 115 sett = SettingsModel().create_or_update_setting(
116 116 setting, form_result[form_key])
117 117 Session().add(sett)
118 118
119 119 Session().commit()
120 120 h.flash(_('Application permissions updated successfully'),
121 121 category='success')
122 122
123 123 except formencode.Invalid as errors:
124 124 defaults = errors.value
125 125
126 126 data = render(
127 127 'rhodecode:templates/admin/permissions/permissions.mako',
128 128 self._get_template_context(c), self.request)
129 129 html = formencode.htmlfill.render(
130 130 data,
131 131 defaults=defaults,
132 132 errors=errors.error_dict or {},
133 133 prefix_error=False,
134 134 encoding="UTF-8",
135 135 force_defaults=False
136 136 )
137 137 return Response(html)
138 138
139 139 except Exception:
140 140 log.exception("Exception during update of permissions")
141 141 h.flash(_('Error occurred during update of permissions'),
142 142 category='error')
143 143
144 144 affected_user_ids = [User.get_default_user().user_id]
145 events.trigger(events.UserPermissionsChange(affected_user_ids))
145 PermissionModel().trigger_permission_flush(affected_user_ids)
146 146
147 147 raise HTTPFound(h.route_path('admin_permissions_application'))
148 148
149 149 @LoginRequired()
150 150 @HasPermissionAllDecorator('hg.admin')
151 151 @view_config(
152 152 route_name='admin_permissions_object', request_method='GET',
153 153 renderer='rhodecode:templates/admin/permissions/permissions.mako')
154 154 def permissions_objects(self):
155 155 c = self.load_default_context()
156 156 c.active = 'objects'
157 157
158 158 c.user = User.get_default_user(refresh=True)
159 159 defaults = {}
160 160 defaults.update(c.user.get_default_perms())
161 161
162 162 data = render(
163 163 'rhodecode:templates/admin/permissions/permissions.mako',
164 164 self._get_template_context(c), self.request)
165 165 html = formencode.htmlfill.render(
166 166 data,
167 167 defaults=defaults,
168 168 encoding="UTF-8",
169 169 force_defaults=False
170 170 )
171 171 return Response(html)
172 172
173 173 @LoginRequired()
174 174 @HasPermissionAllDecorator('hg.admin')
175 175 @CSRFRequired()
176 176 @view_config(
177 177 route_name='admin_permissions_object_update', request_method='POST',
178 178 renderer='rhodecode:templates/admin/permissions/permissions.mako')
179 179 def permissions_objects_update(self):
180 180 _ = self.request.translate
181 181 c = self.load_default_context()
182 182 c.active = 'objects'
183 183
184 184 _form = ObjectPermissionsForm(
185 185 self.request.translate,
186 186 [x[0] for x in c.repo_perms_choices],
187 187 [x[0] for x in c.group_perms_choices],
188 188 [x[0] for x in c.user_group_perms_choices],
189 189 )()
190 190
191 191 try:
192 192 form_result = _form.to_python(dict(self.request.POST))
193 193 form_result.update({'perm_user_name': User.DEFAULT_USER})
194 194 PermissionModel().update_object_permissions(form_result)
195 195
196 196 Session().commit()
197 197 h.flash(_('Object permissions updated successfully'),
198 198 category='success')
199 199
200 200 except formencode.Invalid as errors:
201 201 defaults = errors.value
202 202
203 203 data = render(
204 204 'rhodecode:templates/admin/permissions/permissions.mako',
205 205 self._get_template_context(c), self.request)
206 206 html = formencode.htmlfill.render(
207 207 data,
208 208 defaults=defaults,
209 209 errors=errors.error_dict or {},
210 210 prefix_error=False,
211 211 encoding="UTF-8",
212 212 force_defaults=False
213 213 )
214 214 return Response(html)
215 215 except Exception:
216 216 log.exception("Exception during update of permissions")
217 217 h.flash(_('Error occurred during update of permissions'),
218 218 category='error')
219 219
220 220 affected_user_ids = [User.get_default_user().user_id]
221 events.trigger(events.UserPermissionsChange(affected_user_ids))
221 PermissionModel().trigger_permission_flush(affected_user_ids)
222 222
223 223 raise HTTPFound(h.route_path('admin_permissions_object'))
224 224
225 225 @LoginRequired()
226 226 @HasPermissionAllDecorator('hg.admin')
227 227 @view_config(
228 228 route_name='admin_permissions_branch', request_method='GET',
229 229 renderer='rhodecode:templates/admin/permissions/permissions.mako')
230 230 def permissions_branch(self):
231 231 c = self.load_default_context()
232 232 c.active = 'branch'
233 233
234 234 c.user = User.get_default_user(refresh=True)
235 235 defaults = {}
236 236 defaults.update(c.user.get_default_perms())
237 237
238 238 data = render(
239 239 'rhodecode:templates/admin/permissions/permissions.mako',
240 240 self._get_template_context(c), self.request)
241 241 html = formencode.htmlfill.render(
242 242 data,
243 243 defaults=defaults,
244 244 encoding="UTF-8",
245 245 force_defaults=False
246 246 )
247 247 return Response(html)
248 248
249 249 @LoginRequired()
250 250 @HasPermissionAllDecorator('hg.admin')
251 251 @view_config(
252 252 route_name='admin_permissions_global', request_method='GET',
253 253 renderer='rhodecode:templates/admin/permissions/permissions.mako')
254 254 def permissions_global(self):
255 255 c = self.load_default_context()
256 256 c.active = 'global'
257 257
258 258 c.user = User.get_default_user(refresh=True)
259 259 defaults = {}
260 260 defaults.update(c.user.get_default_perms())
261 261
262 262 data = render(
263 263 'rhodecode:templates/admin/permissions/permissions.mako',
264 264 self._get_template_context(c), self.request)
265 265 html = formencode.htmlfill.render(
266 266 data,
267 267 defaults=defaults,
268 268 encoding="UTF-8",
269 269 force_defaults=False
270 270 )
271 271 return Response(html)
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @CSRFRequired()
276 276 @view_config(
277 277 route_name='admin_permissions_global_update', request_method='POST',
278 278 renderer='rhodecode:templates/admin/permissions/permissions.mako')
279 279 def permissions_global_update(self):
280 280 _ = self.request.translate
281 281 c = self.load_default_context()
282 282 c.active = 'global'
283 283
284 284 _form = UserPermissionsForm(
285 285 self.request.translate,
286 286 [x[0] for x in c.repo_create_choices],
287 287 [x[0] for x in c.repo_create_on_write_choices],
288 288 [x[0] for x in c.repo_group_create_choices],
289 289 [x[0] for x in c.user_group_create_choices],
290 290 [x[0] for x in c.fork_choices],
291 291 [x[0] for x in c.inherit_default_permission_choices])()
292 292
293 293 try:
294 294 form_result = _form.to_python(dict(self.request.POST))
295 295 form_result.update({'perm_user_name': User.DEFAULT_USER})
296 296 PermissionModel().update_user_permissions(form_result)
297 297
298 298 Session().commit()
299 299 h.flash(_('Global permissions updated successfully'),
300 300 category='success')
301 301
302 302 except formencode.Invalid as errors:
303 303 defaults = errors.value
304 304
305 305 data = render(
306 306 'rhodecode:templates/admin/permissions/permissions.mako',
307 307 self._get_template_context(c), self.request)
308 308 html = formencode.htmlfill.render(
309 309 data,
310 310 defaults=defaults,
311 311 errors=errors.error_dict or {},
312 312 prefix_error=False,
313 313 encoding="UTF-8",
314 314 force_defaults=False
315 315 )
316 316 return Response(html)
317 317 except Exception:
318 318 log.exception("Exception during update of permissions")
319 319 h.flash(_('Error occurred during update of permissions'),
320 320 category='error')
321 321
322 322 affected_user_ids = [User.get_default_user().user_id]
323 events.trigger(events.UserPermissionsChange(affected_user_ids))
323 PermissionModel().trigger_permission_flush(affected_user_ids)
324 324
325 325 raise HTTPFound(h.route_path('admin_permissions_global'))
326 326
327 327 @LoginRequired()
328 328 @HasPermissionAllDecorator('hg.admin')
329 329 @view_config(
330 330 route_name='admin_permissions_ips', request_method='GET',
331 331 renderer='rhodecode:templates/admin/permissions/permissions.mako')
332 332 def permissions_ips(self):
333 333 c = self.load_default_context()
334 334 c.active = 'ips'
335 335
336 336 c.user = User.get_default_user(refresh=True)
337 337 c.user_ip_map = (
338 338 UserIpMap.query().filter(UserIpMap.user == c.user).all())
339 339
340 340 return self._get_template_context(c)
341 341
342 342 @LoginRequired()
343 343 @HasPermissionAllDecorator('hg.admin')
344 344 @view_config(
345 345 route_name='admin_permissions_overview', request_method='GET',
346 346 renderer='rhodecode:templates/admin/permissions/permissions.mako')
347 347 def permissions_overview(self):
348 348 c = self.load_default_context()
349 349 c.active = 'perms'
350 350
351 351 c.user = User.get_default_user(refresh=True)
352 352 c.perm_user = c.user.AuthUser()
353 353 return self._get_template_context(c)
354 354
355 355 @LoginRequired()
356 356 @HasPermissionAllDecorator('hg.admin')
357 357 @view_config(
358 358 route_name='admin_permissions_auth_token_access', request_method='GET',
359 359 renderer='rhodecode:templates/admin/permissions/permissions.mako')
360 360 def auth_token_access(self):
361 361 from rhodecode import CONFIG
362 362
363 363 c = self.load_default_context()
364 364 c.active = 'auth_token_access'
365 365
366 366 c.user = User.get_default_user(refresh=True)
367 367 c.perm_user = c.user.AuthUser()
368 368
369 369 mapper = self.request.registry.queryUtility(IRoutesMapper)
370 370 c.view_data = []
371 371
372 372 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
373 373 introspector = self.request.registry.introspector
374 374
375 375 view_intr = {}
376 376 for view_data in introspector.get_category('views'):
377 377 intr = view_data['introspectable']
378 378
379 379 if 'route_name' in intr and intr['attr']:
380 380 view_intr[intr['route_name']] = '{}:{}'.format(
381 381 str(intr['derived_callable'].func_name), intr['attr']
382 382 )
383 383
384 384 c.whitelist_key = 'api_access_controllers_whitelist'
385 385 c.whitelist_file = CONFIG.get('__file__')
386 386 whitelist_views = aslist(
387 387 CONFIG.get(c.whitelist_key), sep=',')
388 388
389 389 for route_info in mapper.get_routes():
390 390 if not route_info.name.startswith('__'):
391 391 routepath = route_info.pattern
392 392
393 393 def replace(matchobj):
394 394 if matchobj.group(1):
395 395 return "{%s}" % matchobj.group(1).split(':')[0]
396 396 else:
397 397 return "{%s}" % matchobj.group(2)
398 398
399 399 routepath = _argument_prog.sub(replace, routepath)
400 400
401 401 if not routepath.startswith('/'):
402 402 routepath = '/' + routepath
403 403
404 404 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
405 405 active = view_fqn in whitelist_views
406 406 c.view_data.append((route_info.name, view_fqn, routepath, active))
407 407
408 408 c.whitelist_views = whitelist_views
409 409 return self._get_template_context(c)
410 410
411 411 def ssh_enabled(self):
412 412 return self.request.registry.settings.get(
413 413 'ssh.generate_authorized_keyfile')
414 414
415 415 @LoginRequired()
416 416 @HasPermissionAllDecorator('hg.admin')
417 417 @view_config(
418 418 route_name='admin_permissions_ssh_keys', request_method='GET',
419 419 renderer='rhodecode:templates/admin/permissions/permissions.mako')
420 420 def ssh_keys(self):
421 421 c = self.load_default_context()
422 422 c.active = 'ssh_keys'
423 423 c.ssh_enabled = self.ssh_enabled()
424 424 return self._get_template_context(c)
425 425
426 426 @LoginRequired()
427 427 @HasPermissionAllDecorator('hg.admin')
428 428 @view_config(
429 429 route_name='admin_permissions_ssh_keys_data', request_method='GET',
430 430 renderer='json_ext', xhr=True)
431 431 def ssh_keys_data(self):
432 432 _ = self.request.translate
433 433 self.load_default_context()
434 434 column_map = {
435 435 'fingerprint': 'ssh_key_fingerprint',
436 436 'username': User.username
437 437 }
438 438 draw, start, limit = self._extract_chunk(self.request)
439 439 search_q, order_by, order_dir = self._extract_ordering(
440 440 self.request, column_map=column_map)
441 441
442 442 ssh_keys_data_total_count = UserSshKeys.query()\
443 443 .count()
444 444
445 445 # json generate
446 446 base_q = UserSshKeys.query().join(UserSshKeys.user)
447 447
448 448 if search_q:
449 449 like_expression = u'%{}%'.format(safe_unicode(search_q))
450 450 base_q = base_q.filter(or_(
451 451 User.username.ilike(like_expression),
452 452 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
453 453 ))
454 454
455 455 users_data_total_filtered_count = base_q.count()
456 456
457 457 sort_col = self._get_order_col(order_by, UserSshKeys)
458 458 if sort_col:
459 459 if order_dir == 'asc':
460 460 # handle null values properly to order by NULL last
461 461 if order_by in ['created_on']:
462 462 sort_col = coalesce(sort_col, datetime.date.max)
463 463 sort_col = sort_col.asc()
464 464 else:
465 465 # handle null values properly to order by NULL last
466 466 if order_by in ['created_on']:
467 467 sort_col = coalesce(sort_col, datetime.date.min)
468 468 sort_col = sort_col.desc()
469 469
470 470 base_q = base_q.order_by(sort_col)
471 471 base_q = base_q.offset(start).limit(limit)
472 472
473 473 ssh_keys = base_q.all()
474 474
475 475 ssh_keys_data = []
476 476 for ssh_key in ssh_keys:
477 477 ssh_keys_data.append({
478 478 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
479 479 "fingerprint": ssh_key.ssh_key_fingerprint,
480 480 "description": ssh_key.description,
481 481 "created_on": h.format_date(ssh_key.created_on),
482 482 "accessed_on": h.format_date(ssh_key.accessed_on),
483 483 "action": h.link_to(
484 484 _('Edit'), h.route_path('edit_user_ssh_keys',
485 485 user_id=ssh_key.user.user_id))
486 486 })
487 487
488 488 data = ({
489 489 'draw': draw,
490 490 'data': ssh_keys_data,
491 491 'recordsTotal': ssh_keys_data_total_count,
492 492 'recordsFiltered': users_data_total_filtered_count,
493 493 })
494 494
495 495 return data
496 496
497 497 @LoginRequired()
498 498 @HasPermissionAllDecorator('hg.admin')
499 499 @CSRFRequired()
500 500 @view_config(
501 501 route_name='admin_permissions_ssh_keys_update', request_method='POST',
502 502 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 503 def ssh_keys_update(self):
504 504 _ = self.request.translate
505 505 self.load_default_context()
506 506
507 507 ssh_enabled = self.ssh_enabled()
508 508 key_file = self.request.registry.settings.get(
509 509 'ssh.authorized_keys_file_path')
510 510 if ssh_enabled:
511 511 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
512 512 h.flash(_('Updated SSH keys file: {}').format(key_file),
513 513 category='success')
514 514 else:
515 515 h.flash(_('SSH key support is disabled in .ini file'),
516 516 category='warning')
517 517
518 518 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,189 +1,190 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.landing_revs_choices, c.landing_revs = \
63 64 ScmModel().get_repo_landing_revs(self.request.translate)
64 65 c.personal_repo_group = self._rhodecode_user.personal_repo_group
65 66
66 67 @LoginRequired()
67 68 @NotAnonymous()
68 69 # perms check inside
69 70 @view_config(
70 71 route_name='repos', request_method='GET',
71 72 renderer='rhodecode:templates/admin/repos/repos.mako')
72 73 def repository_list(self):
73 74 c = self.load_default_context()
74 75
75 76 repo_list = Repository.get_all_repos()
76 77 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
77 78 repos_data = RepoModel().get_repos_as_dict(
78 79 repo_list=c.repo_list, admin=True, super_user_actions=True)
79 80 # json used to render the grid
80 81 c.data = json.dumps(repos_data)
81 82
82 83 return self._get_template_context(c)
83 84
84 85 @LoginRequired()
85 86 @NotAnonymous()
86 87 # perms check inside
87 88 @view_config(
88 89 route_name='repo_new', request_method='GET',
89 90 renderer='rhodecode:templates/admin/repos/repo_add.mako')
90 91 def repository_new(self):
91 92 c = self.load_default_context()
92 93
93 94 new_repo = self.request.GET.get('repo', '')
94 95 parent_group = safe_int(self.request.GET.get('parent_group'))
95 96 _gr = RepoGroup.get(parent_group)
96 97
97 98 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
98 99 # you're not super admin nor have global create permissions,
99 100 # but maybe you have at least write permission to a parent group ?
100 101
101 102 gr_name = _gr.group_name if _gr else None
102 103 # create repositories with write permission on group is set to true
103 104 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
104 105 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
105 106 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
106 107 if not (group_admin or (group_write and create_on_write)):
107 108 raise HTTPForbidden()
108 109
109 110 self._load_form_data(c)
110 111 c.new_repo = repo_name_slug(new_repo)
111 112
112 113 # apply the defaults from defaults page
113 114 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
114 115 # set checkbox to autochecked
115 116 defaults['repo_copy_permissions'] = True
116 117
117 118 parent_group_choice = '-1'
118 119 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
119 120 parent_group_choice = self._rhodecode_user.personal_repo_group
120 121
121 122 if parent_group and _gr:
122 123 if parent_group in [x[0] for x in c.repo_groups]:
123 124 parent_group_choice = safe_unicode(parent_group)
124 125
125 126 defaults.update({'repo_group': parent_group_choice})
126 127
127 128 data = render('rhodecode:templates/admin/repos/repo_add.mako',
128 129 self._get_template_context(c), self.request)
129 130 html = formencode.htmlfill.render(
130 131 data,
131 132 defaults=defaults,
132 133 encoding="UTF-8",
133 134 force_defaults=False
134 135 )
135 136 return Response(html)
136 137
137 138 @LoginRequired()
138 139 @NotAnonymous()
139 140 @CSRFRequired()
140 141 # perms check inside
141 142 @view_config(
142 143 route_name='repo_create', request_method='POST',
143 144 renderer='rhodecode:templates/admin/repos/repos.mako')
144 145 def repository_create(self):
145 146 c = self.load_default_context()
146 147
147 148 form_result = {}
148 149 self._load_form_data(c)
149 150
150 151 try:
151 152 # CanWriteToGroup validators checks permissions of this POST
152 153 form = RepoForm(
153 154 self.request.translate, repo_groups=c.repo_groups_choices,
154 155 landing_revs=c.landing_revs_choices)()
155 156 form_result = form.to_python(dict(self.request.POST))
156 157 copy_permissions = form_result.get('repo_copy_permissions')
157 158 # create is done sometimes async on celery, db transaction
158 159 # management is handled there.
159 160 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
160 161 task_id = get_task_id(task)
161 162 except formencode.Invalid as errors:
162 163 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 164 self._get_template_context(c), self.request)
164 165 html = formencode.htmlfill.render(
165 166 data,
166 167 defaults=errors.value,
167 168 errors=errors.error_dict or {},
168 169 prefix_error=False,
169 170 encoding="UTF-8",
170 171 force_defaults=False
171 172 )
172 173 return Response(html)
173 174
174 175 except Exception as e:
175 176 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 177 h.flash(msg, category='error')
177 178 raise HTTPFound(h.route_path('home'))
178 179
179 180 repo_name = form_result.get('repo_name_full')
180 181
181 182 affected_user_ids = [self._rhodecode_user.user_id]
182 183 if copy_permissions:
183 184 # permission flush is done in repo creating
184 185 pass
185 events.trigger(events.UserPermissionsChange(affected_user_ids))
186 PermissionModel().trigger_permission_flush(affected_user_ids)
186 187
187 188 raise HTTPFound(
188 189 h.route_path('repo_creating', repo_name=repo_name,
189 190 _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,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 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 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
59 60 self.request.translate)
60 61 c.landing_revs_choices = choices
61 62 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62 63
63 64 return c
64 65
65 66 @LoginRequired()
66 67 @HasRepoPermissionAnyDecorator(
67 68 'repository.read', 'repository.write', 'repository.admin')
68 69 @view_config(
69 70 route_name='repo_forks_show_all', request_method='GET',
70 71 renderer='rhodecode:templates/forks/forks.mako')
71 72 def repo_forks_show_all(self):
72 73 c = self.load_default_context()
73 74 return self._get_template_context(c)
74 75
75 76 @LoginRequired()
76 77 @HasRepoPermissionAnyDecorator(
77 78 'repository.read', 'repository.write', 'repository.admin')
78 79 @view_config(
79 80 route_name='repo_forks_data', request_method='GET',
80 81 renderer='json_ext', xhr=True)
81 82 def repo_forks_data(self):
82 83 _ = self.request.translate
83 84 self.load_default_context()
84 85 column_map = {
85 86 'fork_name': 'repo_name',
86 87 'fork_date': 'created_on',
87 88 'last_activity': 'updated_on'
88 89 }
89 90 draw, start, limit = self._extract_chunk(self.request)
90 91 search_q, order_by, order_dir = self._extract_ordering(
91 92 self.request, column_map=column_map)
92 93
93 94 acl_check = HasRepoPermissionAny(
94 95 'repository.read', 'repository.write', 'repository.admin')
95 96 repo_id = self.db_repo.repo_id
96 97 allowed_ids = [-1]
97 98 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 99 if acl_check(f.repo_name, 'get forks check'):
99 100 allowed_ids.append(f.repo_id)
100 101
101 102 forks_data_total_count = Repository.query()\
102 103 .filter(Repository.fork_id == repo_id)\
103 104 .filter(Repository.repo_id.in_(allowed_ids))\
104 105 .count()
105 106
106 107 # json generate
107 108 base_q = Repository.query()\
108 109 .filter(Repository.fork_id == repo_id)\
109 110 .filter(Repository.repo_id.in_(allowed_ids))\
110 111
111 112 if search_q:
112 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 114 base_q = base_q.filter(or_(
114 115 Repository.repo_name.ilike(like_expression),
115 116 Repository.description.ilike(like_expression),
116 117 ))
117 118
118 119 forks_data_total_filtered_count = base_q.count()
119 120
120 121 sort_col = getattr(Repository, order_by, None)
121 122 if sort_col:
122 123 if order_dir == 'asc':
123 124 # handle null values properly to order by NULL last
124 125 if order_by in ['last_activity']:
125 126 sort_col = coalesce(sort_col, datetime.date.max)
126 127 sort_col = sort_col.asc()
127 128 else:
128 129 # handle null values properly to order by NULL last
129 130 if order_by in ['last_activity']:
130 131 sort_col = coalesce(sort_col, datetime.date.min)
131 132 sort_col = sort_col.desc()
132 133
133 134 base_q = base_q.order_by(sort_col)
134 135 base_q = base_q.offset(start).limit(limit)
135 136
136 137 fork_list = base_q.all()
137 138
138 139 def fork_actions(fork):
139 140 url_link = h.route_path(
140 141 'repo_compare',
141 142 repo_name=fork.repo_name,
142 143 source_ref_type=self.db_repo.landing_rev[0],
143 144 source_ref=self.db_repo.landing_rev[1],
144 145 target_ref_type=self.db_repo.landing_rev[0],
145 146 target_ref=self.db_repo.landing_rev[1],
146 147 _query=dict(merge=1, target_repo=f.repo_name))
147 148 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148 149
149 150 def fork_name(fork):
150 151 return h.link_to(fork.repo_name,
151 152 h.route_path('repo_summary', repo_name=fork.repo_name))
152 153
153 154 forks_data = []
154 155 for fork in fork_list:
155 156 forks_data.append({
156 157 "username": h.gravatar_with_user(self.request, fork.user.username),
157 158 "fork_name": fork_name(fork),
158 159 "description": fork.description_safe,
159 160 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 161 "last_activity": h.format_date(fork.updated_on),
161 162 "action": fork_actions(fork),
162 163 })
163 164
164 165 data = ({
165 166 'draw': draw,
166 167 'data': forks_data,
167 168 'recordsTotal': forks_data_total_count,
168 169 'recordsFiltered': forks_data_total_filtered_count,
169 170 })
170 171
171 172 return data
172 173
173 174 @LoginRequired()
174 175 @NotAnonymous()
175 176 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 177 @HasRepoPermissionAnyDecorator(
177 178 'repository.read', 'repository.write', 'repository.admin')
178 179 @view_config(
179 180 route_name='repo_fork_new', request_method='GET',
180 181 renderer='rhodecode:templates/forks/forks.mako')
181 182 def repo_fork_new(self):
182 183 c = self.load_default_context()
183 184
184 185 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 186 # alter the description to indicate a fork
186 187 defaults['description'] = (
187 188 'fork of repository: %s \n%s' % (
188 189 defaults['repo_name'], defaults['description']))
189 190 # add suffix to fork
190 191 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191 192
192 193 data = render('rhodecode:templates/forks/fork.mako',
193 194 self._get_template_context(c), self.request)
194 195 html = formencode.htmlfill.render(
195 196 data,
196 197 defaults=defaults,
197 198 encoding="UTF-8",
198 199 force_defaults=False
199 200 )
200 201 return Response(html)
201 202
202 203 @LoginRequired()
203 204 @NotAnonymous()
204 205 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 206 @HasRepoPermissionAnyDecorator(
206 207 'repository.read', 'repository.write', 'repository.admin')
207 208 @CSRFRequired()
208 209 @view_config(
209 210 route_name='repo_fork_create', request_method='POST',
210 211 renderer='rhodecode:templates/forks/fork.mako')
211 212 def repo_fork_create(self):
212 213 _ = self.request.translate
213 214 c = self.load_default_context()
214 215
215 216 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
216 217 repo_groups=c.repo_groups_choices,
217 218 landing_revs=c.landing_revs_choices)()
218 219 post_data = dict(self.request.POST)
219 220
220 221 # forbid injecting other repo by forging a request
221 222 post_data['fork_parent_id'] = self.db_repo.repo_id
222 223
223 224 form_result = {}
224 225 task_id = None
225 226 try:
226 227 form_result = _form.to_python(post_data)
227 228 copy_permissions = form_result.get('copy_permissions')
228 229 # create fork is done sometimes async on celery, db transaction
229 230 # management is handled there.
230 231 task = RepoModel().create_fork(
231 232 form_result, c.rhodecode_user.user_id)
232 233
233 234 task_id = get_task_id(task)
234 235 except formencode.Invalid as errors:
235 236 c.rhodecode_db_repo = self.db_repo
236 237
237 238 data = render('rhodecode:templates/forks/fork.mako',
238 239 self._get_template_context(c), self.request)
239 240 html = formencode.htmlfill.render(
240 241 data,
241 242 defaults=errors.value,
242 243 errors=errors.error_dict or {},
243 244 prefix_error=False,
244 245 encoding="UTF-8",
245 246 force_defaults=False
246 247 )
247 248 return Response(html)
248 249 except Exception:
249 250 log.exception(
250 251 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 252 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 253 h.flash(msg, category='error')
253 254 raise HTTPFound(h.route_path('home'))
254 255
255 256 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256 257
257 258 affected_user_ids = [self._rhodecode_user.user_id]
258 259 if copy_permissions:
259 260 # permission flush is done in repo creating
260 261 pass
261 262
262 events.trigger(events.UserPermissionsChange(affected_user_ids))
263 PermissionModel().trigger_permission_flush(affected_user_ids)
263 264
264 265 raise HTTPFound(
265 266 h.route_path('repo_creating', repo_name=repo_name,
266 267 _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