##// END OF EJS Templates
permissions: use constant for fork enable/disable
milka -
r4663:b7737b7b default
parent child Browse files
Show More
@@ -1,2524 +1,2524 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 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, channelstream
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import (
35 35 HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
36 36 HasRepoPermissionAnyApi)
37 37 from rhodecode.lib.celerylib.utils import get_task_id
38 38 from rhodecode.lib.utils2 import (
39 39 str2bool, time_to_datetime, safe_str, safe_int, safe_unicode)
40 40 from rhodecode.lib.ext_json import json
41 41 from rhodecode.lib.exceptions import (
42 42 StatusChangeOnClosedPullRequestError, CommentVersionMismatch)
43 43 from rhodecode.lib.vcs import RepositoryError
44 44 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
45 45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 46 from rhodecode.model.comment import CommentsModel
47 47 from rhodecode.model.db import (
48 48 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
49 49 ChangesetComment)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.pull_request import PullRequestModel
52 52 from rhodecode.model.repo import RepoModel
53 53 from rhodecode.model.scm import ScmModel, RepoList
54 54 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
55 55 from rhodecode.model import validation_schema
56 56 from rhodecode.model.validation_schema.schemas import repo_schema
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 @jsonrpc_method()
62 62 def get_repo(request, apiuser, repoid, cache=Optional(True)):
63 63 """
64 64 Gets an existing repository by its name or repository_id.
65 65
66 66 The members section so the output returns users groups or users
67 67 associated with that repository.
68 68
69 69 This command can only be run using an |authtoken| with admin rights,
70 70 or users with at least read rights to the |repo|.
71 71
72 72 :param apiuser: This is filled automatically from the |authtoken|.
73 73 :type apiuser: AuthUser
74 74 :param repoid: The repository name or repository id.
75 75 :type repoid: str or int
76 76 :param cache: use the cached value for last changeset
77 77 :type: cache: Optional(bool)
78 78
79 79 Example output:
80 80
81 81 .. code-block:: bash
82 82
83 83 {
84 84 "error": null,
85 85 "id": <repo_id>,
86 86 "result": {
87 87 "clone_uri": null,
88 88 "created_on": "timestamp",
89 89 "description": "repo description",
90 90 "enable_downloads": false,
91 91 "enable_locking": false,
92 92 "enable_statistics": false,
93 93 "followers": [
94 94 {
95 95 "active": true,
96 96 "admin": false,
97 97 "api_key": "****************************************",
98 98 "api_keys": [
99 99 "****************************************"
100 100 ],
101 101 "email": "user@example.com",
102 102 "emails": [
103 103 "user@example.com"
104 104 ],
105 105 "extern_name": "rhodecode",
106 106 "extern_type": "rhodecode",
107 107 "firstname": "username",
108 108 "ip_addresses": [],
109 109 "language": null,
110 110 "last_login": "2015-09-16T17:16:35.854",
111 111 "lastname": "surname",
112 112 "user_id": <user_id>,
113 113 "username": "name"
114 114 }
115 115 ],
116 116 "fork_of": "parent-repo",
117 117 "landing_rev": [
118 118 "rev",
119 119 "tip"
120 120 ],
121 121 "last_changeset": {
122 122 "author": "User <user@example.com>",
123 123 "branch": "default",
124 124 "date": "timestamp",
125 125 "message": "last commit message",
126 126 "parents": [
127 127 {
128 128 "raw_id": "commit-id"
129 129 }
130 130 ],
131 131 "raw_id": "commit-id",
132 132 "revision": <revision number>,
133 133 "short_id": "short id"
134 134 },
135 135 "lock_reason": null,
136 136 "locked_by": null,
137 137 "locked_date": null,
138 138 "owner": "owner-name",
139 139 "permissions": [
140 140 {
141 141 "name": "super-admin-name",
142 142 "origin": "super-admin",
143 143 "permission": "repository.admin",
144 144 "type": "user"
145 145 },
146 146 {
147 147 "name": "owner-name",
148 148 "origin": "owner",
149 149 "permission": "repository.admin",
150 150 "type": "user"
151 151 },
152 152 {
153 153 "name": "user-group-name",
154 154 "origin": "permission",
155 155 "permission": "repository.write",
156 156 "type": "user_group"
157 157 }
158 158 ],
159 159 "private": true,
160 160 "repo_id": 676,
161 161 "repo_name": "user-group/repo-name",
162 162 "repo_type": "hg"
163 163 }
164 164 }
165 165 """
166 166
167 167 repo = get_repo_or_error(repoid)
168 168 cache = Optional.extract(cache)
169 169
170 170 include_secrets = False
171 171 if has_superadmin_permission(apiuser):
172 172 include_secrets = True
173 173 else:
174 174 # check if we have at least read permission for this repo !
175 175 _perms = (
176 176 'repository.admin', 'repository.write', 'repository.read',)
177 177 validate_repo_permissions(apiuser, repoid, repo, _perms)
178 178
179 179 permissions = []
180 180 for _user in repo.permissions():
181 181 user_data = {
182 182 'name': _user.username,
183 183 'permission': _user.permission,
184 184 'origin': get_origin(_user),
185 185 'type': "user",
186 186 }
187 187 permissions.append(user_data)
188 188
189 189 for _user_group in repo.permission_user_groups():
190 190 user_group_data = {
191 191 'name': _user_group.users_group_name,
192 192 'permission': _user_group.permission,
193 193 'origin': get_origin(_user_group),
194 194 'type': "user_group",
195 195 }
196 196 permissions.append(user_group_data)
197 197
198 198 following_users = [
199 199 user.user.get_api_data(include_secrets=include_secrets)
200 200 for user in repo.followers]
201 201
202 202 if not cache:
203 203 repo.update_commit_cache()
204 204 data = repo.get_api_data(include_secrets=include_secrets)
205 205 data['permissions'] = permissions
206 206 data['followers'] = following_users
207 207 return data
208 208
209 209
210 210 @jsonrpc_method()
211 211 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
212 212 """
213 213 Lists all existing repositories.
214 214
215 215 This command can only be run using an |authtoken| with admin rights,
216 216 or users with at least read rights to |repos|.
217 217
218 218 :param apiuser: This is filled automatically from the |authtoken|.
219 219 :type apiuser: AuthUser
220 220 :param root: specify root repository group to fetch repositories.
221 221 filters the returned repositories to be members of given root group.
222 222 :type root: Optional(None)
223 223 :param traverse: traverse given root into subrepositories. With this flag
224 224 set to False, it will only return top-level repositories from `root`.
225 225 if root is empty it will return just top-level repositories.
226 226 :type traverse: Optional(True)
227 227
228 228
229 229 Example output:
230 230
231 231 .. code-block:: bash
232 232
233 233 id : <id_given_in_input>
234 234 result: [
235 235 {
236 236 "repo_id" : "<repo_id>",
237 237 "repo_name" : "<reponame>"
238 238 "repo_type" : "<repo_type>",
239 239 "clone_uri" : "<clone_uri>",
240 240 "private": : "<bool>",
241 241 "created_on" : "<datetimecreated>",
242 242 "description" : "<description>",
243 243 "landing_rev": "<landing_rev>",
244 244 "owner": "<repo_owner>",
245 245 "fork_of": "<name_of_fork_parent>",
246 246 "enable_downloads": "<bool>",
247 247 "enable_locking": "<bool>",
248 248 "enable_statistics": "<bool>",
249 249 },
250 250 ...
251 251 ]
252 252 error: null
253 253 """
254 254
255 255 include_secrets = has_superadmin_permission(apiuser)
256 256 _perms = ('repository.read', 'repository.write', 'repository.admin',)
257 257 extras = {'user': apiuser}
258 258
259 259 root = Optional.extract(root)
260 260 traverse = Optional.extract(traverse, binary=True)
261 261
262 262 if root:
263 263 # verify parent existance, if it's empty return an error
264 264 parent = RepoGroup.get_by_group_name(root)
265 265 if not parent:
266 266 raise JSONRPCError(
267 267 'Root repository group `{}` does not exist'.format(root))
268 268
269 269 if traverse:
270 270 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
271 271 else:
272 272 repos = RepoModel().get_repos_for_root(root=parent)
273 273 else:
274 274 if traverse:
275 275 repos = RepoModel().get_all()
276 276 else:
277 277 # return just top-level
278 278 repos = RepoModel().get_repos_for_root(root=None)
279 279
280 280 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
281 281 return [repo.get_api_data(include_secrets=include_secrets)
282 282 for repo in repo_list]
283 283
284 284
285 285 @jsonrpc_method()
286 286 def get_repo_changeset(request, apiuser, repoid, revision,
287 287 details=Optional('basic')):
288 288 """
289 289 Returns information about a changeset.
290 290
291 291 Additionally parameters define the amount of details returned by
292 292 this function.
293 293
294 294 This command can only be run using an |authtoken| with admin rights,
295 295 or users with at least read rights to the |repo|.
296 296
297 297 :param apiuser: This is filled automatically from the |authtoken|.
298 298 :type apiuser: AuthUser
299 299 :param repoid: The repository name or repository id
300 300 :type repoid: str or int
301 301 :param revision: revision for which listing should be done
302 302 :type revision: str
303 303 :param details: details can be 'basic|extended|full' full gives diff
304 304 info details like the diff itself, and number of changed files etc.
305 305 :type details: Optional(str)
306 306
307 307 """
308 308 repo = get_repo_or_error(repoid)
309 309 if not has_superadmin_permission(apiuser):
310 310 _perms = ('repository.admin', 'repository.write', 'repository.read',)
311 311 validate_repo_permissions(apiuser, repoid, repo, _perms)
312 312
313 313 changes_details = Optional.extract(details)
314 314 _changes_details_types = ['basic', 'extended', 'full']
315 315 if changes_details not in _changes_details_types:
316 316 raise JSONRPCError(
317 317 'ret_type must be one of %s' % (
318 318 ','.join(_changes_details_types)))
319 319
320 320 vcs_repo = repo.scm_instance()
321 321 pre_load = ['author', 'branch', 'date', 'message', 'parents',
322 322 'status', '_commit', '_file_paths']
323 323
324 324 try:
325 325 commit = repo.get_commit(commit_id=revision, pre_load=pre_load)
326 326 except TypeError as e:
327 327 raise JSONRPCError(safe_str(e))
328 328 _cs_json = commit.__json__()
329 329 _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details)
330 330 if changes_details == 'full':
331 331 _cs_json['refs'] = commit._get_refs()
332 332 return _cs_json
333 333
334 334
335 335 @jsonrpc_method()
336 336 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
337 337 details=Optional('basic')):
338 338 """
339 339 Returns a set of commits limited by the number starting
340 340 from the `start_rev` option.
341 341
342 342 Additional parameters define the amount of details returned by this
343 343 function.
344 344
345 345 This command can only be run using an |authtoken| with admin rights,
346 346 or users with at least read rights to |repos|.
347 347
348 348 :param apiuser: This is filled automatically from the |authtoken|.
349 349 :type apiuser: AuthUser
350 350 :param repoid: The repository name or repository ID.
351 351 :type repoid: str or int
352 352 :param start_rev: The starting revision from where to get changesets.
353 353 :type start_rev: str
354 354 :param limit: Limit the number of commits to this amount
355 355 :type limit: str or int
356 356 :param details: Set the level of detail returned. Valid option are:
357 357 ``basic``, ``extended`` and ``full``.
358 358 :type details: Optional(str)
359 359
360 360 .. note::
361 361
362 362 Setting the parameter `details` to the value ``full`` is extensive
363 363 and returns details like the diff itself, and the number
364 364 of changed files.
365 365
366 366 """
367 367 repo = get_repo_or_error(repoid)
368 368 if not has_superadmin_permission(apiuser):
369 369 _perms = ('repository.admin', 'repository.write', 'repository.read',)
370 370 validate_repo_permissions(apiuser, repoid, repo, _perms)
371 371
372 372 changes_details = Optional.extract(details)
373 373 _changes_details_types = ['basic', 'extended', 'full']
374 374 if changes_details not in _changes_details_types:
375 375 raise JSONRPCError(
376 376 'ret_type must be one of %s' % (
377 377 ','.join(_changes_details_types)))
378 378
379 379 limit = int(limit)
380 380 pre_load = ['author', 'branch', 'date', 'message', 'parents',
381 381 'status', '_commit', '_file_paths']
382 382
383 383 vcs_repo = repo.scm_instance()
384 384 # SVN needs a special case to distinguish its index and commit id
385 385 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
386 386 start_rev = vcs_repo.commit_ids[0]
387 387
388 388 try:
389 389 commits = vcs_repo.get_commits(
390 390 start_id=start_rev, pre_load=pre_load, translate_tags=False)
391 391 except TypeError as e:
392 392 raise JSONRPCError(safe_str(e))
393 393 except Exception:
394 394 log.exception('Fetching of commits failed')
395 395 raise JSONRPCError('Error occurred during commit fetching')
396 396
397 397 ret = []
398 398 for cnt, commit in enumerate(commits):
399 399 if cnt >= limit != -1:
400 400 break
401 401 _cs_json = commit.__json__()
402 402 _cs_json['diff'] = build_commit_data(vcs_repo, commit, changes_details)
403 403 if changes_details == 'full':
404 404 _cs_json['refs'] = {
405 405 'branches': [commit.branch],
406 406 'bookmarks': getattr(commit, 'bookmarks', []),
407 407 'tags': commit.tags
408 408 }
409 409 ret.append(_cs_json)
410 410 return ret
411 411
412 412
413 413 @jsonrpc_method()
414 414 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
415 415 ret_type=Optional('all'), details=Optional('basic'),
416 416 max_file_bytes=Optional(None)):
417 417 """
418 418 Returns a list of nodes and children in a flat list for a given
419 419 path at given revision.
420 420
421 421 It's possible to specify ret_type to show only `files` or `dirs`.
422 422
423 423 This command can only be run using an |authtoken| with admin rights,
424 424 or users with at least read rights to |repos|.
425 425
426 426 :param apiuser: This is filled automatically from the |authtoken|.
427 427 :type apiuser: AuthUser
428 428 :param repoid: The repository name or repository ID.
429 429 :type repoid: str or int
430 430 :param revision: The revision for which listing should be done.
431 431 :type revision: str
432 432 :param root_path: The path from which to start displaying.
433 433 :type root_path: str
434 434 :param ret_type: Set the return type. Valid options are
435 435 ``all`` (default), ``files`` and ``dirs``.
436 436 :type ret_type: Optional(str)
437 437 :param details: Returns extended information about nodes, such as
438 438 md5, binary, and or content.
439 439 The valid options are ``basic`` and ``full``.
440 440 :type details: Optional(str)
441 441 :param max_file_bytes: Only return file content under this file size bytes
442 442 :type details: Optional(int)
443 443
444 444 Example output:
445 445
446 446 .. code-block:: bash
447 447
448 448 id : <id_given_in_input>
449 449 result: [
450 450 {
451 451 "binary": false,
452 452 "content": "File line",
453 453 "extension": "md",
454 454 "lines": 2,
455 455 "md5": "059fa5d29b19c0657e384749480f6422",
456 456 "mimetype": "text/x-minidsrc",
457 457 "name": "file.md",
458 458 "size": 580,
459 459 "type": "file"
460 460 },
461 461 ...
462 462 ]
463 463 error: null
464 464 """
465 465
466 466 repo = get_repo_or_error(repoid)
467 467 if not has_superadmin_permission(apiuser):
468 468 _perms = ('repository.admin', 'repository.write', 'repository.read',)
469 469 validate_repo_permissions(apiuser, repoid, repo, _perms)
470 470
471 471 ret_type = Optional.extract(ret_type)
472 472 details = Optional.extract(details)
473 473 _extended_types = ['basic', 'full']
474 474 if details not in _extended_types:
475 475 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
476 476 extended_info = False
477 477 content = False
478 478 if details == 'basic':
479 479 extended_info = True
480 480
481 481 if details == 'full':
482 482 extended_info = content = True
483 483
484 484 _map = {}
485 485 try:
486 486 # check if repo is not empty by any chance, skip quicker if it is.
487 487 _scm = repo.scm_instance()
488 488 if _scm.is_empty():
489 489 return []
490 490
491 491 _d, _f = ScmModel().get_nodes(
492 492 repo, revision, root_path, flat=False,
493 493 extended_info=extended_info, content=content,
494 494 max_file_bytes=max_file_bytes)
495 495 _map = {
496 496 'all': _d + _f,
497 497 'files': _f,
498 498 'dirs': _d,
499 499 }
500 500 return _map[ret_type]
501 501 except KeyError:
502 502 raise JSONRPCError(
503 503 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
504 504 except Exception:
505 505 log.exception("Exception occurred while trying to get repo nodes")
506 506 raise JSONRPCError(
507 507 'failed to get repo: `%s` nodes' % repo.repo_name
508 508 )
509 509
510 510
511 511 @jsonrpc_method()
512 512 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
513 513 max_file_bytes=Optional(None), details=Optional('basic'),
514 514 cache=Optional(True)):
515 515 """
516 516 Returns a single file from repository at given revision.
517 517
518 518 This command can only be run using an |authtoken| with admin rights,
519 519 or users with at least read rights to |repos|.
520 520
521 521 :param apiuser: This is filled automatically from the |authtoken|.
522 522 :type apiuser: AuthUser
523 523 :param repoid: The repository name or repository ID.
524 524 :type repoid: str or int
525 525 :param commit_id: The revision for which listing should be done.
526 526 :type commit_id: str
527 527 :param file_path: The path from which to start displaying.
528 528 :type file_path: str
529 529 :param details: Returns different set of information about nodes.
530 530 The valid options are ``minimal`` ``basic`` and ``full``.
531 531 :type details: Optional(str)
532 532 :param max_file_bytes: Only return file content under this file size bytes
533 533 :type max_file_bytes: Optional(int)
534 534 :param cache: Use internal caches for fetching files. If disabled fetching
535 535 files is slower but more memory efficient
536 536 :type cache: Optional(bool)
537 537
538 538 Example output:
539 539
540 540 .. code-block:: bash
541 541
542 542 id : <id_given_in_input>
543 543 result: {
544 544 "binary": false,
545 545 "extension": "py",
546 546 "lines": 35,
547 547 "content": "....",
548 548 "md5": "76318336366b0f17ee249e11b0c99c41",
549 549 "mimetype": "text/x-python",
550 550 "name": "python.py",
551 551 "size": 817,
552 552 "type": "file",
553 553 }
554 554 error: null
555 555 """
556 556
557 557 repo = get_repo_or_error(repoid)
558 558 if not has_superadmin_permission(apiuser):
559 559 _perms = ('repository.admin', 'repository.write', 'repository.read',)
560 560 validate_repo_permissions(apiuser, repoid, repo, _perms)
561 561
562 562 cache = Optional.extract(cache, binary=True)
563 563 details = Optional.extract(details)
564 564 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
565 565 if details not in _extended_types:
566 566 raise JSONRPCError(
567 567 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
568 568 extended_info = False
569 569 content = False
570 570
571 571 if details == 'minimal':
572 572 extended_info = False
573 573
574 574 elif details == 'basic':
575 575 extended_info = True
576 576
577 577 elif details == 'full':
578 578 extended_info = content = True
579 579
580 580 file_path = safe_unicode(file_path)
581 581 try:
582 582 # check if repo is not empty by any chance, skip quicker if it is.
583 583 _scm = repo.scm_instance()
584 584 if _scm.is_empty():
585 585 return None
586 586
587 587 node = ScmModel().get_node(
588 588 repo, commit_id, file_path, extended_info=extended_info,
589 589 content=content, max_file_bytes=max_file_bytes, cache=cache)
590 590 except NodeDoesNotExistError:
591 591 raise JSONRPCError(u'There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
592 592 repo.repo_name, file_path, commit_id))
593 593 except Exception:
594 594 log.exception(u"Exception occurred while trying to get repo %s file",
595 595 repo.repo_name)
596 596 raise JSONRPCError(u'failed to get repo: `{}` file at path {}'.format(
597 597 repo.repo_name, file_path))
598 598
599 599 return node
600 600
601 601
602 602 @jsonrpc_method()
603 603 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
604 604 """
605 605 Returns a list of tree nodes for path at given revision. This api is built
606 606 strictly for usage in full text search building, and shouldn't be consumed
607 607
608 608 This command can only be run using an |authtoken| with admin rights,
609 609 or users with at least read rights to |repos|.
610 610
611 611 """
612 612
613 613 repo = get_repo_or_error(repoid)
614 614 if not has_superadmin_permission(apiuser):
615 615 _perms = ('repository.admin', 'repository.write', 'repository.read',)
616 616 validate_repo_permissions(apiuser, repoid, repo, _perms)
617 617
618 618 repo_id = repo.repo_id
619 619 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
620 620 cache_on = cache_seconds > 0
621 621
622 622 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
623 623 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
624 624
625 625 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
626 626 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
627 627
628 628 try:
629 629 # check if repo is not empty by any chance, skip quicker if it is.
630 630 _scm = repo.scm_instance()
631 631 if _scm.is_empty():
632 632 return []
633 633 except RepositoryError:
634 634 log.exception("Exception occurred while trying to get repo nodes")
635 635 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
636 636
637 637 try:
638 638 # we need to resolve commit_id to a FULL sha for cache to work correctly.
639 639 # sending 'master' is a pointer that needs to be translated to current commit.
640 640 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
641 641 log.debug(
642 642 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
643 643 'with caching: %s[TTL: %ss]' % (
644 644 repo_id, commit_id, cache_on, cache_seconds or 0))
645 645
646 646 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
647 647 return tree_files
648 648
649 649 except Exception:
650 650 log.exception("Exception occurred while trying to get repo nodes")
651 651 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
652 652
653 653
654 654 @jsonrpc_method()
655 655 def get_repo_refs(request, apiuser, repoid):
656 656 """
657 657 Returns a dictionary of current references. It returns
658 658 bookmarks, branches, closed_branches, and tags for given repository
659 659
660 660 It's possible to specify ret_type to show only `files` or `dirs`.
661 661
662 662 This command can only be run using an |authtoken| with admin rights,
663 663 or users with at least read rights to |repos|.
664 664
665 665 :param apiuser: This is filled automatically from the |authtoken|.
666 666 :type apiuser: AuthUser
667 667 :param repoid: The repository name or repository ID.
668 668 :type repoid: str or int
669 669
670 670 Example output:
671 671
672 672 .. code-block:: bash
673 673
674 674 id : <id_given_in_input>
675 675 "result": {
676 676 "bookmarks": {
677 677 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 678 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 679 },
680 680 "branches": {
681 681 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 682 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
683 683 },
684 684 "branches_closed": {},
685 685 "tags": {
686 686 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
687 687 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
688 688 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
689 689 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
690 690 }
691 691 }
692 692 error: null
693 693 """
694 694
695 695 repo = get_repo_or_error(repoid)
696 696 if not has_superadmin_permission(apiuser):
697 697 _perms = ('repository.admin', 'repository.write', 'repository.read',)
698 698 validate_repo_permissions(apiuser, repoid, repo, _perms)
699 699
700 700 try:
701 701 # check if repo is not empty by any chance, skip quicker if it is.
702 702 vcs_instance = repo.scm_instance()
703 703 refs = vcs_instance.refs()
704 704 return refs
705 705 except Exception:
706 706 log.exception("Exception occurred while trying to get repo refs")
707 707 raise JSONRPCError(
708 708 'failed to get repo: `%s` references' % repo.repo_name
709 709 )
710 710
711 711
712 712 @jsonrpc_method()
713 713 def create_repo(
714 714 request, apiuser, repo_name, repo_type,
715 715 owner=Optional(OAttr('apiuser')),
716 716 description=Optional(''),
717 717 private=Optional(False),
718 718 clone_uri=Optional(None),
719 719 push_uri=Optional(None),
720 720 landing_rev=Optional(None),
721 721 enable_statistics=Optional(False),
722 722 enable_locking=Optional(False),
723 723 enable_downloads=Optional(False),
724 724 copy_permissions=Optional(False)):
725 725 """
726 726 Creates a repository.
727 727
728 728 * If the repository name contains "/", repository will be created inside
729 729 a repository group or nested repository groups
730 730
731 731 For example "foo/bar/repo1" will create |repo| called "repo1" inside
732 732 group "foo/bar". You have to have permissions to access and write to
733 733 the last repository group ("bar" in this example)
734 734
735 735 This command can only be run using an |authtoken| with at least
736 736 permissions to create repositories, or write permissions to
737 737 parent repository groups.
738 738
739 739 :param apiuser: This is filled automatically from the |authtoken|.
740 740 :type apiuser: AuthUser
741 741 :param repo_name: Set the repository name.
742 742 :type repo_name: str
743 743 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
744 744 :type repo_type: str
745 745 :param owner: user_id or username
746 746 :type owner: Optional(str)
747 747 :param description: Set the repository description.
748 748 :type description: Optional(str)
749 749 :param private: set repository as private
750 750 :type private: bool
751 751 :param clone_uri: set clone_uri
752 752 :type clone_uri: str
753 753 :param push_uri: set push_uri
754 754 :type push_uri: str
755 755 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
756 756 :type landing_rev: str
757 757 :param enable_locking:
758 758 :type enable_locking: bool
759 759 :param enable_downloads:
760 760 :type enable_downloads: bool
761 761 :param enable_statistics:
762 762 :type enable_statistics: bool
763 763 :param copy_permissions: Copy permission from group in which the
764 764 repository is being created.
765 765 :type copy_permissions: bool
766 766
767 767
768 768 Example output:
769 769
770 770 .. code-block:: bash
771 771
772 772 id : <id_given_in_input>
773 773 result: {
774 774 "msg": "Created new repository `<reponame>`",
775 775 "success": true,
776 776 "task": "<celery task id or None if done sync>"
777 777 }
778 778 error: null
779 779
780 780
781 781 Example error output:
782 782
783 783 .. code-block:: bash
784 784
785 785 id : <id_given_in_input>
786 786 result : null
787 787 error : {
788 788 'failed to create repository `<repo_name>`'
789 789 }
790 790
791 791 """
792 792
793 793 owner = validate_set_owner_permissions(apiuser, owner)
794 794
795 795 description = Optional.extract(description)
796 796 copy_permissions = Optional.extract(copy_permissions)
797 797 clone_uri = Optional.extract(clone_uri)
798 798 push_uri = Optional.extract(push_uri)
799 799
800 800 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
801 801 if isinstance(private, Optional):
802 802 private = defs.get('repo_private') or Optional.extract(private)
803 803 if isinstance(repo_type, Optional):
804 804 repo_type = defs.get('repo_type')
805 805 if isinstance(enable_statistics, Optional):
806 806 enable_statistics = defs.get('repo_enable_statistics')
807 807 if isinstance(enable_locking, Optional):
808 808 enable_locking = defs.get('repo_enable_locking')
809 809 if isinstance(enable_downloads, Optional):
810 810 enable_downloads = defs.get('repo_enable_downloads')
811 811
812 812 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
813 813 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
814 814 ref_choices = list(set(ref_choices + [landing_ref]))
815 815
816 816 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
817 817
818 818 schema = repo_schema.RepoSchema().bind(
819 819 repo_type_options=rhodecode.BACKENDS.keys(),
820 820 repo_ref_options=ref_choices,
821 821 repo_type=repo_type,
822 822 # user caller
823 823 user=apiuser)
824 824
825 825 try:
826 826 schema_data = schema.deserialize(dict(
827 827 repo_name=repo_name,
828 828 repo_type=repo_type,
829 829 repo_owner=owner.username,
830 830 repo_description=description,
831 831 repo_landing_commit_ref=landing_commit_ref,
832 832 repo_clone_uri=clone_uri,
833 833 repo_push_uri=push_uri,
834 834 repo_private=private,
835 835 repo_copy_permissions=copy_permissions,
836 836 repo_enable_statistics=enable_statistics,
837 837 repo_enable_downloads=enable_downloads,
838 838 repo_enable_locking=enable_locking))
839 839 except validation_schema.Invalid as err:
840 840 raise JSONRPCValidationError(colander_exc=err)
841 841
842 842 try:
843 843 data = {
844 844 'owner': owner,
845 845 'repo_name': schema_data['repo_group']['repo_name_without_group'],
846 846 'repo_name_full': schema_data['repo_name'],
847 847 'repo_group': schema_data['repo_group']['repo_group_id'],
848 848 'repo_type': schema_data['repo_type'],
849 849 'repo_description': schema_data['repo_description'],
850 850 'repo_private': schema_data['repo_private'],
851 851 'clone_uri': schema_data['repo_clone_uri'],
852 852 'push_uri': schema_data['repo_push_uri'],
853 853 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
854 854 'enable_statistics': schema_data['repo_enable_statistics'],
855 855 'enable_locking': schema_data['repo_enable_locking'],
856 856 'enable_downloads': schema_data['repo_enable_downloads'],
857 857 'repo_copy_permissions': schema_data['repo_copy_permissions'],
858 858 }
859 859
860 860 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
861 861 task_id = get_task_id(task)
862 862 # no commit, it's done in RepoModel, or async via celery
863 863 return {
864 864 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
865 865 'success': True, # cannot return the repo data here since fork
866 866 # can be done async
867 867 'task': task_id
868 868 }
869 869 except Exception:
870 870 log.exception(
871 871 u"Exception while trying to create the repository %s",
872 872 schema_data['repo_name'])
873 873 raise JSONRPCError(
874 874 'failed to create repository `%s`' % (schema_data['repo_name'],))
875 875
876 876
877 877 @jsonrpc_method()
878 878 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
879 879 description=Optional('')):
880 880 """
881 881 Adds an extra field to a repository.
882 882
883 883 This command can only be run using an |authtoken| with at least
884 884 write permissions to the |repo|.
885 885
886 886 :param apiuser: This is filled automatically from the |authtoken|.
887 887 :type apiuser: AuthUser
888 888 :param repoid: Set the repository name or repository id.
889 889 :type repoid: str or int
890 890 :param key: Create a unique field key for this repository.
891 891 :type key: str
892 892 :param label:
893 893 :type label: Optional(str)
894 894 :param description:
895 895 :type description: Optional(str)
896 896 """
897 897 repo = get_repo_or_error(repoid)
898 898 if not has_superadmin_permission(apiuser):
899 899 _perms = ('repository.admin',)
900 900 validate_repo_permissions(apiuser, repoid, repo, _perms)
901 901
902 902 label = Optional.extract(label) or key
903 903 description = Optional.extract(description)
904 904
905 905 field = RepositoryField.get_by_key_name(key, repo)
906 906 if field:
907 907 raise JSONRPCError('Field with key '
908 908 '`%s` exists for repo `%s`' % (key, repoid))
909 909
910 910 try:
911 911 RepoModel().add_repo_field(repo, key, field_label=label,
912 912 field_desc=description)
913 913 Session().commit()
914 914 return {
915 915 'msg': "Added new repository field `%s`" % (key,),
916 916 'success': True,
917 917 }
918 918 except Exception:
919 919 log.exception("Exception occurred while trying to add field to repo")
920 920 raise JSONRPCError(
921 921 'failed to create new field for repository `%s`' % (repoid,))
922 922
923 923
924 924 @jsonrpc_method()
925 925 def remove_field_from_repo(request, apiuser, repoid, key):
926 926 """
927 927 Removes an extra field from a repository.
928 928
929 929 This command can only be run using an |authtoken| with at least
930 930 write permissions to the |repo|.
931 931
932 932 :param apiuser: This is filled automatically from the |authtoken|.
933 933 :type apiuser: AuthUser
934 934 :param repoid: Set the repository name or repository ID.
935 935 :type repoid: str or int
936 936 :param key: Set the unique field key for this repository.
937 937 :type key: str
938 938 """
939 939
940 940 repo = get_repo_or_error(repoid)
941 941 if not has_superadmin_permission(apiuser):
942 942 _perms = ('repository.admin',)
943 943 validate_repo_permissions(apiuser, repoid, repo, _perms)
944 944
945 945 field = RepositoryField.get_by_key_name(key, repo)
946 946 if not field:
947 947 raise JSONRPCError('Field with key `%s` does not '
948 948 'exists for repo `%s`' % (key, repoid))
949 949
950 950 try:
951 951 RepoModel().delete_repo_field(repo, field_key=key)
952 952 Session().commit()
953 953 return {
954 954 'msg': "Deleted repository field `%s`" % (key,),
955 955 'success': True,
956 956 }
957 957 except Exception:
958 958 log.exception(
959 959 "Exception occurred while trying to delete field from repo")
960 960 raise JSONRPCError(
961 961 'failed to delete field for repository `%s`' % (repoid,))
962 962
963 963
964 964 @jsonrpc_method()
965 965 def update_repo(
966 966 request, apiuser, repoid, repo_name=Optional(None),
967 967 owner=Optional(OAttr('apiuser')), description=Optional(''),
968 968 private=Optional(False),
969 969 clone_uri=Optional(None), push_uri=Optional(None),
970 970 landing_rev=Optional(None), fork_of=Optional(None),
971 971 enable_statistics=Optional(False),
972 972 enable_locking=Optional(False),
973 973 enable_downloads=Optional(False), fields=Optional('')):
974 974 """
975 975 Updates a repository with the given information.
976 976
977 977 This command can only be run using an |authtoken| with at least
978 978 admin permissions to the |repo|.
979 979
980 980 * If the repository name contains "/", repository will be updated
981 981 accordingly with a repository group or nested repository groups
982 982
983 983 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
984 984 called "repo-test" and place it inside group "foo/bar".
985 985 You have to have permissions to access and write to the last repository
986 986 group ("bar" in this example)
987 987
988 988 :param apiuser: This is filled automatically from the |authtoken|.
989 989 :type apiuser: AuthUser
990 990 :param repoid: repository name or repository ID.
991 991 :type repoid: str or int
992 992 :param repo_name: Update the |repo| name, including the
993 993 repository group it's in.
994 994 :type repo_name: str
995 995 :param owner: Set the |repo| owner.
996 996 :type owner: str
997 997 :param fork_of: Set the |repo| as fork of another |repo|.
998 998 :type fork_of: str
999 999 :param description: Update the |repo| description.
1000 1000 :type description: str
1001 1001 :param private: Set the |repo| as private. (True | False)
1002 1002 :type private: bool
1003 1003 :param clone_uri: Update the |repo| clone URI.
1004 1004 :type clone_uri: str
1005 1005 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1006 1006 :type landing_rev: str
1007 1007 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1008 1008 :type enable_statistics: bool
1009 1009 :param enable_locking: Enable |repo| locking.
1010 1010 :type enable_locking: bool
1011 1011 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1012 1012 :type enable_downloads: bool
1013 1013 :param fields: Add extra fields to the |repo|. Use the following
1014 1014 example format: ``field_key=field_val,field_key2=fieldval2``.
1015 1015 Escape ', ' with \,
1016 1016 :type fields: str
1017 1017 """
1018 1018
1019 1019 repo = get_repo_or_error(repoid)
1020 1020
1021 1021 include_secrets = False
1022 1022 if not has_superadmin_permission(apiuser):
1023 1023 _perms = ('repository.admin',)
1024 1024 validate_repo_permissions(apiuser, repoid, repo, _perms)
1025 1025 else:
1026 1026 include_secrets = True
1027 1027
1028 1028 updates = dict(
1029 1029 repo_name=repo_name
1030 1030 if not isinstance(repo_name, Optional) else repo.repo_name,
1031 1031
1032 1032 fork_id=fork_of
1033 1033 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1034 1034
1035 1035 user=owner
1036 1036 if not isinstance(owner, Optional) else repo.user.username,
1037 1037
1038 1038 repo_description=description
1039 1039 if not isinstance(description, Optional) else repo.description,
1040 1040
1041 1041 repo_private=private
1042 1042 if not isinstance(private, Optional) else repo.private,
1043 1043
1044 1044 clone_uri=clone_uri
1045 1045 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1046 1046
1047 1047 push_uri=push_uri
1048 1048 if not isinstance(push_uri, Optional) else repo.push_uri,
1049 1049
1050 1050 repo_landing_rev=landing_rev
1051 1051 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1052 1052
1053 1053 repo_enable_statistics=enable_statistics
1054 1054 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1055 1055
1056 1056 repo_enable_locking=enable_locking
1057 1057 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1058 1058
1059 1059 repo_enable_downloads=enable_downloads
1060 1060 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1061 1061
1062 1062 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1063 1063 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1064 1064 request.translate, repo=repo)
1065 1065 ref_choices = list(set(ref_choices + [landing_ref]))
1066 1066
1067 1067 old_values = repo.get_api_data()
1068 1068 repo_type = repo.repo_type
1069 1069 schema = repo_schema.RepoSchema().bind(
1070 1070 repo_type_options=rhodecode.BACKENDS.keys(),
1071 1071 repo_ref_options=ref_choices,
1072 1072 repo_type=repo_type,
1073 1073 # user caller
1074 1074 user=apiuser,
1075 1075 old_values=old_values)
1076 1076 try:
1077 1077 schema_data = schema.deserialize(dict(
1078 1078 # we save old value, users cannot change type
1079 1079 repo_type=repo_type,
1080 1080
1081 1081 repo_name=updates['repo_name'],
1082 1082 repo_owner=updates['user'],
1083 1083 repo_description=updates['repo_description'],
1084 1084 repo_clone_uri=updates['clone_uri'],
1085 1085 repo_push_uri=updates['push_uri'],
1086 1086 repo_fork_of=updates['fork_id'],
1087 1087 repo_private=updates['repo_private'],
1088 1088 repo_landing_commit_ref=updates['repo_landing_rev'],
1089 1089 repo_enable_statistics=updates['repo_enable_statistics'],
1090 1090 repo_enable_downloads=updates['repo_enable_downloads'],
1091 1091 repo_enable_locking=updates['repo_enable_locking']))
1092 1092 except validation_schema.Invalid as err:
1093 1093 raise JSONRPCValidationError(colander_exc=err)
1094 1094
1095 1095 # save validated data back into the updates dict
1096 1096 validated_updates = dict(
1097 1097 repo_name=schema_data['repo_group']['repo_name_without_group'],
1098 1098 repo_group=schema_data['repo_group']['repo_group_id'],
1099 1099
1100 1100 user=schema_data['repo_owner'],
1101 1101 repo_description=schema_data['repo_description'],
1102 1102 repo_private=schema_data['repo_private'],
1103 1103 clone_uri=schema_data['repo_clone_uri'],
1104 1104 push_uri=schema_data['repo_push_uri'],
1105 1105 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1106 1106 repo_enable_statistics=schema_data['repo_enable_statistics'],
1107 1107 repo_enable_locking=schema_data['repo_enable_locking'],
1108 1108 repo_enable_downloads=schema_data['repo_enable_downloads'],
1109 1109 )
1110 1110
1111 1111 if schema_data['repo_fork_of']:
1112 1112 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1113 1113 validated_updates['fork_id'] = fork_repo.repo_id
1114 1114
1115 1115 # extra fields
1116 1116 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1117 1117 if fields:
1118 1118 validated_updates.update(fields)
1119 1119
1120 1120 try:
1121 1121 RepoModel().update(repo, **validated_updates)
1122 1122 audit_logger.store_api(
1123 1123 'repo.edit', action_data={'old_data': old_values},
1124 1124 user=apiuser, repo=repo)
1125 1125 Session().commit()
1126 1126 return {
1127 1127 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1128 1128 'repository': repo.get_api_data(include_secrets=include_secrets)
1129 1129 }
1130 1130 except Exception:
1131 1131 log.exception(
1132 1132 u"Exception while trying to update the repository %s",
1133 1133 repoid)
1134 1134 raise JSONRPCError('failed to update repo `%s`' % repoid)
1135 1135
1136 1136
1137 1137 @jsonrpc_method()
1138 1138 def fork_repo(request, apiuser, repoid, fork_name,
1139 1139 owner=Optional(OAttr('apiuser')),
1140 1140 description=Optional(''),
1141 1141 private=Optional(False),
1142 1142 clone_uri=Optional(None),
1143 1143 landing_rev=Optional(None),
1144 1144 copy_permissions=Optional(False)):
1145 1145 """
1146 1146 Creates a fork of the specified |repo|.
1147 1147
1148 1148 * If the fork_name contains "/", fork will be created inside
1149 1149 a repository group or nested repository groups
1150 1150
1151 1151 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1152 1152 inside group "foo/bar". You have to have permissions to access and
1153 1153 write to the last repository group ("bar" in this example)
1154 1154
1155 1155 This command can only be run using an |authtoken| with minimum
1156 1156 read permissions of the forked repo, create fork permissions for an user.
1157 1157
1158 1158 :param apiuser: This is filled automatically from the |authtoken|.
1159 1159 :type apiuser: AuthUser
1160 1160 :param repoid: Set repository name or repository ID.
1161 1161 :type repoid: str or int
1162 1162 :param fork_name: Set the fork name, including it's repository group membership.
1163 1163 :type fork_name: str
1164 1164 :param owner: Set the fork owner.
1165 1165 :type owner: str
1166 1166 :param description: Set the fork description.
1167 1167 :type description: str
1168 1168 :param copy_permissions: Copy permissions from parent |repo|. The
1169 1169 default is False.
1170 1170 :type copy_permissions: bool
1171 1171 :param private: Make the fork private. The default is False.
1172 1172 :type private: bool
1173 1173 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1174 1174
1175 1175 Example output:
1176 1176
1177 1177 .. code-block:: bash
1178 1178
1179 1179 id : <id_for_response>
1180 1180 api_key : "<api_key>"
1181 1181 args: {
1182 1182 "repoid" : "<reponame or repo_id>",
1183 1183 "fork_name": "<forkname>",
1184 1184 "owner": "<username or user_id = Optional(=apiuser)>",
1185 1185 "description": "<description>",
1186 1186 "copy_permissions": "<bool>",
1187 1187 "private": "<bool>",
1188 1188 "landing_rev": "<landing_rev>"
1189 1189 }
1190 1190
1191 1191 Example error output:
1192 1192
1193 1193 .. code-block:: bash
1194 1194
1195 1195 id : <id_given_in_input>
1196 1196 result: {
1197 1197 "msg": "Created fork of `<reponame>` as `<forkname>`",
1198 1198 "success": true,
1199 1199 "task": "<celery task id or None if done sync>"
1200 1200 }
1201 1201 error: null
1202 1202
1203 1203 """
1204 1204
1205 1205 repo = get_repo_or_error(repoid)
1206 1206 repo_name = repo.repo_name
1207 1207
1208 1208 if not has_superadmin_permission(apiuser):
1209 1209 # check if we have at least read permission for
1210 1210 # this repo that we fork !
1211 1211 _perms = ('repository.admin', 'repository.write', 'repository.read')
1212 1212 validate_repo_permissions(apiuser, repoid, repo, _perms)
1213 1213
1214 1214 # check if the regular user has at least fork permissions as well
1215 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1215 if not HasPermissionAnyApi(PermissionModel.FORKING_ENABLED)(user=apiuser):
1216 1216 raise JSONRPCForbidden()
1217 1217
1218 1218 # check if user can set owner parameter
1219 1219 owner = validate_set_owner_permissions(apiuser, owner)
1220 1220
1221 1221 description = Optional.extract(description)
1222 1222 copy_permissions = Optional.extract(copy_permissions)
1223 1223 clone_uri = Optional.extract(clone_uri)
1224 1224
1225 1225 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1226 1226 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1227 1227 ref_choices = list(set(ref_choices + [landing_ref]))
1228 1228 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1229 1229
1230 1230 private = Optional.extract(private)
1231 1231
1232 1232 schema = repo_schema.RepoSchema().bind(
1233 1233 repo_type_options=rhodecode.BACKENDS.keys(),
1234 1234 repo_ref_options=ref_choices,
1235 1235 repo_type=repo.repo_type,
1236 1236 # user caller
1237 1237 user=apiuser)
1238 1238
1239 1239 try:
1240 1240 schema_data = schema.deserialize(dict(
1241 1241 repo_name=fork_name,
1242 1242 repo_type=repo.repo_type,
1243 1243 repo_owner=owner.username,
1244 1244 repo_description=description,
1245 1245 repo_landing_commit_ref=landing_commit_ref,
1246 1246 repo_clone_uri=clone_uri,
1247 1247 repo_private=private,
1248 1248 repo_copy_permissions=copy_permissions))
1249 1249 except validation_schema.Invalid as err:
1250 1250 raise JSONRPCValidationError(colander_exc=err)
1251 1251
1252 1252 try:
1253 1253 data = {
1254 1254 'fork_parent_id': repo.repo_id,
1255 1255
1256 1256 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1257 1257 'repo_name_full': schema_data['repo_name'],
1258 1258 'repo_group': schema_data['repo_group']['repo_group_id'],
1259 1259 'repo_type': schema_data['repo_type'],
1260 1260 'description': schema_data['repo_description'],
1261 1261 'private': schema_data['repo_private'],
1262 1262 'copy_permissions': schema_data['repo_copy_permissions'],
1263 1263 'landing_rev': schema_data['repo_landing_commit_ref'],
1264 1264 }
1265 1265
1266 1266 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1267 1267 # no commit, it's done in RepoModel, or async via celery
1268 1268 task_id = get_task_id(task)
1269 1269
1270 1270 return {
1271 1271 'msg': 'Created fork of `%s` as `%s`' % (
1272 1272 repo.repo_name, schema_data['repo_name']),
1273 1273 'success': True, # cannot return the repo data here since fork
1274 1274 # can be done async
1275 1275 'task': task_id
1276 1276 }
1277 1277 except Exception:
1278 1278 log.exception(
1279 1279 u"Exception while trying to create fork %s",
1280 1280 schema_data['repo_name'])
1281 1281 raise JSONRPCError(
1282 1282 'failed to fork repository `%s` as `%s`' % (
1283 1283 repo_name, schema_data['repo_name']))
1284 1284
1285 1285
1286 1286 @jsonrpc_method()
1287 1287 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1288 1288 """
1289 1289 Deletes a repository.
1290 1290
1291 1291 * When the `forks` parameter is set it's possible to detach or delete
1292 1292 forks of deleted repository.
1293 1293
1294 1294 This command can only be run using an |authtoken| with admin
1295 1295 permissions on the |repo|.
1296 1296
1297 1297 :param apiuser: This is filled automatically from the |authtoken|.
1298 1298 :type apiuser: AuthUser
1299 1299 :param repoid: Set the repository name or repository ID.
1300 1300 :type repoid: str or int
1301 1301 :param forks: Set to `detach` or `delete` forks from the |repo|.
1302 1302 :type forks: Optional(str)
1303 1303
1304 1304 Example error output:
1305 1305
1306 1306 .. code-block:: bash
1307 1307
1308 1308 id : <id_given_in_input>
1309 1309 result: {
1310 1310 "msg": "Deleted repository `<reponame>`",
1311 1311 "success": true
1312 1312 }
1313 1313 error: null
1314 1314 """
1315 1315
1316 1316 repo = get_repo_or_error(repoid)
1317 1317 repo_name = repo.repo_name
1318 1318 if not has_superadmin_permission(apiuser):
1319 1319 _perms = ('repository.admin',)
1320 1320 validate_repo_permissions(apiuser, repoid, repo, _perms)
1321 1321
1322 1322 try:
1323 1323 handle_forks = Optional.extract(forks)
1324 1324 _forks_msg = ''
1325 1325 _forks = [f for f in repo.forks]
1326 1326 if handle_forks == 'detach':
1327 1327 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1328 1328 elif handle_forks == 'delete':
1329 1329 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1330 1330 elif _forks:
1331 1331 raise JSONRPCError(
1332 1332 'Cannot delete `%s` it still contains attached forks' %
1333 1333 (repo.repo_name,)
1334 1334 )
1335 1335 old_data = repo.get_api_data()
1336 1336 RepoModel().delete(repo, forks=forks)
1337 1337
1338 1338 repo = audit_logger.RepoWrap(repo_id=None,
1339 1339 repo_name=repo.repo_name)
1340 1340
1341 1341 audit_logger.store_api(
1342 1342 'repo.delete', action_data={'old_data': old_data},
1343 1343 user=apiuser, repo=repo)
1344 1344
1345 1345 ScmModel().mark_for_invalidation(repo_name, delete=True)
1346 1346 Session().commit()
1347 1347 return {
1348 1348 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1349 1349 'success': True
1350 1350 }
1351 1351 except Exception:
1352 1352 log.exception("Exception occurred while trying to delete repo")
1353 1353 raise JSONRPCError(
1354 1354 'failed to delete repository `%s`' % (repo_name,)
1355 1355 )
1356 1356
1357 1357
1358 1358 #TODO: marcink, change name ?
1359 1359 @jsonrpc_method()
1360 1360 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1361 1361 """
1362 1362 Invalidates the cache for the specified repository.
1363 1363
1364 1364 This command can only be run using an |authtoken| with admin rights to
1365 1365 the specified repository.
1366 1366
1367 1367 This command takes the following options:
1368 1368
1369 1369 :param apiuser: This is filled automatically from |authtoken|.
1370 1370 :type apiuser: AuthUser
1371 1371 :param repoid: Sets the repository name or repository ID.
1372 1372 :type repoid: str or int
1373 1373 :param delete_keys: This deletes the invalidated keys instead of
1374 1374 just flagging them.
1375 1375 :type delete_keys: Optional(``True`` | ``False``)
1376 1376
1377 1377 Example output:
1378 1378
1379 1379 .. code-block:: bash
1380 1380
1381 1381 id : <id_given_in_input>
1382 1382 result : {
1383 1383 'msg': Cache for repository `<repository name>` was invalidated,
1384 1384 'repository': <repository name>
1385 1385 }
1386 1386 error : null
1387 1387
1388 1388 Example error output:
1389 1389
1390 1390 .. code-block:: bash
1391 1391
1392 1392 id : <id_given_in_input>
1393 1393 result : null
1394 1394 error : {
1395 1395 'Error occurred during cache invalidation action'
1396 1396 }
1397 1397
1398 1398 """
1399 1399
1400 1400 repo = get_repo_or_error(repoid)
1401 1401 if not has_superadmin_permission(apiuser):
1402 1402 _perms = ('repository.admin', 'repository.write',)
1403 1403 validate_repo_permissions(apiuser, repoid, repo, _perms)
1404 1404
1405 1405 delete = Optional.extract(delete_keys)
1406 1406 try:
1407 1407 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1408 1408 return {
1409 1409 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1410 1410 'repository': repo.repo_name
1411 1411 }
1412 1412 except Exception:
1413 1413 log.exception(
1414 1414 "Exception occurred while trying to invalidate repo cache")
1415 1415 raise JSONRPCError(
1416 1416 'Error occurred during cache invalidation action'
1417 1417 )
1418 1418
1419 1419
1420 1420 #TODO: marcink, change name ?
1421 1421 @jsonrpc_method()
1422 1422 def lock(request, apiuser, repoid, locked=Optional(None),
1423 1423 userid=Optional(OAttr('apiuser'))):
1424 1424 """
1425 1425 Sets the lock state of the specified |repo| by the given user.
1426 1426 From more information, see :ref:`repo-locking`.
1427 1427
1428 1428 * If the ``userid`` option is not set, the repository is locked to the
1429 1429 user who called the method.
1430 1430 * If the ``locked`` parameter is not set, the current lock state of the
1431 1431 repository is displayed.
1432 1432
1433 1433 This command can only be run using an |authtoken| with admin rights to
1434 1434 the specified repository.
1435 1435
1436 1436 This command takes the following options:
1437 1437
1438 1438 :param apiuser: This is filled automatically from the |authtoken|.
1439 1439 :type apiuser: AuthUser
1440 1440 :param repoid: Sets the repository name or repository ID.
1441 1441 :type repoid: str or int
1442 1442 :param locked: Sets the lock state.
1443 1443 :type locked: Optional(``True`` | ``False``)
1444 1444 :param userid: Set the repository lock to this user.
1445 1445 :type userid: Optional(str or int)
1446 1446
1447 1447 Example error output:
1448 1448
1449 1449 .. code-block:: bash
1450 1450
1451 1451 id : <id_given_in_input>
1452 1452 result : {
1453 1453 'repo': '<reponame>',
1454 1454 'locked': <bool: lock state>,
1455 1455 'locked_since': <int: lock timestamp>,
1456 1456 'locked_by': <username of person who made the lock>,
1457 1457 'lock_reason': <str: reason for locking>,
1458 1458 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1459 1459 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1460 1460 or
1461 1461 'msg': 'Repo `<repository name>` not locked.'
1462 1462 or
1463 1463 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1464 1464 }
1465 1465 error : null
1466 1466
1467 1467 Example error output:
1468 1468
1469 1469 .. code-block:: bash
1470 1470
1471 1471 id : <id_given_in_input>
1472 1472 result : null
1473 1473 error : {
1474 1474 'Error occurred locking repository `<reponame>`'
1475 1475 }
1476 1476 """
1477 1477
1478 1478 repo = get_repo_or_error(repoid)
1479 1479 if not has_superadmin_permission(apiuser):
1480 1480 # check if we have at least write permission for this repo !
1481 1481 _perms = ('repository.admin', 'repository.write',)
1482 1482 validate_repo_permissions(apiuser, repoid, repo, _perms)
1483 1483
1484 1484 # make sure normal user does not pass someone else userid,
1485 1485 # he is not allowed to do that
1486 1486 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1487 1487 raise JSONRPCError('userid is not the same as your user')
1488 1488
1489 1489 if isinstance(userid, Optional):
1490 1490 userid = apiuser.user_id
1491 1491
1492 1492 user = get_user_or_error(userid)
1493 1493
1494 1494 if isinstance(locked, Optional):
1495 1495 lockobj = repo.locked
1496 1496
1497 1497 if lockobj[0] is None:
1498 1498 _d = {
1499 1499 'repo': repo.repo_name,
1500 1500 'locked': False,
1501 1501 'locked_since': None,
1502 1502 'locked_by': None,
1503 1503 'lock_reason': None,
1504 1504 'lock_state_changed': False,
1505 1505 'msg': 'Repo `%s` not locked.' % repo.repo_name
1506 1506 }
1507 1507 return _d
1508 1508 else:
1509 1509 _user_id, _time, _reason = lockobj
1510 1510 lock_user = get_user_or_error(userid)
1511 1511 _d = {
1512 1512 'repo': repo.repo_name,
1513 1513 'locked': True,
1514 1514 'locked_since': _time,
1515 1515 'locked_by': lock_user.username,
1516 1516 'lock_reason': _reason,
1517 1517 'lock_state_changed': False,
1518 1518 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1519 1519 % (repo.repo_name, lock_user.username,
1520 1520 json.dumps(time_to_datetime(_time))))
1521 1521 }
1522 1522 return _d
1523 1523
1524 1524 # force locked state through a flag
1525 1525 else:
1526 1526 locked = str2bool(locked)
1527 1527 lock_reason = Repository.LOCK_API
1528 1528 try:
1529 1529 if locked:
1530 1530 lock_time = time.time()
1531 1531 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1532 1532 else:
1533 1533 lock_time = None
1534 1534 Repository.unlock(repo)
1535 1535 _d = {
1536 1536 'repo': repo.repo_name,
1537 1537 'locked': locked,
1538 1538 'locked_since': lock_time,
1539 1539 'locked_by': user.username,
1540 1540 'lock_reason': lock_reason,
1541 1541 'lock_state_changed': True,
1542 1542 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1543 1543 % (user.username, repo.repo_name, locked))
1544 1544 }
1545 1545 return _d
1546 1546 except Exception:
1547 1547 log.exception(
1548 1548 "Exception occurred while trying to lock repository")
1549 1549 raise JSONRPCError(
1550 1550 'Error occurred locking repository `%s`' % repo.repo_name
1551 1551 )
1552 1552
1553 1553
1554 1554 @jsonrpc_method()
1555 1555 def comment_commit(
1556 1556 request, apiuser, repoid, commit_id, message, status=Optional(None),
1557 1557 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1558 1558 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1559 1559 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1560 1560 """
1561 1561 Set a commit comment, and optionally change the status of the commit.
1562 1562
1563 1563 :param apiuser: This is filled automatically from the |authtoken|.
1564 1564 :type apiuser: AuthUser
1565 1565 :param repoid: Set the repository name or repository ID.
1566 1566 :type repoid: str or int
1567 1567 :param commit_id: Specify the commit_id for which to set a comment.
1568 1568 :type commit_id: str
1569 1569 :param message: The comment text.
1570 1570 :type message: str
1571 1571 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1572 1572 'approved', 'rejected', 'under_review'
1573 1573 :type status: str
1574 1574 :param comment_type: Comment type, one of: 'note', 'todo'
1575 1575 :type comment_type: Optional(str), default: 'note'
1576 1576 :param resolves_comment_id: id of comment which this one will resolve
1577 1577 :type resolves_comment_id: Optional(int)
1578 1578 :param extra_recipients: list of user ids or usernames to add
1579 1579 notifications for this comment. Acts like a CC for notification
1580 1580 :type extra_recipients: Optional(list)
1581 1581 :param userid: Set the user name of the comment creator.
1582 1582 :type userid: Optional(str or int)
1583 1583 :param send_email: Define if this comment should also send email notification
1584 1584 :type send_email: Optional(bool)
1585 1585
1586 1586 Example error output:
1587 1587
1588 1588 .. code-block:: bash
1589 1589
1590 1590 {
1591 1591 "id" : <id_given_in_input>,
1592 1592 "result" : {
1593 1593 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1594 1594 "status_change": null or <status>,
1595 1595 "success": true
1596 1596 },
1597 1597 "error" : null
1598 1598 }
1599 1599
1600 1600 """
1601 1601 _ = request.translate
1602 1602
1603 1603 repo = get_repo_or_error(repoid)
1604 1604 if not has_superadmin_permission(apiuser):
1605 1605 _perms = ('repository.read', 'repository.write', 'repository.admin')
1606 1606 validate_repo_permissions(apiuser, repoid, repo, _perms)
1607 1607 db_repo_name = repo.repo_name
1608 1608
1609 1609 try:
1610 1610 commit = repo.scm_instance().get_commit(commit_id=commit_id)
1611 1611 commit_id = commit.raw_id
1612 1612 except Exception as e:
1613 1613 log.exception('Failed to fetch commit')
1614 1614 raise JSONRPCError(safe_str(e))
1615 1615
1616 1616 if isinstance(userid, Optional):
1617 1617 userid = apiuser.user_id
1618 1618
1619 1619 user = get_user_or_error(userid)
1620 1620 status = Optional.extract(status)
1621 1621 comment_type = Optional.extract(comment_type)
1622 1622 resolves_comment_id = Optional.extract(resolves_comment_id)
1623 1623 extra_recipients = Optional.extract(extra_recipients)
1624 1624 send_email = Optional.extract(send_email, binary=True)
1625 1625
1626 1626 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1627 1627 if status and status not in allowed_statuses:
1628 1628 raise JSONRPCError('Bad status, must be on '
1629 1629 'of %s got %s' % (allowed_statuses, status,))
1630 1630
1631 1631 if resolves_comment_id:
1632 1632 comment = ChangesetComment.get(resolves_comment_id)
1633 1633 if not comment:
1634 1634 raise JSONRPCError(
1635 1635 'Invalid resolves_comment_id `%s` for this commit.'
1636 1636 % resolves_comment_id)
1637 1637 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1638 1638 raise JSONRPCError(
1639 1639 'Comment `%s` is wrong type for setting status to resolved.'
1640 1640 % resolves_comment_id)
1641 1641
1642 1642 try:
1643 1643 rc_config = SettingsModel().get_all_settings()
1644 1644 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1645 1645 status_change_label = ChangesetStatus.get_status_lbl(status)
1646 1646 comment = CommentsModel().create(
1647 1647 message, repo, user, commit_id=commit_id,
1648 1648 status_change=status_change_label,
1649 1649 status_change_type=status,
1650 1650 renderer=renderer,
1651 1651 comment_type=comment_type,
1652 1652 resolves_comment_id=resolves_comment_id,
1653 1653 auth_user=apiuser,
1654 1654 extra_recipients=extra_recipients,
1655 1655 send_email=send_email
1656 1656 )
1657 1657 is_inline = comment.is_inline
1658 1658
1659 1659 if status:
1660 1660 # also do a status change
1661 1661 try:
1662 1662 ChangesetStatusModel().set_status(
1663 1663 repo, status, user, comment, revision=commit_id,
1664 1664 dont_allow_on_closed_pull_request=True
1665 1665 )
1666 1666 except StatusChangeOnClosedPullRequestError:
1667 1667 log.exception(
1668 1668 "Exception occurred while trying to change repo commit status")
1669 1669 msg = ('Changing status on a commit associated with '
1670 1670 'a closed pull request is not allowed')
1671 1671 raise JSONRPCError(msg)
1672 1672
1673 1673 CommentsModel().trigger_commit_comment_hook(
1674 1674 repo, apiuser, 'create',
1675 1675 data={'comment': comment, 'commit': commit})
1676 1676
1677 1677 Session().commit()
1678 1678
1679 1679 comment_broadcast_channel = channelstream.comment_channel(
1680 1680 db_repo_name, commit_obj=commit)
1681 1681
1682 1682 comment_data = {'comment': comment, 'comment_id': comment.comment_id}
1683 1683 comment_type = 'inline' if is_inline else 'general'
1684 1684 channelstream.comment_channelstream_push(
1685 1685 request, comment_broadcast_channel, apiuser,
1686 1686 _('posted a new {} comment').format(comment_type),
1687 1687 comment_data=comment_data)
1688 1688
1689 1689 return {
1690 1690 'msg': (
1691 1691 'Commented on commit `%s` for repository `%s`' % (
1692 1692 comment.revision, repo.repo_name)),
1693 1693 'status_change': status,
1694 1694 'success': True,
1695 1695 }
1696 1696 except JSONRPCError:
1697 1697 # catch any inside errors, and re-raise them to prevent from
1698 1698 # below global catch to silence them
1699 1699 raise
1700 1700 except Exception:
1701 1701 log.exception("Exception occurred while trying to comment on commit")
1702 1702 raise JSONRPCError(
1703 1703 'failed to set comment on repository `%s`' % (repo.repo_name,)
1704 1704 )
1705 1705
1706 1706
1707 1707 @jsonrpc_method()
1708 1708 def get_repo_comments(request, apiuser, repoid,
1709 1709 commit_id=Optional(None), comment_type=Optional(None),
1710 1710 userid=Optional(None)):
1711 1711 """
1712 1712 Get all comments for a repository
1713 1713
1714 1714 :param apiuser: This is filled automatically from the |authtoken|.
1715 1715 :type apiuser: AuthUser
1716 1716 :param repoid: Set the repository name or repository ID.
1717 1717 :type repoid: str or int
1718 1718 :param commit_id: Optionally filter the comments by the commit_id
1719 1719 :type commit_id: Optional(str), default: None
1720 1720 :param comment_type: Optionally filter the comments by the comment_type
1721 1721 one of: 'note', 'todo'
1722 1722 :type comment_type: Optional(str), default: None
1723 1723 :param userid: Optionally filter the comments by the author of comment
1724 1724 :type userid: Optional(str or int), Default: None
1725 1725
1726 1726 Example error output:
1727 1727
1728 1728 .. code-block:: bash
1729 1729
1730 1730 {
1731 1731 "id" : <id_given_in_input>,
1732 1732 "result" : [
1733 1733 {
1734 1734 "comment_author": <USER_DETAILS>,
1735 1735 "comment_created_on": "2017-02-01T14:38:16.309",
1736 1736 "comment_f_path": "file.txt",
1737 1737 "comment_id": 282,
1738 1738 "comment_lineno": "n1",
1739 1739 "comment_resolved_by": null,
1740 1740 "comment_status": [],
1741 1741 "comment_text": "This file needs a header",
1742 1742 "comment_type": "todo",
1743 1743 "comment_last_version: 0
1744 1744 }
1745 1745 ],
1746 1746 "error" : null
1747 1747 }
1748 1748
1749 1749 """
1750 1750 repo = get_repo_or_error(repoid)
1751 1751 if not has_superadmin_permission(apiuser):
1752 1752 _perms = ('repository.read', 'repository.write', 'repository.admin')
1753 1753 validate_repo_permissions(apiuser, repoid, repo, _perms)
1754 1754
1755 1755 commit_id = Optional.extract(commit_id)
1756 1756
1757 1757 userid = Optional.extract(userid)
1758 1758 if userid:
1759 1759 user = get_user_or_error(userid)
1760 1760 else:
1761 1761 user = None
1762 1762
1763 1763 comment_type = Optional.extract(comment_type)
1764 1764 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1765 1765 raise JSONRPCError(
1766 1766 'comment_type must be one of `{}` got {}'.format(
1767 1767 ChangesetComment.COMMENT_TYPES, comment_type)
1768 1768 )
1769 1769
1770 1770 comments = CommentsModel().get_repository_comments(
1771 1771 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1772 1772 return comments
1773 1773
1774 1774
1775 1775 @jsonrpc_method()
1776 1776 def get_comment(request, apiuser, comment_id):
1777 1777 """
1778 1778 Get single comment from repository or pull_request
1779 1779
1780 1780 :param apiuser: This is filled automatically from the |authtoken|.
1781 1781 :type apiuser: AuthUser
1782 1782 :param comment_id: comment id found in the URL of comment
1783 1783 :type comment_id: str or int
1784 1784
1785 1785 Example error output:
1786 1786
1787 1787 .. code-block:: bash
1788 1788
1789 1789 {
1790 1790 "id" : <id_given_in_input>,
1791 1791 "result" : {
1792 1792 "comment_author": <USER_DETAILS>,
1793 1793 "comment_created_on": "2017-02-01T14:38:16.309",
1794 1794 "comment_f_path": "file.txt",
1795 1795 "comment_id": 282,
1796 1796 "comment_lineno": "n1",
1797 1797 "comment_resolved_by": null,
1798 1798 "comment_status": [],
1799 1799 "comment_text": "This file needs a header",
1800 1800 "comment_type": "todo",
1801 1801 "comment_last_version: 0
1802 1802 },
1803 1803 "error" : null
1804 1804 }
1805 1805
1806 1806 """
1807 1807
1808 1808 comment = ChangesetComment.get(comment_id)
1809 1809 if not comment:
1810 1810 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1811 1811
1812 1812 perms = ('repository.read', 'repository.write', 'repository.admin')
1813 1813 has_comment_perm = HasRepoPermissionAnyApi(*perms)\
1814 1814 (user=apiuser, repo_name=comment.repo.repo_name)
1815 1815
1816 1816 if not has_comment_perm:
1817 1817 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1818 1818
1819 1819 return comment
1820 1820
1821 1821
1822 1822 @jsonrpc_method()
1823 1823 def edit_comment(request, apiuser, message, comment_id, version,
1824 1824 userid=Optional(OAttr('apiuser'))):
1825 1825 """
1826 1826 Edit comment on the pull request or commit,
1827 1827 specified by the `comment_id` and version. Initially version should be 0
1828 1828
1829 1829 :param apiuser: This is filled automatically from the |authtoken|.
1830 1830 :type apiuser: AuthUser
1831 1831 :param comment_id: Specify the comment_id for editing
1832 1832 :type comment_id: int
1833 1833 :param version: version of the comment that will be created, starts from 0
1834 1834 :type version: int
1835 1835 :param message: The text content of the comment.
1836 1836 :type message: str
1837 1837 :param userid: Comment on the pull request as this user
1838 1838 :type userid: Optional(str or int)
1839 1839
1840 1840 Example output:
1841 1841
1842 1842 .. code-block:: bash
1843 1843
1844 1844 id : <id_given_in_input>
1845 1845 result : {
1846 1846 "comment": "<comment data>",
1847 1847 "version": "<Integer>",
1848 1848 },
1849 1849 error : null
1850 1850 """
1851 1851
1852 1852 auth_user = apiuser
1853 1853 comment = ChangesetComment.get(comment_id)
1854 1854 if not comment:
1855 1855 raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1856 1856
1857 1857 is_super_admin = has_superadmin_permission(apiuser)
1858 1858 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1859 1859 (user=apiuser, repo_name=comment.repo.repo_name)
1860 1860
1861 1861 if not isinstance(userid, Optional):
1862 1862 if is_super_admin or is_repo_admin:
1863 1863 apiuser = get_user_or_error(userid)
1864 1864 auth_user = apiuser.AuthUser()
1865 1865 else:
1866 1866 raise JSONRPCError('userid is not the same as your user')
1867 1867
1868 1868 comment_author = comment.author.user_id == auth_user.user_id
1869 1869 if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1870 1870 raise JSONRPCError("you don't have access to edit this comment")
1871 1871
1872 1872 try:
1873 1873 comment_history = CommentsModel().edit(
1874 1874 comment_id=comment_id,
1875 1875 text=message,
1876 1876 auth_user=auth_user,
1877 1877 version=version,
1878 1878 )
1879 1879 Session().commit()
1880 1880 except CommentVersionMismatch:
1881 1881 raise JSONRPCError(
1882 1882 'comment ({}) version ({}) mismatch'.format(comment_id, version)
1883 1883 )
1884 1884 if not comment_history and not message:
1885 1885 raise JSONRPCError(
1886 1886 "comment ({}) can't be changed with empty string".format(comment_id)
1887 1887 )
1888 1888
1889 1889 if comment.pull_request:
1890 1890 pull_request = comment.pull_request
1891 1891 PullRequestModel().trigger_pull_request_hook(
1892 1892 pull_request, apiuser, 'comment_edit',
1893 1893 data={'comment': comment})
1894 1894 else:
1895 1895 db_repo = comment.repo
1896 1896 commit_id = comment.revision
1897 1897 commit = db_repo.get_commit(commit_id)
1898 1898 CommentsModel().trigger_commit_comment_hook(
1899 1899 db_repo, apiuser, 'edit',
1900 1900 data={'comment': comment, 'commit': commit})
1901 1901
1902 1902 data = {
1903 1903 'comment': comment,
1904 1904 'version': comment_history.version if comment_history else None,
1905 1905 }
1906 1906 return data
1907 1907
1908 1908
1909 1909 # TODO(marcink): write this with all required logic for deleting a comments in PR or commits
1910 1910 # @jsonrpc_method()
1911 1911 # def delete_comment(request, apiuser, comment_id):
1912 1912 # auth_user = apiuser
1913 1913 #
1914 1914 # comment = ChangesetComment.get(comment_id)
1915 1915 # if not comment:
1916 1916 # raise JSONRPCError('comment `%s` does not exist' % (comment_id,))
1917 1917 #
1918 1918 # is_super_admin = has_superadmin_permission(apiuser)
1919 1919 # is_repo_admin = HasRepoPermissionAnyApi('repository.admin')\
1920 1920 # (user=apiuser, repo_name=comment.repo.repo_name)
1921 1921 #
1922 1922 # comment_author = comment.author.user_id == auth_user.user_id
1923 1923 # if not (comment.immutable is False and (is_super_admin or is_repo_admin) or comment_author):
1924 1924 # raise JSONRPCError("you don't have access to edit this comment")
1925 1925
1926 1926 @jsonrpc_method()
1927 1927 def grant_user_permission(request, apiuser, repoid, userid, perm):
1928 1928 """
1929 1929 Grant permissions for the specified user on the given repository,
1930 1930 or update existing permissions if found.
1931 1931
1932 1932 This command can only be run using an |authtoken| with admin
1933 1933 permissions on the |repo|.
1934 1934
1935 1935 :param apiuser: This is filled automatically from the |authtoken|.
1936 1936 :type apiuser: AuthUser
1937 1937 :param repoid: Set the repository name or repository ID.
1938 1938 :type repoid: str or int
1939 1939 :param userid: Set the user name.
1940 1940 :type userid: str
1941 1941 :param perm: Set the user permissions, using the following format
1942 1942 ``(repository.(none|read|write|admin))``
1943 1943 :type perm: str
1944 1944
1945 1945 Example output:
1946 1946
1947 1947 .. code-block:: bash
1948 1948
1949 1949 id : <id_given_in_input>
1950 1950 result: {
1951 1951 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1952 1952 "success": true
1953 1953 }
1954 1954 error: null
1955 1955 """
1956 1956
1957 1957 repo = get_repo_or_error(repoid)
1958 1958 user = get_user_or_error(userid)
1959 1959 perm = get_perm_or_error(perm)
1960 1960 if not has_superadmin_permission(apiuser):
1961 1961 _perms = ('repository.admin',)
1962 1962 validate_repo_permissions(apiuser, repoid, repo, _perms)
1963 1963
1964 1964 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1965 1965 try:
1966 1966 changes = RepoModel().update_permissions(
1967 1967 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1968 1968
1969 1969 action_data = {
1970 1970 'added': changes['added'],
1971 1971 'updated': changes['updated'],
1972 1972 'deleted': changes['deleted'],
1973 1973 }
1974 1974 audit_logger.store_api(
1975 1975 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1976 1976 Session().commit()
1977 1977 PermissionModel().flush_user_permission_caches(changes)
1978 1978
1979 1979 return {
1980 1980 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1981 1981 perm.permission_name, user.username, repo.repo_name
1982 1982 ),
1983 1983 'success': True
1984 1984 }
1985 1985 except Exception:
1986 1986 log.exception("Exception occurred while trying edit permissions for repo")
1987 1987 raise JSONRPCError(
1988 1988 'failed to edit permission for user: `%s` in repo: `%s`' % (
1989 1989 userid, repoid
1990 1990 )
1991 1991 )
1992 1992
1993 1993
1994 1994 @jsonrpc_method()
1995 1995 def revoke_user_permission(request, apiuser, repoid, userid):
1996 1996 """
1997 1997 Revoke permission for a user on the specified repository.
1998 1998
1999 1999 This command can only be run using an |authtoken| with admin
2000 2000 permissions on the |repo|.
2001 2001
2002 2002 :param apiuser: This is filled automatically from the |authtoken|.
2003 2003 :type apiuser: AuthUser
2004 2004 :param repoid: Set the repository name or repository ID.
2005 2005 :type repoid: str or int
2006 2006 :param userid: Set the user name of revoked user.
2007 2007 :type userid: str or int
2008 2008
2009 2009 Example error output:
2010 2010
2011 2011 .. code-block:: bash
2012 2012
2013 2013 id : <id_given_in_input>
2014 2014 result: {
2015 2015 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
2016 2016 "success": true
2017 2017 }
2018 2018 error: null
2019 2019 """
2020 2020
2021 2021 repo = get_repo_or_error(repoid)
2022 2022 user = get_user_or_error(userid)
2023 2023 if not has_superadmin_permission(apiuser):
2024 2024 _perms = ('repository.admin',)
2025 2025 validate_repo_permissions(apiuser, repoid, repo, _perms)
2026 2026
2027 2027 perm_deletions = [[user.user_id, None, "user"]]
2028 2028 try:
2029 2029 changes = RepoModel().update_permissions(
2030 2030 repo=repo, perm_deletions=perm_deletions, cur_user=user)
2031 2031
2032 2032 action_data = {
2033 2033 'added': changes['added'],
2034 2034 'updated': changes['updated'],
2035 2035 'deleted': changes['deleted'],
2036 2036 }
2037 2037 audit_logger.store_api(
2038 2038 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2039 2039 Session().commit()
2040 2040 PermissionModel().flush_user_permission_caches(changes)
2041 2041
2042 2042 return {
2043 2043 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
2044 2044 user.username, repo.repo_name
2045 2045 ),
2046 2046 'success': True
2047 2047 }
2048 2048 except Exception:
2049 2049 log.exception("Exception occurred while trying revoke permissions to repo")
2050 2050 raise JSONRPCError(
2051 2051 'failed to edit permission for user: `%s` in repo: `%s`' % (
2052 2052 userid, repoid
2053 2053 )
2054 2054 )
2055 2055
2056 2056
2057 2057 @jsonrpc_method()
2058 2058 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
2059 2059 """
2060 2060 Grant permission for a user group on the specified repository,
2061 2061 or update existing permissions.
2062 2062
2063 2063 This command can only be run using an |authtoken| with admin
2064 2064 permissions on the |repo|.
2065 2065
2066 2066 :param apiuser: This is filled automatically from the |authtoken|.
2067 2067 :type apiuser: AuthUser
2068 2068 :param repoid: Set the repository name or repository ID.
2069 2069 :type repoid: str or int
2070 2070 :param usergroupid: Specify the ID of the user group.
2071 2071 :type usergroupid: str or int
2072 2072 :param perm: Set the user group permissions using the following
2073 2073 format: (repository.(none|read|write|admin))
2074 2074 :type perm: str
2075 2075
2076 2076 Example output:
2077 2077
2078 2078 .. code-block:: bash
2079 2079
2080 2080 id : <id_given_in_input>
2081 2081 result : {
2082 2082 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2083 2083 "success": true
2084 2084
2085 2085 }
2086 2086 error : null
2087 2087
2088 2088 Example error output:
2089 2089
2090 2090 .. code-block:: bash
2091 2091
2092 2092 id : <id_given_in_input>
2093 2093 result : null
2094 2094 error : {
2095 2095 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2096 2096 }
2097 2097
2098 2098 """
2099 2099
2100 2100 repo = get_repo_or_error(repoid)
2101 2101 perm = get_perm_or_error(perm)
2102 2102 if not has_superadmin_permission(apiuser):
2103 2103 _perms = ('repository.admin',)
2104 2104 validate_repo_permissions(apiuser, repoid, repo, _perms)
2105 2105
2106 2106 user_group = get_user_group_or_error(usergroupid)
2107 2107 if not has_superadmin_permission(apiuser):
2108 2108 # check if we have at least read permission for this user group !
2109 2109 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2110 2110 if not HasUserGroupPermissionAnyApi(*_perms)(
2111 2111 user=apiuser, user_group_name=user_group.users_group_name):
2112 2112 raise JSONRPCError(
2113 2113 'user group `%s` does not exist' % (usergroupid,))
2114 2114
2115 2115 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
2116 2116 try:
2117 2117 changes = RepoModel().update_permissions(
2118 2118 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
2119 2119 action_data = {
2120 2120 'added': changes['added'],
2121 2121 'updated': changes['updated'],
2122 2122 'deleted': changes['deleted'],
2123 2123 }
2124 2124 audit_logger.store_api(
2125 2125 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2126 2126 Session().commit()
2127 2127 PermissionModel().flush_user_permission_caches(changes)
2128 2128
2129 2129 return {
2130 2130 'msg': 'Granted perm: `%s` for user group: `%s` in '
2131 2131 'repo: `%s`' % (
2132 2132 perm.permission_name, user_group.users_group_name,
2133 2133 repo.repo_name
2134 2134 ),
2135 2135 'success': True
2136 2136 }
2137 2137 except Exception:
2138 2138 log.exception(
2139 2139 "Exception occurred while trying change permission on repo")
2140 2140 raise JSONRPCError(
2141 2141 'failed to edit permission for user group: `%s` in '
2142 2142 'repo: `%s`' % (
2143 2143 usergroupid, repo.repo_name
2144 2144 )
2145 2145 )
2146 2146
2147 2147
2148 2148 @jsonrpc_method()
2149 2149 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
2150 2150 """
2151 2151 Revoke the permissions of a user group on a given repository.
2152 2152
2153 2153 This command can only be run using an |authtoken| with admin
2154 2154 permissions on the |repo|.
2155 2155
2156 2156 :param apiuser: This is filled automatically from the |authtoken|.
2157 2157 :type apiuser: AuthUser
2158 2158 :param repoid: Set the repository name or repository ID.
2159 2159 :type repoid: str or int
2160 2160 :param usergroupid: Specify the user group ID.
2161 2161 :type usergroupid: str or int
2162 2162
2163 2163 Example output:
2164 2164
2165 2165 .. code-block:: bash
2166 2166
2167 2167 id : <id_given_in_input>
2168 2168 result: {
2169 2169 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2170 2170 "success": true
2171 2171 }
2172 2172 error: null
2173 2173 """
2174 2174
2175 2175 repo = get_repo_or_error(repoid)
2176 2176 if not has_superadmin_permission(apiuser):
2177 2177 _perms = ('repository.admin',)
2178 2178 validate_repo_permissions(apiuser, repoid, repo, _perms)
2179 2179
2180 2180 user_group = get_user_group_or_error(usergroupid)
2181 2181 if not has_superadmin_permission(apiuser):
2182 2182 # check if we have at least read permission for this user group !
2183 2183 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2184 2184 if not HasUserGroupPermissionAnyApi(*_perms)(
2185 2185 user=apiuser, user_group_name=user_group.users_group_name):
2186 2186 raise JSONRPCError(
2187 2187 'user group `%s` does not exist' % (usergroupid,))
2188 2188
2189 2189 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2190 2190 try:
2191 2191 changes = RepoModel().update_permissions(
2192 2192 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2193 2193 action_data = {
2194 2194 'added': changes['added'],
2195 2195 'updated': changes['updated'],
2196 2196 'deleted': changes['deleted'],
2197 2197 }
2198 2198 audit_logger.store_api(
2199 2199 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2200 2200 Session().commit()
2201 2201 PermissionModel().flush_user_permission_caches(changes)
2202 2202
2203 2203 return {
2204 2204 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2205 2205 user_group.users_group_name, repo.repo_name
2206 2206 ),
2207 2207 'success': True
2208 2208 }
2209 2209 except Exception:
2210 2210 log.exception("Exception occurred while trying revoke "
2211 2211 "user group permission on repo")
2212 2212 raise JSONRPCError(
2213 2213 'failed to edit permission for user group: `%s` in '
2214 2214 'repo: `%s`' % (
2215 2215 user_group.users_group_name, repo.repo_name
2216 2216 )
2217 2217 )
2218 2218
2219 2219
2220 2220 @jsonrpc_method()
2221 2221 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2222 2222 """
2223 2223 Triggers a pull on the given repository from a remote location. You
2224 2224 can use this to keep remote repositories up-to-date.
2225 2225
2226 2226 This command can only be run using an |authtoken| with admin
2227 2227 rights to the specified repository. For more information,
2228 2228 see :ref:`config-token-ref`.
2229 2229
2230 2230 This command takes the following options:
2231 2231
2232 2232 :param apiuser: This is filled automatically from the |authtoken|.
2233 2233 :type apiuser: AuthUser
2234 2234 :param repoid: The repository name or repository ID.
2235 2235 :type repoid: str or int
2236 2236 :param remote_uri: Optional remote URI to pass in for pull
2237 2237 :type remote_uri: str
2238 2238
2239 2239 Example output:
2240 2240
2241 2241 .. code-block:: bash
2242 2242
2243 2243 id : <id_given_in_input>
2244 2244 result : {
2245 2245 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2246 2246 "repository": "<repository name>"
2247 2247 }
2248 2248 error : null
2249 2249
2250 2250 Example error output:
2251 2251
2252 2252 .. code-block:: bash
2253 2253
2254 2254 id : <id_given_in_input>
2255 2255 result : null
2256 2256 error : {
2257 2257 "Unable to push changes from `<remote_url>`"
2258 2258 }
2259 2259
2260 2260 """
2261 2261
2262 2262 repo = get_repo_or_error(repoid)
2263 2263 remote_uri = Optional.extract(remote_uri)
2264 2264 remote_uri_display = remote_uri or repo.clone_uri_hidden
2265 2265 if not has_superadmin_permission(apiuser):
2266 2266 _perms = ('repository.admin',)
2267 2267 validate_repo_permissions(apiuser, repoid, repo, _perms)
2268 2268
2269 2269 try:
2270 2270 ScmModel().pull_changes(
2271 2271 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2272 2272 return {
2273 2273 'msg': 'Pulled from url `%s` on repo `%s`' % (
2274 2274 remote_uri_display, repo.repo_name),
2275 2275 'repository': repo.repo_name
2276 2276 }
2277 2277 except Exception:
2278 2278 log.exception("Exception occurred while trying to "
2279 2279 "pull changes from remote location")
2280 2280 raise JSONRPCError(
2281 2281 'Unable to pull changes from `%s`' % remote_uri_display
2282 2282 )
2283 2283
2284 2284
2285 2285 @jsonrpc_method()
2286 2286 def strip(request, apiuser, repoid, revision, branch):
2287 2287 """
2288 2288 Strips the given revision from the specified repository.
2289 2289
2290 2290 * This will remove the revision and all of its decendants.
2291 2291
2292 2292 This command can only be run using an |authtoken| with admin rights to
2293 2293 the specified repository.
2294 2294
2295 2295 This command takes the following options:
2296 2296
2297 2297 :param apiuser: This is filled automatically from the |authtoken|.
2298 2298 :type apiuser: AuthUser
2299 2299 :param repoid: The repository name or repository ID.
2300 2300 :type repoid: str or int
2301 2301 :param revision: The revision you wish to strip.
2302 2302 :type revision: str
2303 2303 :param branch: The branch from which to strip the revision.
2304 2304 :type branch: str
2305 2305
2306 2306 Example output:
2307 2307
2308 2308 .. code-block:: bash
2309 2309
2310 2310 id : <id_given_in_input>
2311 2311 result : {
2312 2312 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2313 2313 "repository": "<repository name>"
2314 2314 }
2315 2315 error : null
2316 2316
2317 2317 Example error output:
2318 2318
2319 2319 .. code-block:: bash
2320 2320
2321 2321 id : <id_given_in_input>
2322 2322 result : null
2323 2323 error : {
2324 2324 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2325 2325 }
2326 2326
2327 2327 """
2328 2328
2329 2329 repo = get_repo_or_error(repoid)
2330 2330 if not has_superadmin_permission(apiuser):
2331 2331 _perms = ('repository.admin',)
2332 2332 validate_repo_permissions(apiuser, repoid, repo, _perms)
2333 2333
2334 2334 try:
2335 2335 ScmModel().strip(repo, revision, branch)
2336 2336 audit_logger.store_api(
2337 2337 'repo.commit.strip', action_data={'commit_id': revision},
2338 2338 repo=repo,
2339 2339 user=apiuser, commit=True)
2340 2340
2341 2341 return {
2342 2342 'msg': 'Stripped commit %s from repo `%s`' % (
2343 2343 revision, repo.repo_name),
2344 2344 'repository': repo.repo_name
2345 2345 }
2346 2346 except Exception:
2347 2347 log.exception("Exception while trying to strip")
2348 2348 raise JSONRPCError(
2349 2349 'Unable to strip commit %s from repo `%s`' % (
2350 2350 revision, repo.repo_name)
2351 2351 )
2352 2352
2353 2353
2354 2354 @jsonrpc_method()
2355 2355 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2356 2356 """
2357 2357 Returns all settings for a repository. If key is given it only returns the
2358 2358 setting identified by the key or null.
2359 2359
2360 2360 :param apiuser: This is filled automatically from the |authtoken|.
2361 2361 :type apiuser: AuthUser
2362 2362 :param repoid: The repository name or repository id.
2363 2363 :type repoid: str or int
2364 2364 :param key: Key of the setting to return.
2365 2365 :type: key: Optional(str)
2366 2366
2367 2367 Example output:
2368 2368
2369 2369 .. code-block:: bash
2370 2370
2371 2371 {
2372 2372 "error": null,
2373 2373 "id": 237,
2374 2374 "result": {
2375 2375 "extensions_largefiles": true,
2376 2376 "extensions_evolve": true,
2377 2377 "hooks_changegroup_push_logger": true,
2378 2378 "hooks_changegroup_repo_size": false,
2379 2379 "hooks_outgoing_pull_logger": true,
2380 2380 "phases_publish": "True",
2381 2381 "rhodecode_hg_use_rebase_for_merging": true,
2382 2382 "rhodecode_pr_merge_enabled": true,
2383 2383 "rhodecode_use_outdated_comments": true
2384 2384 }
2385 2385 }
2386 2386 """
2387 2387
2388 2388 # Restrict access to this api method to super-admins, and repo admins only.
2389 2389 repo = get_repo_or_error(repoid)
2390 2390 if not has_superadmin_permission(apiuser):
2391 2391 _perms = ('repository.admin',)
2392 2392 validate_repo_permissions(apiuser, repoid, repo, _perms)
2393 2393
2394 2394 try:
2395 2395 settings_model = VcsSettingsModel(repo=repo)
2396 2396 settings = settings_model.get_global_settings()
2397 2397 settings.update(settings_model.get_repo_settings())
2398 2398
2399 2399 # If only a single setting is requested fetch it from all settings.
2400 2400 key = Optional.extract(key)
2401 2401 if key is not None:
2402 2402 settings = settings.get(key, None)
2403 2403 except Exception:
2404 2404 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2405 2405 log.exception(msg)
2406 2406 raise JSONRPCError(msg)
2407 2407
2408 2408 return settings
2409 2409
2410 2410
2411 2411 @jsonrpc_method()
2412 2412 def set_repo_settings(request, apiuser, repoid, settings):
2413 2413 """
2414 2414 Update repository settings. Returns true on success.
2415 2415
2416 2416 :param apiuser: This is filled automatically from the |authtoken|.
2417 2417 :type apiuser: AuthUser
2418 2418 :param repoid: The repository name or repository id.
2419 2419 :type repoid: str or int
2420 2420 :param settings: The new settings for the repository.
2421 2421 :type: settings: dict
2422 2422
2423 2423 Example output:
2424 2424
2425 2425 .. code-block:: bash
2426 2426
2427 2427 {
2428 2428 "error": null,
2429 2429 "id": 237,
2430 2430 "result": true
2431 2431 }
2432 2432 """
2433 2433 # Restrict access to this api method to super-admins, and repo admins only.
2434 2434 repo = get_repo_or_error(repoid)
2435 2435 if not has_superadmin_permission(apiuser):
2436 2436 _perms = ('repository.admin',)
2437 2437 validate_repo_permissions(apiuser, repoid, repo, _perms)
2438 2438
2439 2439 if type(settings) is not dict:
2440 2440 raise JSONRPCError('Settings have to be a JSON Object.')
2441 2441
2442 2442 try:
2443 2443 settings_model = VcsSettingsModel(repo=repoid)
2444 2444
2445 2445 # Merge global, repo and incoming settings.
2446 2446 new_settings = settings_model.get_global_settings()
2447 2447 new_settings.update(settings_model.get_repo_settings())
2448 2448 new_settings.update(settings)
2449 2449
2450 2450 # Update the settings.
2451 2451 inherit_global_settings = new_settings.get(
2452 2452 'inherit_global_settings', False)
2453 2453 settings_model.create_or_update_repo_settings(
2454 2454 new_settings, inherit_global_settings=inherit_global_settings)
2455 2455 Session().commit()
2456 2456 except Exception:
2457 2457 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2458 2458 log.exception(msg)
2459 2459 raise JSONRPCError(msg)
2460 2460
2461 2461 # Indicate success.
2462 2462 return True
2463 2463
2464 2464
2465 2465 @jsonrpc_method()
2466 2466 def maintenance(request, apiuser, repoid):
2467 2467 """
2468 2468 Triggers a maintenance on the given repository.
2469 2469
2470 2470 This command can only be run using an |authtoken| with admin
2471 2471 rights to the specified repository. For more information,
2472 2472 see :ref:`config-token-ref`.
2473 2473
2474 2474 This command takes the following options:
2475 2475
2476 2476 :param apiuser: This is filled automatically from the |authtoken|.
2477 2477 :type apiuser: AuthUser
2478 2478 :param repoid: The repository name or repository ID.
2479 2479 :type repoid: str or int
2480 2480
2481 2481 Example output:
2482 2482
2483 2483 .. code-block:: bash
2484 2484
2485 2485 id : <id_given_in_input>
2486 2486 result : {
2487 2487 "msg": "executed maintenance command",
2488 2488 "executed_actions": [
2489 2489 <action_message>, <action_message2>...
2490 2490 ],
2491 2491 "repository": "<repository name>"
2492 2492 }
2493 2493 error : null
2494 2494
2495 2495 Example error output:
2496 2496
2497 2497 .. code-block:: bash
2498 2498
2499 2499 id : <id_given_in_input>
2500 2500 result : null
2501 2501 error : {
2502 2502 "Unable to execute maintenance on `<reponame>`"
2503 2503 }
2504 2504
2505 2505 """
2506 2506
2507 2507 repo = get_repo_or_error(repoid)
2508 2508 if not has_superadmin_permission(apiuser):
2509 2509 _perms = ('repository.admin',)
2510 2510 validate_repo_permissions(apiuser, repoid, repo, _perms)
2511 2511
2512 2512 try:
2513 2513 maintenance = repo_maintenance.RepoMaintenance()
2514 2514 executed_actions = maintenance.execute(repo)
2515 2515
2516 2516 return {
2517 2517 'msg': 'executed maintenance command',
2518 2518 'executed_actions': executed_actions,
2519 2519 'repository': repo.repo_name
2520 2520 }
2521 2521 except Exception:
2522 2522 log.exception("Exception occurred while trying to run maintenance")
2523 2523 raise JSONRPCError(
2524 2524 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,254 +1,254 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2020 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
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 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.forms import RepoForkForm
42 42 from rhodecode.model.scm import ScmModel, RepoGroupList
43 43 from rhodecode.lib.utils2 import safe_int, safe_unicode
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoForksView(RepoAppView, DataGridAppView):
49 49
50 50 def load_default_context(self):
51 51 c = self._get_local_tmpl_context(include_app_defaults=True)
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53
54 54 acl_groups = RepoGroupList(
55 55 RepoGroup.query().all(),
56 56 perm_set=['group.write', 'group.admin'])
57 57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59 59
60 60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
61 61
62 62 return c
63 63
64 64 @LoginRequired()
65 65 @HasRepoPermissionAnyDecorator(
66 66 'repository.read', 'repository.write', 'repository.admin')
67 67 def repo_forks_show_all(self):
68 68 c = self.load_default_context()
69 69 return self._get_template_context(c)
70 70
71 71 @LoginRequired()
72 72 @HasRepoPermissionAnyDecorator(
73 73 'repository.read', 'repository.write', 'repository.admin')
74 74 def repo_forks_data(self):
75 75 _ = self.request.translate
76 76 self.load_default_context()
77 77 column_map = {
78 78 'fork_name': 'repo_name',
79 79 'fork_date': 'created_on',
80 80 'last_activity': 'updated_on'
81 81 }
82 82 draw, start, limit = self._extract_chunk(self.request)
83 83 search_q, order_by, order_dir = self._extract_ordering(
84 84 self.request, column_map=column_map)
85 85
86 86 acl_check = HasRepoPermissionAny(
87 87 'repository.read', 'repository.write', 'repository.admin')
88 88 repo_id = self.db_repo.repo_id
89 89 allowed_ids = [-1]
90 90 for f in Repository.query().filter(Repository.fork_id == repo_id):
91 91 if acl_check(f.repo_name, 'get forks check'):
92 92 allowed_ids.append(f.repo_id)
93 93
94 94 forks_data_total_count = Repository.query()\
95 95 .filter(Repository.fork_id == repo_id)\
96 96 .filter(Repository.repo_id.in_(allowed_ids))\
97 97 .count()
98 98
99 99 # json generate
100 100 base_q = Repository.query()\
101 101 .filter(Repository.fork_id == repo_id)\
102 102 .filter(Repository.repo_id.in_(allowed_ids))\
103 103
104 104 if search_q:
105 105 like_expression = u'%{}%'.format(safe_unicode(search_q))
106 106 base_q = base_q.filter(or_(
107 107 Repository.repo_name.ilike(like_expression),
108 108 Repository.description.ilike(like_expression),
109 109 ))
110 110
111 111 forks_data_total_filtered_count = base_q.count()
112 112
113 113 sort_col = getattr(Repository, order_by, None)
114 114 if sort_col:
115 115 if order_dir == 'asc':
116 116 # handle null values properly to order by NULL last
117 117 if order_by in ['last_activity']:
118 118 sort_col = coalesce(sort_col, datetime.date.max)
119 119 sort_col = sort_col.asc()
120 120 else:
121 121 # handle null values properly to order by NULL last
122 122 if order_by in ['last_activity']:
123 123 sort_col = coalesce(sort_col, datetime.date.min)
124 124 sort_col = sort_col.desc()
125 125
126 126 base_q = base_q.order_by(sort_col)
127 127 base_q = base_q.offset(start).limit(limit)
128 128
129 129 fork_list = base_q.all()
130 130
131 131 def fork_actions(fork):
132 132 url_link = h.route_path(
133 133 'repo_compare',
134 134 repo_name=fork.repo_name,
135 135 source_ref_type=self.db_repo.landing_ref_type,
136 136 source_ref=self.db_repo.landing_ref_name,
137 137 target_ref_type=self.db_repo.landing_ref_type,
138 138 target_ref=self.db_repo.landing_ref_name,
139 139 _query=dict(merge=1, target_repo=f.repo_name))
140 140 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
141 141
142 142 def fork_name(fork):
143 143 return h.link_to(fork.repo_name,
144 144 h.route_path('repo_summary', repo_name=fork.repo_name))
145 145
146 146 forks_data = []
147 147 for fork in fork_list:
148 148 forks_data.append({
149 149 "username": h.gravatar_with_user(self.request, fork.user.username),
150 150 "fork_name": fork_name(fork),
151 151 "description": fork.description_safe,
152 152 "fork_date": h.age_component(fork.created_on, time_is_local=True),
153 153 "last_activity": h.format_date(fork.updated_on),
154 154 "action": fork_actions(fork),
155 155 })
156 156
157 157 data = ({
158 158 'draw': draw,
159 159 'data': forks_data,
160 160 'recordsTotal': forks_data_total_count,
161 161 'recordsFiltered': forks_data_total_filtered_count,
162 162 })
163 163
164 164 return data
165 165
166 166 @LoginRequired()
167 167 @NotAnonymous()
168 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
168 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
169 169 @HasRepoPermissionAnyDecorator(
170 170 'repository.read', 'repository.write', 'repository.admin')
171 171 def repo_fork_new(self):
172 172 c = self.load_default_context()
173 173
174 174 defaults = RepoModel()._get_defaults(self.db_repo_name)
175 175 # alter the description to indicate a fork
176 176 defaults['description'] = (
177 177 'fork of repository: %s \n%s' % (
178 178 defaults['repo_name'], defaults['description']))
179 179 # add suffix to fork
180 180 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
181 181
182 182 data = render('rhodecode:templates/forks/fork.mako',
183 183 self._get_template_context(c), self.request)
184 184 html = formencode.htmlfill.render(
185 185 data,
186 186 defaults=defaults,
187 187 encoding="UTF-8",
188 188 force_defaults=False
189 189 )
190 190 return Response(html)
191 191
192 192 @LoginRequired()
193 193 @NotAnonymous()
194 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
194 @HasPermissionAnyDecorator('hg.admin', PermissionModel.FORKING_ENABLED)
195 195 @HasRepoPermissionAnyDecorator(
196 196 'repository.read', 'repository.write', 'repository.admin')
197 197 @CSRFRequired()
198 198 def repo_fork_create(self):
199 199 _ = self.request.translate
200 200 c = self.load_default_context()
201 201
202 202 _form = RepoForkForm(self.request.translate,
203 203 old_data={'repo_type': self.db_repo.repo_type},
204 204 repo_groups=c.repo_groups_choices)()
205 205 post_data = dict(self.request.POST)
206 206
207 207 # forbid injecting other repo by forging a request
208 208 post_data['fork_parent_id'] = self.db_repo.repo_id
209 209 post_data['landing_rev'] = self.db_repo._landing_revision
210 210
211 211 form_result = {}
212 212 task_id = None
213 213 try:
214 214 form_result = _form.to_python(post_data)
215 215 copy_permissions = form_result.get('copy_permissions')
216 216 # create fork is done sometimes async on celery, db transaction
217 217 # management is handled there.
218 218 task = RepoModel().create_fork(
219 219 form_result, c.rhodecode_user.user_id)
220 220
221 221 task_id = get_task_id(task)
222 222 except formencode.Invalid as errors:
223 223 c.rhodecode_db_repo = self.db_repo
224 224
225 225 data = render('rhodecode:templates/forks/fork.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 Exception:
237 237 log.exception(
238 238 u'Exception while trying to fork the repository %s', self.db_repo_name)
239 239 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
240 240 h.flash(msg, category='error')
241 241 raise HTTPFound(h.route_path('home'))
242 242
243 243 repo_name = form_result.get('repo_name_full', self.db_repo_name)
244 244
245 245 affected_user_ids = [self._rhodecode_user.user_id]
246 246 if copy_permissions:
247 247 # permission flush is done in repo creating
248 248 pass
249 249
250 250 PermissionModel().trigger_permission_flush(affected_user_ids)
251 251
252 252 raise HTTPFound(
253 253 h.route_path('repo_creating', repo_name=repo_name,
254 254 _query=dict(task_id=task_id)))
@@ -1,2511 +1,2513 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26
27 27 import colander
28 28 import time
29 29 import collections
30 30 import fnmatch
31 31 import hashlib
32 32 import itertools
33 33 import logging
34 34 import random
35 35 import traceback
36 36 from functools import wraps
37 37
38 38 import ipaddress
39 39
40 40 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
41 41 from sqlalchemy.orm.exc import ObjectDeletedError
42 42 from sqlalchemy.orm import joinedload
43 43 from zope.cachedescriptors.property import Lazy as LazyProperty
44 44
45 45 import rhodecode
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import (
50 50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
52 52 from rhodecode.lib import rc_cache
53 53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
54 54 from rhodecode.lib.utils import (
55 55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 56 from rhodecode.lib.caching_query import FromCache
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default', 1
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin', 2
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin, obj_id)):
370 370 self.perm_origin_stack.setdefault(key, []).append((perm, origin, obj_id))
371 371 dict.__setitem__(self, key, perm)
372 372
373 373
374 374 class BranchPermOriginDict(PermOriginDict):
375 375 """
376 376 Dedicated branch permissions dict, with tracking of patterns and origins.
377 377
378 378 >>> perms = BranchPermOriginDict()
379 379 >>> perms['resource'] = '*pattern', 'read', 'default'
380 380 >>> perms['resource']
381 381 {'*pattern': 'read'}
382 382 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 383 >>> perms['resource']
384 384 {'*pattern': 'write'}
385 385 >>> perms.perm_origin_stack
386 386 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 387 """
388 388 def __setitem__(self, key, (pattern, perm, origin)):
389 389
390 390 self.perm_origin_stack.setdefault(key, {}) \
391 391 .setdefault(pattern, []).append((perm, origin))
392 392
393 393 if key in self:
394 394 self[key].__setitem__(pattern, perm)
395 395 else:
396 396 patterns = collections.OrderedDict()
397 397 patterns[pattern] = perm
398 398 dict.__setitem__(self, key, patterns)
399 399
400 400
401 401 class PermissionCalculator(object):
402 402
403 403 def __init__(
404 404 self, user_id, scope, user_is_admin,
405 405 user_inherit_default_permissions, explicit, algo,
406 406 calculate_super_admin_as_user=False):
407 407
408 408 self.user_id = user_id
409 409 self.user_is_admin = user_is_admin
410 410 self.inherit_default_permissions = user_inherit_default_permissions
411 411 self.explicit = explicit
412 412 self.algo = algo
413 413 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414 414
415 415 scope = scope or {}
416 416 self.scope_repo_id = scope.get('repo_id')
417 417 self.scope_repo_group_id = scope.get('repo_group_id')
418 418 self.scope_user_group_id = scope.get('user_group_id')
419 419
420 420 self.default_user_id = User.get_default_user(cache=True).user_id
421 421
422 422 self.permissions_repositories = PermOriginDict()
423 423 self.permissions_repository_groups = PermOriginDict()
424 424 self.permissions_user_groups = PermOriginDict()
425 425 self.permissions_repository_branches = BranchPermOriginDict()
426 426 self.permissions_global = set()
427 427
428 428 self.default_repo_perms = Permission.get_default_repo_perms(
429 429 self.default_user_id, self.scope_repo_id)
430 430 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 431 self.default_user_id, self.scope_repo_group_id)
432 432 self.default_user_group_perms = \
433 433 Permission.get_default_user_group_perms(
434 434 self.default_user_id, self.scope_user_group_id)
435 435
436 436 # default branch perms
437 437 self.default_branch_repo_perms = \
438 438 Permission.get_default_repo_branch_perms(
439 439 self.default_user_id, self.scope_repo_id)
440 440
441 441 def calculate(self):
442 442 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 443 return self._calculate_super_admin_permissions()
444 444
445 445 self._calculate_global_default_permissions()
446 446 self._calculate_global_permissions()
447 447 self._calculate_default_permissions()
448 448 self._calculate_repository_permissions()
449 449 self._calculate_repository_branch_permissions()
450 450 self._calculate_repository_group_permissions()
451 451 self._calculate_user_group_permissions()
452 452 return self._permission_structure()
453 453
454 454 def _calculate_super_admin_permissions(self):
455 455 """
456 456 super-admin user have all default rights for repositories
457 457 and groups set to admin
458 458 """
459 459 self.permissions_global.add('hg.admin')
460 460 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 461
462 462 # repositories
463 463 for perm in self.default_repo_perms:
464 464 r_k = perm.UserRepoToPerm.repository.repo_name
465 465 obj_id = perm.UserRepoToPerm.repository.repo_id
466 466 archived = perm.UserRepoToPerm.repository.archived
467 467 p = 'repository.admin'
468 468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
469 469 # special case for archived repositories, which we block still even for
470 470 # super admins
471 471 if archived:
472 472 p = 'repository.read'
473 473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
474 474
475 475 # repository groups
476 476 for perm in self.default_repo_groups_perms:
477 477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 478 obj_id = perm.UserRepoGroupToPerm.group.group_id
479 479 p = 'group.admin'
480 480 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
481 481
482 482 # user groups
483 483 for perm in self.default_user_group_perms:
484 484 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
485 485 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
486 486 p = 'usergroup.admin'
487 487 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
488 488
489 489 # branch permissions
490 490 # since super-admin also can have custom rule permissions
491 491 # we *always* need to calculate those inherited from default, and also explicit
492 492 self._calculate_default_permissions_repository_branches(
493 493 user_inherit_object_permissions=False)
494 494 self._calculate_repository_branch_permissions()
495 495
496 496 return self._permission_structure()
497 497
498 498 def _calculate_global_default_permissions(self):
499 499 """
500 500 global permissions taken from the default user
501 501 """
502 502 default_global_perms = UserToPerm.query()\
503 503 .filter(UserToPerm.user_id == self.default_user_id)\
504 504 .options(joinedload(UserToPerm.permission))
505 505
506 506 for perm in default_global_perms:
507 507 self.permissions_global.add(perm.permission.permission_name)
508 508
509 509 if self.user_is_admin:
510 510 self.permissions_global.add('hg.admin')
511 511 self.permissions_global.add('hg.create.write_on_repogroup.true')
512 512
513 513 def _calculate_global_permissions(self):
514 514 """
515 515 Set global system permissions with user permissions or permissions
516 516 taken from the user groups of the current user.
517 517
518 518 The permissions include repo creating, repo group creating, forking
519 519 etc.
520 520 """
521 521
522 522 # now we read the defined permissions and overwrite what we have set
523 523 # before those can be configured from groups or users explicitly.
524 524
525 525 # In case we want to extend this list we should make sure
526 526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 from rhodecode.model.permission import PermissionModel
528
527 529 _configurable = frozenset([
528 'hg.fork.none', 'hg.fork.repository',
530 PermissionModel.FORKING_DISABLED, PermissionModel.FORKING_ENABLED,
529 531 'hg.create.none', 'hg.create.repository',
530 532 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 533 'hg.repogroup.create.false', 'hg.repogroup.create.true',
532 534 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
533 535 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
534 536 ])
535 537
536 538 # USER GROUPS comes first user group global permissions
537 539 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
538 540 .options(joinedload(UserGroupToPerm.permission))\
539 541 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
540 542 UserGroupMember.users_group_id))\
541 543 .filter(UserGroupMember.user_id == self.user_id)\
542 544 .order_by(UserGroupToPerm.users_group_id)\
543 545 .all()
544 546
545 547 # need to group here by groups since user can be in more than
546 548 # one group, so we get all groups
547 549 _explicit_grouped_perms = [
548 550 [x, list(y)] for x, y in
549 551 itertools.groupby(user_perms_from_users_groups,
550 552 lambda _x: _x.users_group)]
551 553
552 554 for gr, perms in _explicit_grouped_perms:
553 555 # since user can be in multiple groups iterate over them and
554 556 # select the lowest permissions first (more explicit)
555 557 # TODO(marcink): do this^^
556 558
557 559 # group doesn't inherit default permissions so we actually set them
558 560 if not gr.inherit_default_permissions:
559 561 # NEED TO IGNORE all previously set configurable permissions
560 562 # and replace them with explicitly set from this user
561 563 # group permissions
562 564 self.permissions_global = self.permissions_global.difference(
563 565 _configurable)
564 566 for perm in perms:
565 567 self.permissions_global.add(perm.permission.permission_name)
566 568
567 569 # user explicit global permissions
568 570 user_perms = Session().query(UserToPerm)\
569 571 .options(joinedload(UserToPerm.permission))\
570 572 .filter(UserToPerm.user_id == self.user_id).all()
571 573
572 574 if not self.inherit_default_permissions:
573 575 # NEED TO IGNORE all configurable permissions and
574 576 # replace them with explicitly set from this user permissions
575 577 self.permissions_global = self.permissions_global.difference(
576 578 _configurable)
577 579 for perm in user_perms:
578 580 self.permissions_global.add(perm.permission.permission_name)
579 581
580 582 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
581 583 for perm in self.default_repo_perms:
582 584 r_k = perm.UserRepoToPerm.repository.repo_name
583 585 obj_id = perm.UserRepoToPerm.repository.repo_id
584 586 archived = perm.UserRepoToPerm.repository.archived
585 587 p = perm.Permission.permission_name
586 588 o = PermOrigin.REPO_DEFAULT
587 589 self.permissions_repositories[r_k] = p, o, obj_id
588 590
589 591 # if we decide this user isn't inheriting permissions from
590 592 # default user we set him to .none so only explicit
591 593 # permissions work
592 594 if not user_inherit_object_permissions:
593 595 p = 'repository.none'
594 596 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
595 597 self.permissions_repositories[r_k] = p, o, obj_id
596 598
597 599 if perm.Repository.private and not (
598 600 perm.Repository.user_id == self.user_id):
599 601 # disable defaults for private repos,
600 602 p = 'repository.none'
601 603 o = PermOrigin.REPO_PRIVATE
602 604 self.permissions_repositories[r_k] = p, o, obj_id
603 605
604 606 elif perm.Repository.user_id == self.user_id:
605 607 # set admin if owner
606 608 p = 'repository.admin'
607 609 o = PermOrigin.REPO_OWNER
608 610 self.permissions_repositories[r_k] = p, o, obj_id
609 611
610 612 if self.user_is_admin:
611 613 p = 'repository.admin'
612 614 o = PermOrigin.SUPER_ADMIN
613 615 self.permissions_repositories[r_k] = p, o, obj_id
614 616
615 617 # finally in case of archived repositories, we downgrade higher
616 618 # permissions to read
617 619 if archived:
618 620 current_perm = self.permissions_repositories[r_k]
619 621 if current_perm in ['repository.write', 'repository.admin']:
620 622 p = 'repository.read'
621 623 o = PermOrigin.ARCHIVED
622 624 self.permissions_repositories[r_k] = p, o, obj_id
623 625
624 626 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
625 627 for perm in self.default_branch_repo_perms:
626 628
627 629 r_k = perm.UserRepoToPerm.repository.repo_name
628 630 p = perm.Permission.permission_name
629 631 pattern = perm.UserToRepoBranchPermission.branch_pattern
630 632 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
631 633
632 634 if not self.explicit:
633 635 cur_perm = self.permissions_repository_branches.get(r_k)
634 636 if cur_perm:
635 637 cur_perm = cur_perm[pattern]
636 638 cur_perm = cur_perm or 'branch.none'
637 639
638 640 p = self._choose_permission(p, cur_perm)
639 641
640 642 # NOTE(marcink): register all pattern/perm instances in this
641 643 # special dict that aggregates entries
642 644 self.permissions_repository_branches[r_k] = pattern, p, o
643 645
644 646 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
645 647 for perm in self.default_repo_groups_perms:
646 648 rg_k = perm.UserRepoGroupToPerm.group.group_name
647 649 obj_id = perm.UserRepoGroupToPerm.group.group_id
648 650 p = perm.Permission.permission_name
649 651 o = PermOrigin.REPOGROUP_DEFAULT
650 652 self.permissions_repository_groups[rg_k] = p, o, obj_id
651 653
652 654 # if we decide this user isn't inheriting permissions from default
653 655 # user we set him to .none so only explicit permissions work
654 656 if not user_inherit_object_permissions:
655 657 p = 'group.none'
656 658 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
657 659 self.permissions_repository_groups[rg_k] = p, o, obj_id
658 660
659 661 if perm.RepoGroup.user_id == self.user_id:
660 662 # set admin if owner
661 663 p = 'group.admin'
662 664 o = PermOrigin.REPOGROUP_OWNER
663 665 self.permissions_repository_groups[rg_k] = p, o, obj_id
664 666
665 667 if self.user_is_admin:
666 668 p = 'group.admin'
667 669 o = PermOrigin.SUPER_ADMIN
668 670 self.permissions_repository_groups[rg_k] = p, o, obj_id
669 671
670 672 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
671 673 for perm in self.default_user_group_perms:
672 674 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
673 675 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
674 676 p = perm.Permission.permission_name
675 677 o = PermOrigin.USERGROUP_DEFAULT
676 678 self.permissions_user_groups[u_k] = p, o, obj_id
677 679
678 680 # if we decide this user isn't inheriting permissions from default
679 681 # user we set him to .none so only explicit permissions work
680 682 if not user_inherit_object_permissions:
681 683 p = 'usergroup.none'
682 684 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
683 685 self.permissions_user_groups[u_k] = p, o, obj_id
684 686
685 687 if perm.UserGroup.user_id == self.user_id:
686 688 # set admin if owner
687 689 p = 'usergroup.admin'
688 690 o = PermOrigin.USERGROUP_OWNER
689 691 self.permissions_user_groups[u_k] = p, o, obj_id
690 692
691 693 if self.user_is_admin:
692 694 p = 'usergroup.admin'
693 695 o = PermOrigin.SUPER_ADMIN
694 696 self.permissions_user_groups[u_k] = p, o, obj_id
695 697
696 698 def _calculate_default_permissions(self):
697 699 """
698 700 Set default user permissions for repositories, repository branches,
699 701 repository groups, user groups taken from the default user.
700 702
701 703 Calculate inheritance of object permissions based on what we have now
702 704 in GLOBAL permissions. We check if .false is in GLOBAL since this is
703 705 explicitly set. Inherit is the opposite of .false being there.
704 706
705 707 .. note::
706 708
707 709 the syntax is little bit odd but what we need to check here is
708 710 the opposite of .false permission being in the list so even for
709 711 inconsistent state when both .true/.false is there
710 712 .false is more important
711 713
712 714 """
713 715 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
714 716 in self.permissions_global)
715 717
716 718 # default permissions inherited from `default` user permissions
717 719 self._calculate_default_permissions_repositories(
718 720 user_inherit_object_permissions)
719 721
720 722 self._calculate_default_permissions_repository_branches(
721 723 user_inherit_object_permissions)
722 724
723 725 self._calculate_default_permissions_repository_groups(
724 726 user_inherit_object_permissions)
725 727
726 728 self._calculate_default_permissions_user_groups(
727 729 user_inherit_object_permissions)
728 730
729 731 def _calculate_repository_permissions(self):
730 732 """
731 733 Repository access permissions for the current user.
732 734
733 735 Check if the user is part of user groups for this repository and
734 736 fill in the permission from it. `_choose_permission` decides of which
735 737 permission should be selected based on selected method.
736 738 """
737 739
738 740 # user group for repositories permissions
739 741 user_repo_perms_from_user_group = Permission\
740 742 .get_default_repo_perms_from_user_group(
741 743 self.user_id, self.scope_repo_id)
742 744
743 745 multiple_counter = collections.defaultdict(int)
744 746 for perm in user_repo_perms_from_user_group:
745 747 r_k = perm.UserGroupRepoToPerm.repository.repo_name
746 748 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
747 749 multiple_counter[r_k] += 1
748 750 p = perm.Permission.permission_name
749 751 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
750 752 .users_group.users_group_name
751 753
752 754 if multiple_counter[r_k] > 1:
753 755 cur_perm = self.permissions_repositories[r_k]
754 756 p = self._choose_permission(p, cur_perm)
755 757
756 758 self.permissions_repositories[r_k] = p, o, obj_id
757 759
758 760 if perm.Repository.user_id == self.user_id:
759 761 # set admin if owner
760 762 p = 'repository.admin'
761 763 o = PermOrigin.REPO_OWNER
762 764 self.permissions_repositories[r_k] = p, o, obj_id
763 765
764 766 if self.user_is_admin:
765 767 p = 'repository.admin'
766 768 o = PermOrigin.SUPER_ADMIN
767 769 self.permissions_repositories[r_k] = p, o, obj_id
768 770
769 771 # user explicit permissions for repositories, overrides any specified
770 772 # by the group permission
771 773 user_repo_perms = Permission.get_default_repo_perms(
772 774 self.user_id, self.scope_repo_id)
773 775 for perm in user_repo_perms:
774 776 r_k = perm.UserRepoToPerm.repository.repo_name
775 777 obj_id = perm.UserRepoToPerm.repository.repo_id
776 778 archived = perm.UserRepoToPerm.repository.archived
777 779 p = perm.Permission.permission_name
778 780 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
779 781
780 782 if not self.explicit:
781 783 cur_perm = self.permissions_repositories.get(
782 784 r_k, 'repository.none')
783 785 p = self._choose_permission(p, cur_perm)
784 786
785 787 self.permissions_repositories[r_k] = p, o, obj_id
786 788
787 789 if perm.Repository.user_id == self.user_id:
788 790 # set admin if owner
789 791 p = 'repository.admin'
790 792 o = PermOrigin.REPO_OWNER
791 793 self.permissions_repositories[r_k] = p, o, obj_id
792 794
793 795 if self.user_is_admin:
794 796 p = 'repository.admin'
795 797 o = PermOrigin.SUPER_ADMIN
796 798 self.permissions_repositories[r_k] = p, o, obj_id
797 799
798 800 # finally in case of archived repositories, we downgrade higher
799 801 # permissions to read
800 802 if archived:
801 803 current_perm = self.permissions_repositories[r_k]
802 804 if current_perm in ['repository.write', 'repository.admin']:
803 805 p = 'repository.read'
804 806 o = PermOrigin.ARCHIVED
805 807 self.permissions_repositories[r_k] = p, o, obj_id
806 808
807 809 def _calculate_repository_branch_permissions(self):
808 810 # user group for repositories permissions
809 811 user_repo_branch_perms_from_user_group = Permission\
810 812 .get_default_repo_branch_perms_from_user_group(
811 813 self.user_id, self.scope_repo_id)
812 814
813 815 multiple_counter = collections.defaultdict(int)
814 816 for perm in user_repo_branch_perms_from_user_group:
815 817 r_k = perm.UserGroupRepoToPerm.repository.repo_name
816 818 p = perm.Permission.permission_name
817 819 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
818 820 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
819 821 .users_group.users_group_name
820 822
821 823 multiple_counter[r_k] += 1
822 824 if multiple_counter[r_k] > 1:
823 825 cur_perm = self.permissions_repository_branches[r_k][pattern]
824 826 p = self._choose_permission(p, cur_perm)
825 827
826 828 self.permissions_repository_branches[r_k] = pattern, p, o
827 829
828 830 # user explicit branch permissions for repositories, overrides
829 831 # any specified by the group permission
830 832 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
831 833 self.user_id, self.scope_repo_id)
832 834
833 835 for perm in user_repo_branch_perms:
834 836
835 837 r_k = perm.UserRepoToPerm.repository.repo_name
836 838 p = perm.Permission.permission_name
837 839 pattern = perm.UserToRepoBranchPermission.branch_pattern
838 840 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
839 841
840 842 if not self.explicit:
841 843 cur_perm = self.permissions_repository_branches.get(r_k)
842 844 if cur_perm:
843 845 cur_perm = cur_perm[pattern]
844 846 cur_perm = cur_perm or 'branch.none'
845 847 p = self._choose_permission(p, cur_perm)
846 848
847 849 # NOTE(marcink): register all pattern/perm instances in this
848 850 # special dict that aggregates entries
849 851 self.permissions_repository_branches[r_k] = pattern, p, o
850 852
851 853 def _calculate_repository_group_permissions(self):
852 854 """
853 855 Repository group permissions for the current user.
854 856
855 857 Check if the user is part of user groups for repository groups and
856 858 fill in the permissions from it. `_choose_permission` decides of which
857 859 permission should be selected based on selected method.
858 860 """
859 861 # user group for repo groups permissions
860 862 user_repo_group_perms_from_user_group = Permission\
861 863 .get_default_group_perms_from_user_group(
862 864 self.user_id, self.scope_repo_group_id)
863 865
864 866 multiple_counter = collections.defaultdict(int)
865 867 for perm in user_repo_group_perms_from_user_group:
866 868 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
867 869 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
868 870 multiple_counter[rg_k] += 1
869 871 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
870 872 .users_group.users_group_name
871 873 p = perm.Permission.permission_name
872 874
873 875 if multiple_counter[rg_k] > 1:
874 876 cur_perm = self.permissions_repository_groups[rg_k]
875 877 p = self._choose_permission(p, cur_perm)
876 878 self.permissions_repository_groups[rg_k] = p, o, obj_id
877 879
878 880 if perm.RepoGroup.user_id == self.user_id:
879 881 # set admin if owner, even for member of other user group
880 882 p = 'group.admin'
881 883 o = PermOrigin.REPOGROUP_OWNER
882 884 self.permissions_repository_groups[rg_k] = p, o, obj_id
883 885
884 886 if self.user_is_admin:
885 887 p = 'group.admin'
886 888 o = PermOrigin.SUPER_ADMIN
887 889 self.permissions_repository_groups[rg_k] = p, o, obj_id
888 890
889 891 # user explicit permissions for repository groups
890 892 user_repo_groups_perms = Permission.get_default_group_perms(
891 893 self.user_id, self.scope_repo_group_id)
892 894 for perm in user_repo_groups_perms:
893 895 rg_k = perm.UserRepoGroupToPerm.group.group_name
894 896 obj_id = perm.UserRepoGroupToPerm.group.group_id
895 897 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
896 898 .user.username
897 899 p = perm.Permission.permission_name
898 900
899 901 if not self.explicit:
900 902 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
901 903 p = self._choose_permission(p, cur_perm)
902 904
903 905 self.permissions_repository_groups[rg_k] = p, o, obj_id
904 906
905 907 if perm.RepoGroup.user_id == self.user_id:
906 908 # set admin if owner
907 909 p = 'group.admin'
908 910 o = PermOrigin.REPOGROUP_OWNER
909 911 self.permissions_repository_groups[rg_k] = p, o, obj_id
910 912
911 913 if self.user_is_admin:
912 914 p = 'group.admin'
913 915 o = PermOrigin.SUPER_ADMIN
914 916 self.permissions_repository_groups[rg_k] = p, o, obj_id
915 917
916 918 def _calculate_user_group_permissions(self):
917 919 """
918 920 User group permissions for the current user.
919 921 """
920 922 # user group for user group permissions
921 923 user_group_from_user_group = Permission\
922 924 .get_default_user_group_perms_from_user_group(
923 925 self.user_id, self.scope_user_group_id)
924 926
925 927 multiple_counter = collections.defaultdict(int)
926 928 for perm in user_group_from_user_group:
927 929 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
928 930 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
929 931 multiple_counter[ug_k] += 1
930 932 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
931 933 .user_group.users_group_name
932 934 p = perm.Permission.permission_name
933 935
934 936 if multiple_counter[ug_k] > 1:
935 937 cur_perm = self.permissions_user_groups[ug_k]
936 938 p = self._choose_permission(p, cur_perm)
937 939
938 940 self.permissions_user_groups[ug_k] = p, o, obj_id
939 941
940 942 if perm.UserGroup.user_id == self.user_id:
941 943 # set admin if owner, even for member of other user group
942 944 p = 'usergroup.admin'
943 945 o = PermOrigin.USERGROUP_OWNER
944 946 self.permissions_user_groups[ug_k] = p, o, obj_id
945 947
946 948 if self.user_is_admin:
947 949 p = 'usergroup.admin'
948 950 o = PermOrigin.SUPER_ADMIN
949 951 self.permissions_user_groups[ug_k] = p, o, obj_id
950 952
951 953 # user explicit permission for user groups
952 954 user_user_groups_perms = Permission.get_default_user_group_perms(
953 955 self.user_id, self.scope_user_group_id)
954 956 for perm in user_user_groups_perms:
955 957 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
956 958 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
957 959 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
958 960 .user.username
959 961 p = perm.Permission.permission_name
960 962
961 963 if not self.explicit:
962 964 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
963 965 p = self._choose_permission(p, cur_perm)
964 966
965 967 self.permissions_user_groups[ug_k] = p, o, obj_id
966 968
967 969 if perm.UserGroup.user_id == self.user_id:
968 970 # set admin if owner
969 971 p = 'usergroup.admin'
970 972 o = PermOrigin.USERGROUP_OWNER
971 973 self.permissions_user_groups[ug_k] = p, o, obj_id
972 974
973 975 if self.user_is_admin:
974 976 p = 'usergroup.admin'
975 977 o = PermOrigin.SUPER_ADMIN
976 978 self.permissions_user_groups[ug_k] = p, o, obj_id
977 979
978 980 def _choose_permission(self, new_perm, cur_perm):
979 981 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
980 982 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
981 983 if self.algo == 'higherwin':
982 984 if new_perm_val > cur_perm_val:
983 985 return new_perm
984 986 return cur_perm
985 987 elif self.algo == 'lowerwin':
986 988 if new_perm_val < cur_perm_val:
987 989 return new_perm
988 990 return cur_perm
989 991
990 992 def _permission_structure(self):
991 993 return {
992 994 'global': self.permissions_global,
993 995 'repositories': self.permissions_repositories,
994 996 'repository_branches': self.permissions_repository_branches,
995 997 'repositories_groups': self.permissions_repository_groups,
996 998 'user_groups': self.permissions_user_groups,
997 999 }
998 1000
999 1001
1000 1002 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
1001 1003 """
1002 1004 Check if given controller_name is in whitelist of auth token access
1003 1005 """
1004 1006 if not whitelist:
1005 1007 from rhodecode import CONFIG
1006 1008 whitelist = aslist(
1007 1009 CONFIG.get('api_access_controllers_whitelist'), sep=',')
1008 1010 # backward compat translation
1009 1011 compat = {
1010 1012 # old controller, new VIEW
1011 1013 'ChangesetController:*': 'RepoCommitsView:*',
1012 1014 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1013 1015 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1014 1016 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1015 1017 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1016 1018 'GistsController:*': 'GistView:*',
1017 1019 }
1018 1020
1019 1021 log.debug(
1020 1022 'Allowed views for AUTH TOKEN access: %s', whitelist)
1021 1023 auth_token_access_valid = False
1022 1024
1023 1025 for entry in whitelist:
1024 1026 token_match = True
1025 1027 if entry in compat:
1026 1028 # translate from old Controllers to Pyramid Views
1027 1029 entry = compat[entry]
1028 1030
1029 1031 if '@' in entry:
1030 1032 # specific AuthToken
1031 1033 entry, allowed_token = entry.split('@', 1)
1032 1034 token_match = auth_token == allowed_token
1033 1035
1034 1036 if fnmatch.fnmatch(view_name, entry) and token_match:
1035 1037 auth_token_access_valid = True
1036 1038 break
1037 1039
1038 1040 if auth_token_access_valid:
1039 1041 log.debug('view: `%s` matches entry in whitelist: %s',
1040 1042 view_name, whitelist)
1041 1043
1042 1044 else:
1043 1045 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1044 1046 % (view_name, whitelist))
1045 1047 if auth_token:
1046 1048 # if we use auth token key and don't have access it's a warning
1047 1049 log.warning(msg)
1048 1050 else:
1049 1051 log.debug(msg)
1050 1052
1051 1053 return auth_token_access_valid
1052 1054
1053 1055
1054 1056 class AuthUser(object):
1055 1057 """
1056 1058 A simple object that handles all attributes of user in RhodeCode
1057 1059
1058 1060 It does lookup based on API key,given user, or user present in session
1059 1061 Then it fills all required information for such user. It also checks if
1060 1062 anonymous access is enabled and if so, it returns default user as logged in
1061 1063 """
1062 1064 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1063 1065 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1064 1066 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1065 1067 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1066 1068
1067 1069 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1068 1070
1069 1071 self.user_id = user_id
1070 1072 self._api_key = api_key
1071 1073
1072 1074 self.api_key = None
1073 1075 self.username = username
1074 1076 self.ip_addr = ip_addr
1075 1077 self.name = ''
1076 1078 self.lastname = ''
1077 1079 self.first_name = ''
1078 1080 self.last_name = ''
1079 1081 self.email = ''
1080 1082 self.is_authenticated = False
1081 1083 self.admin = False
1082 1084 self.inherit_default_permissions = False
1083 1085 self.password = ''
1084 1086
1085 1087 self.anonymous_user = None # propagated on propagate_data
1086 1088 self.propagate_data()
1087 1089 self._instance = None
1088 1090 self._permissions_scoped_cache = {} # used to bind scoped calculation
1089 1091
1090 1092 @LazyProperty
1091 1093 def permissions(self):
1092 1094 return self.get_perms(user=self, cache=None)
1093 1095
1094 1096 @LazyProperty
1095 1097 def permissions_safe(self):
1096 1098 """
1097 1099 Filtered permissions excluding not allowed repositories
1098 1100 """
1099 1101 perms = self.get_perms(user=self, cache=None)
1100 1102
1101 1103 perms['repositories'] = {
1102 1104 k: v for k, v in perms['repositories'].items()
1103 1105 if v != 'repository.none'}
1104 1106 perms['repositories_groups'] = {
1105 1107 k: v for k, v in perms['repositories_groups'].items()
1106 1108 if v != 'group.none'}
1107 1109 perms['user_groups'] = {
1108 1110 k: v for k, v in perms['user_groups'].items()
1109 1111 if v != 'usergroup.none'}
1110 1112 perms['repository_branches'] = {
1111 1113 k: v for k, v in perms['repository_branches'].iteritems()
1112 1114 if v != 'branch.none'}
1113 1115 return perms
1114 1116
1115 1117 @LazyProperty
1116 1118 def permissions_full_details(self):
1117 1119 return self.get_perms(
1118 1120 user=self, cache=None, calculate_super_admin=True)
1119 1121
1120 1122 def permissions_with_scope(self, scope):
1121 1123 """
1122 1124 Call the get_perms function with scoped data. The scope in that function
1123 1125 narrows the SQL calls to the given ID of objects resulting in fetching
1124 1126 Just particular permission we want to obtain. If scope is an empty dict
1125 1127 then it basically narrows the scope to GLOBAL permissions only.
1126 1128
1127 1129 :param scope: dict
1128 1130 """
1129 1131 if 'repo_name' in scope:
1130 1132 obj = Repository.get_by_repo_name(scope['repo_name'])
1131 1133 if obj:
1132 1134 scope['repo_id'] = obj.repo_id
1133 1135 _scope = collections.OrderedDict()
1134 1136 _scope['repo_id'] = -1
1135 1137 _scope['user_group_id'] = -1
1136 1138 _scope['repo_group_id'] = -1
1137 1139
1138 1140 for k in sorted(scope.keys()):
1139 1141 _scope[k] = scope[k]
1140 1142
1141 1143 # store in cache to mimic how the @LazyProperty works,
1142 1144 # the difference here is that we use the unique key calculated
1143 1145 # from params and values
1144 1146 return self.get_perms(user=self, cache=None, scope=_scope)
1145 1147
1146 1148 def get_instance(self):
1147 1149 return User.get(self.user_id)
1148 1150
1149 1151 def propagate_data(self):
1150 1152 """
1151 1153 Fills in user data and propagates values to this instance. Maps fetched
1152 1154 user attributes to this class instance attributes
1153 1155 """
1154 1156 log.debug('AuthUser: starting data propagation for new potential user')
1155 1157 user_model = UserModel()
1156 1158 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1157 1159 is_user_loaded = False
1158 1160
1159 1161 # lookup by userid
1160 1162 if self.user_id is not None and self.user_id != anon_user.user_id:
1161 1163 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1162 1164 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1163 1165
1164 1166 # try go get user by api key
1165 1167 elif self._api_key and self._api_key != anon_user.api_key:
1166 1168 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1167 1169 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1168 1170
1169 1171 # lookup by username
1170 1172 elif self.username:
1171 1173 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1172 1174 is_user_loaded = user_model.fill_data(self, username=self.username)
1173 1175 else:
1174 1176 log.debug('No data in %s that could been used to log in', self)
1175 1177
1176 1178 if not is_user_loaded:
1177 1179 log.debug(
1178 1180 'Failed to load user. Fallback to default user %s', anon_user)
1179 1181 # if we cannot authenticate user try anonymous
1180 1182 if anon_user.active:
1181 1183 log.debug('default user is active, using it as a session user')
1182 1184 user_model.fill_data(self, user_id=anon_user.user_id)
1183 1185 # then we set this user is logged in
1184 1186 self.is_authenticated = True
1185 1187 else:
1186 1188 log.debug('default user is NOT active')
1187 1189 # in case of disabled anonymous user we reset some of the
1188 1190 # parameters so such user is "corrupted", skipping the fill_data
1189 1191 for attr in ['user_id', 'username', 'admin', 'active']:
1190 1192 setattr(self, attr, None)
1191 1193 self.is_authenticated = False
1192 1194
1193 1195 if not self.username:
1194 1196 self.username = 'None'
1195 1197
1196 1198 log.debug('AuthUser: propagated user is now %s', self)
1197 1199
1198 1200 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1199 1201 calculate_super_admin=False, cache=None):
1200 1202 """
1201 1203 Fills user permission attribute with permissions taken from database
1202 1204 works for permissions given for repositories, and for permissions that
1203 1205 are granted to groups
1204 1206
1205 1207 :param user: instance of User object from database
1206 1208 :param explicit: In case there are permissions both for user and a group
1207 1209 that user is part of, explicit flag will defiine if user will
1208 1210 explicitly override permissions from group, if it's False it will
1209 1211 make decision based on the algo
1210 1212 :param algo: algorithm to decide what permission should be choose if
1211 1213 it's multiple defined, eg user in two different groups. It also
1212 1214 decides if explicit flag is turned off how to specify the permission
1213 1215 for case when user is in a group + have defined separate permission
1214 1216 :param calculate_super_admin: calculate permissions for super-admin in the
1215 1217 same way as for regular user without speedups
1216 1218 :param cache: Use caching for calculation, None = let the cache backend decide
1217 1219 """
1218 1220 user_id = user.user_id
1219 1221 user_is_admin = user.is_admin
1220 1222
1221 1223 # inheritance of global permissions like create repo/fork repo etc
1222 1224 user_inherit_default_permissions = user.inherit_default_permissions
1223 1225
1224 1226 cache_seconds = safe_int(
1225 1227 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1226 1228
1227 1229 if cache is None:
1228 1230 # let the backend cache decide
1229 1231 cache_on = cache_seconds > 0
1230 1232 else:
1231 1233 cache_on = cache
1232 1234
1233 1235 log.debug(
1234 1236 'Computing PERMISSION tree for user %s scope `%s` '
1235 1237 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1236 1238
1237 1239 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1238 1240 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1239 1241
1240 1242 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1241 1243 condition=cache_on)
1242 1244 def compute_perm_tree(cache_name, cache_ver,
1243 1245 user_id, scope, user_is_admin,user_inherit_default_permissions,
1244 1246 explicit, algo, calculate_super_admin):
1245 1247 return _cached_perms_data(
1246 1248 user_id, scope, user_is_admin, user_inherit_default_permissions,
1247 1249 explicit, algo, calculate_super_admin)
1248 1250
1249 1251 start = time.time()
1250 1252 result = compute_perm_tree(
1251 1253 'permissions', 'v1', user_id, scope, user_is_admin,
1252 1254 user_inherit_default_permissions, explicit, algo,
1253 1255 calculate_super_admin)
1254 1256
1255 1257 result_repr = []
1256 1258 for k in result:
1257 1259 result_repr.append((k, len(result[k])))
1258 1260 total = time.time() - start
1259 1261 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1260 1262 user, total, result_repr)
1261 1263
1262 1264 return result
1263 1265
1264 1266 @property
1265 1267 def is_default(self):
1266 1268 return self.username == User.DEFAULT_USER
1267 1269
1268 1270 @property
1269 1271 def is_admin(self):
1270 1272 return self.admin
1271 1273
1272 1274 @property
1273 1275 def is_user_object(self):
1274 1276 return self.user_id is not None
1275 1277
1276 1278 @property
1277 1279 def repositories_admin(self):
1278 1280 """
1279 1281 Returns list of repositories you're an admin of
1280 1282 """
1281 1283 return [
1282 1284 x[0] for x in self.permissions['repositories'].items()
1283 1285 if x[1] == 'repository.admin']
1284 1286
1285 1287 @property
1286 1288 def repository_groups_admin(self):
1287 1289 """
1288 1290 Returns list of repository groups you're an admin of
1289 1291 """
1290 1292 return [
1291 1293 x[0] for x in self.permissions['repositories_groups'].items()
1292 1294 if x[1] == 'group.admin']
1293 1295
1294 1296 @property
1295 1297 def user_groups_admin(self):
1296 1298 """
1297 1299 Returns list of user groups you're an admin of
1298 1300 """
1299 1301 return [
1300 1302 x[0] for x in self.permissions['user_groups'].items()
1301 1303 if x[1] == 'usergroup.admin']
1302 1304
1303 1305 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1304 1306 if not perms:
1305 1307 perms = AuthUser.repo_read_perms
1306 1308 allowed_ids = []
1307 1309 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1308 1310 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1309 1311 if prefix_filter and not k.startswith(prefix_filter):
1310 1312 continue
1311 1313 if perm in perms:
1312 1314 allowed_ids.append(obj_id)
1313 1315 return allowed_ids
1314 1316
1315 1317 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1316 1318 """
1317 1319 Returns list of repository ids that user have access to based on given
1318 1320 perms. The cache flag should be only used in cases that are used for
1319 1321 display purposes, NOT IN ANY CASE for permission checks.
1320 1322 """
1321 1323 from rhodecode.model.scm import RepoList
1322 1324 if not perms:
1323 1325 perms = AuthUser.repo_read_perms
1324 1326
1325 1327 if not isinstance(perms, list):
1326 1328 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1327 1329
1328 1330 def _cached_repo_acl(perm_def, _name_filter):
1329 1331 qry = Repository.query()
1330 1332 if _name_filter:
1331 1333 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1332 1334 qry = qry.filter(
1333 1335 Repository.repo_name.ilike(ilike_expression))
1334 1336
1335 1337 return [x.repo_id for x in
1336 1338 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1337 1339
1338 1340 log.debug('Computing REPO ACL IDS user %s', self)
1339 1341
1340 1342 cache_namespace_uid = 'cache_user_repo_acl_ids.{}'.format(self.user_id)
1341 1343 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1342 1344
1343 1345 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1344 1346 def compute_repo_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1345 1347 return _cached_repo_acl(perm_def, _name_filter)
1346 1348
1347 1349 start = time.time()
1348 1350 result = compute_repo_acl_ids('v1', self.user_id, perms, name_filter)
1349 1351 total = time.time() - start
1350 1352 log.debug('REPO ACL IDS for user %s computed in %.4fs', self, total)
1351 1353
1352 1354 return result
1353 1355
1354 1356 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1355 1357 if not perms:
1356 1358 perms = AuthUser.repo_group_read_perms
1357 1359 allowed_ids = []
1358 1360 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1359 1361 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1360 1362 if prefix_filter and not k.startswith(prefix_filter):
1361 1363 continue
1362 1364 if perm in perms:
1363 1365 allowed_ids.append(obj_id)
1364 1366 return allowed_ids
1365 1367
1366 1368 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1367 1369 """
1368 1370 Returns list of repository group ids that user have access to based on given
1369 1371 perms. The cache flag should be only used in cases that are used for
1370 1372 display purposes, NOT IN ANY CASE for permission checks.
1371 1373 """
1372 1374 from rhodecode.model.scm import RepoGroupList
1373 1375 if not perms:
1374 1376 perms = AuthUser.repo_group_read_perms
1375 1377
1376 1378 if not isinstance(perms, list):
1377 1379 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1378 1380
1379 1381 def _cached_repo_group_acl(perm_def, _name_filter):
1380 1382 qry = RepoGroup.query()
1381 1383 if _name_filter:
1382 1384 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1383 1385 qry = qry.filter(
1384 1386 RepoGroup.group_name.ilike(ilike_expression))
1385 1387
1386 1388 return [x.group_id for x in
1387 1389 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1388 1390
1389 1391 log.debug('Computing REPO GROUP ACL IDS user %s', self)
1390 1392
1391 1393 cache_namespace_uid = 'cache_user_repo_group_acl_ids.{}'.format(self.user_id)
1392 1394 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1393 1395
1394 1396 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1395 1397 def compute_repo_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1396 1398 return _cached_repo_group_acl(perm_def, _name_filter)
1397 1399
1398 1400 start = time.time()
1399 1401 result = compute_repo_group_acl_ids('v1', self.user_id, perms, name_filter)
1400 1402 total = time.time() - start
1401 1403 log.debug('REPO GROUP ACL IDS for user %s computed in %.4fs', self, total)
1402 1404
1403 1405 return result
1404 1406
1405 1407 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1406 1408 if not perms:
1407 1409 perms = AuthUser.user_group_read_perms
1408 1410 allowed_ids = []
1409 1411 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1410 1412 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1411 1413 if perm in perms:
1412 1414 allowed_ids.append(obj_id)
1413 1415 return allowed_ids
1414 1416
1415 1417 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1416 1418 """
1417 1419 Returns list of user group ids that user have access to based on given
1418 1420 perms. The cache flag should be only used in cases that are used for
1419 1421 display purposes, NOT IN ANY CASE for permission checks.
1420 1422 """
1421 1423 from rhodecode.model.scm import UserGroupList
1422 1424 if not perms:
1423 1425 perms = AuthUser.user_group_read_perms
1424 1426
1425 1427 if not isinstance(perms, list):
1426 1428 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1427 1429
1428 1430 def _cached_user_group_acl(perm_def, _name_filter):
1429 1431 qry = UserGroup.query()
1430 1432 if _name_filter:
1431 1433 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1432 1434 qry = qry.filter(
1433 1435 UserGroup.users_group_name.ilike(ilike_expression))
1434 1436
1435 1437 return [x.users_group_id for x in
1436 1438 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1437 1439
1438 1440 log.debug('Computing USER GROUP ACL IDS user %s', self)
1439 1441
1440 1442 cache_namespace_uid = 'cache_user_user_group_acl_ids.{}'.format(self.user_id)
1441 1443 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1442 1444
1443 1445 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1444 1446 def compute_user_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1445 1447 return _cached_user_group_acl(perm_def, _name_filter)
1446 1448
1447 1449 start = time.time()
1448 1450 result = compute_user_group_acl_ids('v1', self.user_id, perms, name_filter)
1449 1451 total = time.time() - start
1450 1452 log.debug('USER GROUP ACL IDS for user %s computed in %.4fs', self, total)
1451 1453
1452 1454 return result
1453 1455
1454 1456 @property
1455 1457 def ip_allowed(self):
1456 1458 """
1457 1459 Checks if ip_addr used in constructor is allowed from defined list of
1458 1460 allowed ip_addresses for user
1459 1461
1460 1462 :returns: boolean, True if ip is in allowed ip range
1461 1463 """
1462 1464 # check IP
1463 1465 inherit = self.inherit_default_permissions
1464 1466 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1465 1467 inherit_from_default=inherit)
1466 1468
1467 1469 @property
1468 1470 def personal_repo_group(self):
1469 1471 return RepoGroup.get_user_personal_repo_group(self.user_id)
1470 1472
1471 1473 @LazyProperty
1472 1474 def feed_token(self):
1473 1475 return self.get_instance().feed_token
1474 1476
1475 1477 @LazyProperty
1476 1478 def artifact_token(self):
1477 1479 return self.get_instance().artifact_token
1478 1480
1479 1481 @classmethod
1480 1482 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1481 1483 allowed_ips = AuthUser.get_allowed_ips(
1482 1484 user_id, cache=True, inherit_from_default=inherit_from_default)
1483 1485 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1484 1486 log.debug('IP:%s for user %s is in range of %s',
1485 1487 ip_addr, user_id, allowed_ips)
1486 1488 return True
1487 1489 else:
1488 1490 log.info('Access for IP:%s forbidden for user %s, '
1489 1491 'not in %s', ip_addr, user_id, allowed_ips)
1490 1492 return False
1491 1493
1492 1494 def get_branch_permissions(self, repo_name, perms=None):
1493 1495 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1494 1496 branch_perms = perms.get('repository_branches', {})
1495 1497 if not branch_perms:
1496 1498 return {}
1497 1499 repo_branch_perms = branch_perms.get(repo_name)
1498 1500 return repo_branch_perms or {}
1499 1501
1500 1502 def get_rule_and_branch_permission(self, repo_name, branch_name):
1501 1503 """
1502 1504 Check if this AuthUser has defined any permissions for branches. If any of
1503 1505 the rules match in order, we return the matching permissions
1504 1506 """
1505 1507
1506 1508 rule = default_perm = ''
1507 1509
1508 1510 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1509 1511 if not repo_branch_perms:
1510 1512 return rule, default_perm
1511 1513
1512 1514 # now calculate the permissions
1513 1515 for pattern, branch_perm in repo_branch_perms.items():
1514 1516 if fnmatch.fnmatch(branch_name, pattern):
1515 1517 rule = '`{}`=>{}'.format(pattern, branch_perm)
1516 1518 return rule, branch_perm
1517 1519
1518 1520 return rule, default_perm
1519 1521
1520 1522 def get_notice_messages(self):
1521 1523
1522 1524 notice_level = 'notice-error'
1523 1525 notice_messages = []
1524 1526 if self.is_default:
1525 1527 return [], notice_level
1526 1528
1527 1529 notices = UserNotice.query()\
1528 1530 .filter(UserNotice.user_id == self.user_id)\
1529 1531 .filter(UserNotice.notice_read == false())\
1530 1532 .all()
1531 1533
1532 1534 try:
1533 1535 for entry in notices:
1534 1536
1535 1537 msg = {
1536 1538 'msg_id': entry.user_notice_id,
1537 1539 'level': entry.notification_level,
1538 1540 'subject': entry.notice_subject,
1539 1541 'body': entry.notice_body,
1540 1542 }
1541 1543 notice_messages.append(msg)
1542 1544
1543 1545 log.debug('Got user %s %s messages', self, len(notice_messages))
1544 1546
1545 1547 levels = [x['level'] for x in notice_messages]
1546 1548 notice_level = 'notice-error' if 'error' in levels else 'notice-warning'
1547 1549 except Exception:
1548 1550 pass
1549 1551
1550 1552 return notice_messages, notice_level
1551 1553
1552 1554 def __repr__(self):
1553 1555 return self.repr_user(self.user_id, self.username, self.ip_addr, self.is_authenticated)
1554 1556
1555 1557 def set_authenticated(self, authenticated=True):
1556 1558 if self.user_id != self.anonymous_user.user_id:
1557 1559 self.is_authenticated = authenticated
1558 1560
1559 1561 def get_cookie_store(self):
1560 1562 return {
1561 1563 'username': self.username,
1562 1564 'password': md5(self.password or ''),
1563 1565 'user_id': self.user_id,
1564 1566 'is_authenticated': self.is_authenticated
1565 1567 }
1566 1568
1567 1569 @classmethod
1568 1570 def repr_user(cls, user_id=0, username='ANONYMOUS', ip='0.0.0.0', is_authenticated=False):
1569 1571 tmpl = "<AuthUser('id:{}[{}] ip:{} auth:{}')>"
1570 1572 return tmpl.format(user_id, username, ip, is_authenticated)
1571 1573
1572 1574 @classmethod
1573 1575 def from_cookie_store(cls, cookie_store):
1574 1576 """
1575 1577 Creates AuthUser from a cookie store
1576 1578
1577 1579 :param cls:
1578 1580 :param cookie_store:
1579 1581 """
1580 1582 user_id = cookie_store.get('user_id')
1581 1583 username = cookie_store.get('username')
1582 1584 api_key = cookie_store.get('api_key')
1583 1585 return AuthUser(user_id, api_key, username)
1584 1586
1585 1587 @classmethod
1586 1588 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1587 1589 _set = set()
1588 1590
1589 1591 if inherit_from_default:
1590 1592 def_user_id = User.get_default_user(cache=True).user_id
1591 1593 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1592 1594 if cache:
1593 1595 default_ips = default_ips.options(
1594 1596 FromCache("sql_cache_short", "get_user_ips_default"))
1595 1597
1596 1598 # populate from default user
1597 1599 for ip in default_ips:
1598 1600 try:
1599 1601 _set.add(ip.ip_addr)
1600 1602 except ObjectDeletedError:
1601 1603 # since we use heavy caching sometimes it happens that
1602 1604 # we get deleted objects here, we just skip them
1603 1605 pass
1604 1606
1605 1607 # NOTE:(marcink) we don't want to load any rules for empty
1606 1608 # user_id which is the case of access of non logged users when anonymous
1607 1609 # access is disabled
1608 1610 user_ips = []
1609 1611 if user_id:
1610 1612 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1611 1613 if cache:
1612 1614 user_ips = user_ips.options(
1613 1615 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1614 1616
1615 1617 for ip in user_ips:
1616 1618 try:
1617 1619 _set.add(ip.ip_addr)
1618 1620 except ObjectDeletedError:
1619 1621 # since we use heavy caching sometimes it happens that we get
1620 1622 # deleted objects here, we just skip them
1621 1623 pass
1622 1624 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1623 1625
1624 1626
1625 1627 def set_available_permissions(settings):
1626 1628 """
1627 1629 This function will propagate pyramid settings with all available defined
1628 1630 permission given in db. We don't want to check each time from db for new
1629 1631 permissions since adding a new permission also requires application restart
1630 1632 ie. to decorate new views with the newly created permission
1631 1633
1632 1634 :param settings: current pyramid registry.settings
1633 1635
1634 1636 """
1635 1637 log.debug('auth: getting information about all available permissions')
1636 1638 try:
1637 1639 sa = meta.Session
1638 1640 all_perms = sa.query(Permission).all()
1639 1641 settings.setdefault('available_permissions',
1640 1642 [x.permission_name for x in all_perms])
1641 1643 log.debug('auth: set available permissions')
1642 1644 except Exception:
1643 1645 log.exception('Failed to fetch permissions from the database.')
1644 1646 raise
1645 1647
1646 1648
1647 1649 def get_csrf_token(session, force_new=False, save_if_missing=True):
1648 1650 """
1649 1651 Return the current authentication token, creating one if one doesn't
1650 1652 already exist and the save_if_missing flag is present.
1651 1653
1652 1654 :param session: pass in the pyramid session, else we use the global ones
1653 1655 :param force_new: force to re-generate the token and store it in session
1654 1656 :param save_if_missing: save the newly generated token if it's missing in
1655 1657 session
1656 1658 """
1657 1659 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1658 1660 # from pyramid.csrf import get_csrf_token
1659 1661
1660 1662 if (csrf_token_key not in session and save_if_missing) or force_new:
1661 1663 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1662 1664 session[csrf_token_key] = token
1663 1665 if hasattr(session, 'save'):
1664 1666 session.save()
1665 1667 return session.get(csrf_token_key)
1666 1668
1667 1669
1668 1670 def get_request(perm_class_instance):
1669 1671 from pyramid.threadlocal import get_current_request
1670 1672 pyramid_request = get_current_request()
1671 1673 return pyramid_request
1672 1674
1673 1675
1674 1676 # CHECK DECORATORS
1675 1677 class CSRFRequired(object):
1676 1678 """
1677 1679 Decorator for authenticating a form
1678 1680
1679 1681 This decorator uses an authorization token stored in the client's
1680 1682 session for prevention of certain Cross-site request forgery (CSRF)
1681 1683 attacks (See
1682 1684 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1683 1685 information).
1684 1686
1685 1687 For use with the ``secure_form`` helper functions.
1686 1688
1687 1689 """
1688 1690 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1689 1691 self.token = token
1690 1692 self.header = header
1691 1693 self.except_methods = except_methods or []
1692 1694
1693 1695 def __call__(self, func):
1694 1696 return get_cython_compat_decorator(self.__wrapper, func)
1695 1697
1696 1698 def _get_csrf(self, _request):
1697 1699 return _request.POST.get(self.token, _request.headers.get(self.header))
1698 1700
1699 1701 def check_csrf(self, _request, cur_token):
1700 1702 supplied_token = self._get_csrf(_request)
1701 1703 return supplied_token and supplied_token == cur_token
1702 1704
1703 1705 def _get_request(self):
1704 1706 return get_request(self)
1705 1707
1706 1708 def __wrapper(self, func, *fargs, **fkwargs):
1707 1709 request = self._get_request()
1708 1710
1709 1711 if request.method in self.except_methods:
1710 1712 return func(*fargs, **fkwargs)
1711 1713
1712 1714 cur_token = get_csrf_token(request.session, save_if_missing=False)
1713 1715 if self.check_csrf(request, cur_token):
1714 1716 if request.POST.get(self.token):
1715 1717 del request.POST[self.token]
1716 1718 return func(*fargs, **fkwargs)
1717 1719 else:
1718 1720 reason = 'token-missing'
1719 1721 supplied_token = self._get_csrf(request)
1720 1722 if supplied_token and cur_token != supplied_token:
1721 1723 reason = 'token-mismatch [%s:%s]' % (
1722 1724 cur_token or ''[:6], supplied_token or ''[:6])
1723 1725
1724 1726 csrf_message = \
1725 1727 ("Cross-site request forgery detected, request denied. See "
1726 1728 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1727 1729 "more information.")
1728 1730 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1729 1731 'REMOTE_ADDR:%s, HEADERS:%s' % (
1730 1732 request, reason, request.remote_addr, request.headers))
1731 1733
1732 1734 raise HTTPForbidden(explanation=csrf_message)
1733 1735
1734 1736
1735 1737 class LoginRequired(object):
1736 1738 """
1737 1739 Must be logged in to execute this function else
1738 1740 redirect to login page
1739 1741
1740 1742 :param api_access: if enabled this checks only for valid auth token
1741 1743 and grants access based on valid token
1742 1744 """
1743 1745 def __init__(self, auth_token_access=None):
1744 1746 self.auth_token_access = auth_token_access
1745 1747 if self.auth_token_access:
1746 1748 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1747 1749 if not valid_type:
1748 1750 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1749 1751 UserApiKeys.ROLES, auth_token_access))
1750 1752
1751 1753 def __call__(self, func):
1752 1754 return get_cython_compat_decorator(self.__wrapper, func)
1753 1755
1754 1756 def _get_request(self):
1755 1757 return get_request(self)
1756 1758
1757 1759 def __wrapper(self, func, *fargs, **fkwargs):
1758 1760 from rhodecode.lib import helpers as h
1759 1761 cls = fargs[0]
1760 1762 user = cls._rhodecode_user
1761 1763 request = self._get_request()
1762 1764 _ = request.translate
1763 1765
1764 1766 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1765 1767 log.debug('Starting login restriction checks for user: %s', user)
1766 1768 # check if our IP is allowed
1767 1769 ip_access_valid = True
1768 1770 if not user.ip_allowed:
1769 1771 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1770 1772 category='warning')
1771 1773 ip_access_valid = False
1772 1774
1773 1775 # we used stored token that is extract from GET or URL param (if any)
1774 1776 _auth_token = request.user_auth_token
1775 1777
1776 1778 # check if we used an AUTH_TOKEN and it's a valid one
1777 1779 # defined white-list of controllers which API access will be enabled
1778 1780 whitelist = None
1779 1781 if self.auth_token_access:
1780 1782 # since this location is allowed by @LoginRequired decorator it's our
1781 1783 # only whitelist
1782 1784 whitelist = [loc]
1783 1785 auth_token_access_valid = allowed_auth_token_access(
1784 1786 loc, whitelist=whitelist, auth_token=_auth_token)
1785 1787
1786 1788 # explicit controller is enabled or API is in our whitelist
1787 1789 if auth_token_access_valid:
1788 1790 log.debug('Checking AUTH TOKEN access for %s', cls)
1789 1791 db_user = user.get_instance()
1790 1792
1791 1793 if db_user:
1792 1794 if self.auth_token_access:
1793 1795 roles = self.auth_token_access
1794 1796 else:
1795 1797 roles = [UserApiKeys.ROLE_HTTP]
1796 1798 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1797 1799 db_user, roles)
1798 1800 token_match = db_user.authenticate_by_token(
1799 1801 _auth_token, roles=roles)
1800 1802 else:
1801 1803 log.debug('Unable to fetch db instance for auth user: %s', user)
1802 1804 token_match = False
1803 1805
1804 1806 if _auth_token and token_match:
1805 1807 auth_token_access_valid = True
1806 1808 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1807 1809 else:
1808 1810 auth_token_access_valid = False
1809 1811 if not _auth_token:
1810 1812 log.debug("AUTH TOKEN *NOT* present in request")
1811 1813 else:
1812 1814 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1813 1815
1814 1816 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1815 1817 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1816 1818 else 'AUTH_TOKEN_AUTH'
1817 1819
1818 1820 if ip_access_valid and (
1819 1821 user.is_authenticated or auth_token_access_valid):
1820 1822 log.info('user %s authenticating with:%s IS authenticated on func %s',
1821 1823 user, reason, loc)
1822 1824
1823 1825 return func(*fargs, **fkwargs)
1824 1826 else:
1825 1827 log.warning(
1826 1828 'user %s authenticating with:%s NOT authenticated on '
1827 1829 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1828 1830 user, reason, loc, ip_access_valid, auth_token_access_valid)
1829 1831 # we preserve the get PARAM
1830 1832 came_from = get_came_from(request)
1831 1833
1832 1834 log.debug('redirecting to login page with %s', came_from)
1833 1835 raise HTTPFound(
1834 1836 h.route_path('login', _query={'came_from': came_from}))
1835 1837
1836 1838
1837 1839 class NotAnonymous(object):
1838 1840 """
1839 1841 Must be logged in to execute this function else
1840 1842 redirect to login page
1841 1843 """
1842 1844
1843 1845 def __call__(self, func):
1844 1846 return get_cython_compat_decorator(self.__wrapper, func)
1845 1847
1846 1848 def _get_request(self):
1847 1849 return get_request(self)
1848 1850
1849 1851 def __wrapper(self, func, *fargs, **fkwargs):
1850 1852 import rhodecode.lib.helpers as h
1851 1853 cls = fargs[0]
1852 1854 self.user = cls._rhodecode_user
1853 1855 request = self._get_request()
1854 1856 _ = request.translate
1855 1857 log.debug('Checking if user is not anonymous @%s', cls)
1856 1858
1857 1859 anonymous = self.user.username == User.DEFAULT_USER
1858 1860
1859 1861 if anonymous:
1860 1862 came_from = get_came_from(request)
1861 1863 h.flash(_('You need to be a registered user to '
1862 1864 'perform this action'),
1863 1865 category='warning')
1864 1866 raise HTTPFound(
1865 1867 h.route_path('login', _query={'came_from': came_from}))
1866 1868 else:
1867 1869 return func(*fargs, **fkwargs)
1868 1870
1869 1871
1870 1872 class PermsDecorator(object):
1871 1873 """
1872 1874 Base class for controller decorators, we extract the current user from
1873 1875 the class itself, which has it stored in base controllers
1874 1876 """
1875 1877
1876 1878 def __init__(self, *required_perms):
1877 1879 self.required_perms = set(required_perms)
1878 1880
1879 1881 def __call__(self, func):
1880 1882 return get_cython_compat_decorator(self.__wrapper, func)
1881 1883
1882 1884 def _get_request(self):
1883 1885 return get_request(self)
1884 1886
1885 1887 def __wrapper(self, func, *fargs, **fkwargs):
1886 1888 import rhodecode.lib.helpers as h
1887 1889 cls = fargs[0]
1888 1890 _user = cls._rhodecode_user
1889 1891 request = self._get_request()
1890 1892 _ = request.translate
1891 1893
1892 1894 log.debug('checking %s permissions %s for %s %s',
1893 1895 self.__class__.__name__, self.required_perms, cls, _user)
1894 1896
1895 1897 if self.check_permissions(_user):
1896 1898 log.debug('Permission granted for %s %s', cls, _user)
1897 1899 return func(*fargs, **fkwargs)
1898 1900
1899 1901 else:
1900 1902 log.debug('Permission denied for %s %s', cls, _user)
1901 1903 anonymous = _user.username == User.DEFAULT_USER
1902 1904
1903 1905 if anonymous:
1904 1906 came_from = get_came_from(self._get_request())
1905 1907 h.flash(_('You need to be signed in to view this page'),
1906 1908 category='warning')
1907 1909 raise HTTPFound(
1908 1910 h.route_path('login', _query={'came_from': came_from}))
1909 1911
1910 1912 else:
1911 1913 # redirect with 404 to prevent resource discovery
1912 1914 raise HTTPNotFound()
1913 1915
1914 1916 def check_permissions(self, user):
1915 1917 """Dummy function for overriding"""
1916 1918 raise NotImplementedError(
1917 1919 'You have to write this function in child class')
1918 1920
1919 1921
1920 1922 class HasPermissionAllDecorator(PermsDecorator):
1921 1923 """
1922 1924 Checks for access permission for all given predicates. All of them
1923 1925 have to be meet in order to fulfill the request
1924 1926 """
1925 1927
1926 1928 def check_permissions(self, user):
1927 1929 perms = user.permissions_with_scope({})
1928 1930 if self.required_perms.issubset(perms['global']):
1929 1931 return True
1930 1932 return False
1931 1933
1932 1934
1933 1935 class HasPermissionAnyDecorator(PermsDecorator):
1934 1936 """
1935 1937 Checks for access permission for any of given predicates. In order to
1936 1938 fulfill the request any of predicates must be meet
1937 1939 """
1938 1940
1939 1941 def check_permissions(self, user):
1940 1942 perms = user.permissions_with_scope({})
1941 1943 if self.required_perms.intersection(perms['global']):
1942 1944 return True
1943 1945 return False
1944 1946
1945 1947
1946 1948 class HasRepoPermissionAllDecorator(PermsDecorator):
1947 1949 """
1948 1950 Checks for access permission for all given predicates for specific
1949 1951 repository. All of them have to be meet in order to fulfill the request
1950 1952 """
1951 1953 def _get_repo_name(self):
1952 1954 _request = self._get_request()
1953 1955 return get_repo_slug(_request)
1954 1956
1955 1957 def check_permissions(self, user):
1956 1958 perms = user.permissions
1957 1959 repo_name = self._get_repo_name()
1958 1960
1959 1961 try:
1960 1962 user_perms = {perms['repositories'][repo_name]}
1961 1963 except KeyError:
1962 1964 log.debug('cannot locate repo with name: `%s` in permissions defs',
1963 1965 repo_name)
1964 1966 return False
1965 1967
1966 1968 log.debug('checking `%s` permissions for repo `%s`',
1967 1969 user_perms, repo_name)
1968 1970 if self.required_perms.issubset(user_perms):
1969 1971 return True
1970 1972 return False
1971 1973
1972 1974
1973 1975 class HasRepoPermissionAnyDecorator(PermsDecorator):
1974 1976 """
1975 1977 Checks for access permission for any of given predicates for specific
1976 1978 repository. In order to fulfill the request any of predicates must be meet
1977 1979 """
1978 1980 def _get_repo_name(self):
1979 1981 _request = self._get_request()
1980 1982 return get_repo_slug(_request)
1981 1983
1982 1984 def check_permissions(self, user):
1983 1985 perms = user.permissions
1984 1986 repo_name = self._get_repo_name()
1985 1987
1986 1988 try:
1987 1989 user_perms = {perms['repositories'][repo_name]}
1988 1990 except KeyError:
1989 1991 log.debug(
1990 1992 'cannot locate repo with name: `%s` in permissions defs',
1991 1993 repo_name)
1992 1994 return False
1993 1995
1994 1996 log.debug('checking `%s` permissions for repo `%s`',
1995 1997 user_perms, repo_name)
1996 1998 if self.required_perms.intersection(user_perms):
1997 1999 return True
1998 2000 return False
1999 2001
2000 2002
2001 2003 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
2002 2004 """
2003 2005 Checks for access permission for all given predicates for specific
2004 2006 repository group. All of them have to be meet in order to
2005 2007 fulfill the request
2006 2008 """
2007 2009 def _get_repo_group_name(self):
2008 2010 _request = self._get_request()
2009 2011 return get_repo_group_slug(_request)
2010 2012
2011 2013 def check_permissions(self, user):
2012 2014 perms = user.permissions
2013 2015 group_name = self._get_repo_group_name()
2014 2016 try:
2015 2017 user_perms = {perms['repositories_groups'][group_name]}
2016 2018 except KeyError:
2017 2019 log.debug(
2018 2020 'cannot locate repo group with name: `%s` in permissions defs',
2019 2021 group_name)
2020 2022 return False
2021 2023
2022 2024 log.debug('checking `%s` permissions for repo group `%s`',
2023 2025 user_perms, group_name)
2024 2026 if self.required_perms.issubset(user_perms):
2025 2027 return True
2026 2028 return False
2027 2029
2028 2030
2029 2031 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
2030 2032 """
2031 2033 Checks for access permission for any of given predicates for specific
2032 2034 repository group. In order to fulfill the request any
2033 2035 of predicates must be met
2034 2036 """
2035 2037 def _get_repo_group_name(self):
2036 2038 _request = self._get_request()
2037 2039 return get_repo_group_slug(_request)
2038 2040
2039 2041 def check_permissions(self, user):
2040 2042 perms = user.permissions
2041 2043 group_name = self._get_repo_group_name()
2042 2044
2043 2045 try:
2044 2046 user_perms = {perms['repositories_groups'][group_name]}
2045 2047 except KeyError:
2046 2048 log.debug(
2047 2049 'cannot locate repo group with name: `%s` in permissions defs',
2048 2050 group_name)
2049 2051 return False
2050 2052
2051 2053 log.debug('checking `%s` permissions for repo group `%s`',
2052 2054 user_perms, group_name)
2053 2055 if self.required_perms.intersection(user_perms):
2054 2056 return True
2055 2057 return False
2056 2058
2057 2059
2058 2060 class HasUserGroupPermissionAllDecorator(PermsDecorator):
2059 2061 """
2060 2062 Checks for access permission for all given predicates for specific
2061 2063 user group. All of them have to be meet in order to fulfill the request
2062 2064 """
2063 2065 def _get_user_group_name(self):
2064 2066 _request = self._get_request()
2065 2067 return get_user_group_slug(_request)
2066 2068
2067 2069 def check_permissions(self, user):
2068 2070 perms = user.permissions
2069 2071 group_name = self._get_user_group_name()
2070 2072 try:
2071 2073 user_perms = {perms['user_groups'][group_name]}
2072 2074 except KeyError:
2073 2075 return False
2074 2076
2075 2077 if self.required_perms.issubset(user_perms):
2076 2078 return True
2077 2079 return False
2078 2080
2079 2081
2080 2082 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
2081 2083 """
2082 2084 Checks for access permission for any of given predicates for specific
2083 2085 user group. In order to fulfill the request any of predicates must be meet
2084 2086 """
2085 2087 def _get_user_group_name(self):
2086 2088 _request = self._get_request()
2087 2089 return get_user_group_slug(_request)
2088 2090
2089 2091 def check_permissions(self, user):
2090 2092 perms = user.permissions
2091 2093 group_name = self._get_user_group_name()
2092 2094 try:
2093 2095 user_perms = {perms['user_groups'][group_name]}
2094 2096 except KeyError:
2095 2097 return False
2096 2098
2097 2099 if self.required_perms.intersection(user_perms):
2098 2100 return True
2099 2101 return False
2100 2102
2101 2103
2102 2104 # CHECK FUNCTIONS
2103 2105 class PermsFunction(object):
2104 2106 """Base function for other check functions"""
2105 2107
2106 2108 def __init__(self, *perms):
2107 2109 self.required_perms = set(perms)
2108 2110 self.repo_name = None
2109 2111 self.repo_group_name = None
2110 2112 self.user_group_name = None
2111 2113
2112 2114 def __bool__(self):
2113 2115 import inspect
2114 2116 frame = inspect.currentframe()
2115 2117 stack_trace = traceback.format_stack(frame)
2116 2118 log.error('Checking bool value on a class instance of perm '
2117 2119 'function is not allowed: %s', ''.join(stack_trace))
2118 2120 # rather than throwing errors, here we always return False so if by
2119 2121 # accident someone checks truth for just an instance it will always end
2120 2122 # up in returning False
2121 2123 return False
2122 2124 __nonzero__ = __bool__
2123 2125
2124 2126 def __call__(self, check_location='', user=None):
2125 2127 if not user:
2126 2128 log.debug('Using user attribute from global request')
2127 2129 request = self._get_request()
2128 2130 user = request.user
2129 2131
2130 2132 # init auth user if not already given
2131 2133 if not isinstance(user, AuthUser):
2132 2134 log.debug('Wrapping user %s into AuthUser', user)
2133 2135 user = AuthUser(user.user_id)
2134 2136
2135 2137 cls_name = self.__class__.__name__
2136 2138 check_scope = self._get_check_scope(cls_name)
2137 2139 check_location = check_location or 'unspecified location'
2138 2140
2139 2141 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2140 2142 self.required_perms, user, check_scope, check_location)
2141 2143 if not user:
2142 2144 log.warning('Empty user given for permission check')
2143 2145 return False
2144 2146
2145 2147 if self.check_permissions(user):
2146 2148 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2147 2149 check_scope, user, check_location)
2148 2150 return True
2149 2151
2150 2152 else:
2151 2153 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2152 2154 check_scope, user, check_location)
2153 2155 return False
2154 2156
2155 2157 def _get_request(self):
2156 2158 return get_request(self)
2157 2159
2158 2160 def _get_check_scope(self, cls_name):
2159 2161 return {
2160 2162 'HasPermissionAll': 'GLOBAL',
2161 2163 'HasPermissionAny': 'GLOBAL',
2162 2164 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2163 2165 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2164 2166 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2165 2167 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2166 2168 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2167 2169 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2168 2170 }.get(cls_name, '?:%s' % cls_name)
2169 2171
2170 2172 def check_permissions(self, user):
2171 2173 """Dummy function for overriding"""
2172 2174 raise Exception('You have to write this function in child class')
2173 2175
2174 2176
2175 2177 class HasPermissionAll(PermsFunction):
2176 2178 def check_permissions(self, user):
2177 2179 perms = user.permissions_with_scope({})
2178 2180 if self.required_perms.issubset(perms.get('global')):
2179 2181 return True
2180 2182 return False
2181 2183
2182 2184
2183 2185 class HasPermissionAny(PermsFunction):
2184 2186 def check_permissions(self, user):
2185 2187 perms = user.permissions_with_scope({})
2186 2188 if self.required_perms.intersection(perms.get('global')):
2187 2189 return True
2188 2190 return False
2189 2191
2190 2192
2191 2193 class HasRepoPermissionAll(PermsFunction):
2192 2194 def __call__(self, repo_name=None, check_location='', user=None):
2193 2195 self.repo_name = repo_name
2194 2196 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2195 2197
2196 2198 def _get_repo_name(self):
2197 2199 if not self.repo_name:
2198 2200 _request = self._get_request()
2199 2201 self.repo_name = get_repo_slug(_request)
2200 2202 return self.repo_name
2201 2203
2202 2204 def check_permissions(self, user):
2203 2205 self.repo_name = self._get_repo_name()
2204 2206 perms = user.permissions
2205 2207 try:
2206 2208 user_perms = {perms['repositories'][self.repo_name]}
2207 2209 except KeyError:
2208 2210 return False
2209 2211 if self.required_perms.issubset(user_perms):
2210 2212 return True
2211 2213 return False
2212 2214
2213 2215
2214 2216 class HasRepoPermissionAny(PermsFunction):
2215 2217 def __call__(self, repo_name=None, check_location='', user=None):
2216 2218 self.repo_name = repo_name
2217 2219 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2218 2220
2219 2221 def _get_repo_name(self):
2220 2222 if not self.repo_name:
2221 2223 _request = self._get_request()
2222 2224 self.repo_name = get_repo_slug(_request)
2223 2225 return self.repo_name
2224 2226
2225 2227 def check_permissions(self, user):
2226 2228 self.repo_name = self._get_repo_name()
2227 2229 perms = user.permissions
2228 2230 try:
2229 2231 user_perms = {perms['repositories'][self.repo_name]}
2230 2232 except KeyError:
2231 2233 return False
2232 2234 if self.required_perms.intersection(user_perms):
2233 2235 return True
2234 2236 return False
2235 2237
2236 2238
2237 2239 class HasRepoGroupPermissionAny(PermsFunction):
2238 2240 def __call__(self, group_name=None, check_location='', user=None):
2239 2241 self.repo_group_name = group_name
2240 2242 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2241 2243
2242 2244 def check_permissions(self, user):
2243 2245 perms = user.permissions
2244 2246 try:
2245 2247 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2246 2248 except KeyError:
2247 2249 return False
2248 2250 if self.required_perms.intersection(user_perms):
2249 2251 return True
2250 2252 return False
2251 2253
2252 2254
2253 2255 class HasRepoGroupPermissionAll(PermsFunction):
2254 2256 def __call__(self, group_name=None, check_location='', user=None):
2255 2257 self.repo_group_name = group_name
2256 2258 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2257 2259
2258 2260 def check_permissions(self, user):
2259 2261 perms = user.permissions
2260 2262 try:
2261 2263 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2262 2264 except KeyError:
2263 2265 return False
2264 2266 if self.required_perms.issubset(user_perms):
2265 2267 return True
2266 2268 return False
2267 2269
2268 2270
2269 2271 class HasUserGroupPermissionAny(PermsFunction):
2270 2272 def __call__(self, user_group_name=None, check_location='', user=None):
2271 2273 self.user_group_name = user_group_name
2272 2274 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2273 2275
2274 2276 def check_permissions(self, user):
2275 2277 perms = user.permissions
2276 2278 try:
2277 2279 user_perms = {perms['user_groups'][self.user_group_name]}
2278 2280 except KeyError:
2279 2281 return False
2280 2282 if self.required_perms.intersection(user_perms):
2281 2283 return True
2282 2284 return False
2283 2285
2284 2286
2285 2287 class HasUserGroupPermissionAll(PermsFunction):
2286 2288 def __call__(self, user_group_name=None, check_location='', user=None):
2287 2289 self.user_group_name = user_group_name
2288 2290 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2289 2291
2290 2292 def check_permissions(self, user):
2291 2293 perms = user.permissions
2292 2294 try:
2293 2295 user_perms = {perms['user_groups'][self.user_group_name]}
2294 2296 except KeyError:
2295 2297 return False
2296 2298 if self.required_perms.issubset(user_perms):
2297 2299 return True
2298 2300 return False
2299 2301
2300 2302
2301 2303 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2302 2304 class HasPermissionAnyMiddleware(object):
2303 2305 def __init__(self, *perms):
2304 2306 self.required_perms = set(perms)
2305 2307
2306 2308 def __call__(self, auth_user, repo_name):
2307 2309 # repo_name MUST be unicode, since we handle keys in permission
2308 2310 # dict by unicode
2309 2311 repo_name = safe_unicode(repo_name)
2310 2312 log.debug(
2311 2313 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2312 2314 self.required_perms, auth_user, repo_name)
2313 2315
2314 2316 if self.check_permissions(auth_user, repo_name):
2315 2317 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2316 2318 repo_name, auth_user, 'PermissionMiddleware')
2317 2319 return True
2318 2320
2319 2321 else:
2320 2322 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2321 2323 repo_name, auth_user, 'PermissionMiddleware')
2322 2324 return False
2323 2325
2324 2326 def check_permissions(self, user, repo_name):
2325 2327 perms = user.permissions_with_scope({'repo_name': repo_name})
2326 2328
2327 2329 try:
2328 2330 user_perms = {perms['repositories'][repo_name]}
2329 2331 except Exception:
2330 2332 log.exception('Error while accessing user permissions')
2331 2333 return False
2332 2334
2333 2335 if self.required_perms.intersection(user_perms):
2334 2336 return True
2335 2337 return False
2336 2338
2337 2339
2338 2340 # SPECIAL VERSION TO HANDLE API AUTH
2339 2341 class _BaseApiPerm(object):
2340 2342 def __init__(self, *perms):
2341 2343 self.required_perms = set(perms)
2342 2344
2343 2345 def __call__(self, check_location=None, user=None, repo_name=None,
2344 2346 group_name=None, user_group_name=None):
2345 2347 cls_name = self.__class__.__name__
2346 2348 check_scope = 'global:%s' % (self.required_perms,)
2347 2349 if repo_name:
2348 2350 check_scope += ', repo_name:%s' % (repo_name,)
2349 2351
2350 2352 if group_name:
2351 2353 check_scope += ', repo_group_name:%s' % (group_name,)
2352 2354
2353 2355 if user_group_name:
2354 2356 check_scope += ', user_group_name:%s' % (user_group_name,)
2355 2357
2356 2358 log.debug('checking cls:%s %s %s @ %s',
2357 2359 cls_name, self.required_perms, check_scope, check_location)
2358 2360 if not user:
2359 2361 log.debug('Empty User passed into arguments')
2360 2362 return False
2361 2363
2362 2364 # process user
2363 2365 if not isinstance(user, AuthUser):
2364 2366 user = AuthUser(user.user_id)
2365 2367 if not check_location:
2366 2368 check_location = 'unspecified'
2367 2369 if self.check_permissions(user.permissions, repo_name, group_name,
2368 2370 user_group_name):
2369 2371 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2370 2372 check_scope, user, check_location)
2371 2373 return True
2372 2374
2373 2375 else:
2374 2376 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2375 2377 check_scope, user, check_location)
2376 2378 return False
2377 2379
2378 2380 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2379 2381 user_group_name=None):
2380 2382 """
2381 2383 implement in child class should return True if permissions are ok,
2382 2384 False otherwise
2383 2385
2384 2386 :param perm_defs: dict with permission definitions
2385 2387 :param repo_name: repo name
2386 2388 """
2387 2389 raise NotImplementedError()
2388 2390
2389 2391
2390 2392 class HasPermissionAllApi(_BaseApiPerm):
2391 2393 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2392 2394 user_group_name=None):
2393 2395 if self.required_perms.issubset(perm_defs.get('global')):
2394 2396 return True
2395 2397 return False
2396 2398
2397 2399
2398 2400 class HasPermissionAnyApi(_BaseApiPerm):
2399 2401 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2400 2402 user_group_name=None):
2401 2403 if self.required_perms.intersection(perm_defs.get('global')):
2402 2404 return True
2403 2405 return False
2404 2406
2405 2407
2406 2408 class HasRepoPermissionAllApi(_BaseApiPerm):
2407 2409 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2408 2410 user_group_name=None):
2409 2411 try:
2410 2412 _user_perms = {perm_defs['repositories'][repo_name]}
2411 2413 except KeyError:
2412 2414 log.warning(traceback.format_exc())
2413 2415 return False
2414 2416 if self.required_perms.issubset(_user_perms):
2415 2417 return True
2416 2418 return False
2417 2419
2418 2420
2419 2421 class HasRepoPermissionAnyApi(_BaseApiPerm):
2420 2422 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2421 2423 user_group_name=None):
2422 2424 try:
2423 2425 _user_perms = {perm_defs['repositories'][repo_name]}
2424 2426 except KeyError:
2425 2427 log.warning(traceback.format_exc())
2426 2428 return False
2427 2429 if self.required_perms.intersection(_user_perms):
2428 2430 return True
2429 2431 return False
2430 2432
2431 2433
2432 2434 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2433 2435 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2434 2436 user_group_name=None):
2435 2437 try:
2436 2438 _user_perms = {perm_defs['repositories_groups'][group_name]}
2437 2439 except KeyError:
2438 2440 log.warning(traceback.format_exc())
2439 2441 return False
2440 2442 if self.required_perms.intersection(_user_perms):
2441 2443 return True
2442 2444 return False
2443 2445
2444 2446
2445 2447 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2446 2448 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2447 2449 user_group_name=None):
2448 2450 try:
2449 2451 _user_perms = {perm_defs['repositories_groups'][group_name]}
2450 2452 except KeyError:
2451 2453 log.warning(traceback.format_exc())
2452 2454 return False
2453 2455 if self.required_perms.issubset(_user_perms):
2454 2456 return True
2455 2457 return False
2456 2458
2457 2459
2458 2460 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2459 2461 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2460 2462 user_group_name=None):
2461 2463 try:
2462 2464 _user_perms = {perm_defs['user_groups'][user_group_name]}
2463 2465 except KeyError:
2464 2466 log.warning(traceback.format_exc())
2465 2467 return False
2466 2468 if self.required_perms.intersection(_user_perms):
2467 2469 return True
2468 2470 return False
2469 2471
2470 2472
2471 2473 def check_ip_access(source_ip, allowed_ips=None):
2472 2474 """
2473 2475 Checks if source_ip is a subnet of any of allowed_ips.
2474 2476
2475 2477 :param source_ip:
2476 2478 :param allowed_ips: list of allowed ips together with mask
2477 2479 """
2478 2480 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2479 2481 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2480 2482 if isinstance(allowed_ips, (tuple, list, set)):
2481 2483 for ip in allowed_ips:
2482 2484 ip = safe_unicode(ip)
2483 2485 try:
2484 2486 network_address = ipaddress.ip_network(ip, strict=False)
2485 2487 if source_ip_address in network_address:
2486 2488 log.debug('IP %s is network %s', source_ip_address, network_address)
2487 2489 return True
2488 2490 # for any case we cannot determine the IP, don't crash just
2489 2491 # skip it and log as error, we want to say forbidden still when
2490 2492 # sending bad IP
2491 2493 except Exception:
2492 2494 log.error(traceback.format_exc())
2493 2495 continue
2494 2496 return False
2495 2497
2496 2498
2497 2499 def get_cython_compat_decorator(wrapper, func):
2498 2500 """
2499 2501 Creates a cython compatible decorator. The previously used
2500 2502 decorator.decorator() function seems to be incompatible with cython.
2501 2503
2502 2504 :param wrapper: __wrapper method of the decorator class
2503 2505 :param func: decorated function
2504 2506 """
2505 2507 @wraps(func)
2506 2508 def local_wrapper(*args, **kwds):
2507 2509 return wrapper(func, *args, **kwds)
2508 2510 local_wrapper.__wrapped__ = func
2509 2511 return local_wrapper
2510 2512
2511 2513
@@ -1,598 +1,600 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 import collections
25 25 import logging
26 26 import traceback
27 27
28 28 from sqlalchemy.exc import DatabaseError
29 29
30 30 from rhodecode import events
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.db import (
33 33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 34 UserUserGroupToPerm, UserGroup, UserGroupToPerm, UserToRepoBranchPermission)
35 35 from rhodecode.lib.utils2 import str2bool, safe_int
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class PermissionModel(BaseModel):
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 FORKING_DISABLED = 'hg.fork.none'
45 FORKING_ENABLED = 'hg.fork.repository'
44 46
45 47 cls = Permission
46 48 global_perms = {
47 49 'default_repo_create': None,
48 50 # special case for create repos on write access to group
49 51 'default_repo_create_on_write': None,
50 52 'default_repo_group_create': None,
51 53 'default_user_group_create': None,
52 54 'default_fork_create': None,
53 55 'default_inherit_default_permissions': None,
54 56 'default_register': None,
55 57 'default_password_reset': None,
56 58 'default_extern_activate': None,
57 59
58 60 # object permissions below
59 61 'default_repo_perm': None,
60 62 'default_group_perm': None,
61 63 'default_user_group_perm': None,
62 64
63 65 # branch
64 66 'default_branch_perm': None,
65 67 }
66 68
67 69 def set_global_permission_choices(self, c_obj, gettext_translator):
68 70 _ = gettext_translator
69 71
70 72 c_obj.repo_perms_choices = [
71 73 ('repository.none', _('None'),),
72 74 ('repository.read', _('Read'),),
73 75 ('repository.write', _('Write'),),
74 76 ('repository.admin', _('Admin'),)]
75 77
76 78 c_obj.group_perms_choices = [
77 79 ('group.none', _('None'),),
78 80 ('group.read', _('Read'),),
79 81 ('group.write', _('Write'),),
80 82 ('group.admin', _('Admin'),)]
81 83
82 84 c_obj.user_group_perms_choices = [
83 85 ('usergroup.none', _('None'),),
84 86 ('usergroup.read', _('Read'),),
85 87 ('usergroup.write', _('Write'),),
86 88 ('usergroup.admin', _('Admin'),)]
87 89
88 90 c_obj.branch_perms_choices = [
89 91 ('branch.none', _('Protected/No Access'),),
90 92 ('branch.merge', _('Web merge'),),
91 93 ('branch.push', _('Push'),),
92 94 ('branch.push_force', _('Force Push'),)]
93 95
94 96 c_obj.register_choices = [
95 97 ('hg.register.none', _('Disabled')),
96 98 ('hg.register.manual_activate', _('Allowed with manual account activation')),
97 99 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
98 100
99 101 c_obj.password_reset_choices = [
100 102 ('hg.password_reset.enabled', _('Allow password recovery')),
101 103 ('hg.password_reset.hidden', _('Hide password recovery link')),
102 104 ('hg.password_reset.disabled', _('Disable password recovery')),]
103 105
104 106 c_obj.extern_activate_choices = [
105 107 ('hg.extern_activate.manual', _('Manual activation of external account')),
106 108 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
107 109
108 110 c_obj.repo_create_choices = [
109 111 ('hg.create.none', _('Disabled')),
110 112 ('hg.create.repository', _('Enabled'))]
111 113
112 114 c_obj.repo_create_on_write_choices = [
113 115 ('hg.create.write_on_repogroup.false', _('Disabled')),
114 116 ('hg.create.write_on_repogroup.true', _('Enabled'))]
115 117
116 118 c_obj.user_group_create_choices = [
117 119 ('hg.usergroup.create.false', _('Disabled')),
118 120 ('hg.usergroup.create.true', _('Enabled'))]
119 121
120 122 c_obj.repo_group_create_choices = [
121 123 ('hg.repogroup.create.false', _('Disabled')),
122 124 ('hg.repogroup.create.true', _('Enabled'))]
123 125
124 126 c_obj.fork_choices = [
125 ('hg.fork.none', _('Disabled')),
126 ('hg.fork.repository', _('Enabled'))]
127 (self.FORKING_DISABLED, _('Disabled')),
128 (self.FORKING_ENABLED, _('Enabled'))]
127 129
128 130 c_obj.inherit_default_permission_choices = [
129 131 ('hg.inherit_default_perms.false', _('Disabled')),
130 132 ('hg.inherit_default_perms.true', _('Enabled'))]
131 133
132 134 def get_default_perms(self, object_perms, suffix):
133 135 defaults = {}
134 136 for perm in object_perms:
135 137 # perms
136 138 if perm.permission.permission_name.startswith('repository.'):
137 139 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
138 140
139 141 if perm.permission.permission_name.startswith('group.'):
140 142 defaults['default_group_perm' + suffix] = perm.permission.permission_name
141 143
142 144 if perm.permission.permission_name.startswith('usergroup.'):
143 145 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
144 146
145 147 # branch
146 148 if perm.permission.permission_name.startswith('branch.'):
147 149 defaults['default_branch_perm' + suffix] = perm.permission.permission_name
148 150
149 151 # creation of objects
150 152 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
151 153 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
152 154
153 155 elif perm.permission.permission_name.startswith('hg.create.'):
154 156 defaults['default_repo_create' + suffix] = perm.permission.permission_name
155 157
156 158 if perm.permission.permission_name.startswith('hg.fork.'):
157 159 defaults['default_fork_create' + suffix] = perm.permission.permission_name
158 160
159 161 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
160 162 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
161 163
162 164 if perm.permission.permission_name.startswith('hg.repogroup.'):
163 165 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
164 166
165 167 if perm.permission.permission_name.startswith('hg.usergroup.'):
166 168 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
167 169
168 170 # registration and external account activation
169 171 if perm.permission.permission_name.startswith('hg.register.'):
170 172 defaults['default_register' + suffix] = perm.permission.permission_name
171 173
172 174 if perm.permission.permission_name.startswith('hg.password_reset.'):
173 175 defaults['default_password_reset' + suffix] = perm.permission.permission_name
174 176
175 177 if perm.permission.permission_name.startswith('hg.extern_activate.'):
176 178 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
177 179
178 180 return defaults
179 181
180 182 def _make_new_user_perm(self, user, perm_name):
181 183 log.debug('Creating new user permission:%s', perm_name)
182 184 new = UserToPerm()
183 185 new.user = user
184 186 new.permission = Permission.get_by_key(perm_name)
185 187 return new
186 188
187 189 def _make_new_user_group_perm(self, user_group, perm_name):
188 190 log.debug('Creating new user group permission:%s', perm_name)
189 191 new = UserGroupToPerm()
190 192 new.users_group = user_group
191 193 new.permission = Permission.get_by_key(perm_name)
192 194 return new
193 195
194 196 def _keep_perm(self, perm_name, keep_fields):
195 197 def get_pat(field_name):
196 198 return {
197 199 # global perms
198 200 'default_repo_create': 'hg.create.',
199 201 # special case for create repos on write access to group
200 202 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
201 203 'default_repo_group_create': 'hg.repogroup.create.',
202 204 'default_user_group_create': 'hg.usergroup.create.',
203 205 'default_fork_create': 'hg.fork.',
204 206 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
205 207
206 208 # application perms
207 209 'default_register': 'hg.register.',
208 210 'default_password_reset': 'hg.password_reset.',
209 211 'default_extern_activate': 'hg.extern_activate.',
210 212
211 213 # object permissions below
212 214 'default_repo_perm': 'repository.',
213 215 'default_group_perm': 'group.',
214 216 'default_user_group_perm': 'usergroup.',
215 217 # branch
216 218 'default_branch_perm': 'branch.',
217 219
218 220 }[field_name]
219 221 for field in keep_fields:
220 222 pat = get_pat(field)
221 223 if perm_name.startswith(pat):
222 224 return True
223 225 return False
224 226
225 227 def _clear_object_perm(self, object_perms, preserve=None):
226 228 preserve = preserve or []
227 229 _deleted = []
228 230 for perm in object_perms:
229 231 perm_name = perm.permission.permission_name
230 232 if not self._keep_perm(perm_name, keep_fields=preserve):
231 233 _deleted.append(perm_name)
232 234 self.sa.delete(perm)
233 235 return _deleted
234 236
235 237 def _clear_user_perms(self, user_id, preserve=None):
236 238 perms = self.sa.query(UserToPerm)\
237 239 .filter(UserToPerm.user_id == user_id)\
238 240 .all()
239 241 return self._clear_object_perm(perms, preserve=preserve)
240 242
241 243 def _clear_user_group_perms(self, user_group_id, preserve=None):
242 244 perms = self.sa.query(UserGroupToPerm)\
243 245 .filter(UserGroupToPerm.users_group_id == user_group_id)\
244 246 .all()
245 247 return self._clear_object_perm(perms, preserve=preserve)
246 248
247 249 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
248 250 # clear current entries, to make this function idempotent
249 251 # it will fix even if we define more permissions or permissions
250 252 # are somehow missing
251 253 preserve = preserve or []
252 254 _global_perms = self.global_perms.copy()
253 255 if obj_type not in ['user', 'user_group']:
254 256 raise ValueError("obj_type must be on of 'user' or 'user_group'")
255 257 global_perms = len(_global_perms)
256 258 default_user_perms = len(Permission.DEFAULT_USER_PERMISSIONS)
257 259 if global_perms != default_user_perms:
258 260 raise Exception(
259 261 'Inconsistent permissions definition. Got {} vs {}'.format(
260 262 global_perms, default_user_perms))
261 263
262 264 if obj_type == 'user':
263 265 self._clear_user_perms(object.user_id, preserve)
264 266 if obj_type == 'user_group':
265 267 self._clear_user_group_perms(object.users_group_id, preserve)
266 268
267 269 # now kill the keys that we want to preserve from the form.
268 270 for key in preserve:
269 271 del _global_perms[key]
270 272
271 273 for k in _global_perms.copy():
272 274 _global_perms[k] = form_result[k]
273 275
274 276 # at that stage we validate all are passed inside form_result
275 277 for _perm_key, perm_value in _global_perms.items():
276 278 if perm_value is None:
277 279 raise ValueError('Missing permission for %s' % (_perm_key,))
278 280
279 281 if obj_type == 'user':
280 282 p = self._make_new_user_perm(object, perm_value)
281 283 self.sa.add(p)
282 284 if obj_type == 'user_group':
283 285 p = self._make_new_user_group_perm(object, perm_value)
284 286 self.sa.add(p)
285 287
286 288 def _set_new_user_perms(self, user, form_result, preserve=None):
287 289 return self._set_new_object_perms(
288 290 'user', user, form_result, preserve)
289 291
290 292 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
291 293 return self._set_new_object_perms(
292 294 'user_group', user_group, form_result, preserve)
293 295
294 296 def set_new_user_perms(self, user, form_result):
295 297 # calculate what to preserve from what is given in form_result
296 298 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
297 299 return self._set_new_user_perms(user, form_result, preserve)
298 300
299 301 def set_new_user_group_perms(self, user_group, form_result):
300 302 # calculate what to preserve from what is given in form_result
301 303 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
302 304 return self._set_new_user_group_perms(user_group, form_result, preserve)
303 305
304 306 def create_permissions(self):
305 307 """
306 308 Create permissions for whole system
307 309 """
308 310 for p in Permission.PERMS:
309 311 if not Permission.get_by_key(p[0]):
310 312 new_perm = Permission()
311 313 new_perm.permission_name = p[0]
312 314 new_perm.permission_longname = p[0] # translation err with p[1]
313 315 self.sa.add(new_perm)
314 316
315 317 def _create_default_object_permission(self, obj_type, obj, obj_perms,
316 318 force=False):
317 319 if obj_type not in ['user', 'user_group']:
318 320 raise ValueError("obj_type must be on of 'user' or 'user_group'")
319 321
320 322 def _get_group(perm_name):
321 323 return '.'.join(perm_name.split('.')[:1])
322 324
323 325 defined_perms_groups = map(
324 326 _get_group, (x.permission.permission_name for x in obj_perms))
325 327 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
326 328
327 329 if force:
328 330 self._clear_object_perm(obj_perms)
329 331 self.sa.commit()
330 332 defined_perms_groups = []
331 333 # for every default permission that needs to be created, we check if
332 334 # it's group is already defined, if it's not we create default perm
333 335 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
334 336 gr = _get_group(perm_name)
335 337 if gr not in defined_perms_groups:
336 338 log.debug('GR:%s not found, creating permission %s',
337 339 gr, perm_name)
338 340 if obj_type == 'user':
339 341 new_perm = self._make_new_user_perm(obj, perm_name)
340 342 self.sa.add(new_perm)
341 343 if obj_type == 'user_group':
342 344 new_perm = self._make_new_user_group_perm(obj, perm_name)
343 345 self.sa.add(new_perm)
344 346
345 347 def create_default_user_permissions(self, user, force=False):
346 348 """
347 349 Creates only missing default permissions for user, if force is set it
348 350 resets the default permissions for that user
349 351
350 352 :param user:
351 353 :param force:
352 354 """
353 355 user = self._get_user(user)
354 356 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
355 357 return self._create_default_object_permission(
356 358 'user', user, obj_perms, force)
357 359
358 360 def create_default_user_group_permissions(self, user_group, force=False):
359 361 """
360 362 Creates only missing default permissions for user group, if force is
361 363 set it resets the default permissions for that user group
362 364
363 365 :param user_group:
364 366 :param force:
365 367 """
366 368 user_group = self._get_user_group(user_group)
367 369 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
368 370 return self._create_default_object_permission(
369 371 'user_group', user_group, obj_perms, force)
370 372
371 373 def update_application_permissions(self, form_result):
372 374 if 'perm_user_id' in form_result:
373 375 perm_user = User.get(safe_int(form_result['perm_user_id']))
374 376 else:
375 377 # used mostly to do lookup for default user
376 378 perm_user = User.get_by_username(form_result['perm_user_name'])
377 379
378 380 try:
379 381 # stage 1 set anonymous access
380 382 if perm_user.username == User.DEFAULT_USER:
381 383 perm_user.active = str2bool(form_result['anonymous'])
382 384 self.sa.add(perm_user)
383 385
384 386 # stage 2 reset defaults and set them from form data
385 387 self._set_new_user_perms(perm_user, form_result, preserve=[
386 388 'default_repo_perm',
387 389 'default_group_perm',
388 390 'default_user_group_perm',
389 391 'default_branch_perm',
390 392
391 393 'default_repo_group_create',
392 394 'default_user_group_create',
393 395 'default_repo_create_on_write',
394 396 'default_repo_create',
395 397 'default_fork_create',
396 398 'default_inherit_default_permissions',])
397 399
398 400 self.sa.commit()
399 401 except (DatabaseError,):
400 402 log.error(traceback.format_exc())
401 403 self.sa.rollback()
402 404 raise
403 405
404 406 def update_user_permissions(self, form_result):
405 407 if 'perm_user_id' in form_result:
406 408 perm_user = User.get(safe_int(form_result['perm_user_id']))
407 409 else:
408 410 # used mostly to do lookup for default user
409 411 perm_user = User.get_by_username(form_result['perm_user_name'])
410 412 try:
411 413 # stage 2 reset defaults and set them from form data
412 414 self._set_new_user_perms(perm_user, form_result, preserve=[
413 415 'default_repo_perm',
414 416 'default_group_perm',
415 417 'default_user_group_perm',
416 418 'default_branch_perm',
417 419
418 420 'default_register',
419 421 'default_password_reset',
420 422 'default_extern_activate'])
421 423 self.sa.commit()
422 424 except (DatabaseError,):
423 425 log.error(traceback.format_exc())
424 426 self.sa.rollback()
425 427 raise
426 428
427 429 def update_user_group_permissions(self, form_result):
428 430 if 'perm_user_group_id' in form_result:
429 431 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
430 432 else:
431 433 # used mostly to do lookup for default user
432 434 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
433 435 try:
434 436 # stage 2 reset defaults and set them from form data
435 437 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
436 438 'default_repo_perm',
437 439 'default_group_perm',
438 440 'default_user_group_perm',
439 441 'default_branch_perm',
440 442
441 443 'default_register',
442 444 'default_password_reset',
443 445 'default_extern_activate'])
444 446 self.sa.commit()
445 447 except (DatabaseError,):
446 448 log.error(traceback.format_exc())
447 449 self.sa.rollback()
448 450 raise
449 451
450 452 def update_object_permissions(self, form_result):
451 453 if 'perm_user_id' in form_result:
452 454 perm_user = User.get(safe_int(form_result['perm_user_id']))
453 455 else:
454 456 # used mostly to do lookup for default user
455 457 perm_user = User.get_by_username(form_result['perm_user_name'])
456 458 try:
457 459
458 460 # stage 2 reset defaults and set them from form data
459 461 self._set_new_user_perms(perm_user, form_result, preserve=[
460 462 'default_repo_group_create',
461 463 'default_user_group_create',
462 464 'default_repo_create_on_write',
463 465 'default_repo_create',
464 466 'default_fork_create',
465 467 'default_inherit_default_permissions',
466 468 'default_branch_perm',
467 469
468 470 'default_register',
469 471 'default_password_reset',
470 472 'default_extern_activate'])
471 473
472 474 # overwrite default repo permissions
473 475 if form_result['overwrite_default_repo']:
474 476 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
475 477 _def = Permission.get_by_key('repository.' + _def_name)
476 478 for r2p in self.sa.query(UserRepoToPerm)\
477 479 .filter(UserRepoToPerm.user == perm_user)\
478 480 .all():
479 481 # don't reset PRIVATE repositories
480 482 if not r2p.repository.private:
481 483 r2p.permission = _def
482 484 self.sa.add(r2p)
483 485
484 486 # overwrite default repo group permissions
485 487 if form_result['overwrite_default_group']:
486 488 _def_name = form_result['default_group_perm'].split('group.')[-1]
487 489 _def = Permission.get_by_key('group.' + _def_name)
488 490 for g2p in self.sa.query(UserRepoGroupToPerm)\
489 491 .filter(UserRepoGroupToPerm.user == perm_user)\
490 492 .all():
491 493 g2p.permission = _def
492 494 self.sa.add(g2p)
493 495
494 496 # overwrite default user group permissions
495 497 if form_result['overwrite_default_user_group']:
496 498 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
497 499 # user groups
498 500 _def = Permission.get_by_key('usergroup.' + _def_name)
499 501 for g2p in self.sa.query(UserUserGroupToPerm)\
500 502 .filter(UserUserGroupToPerm.user == perm_user)\
501 503 .all():
502 504 g2p.permission = _def
503 505 self.sa.add(g2p)
504 506
505 507 # COMMIT
506 508 self.sa.commit()
507 509 except (DatabaseError,):
508 510 log.exception('Failed to set default object permissions')
509 511 self.sa.rollback()
510 512 raise
511 513
512 514 def update_branch_permissions(self, form_result):
513 515 if 'perm_user_id' in form_result:
514 516 perm_user = User.get(safe_int(form_result['perm_user_id']))
515 517 else:
516 518 # used mostly to do lookup for default user
517 519 perm_user = User.get_by_username(form_result['perm_user_name'])
518 520 try:
519 521
520 522 # stage 2 reset defaults and set them from form data
521 523 self._set_new_user_perms(perm_user, form_result, preserve=[
522 524 'default_repo_perm',
523 525 'default_group_perm',
524 526 'default_user_group_perm',
525 527
526 528 'default_repo_group_create',
527 529 'default_user_group_create',
528 530 'default_repo_create_on_write',
529 531 'default_repo_create',
530 532 'default_fork_create',
531 533 'default_inherit_default_permissions',
532 534
533 535 'default_register',
534 536 'default_password_reset',
535 537 'default_extern_activate'])
536 538
537 539 # overwrite default branch permissions
538 540 if form_result['overwrite_default_branch']:
539 541 _def_name = \
540 542 form_result['default_branch_perm'].split('branch.')[-1]
541 543
542 544 _def = Permission.get_by_key('branch.' + _def_name)
543 545
544 546 user_perms = UserToRepoBranchPermission.query()\
545 547 .join(UserToRepoBranchPermission.user_repo_to_perm)\
546 548 .filter(UserRepoToPerm.user == perm_user).all()
547 549
548 550 for g2p in user_perms:
549 551 g2p.permission = _def
550 552 self.sa.add(g2p)
551 553
552 554 # COMMIT
553 555 self.sa.commit()
554 556 except (DatabaseError,):
555 557 log.exception('Failed to set default branch permissions')
556 558 self.sa.rollback()
557 559 raise
558 560
559 561 def get_users_with_repo_write(self, db_repo):
560 562 write_plus = ['repository.write', 'repository.admin']
561 563 default_user_id = User.get_default_user_id()
562 564 user_write_permissions = collections.OrderedDict()
563 565
564 566 # write or higher and DEFAULT user for inheritance
565 567 for perm in db_repo.permissions():
566 568 if perm.permission in write_plus or perm.user_id == default_user_id:
567 569 user_write_permissions[perm.user_id] = perm
568 570 return user_write_permissions
569 571
570 572 def get_user_groups_with_repo_write(self, db_repo):
571 573 write_plus = ['repository.write', 'repository.admin']
572 574 user_group_write_permissions = collections.OrderedDict()
573 575
574 576 # write or higher and DEFAULT user for inheritance
575 577 for p in db_repo.permission_user_groups():
576 578 if p.permission in write_plus:
577 579 user_group_write_permissions[p.users_group_id] = p
578 580 return user_group_write_permissions
579 581
580 582 def trigger_permission_flush(self, affected_user_ids=None):
581 583 affected_user_ids = affected_user_ids or User.get_all_user_ids()
582 584 events.trigger(events.UserPermissionsChange(affected_user_ids))
583 585
584 586 def flush_user_permission_caches(self, changes, affected_user_ids=None):
585 587 affected_user_ids = affected_user_ids or []
586 588
587 589 for change in changes['added'] + changes['updated'] + changes['deleted']:
588 590 if change['type'] == 'user':
589 591 affected_user_ids.append(change['id'])
590 592 if change['type'] == 'user_group':
591 593 user_group = UserGroup.get(safe_int(change['id']))
592 594 if user_group:
593 595 group_members_ids = [x.user_id for x in user_group.members]
594 596 affected_user_ids.extend(group_members_ids)
595 597
596 598 self.trigger_permission_flush(affected_user_ids)
597 599
598 600 return affected_user_ids
General Comments 0
You need to be logged in to leave comments. Login now