##// END OF EJS Templates
audit-logs: implement enum sources that should be re-used.
marcink -
r1798:bf108a7c default
parent child Browse files
Show More
@@ -1,2062 +1,2063 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 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
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 36 from rhodecode.lib.ext_json import json
37 37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 38 from rhodecode.model.changeset_status import ChangesetStatusModel
39 39 from rhodecode.model.comment import CommentsModel
40 40 from rhodecode.model.db import (
41 41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
42 42 ChangesetComment)
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.scm import ScmModel, RepoList
45 45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
46 46 from rhodecode.model import validation_schema
47 47 from rhodecode.model.validation_schema.schemas import repo_schema
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 @jsonrpc_method()
53 53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 54 """
55 55 Gets an existing repository by its name or repository_id.
56 56
57 57 The members section so the output returns users groups or users
58 58 associated with that repository.
59 59
60 60 This command can only be run using an |authtoken| with admin rights,
61 61 or users with at least read rights to the |repo|.
62 62
63 63 :param apiuser: This is filled automatically from the |authtoken|.
64 64 :type apiuser: AuthUser
65 65 :param repoid: The repository name or repository id.
66 66 :type repoid: str or int
67 67 :param cache: use the cached value for last changeset
68 68 :type: cache: Optional(bool)
69 69
70 70 Example output:
71 71
72 72 .. code-block:: bash
73 73
74 74 {
75 75 "error": null,
76 76 "id": <repo_id>,
77 77 "result": {
78 78 "clone_uri": null,
79 79 "created_on": "timestamp",
80 80 "description": "repo description",
81 81 "enable_downloads": false,
82 82 "enable_locking": false,
83 83 "enable_statistics": false,
84 84 "followers": [
85 85 {
86 86 "active": true,
87 87 "admin": false,
88 88 "api_key": "****************************************",
89 89 "api_keys": [
90 90 "****************************************"
91 91 ],
92 92 "email": "user@example.com",
93 93 "emails": [
94 94 "user@example.com"
95 95 ],
96 96 "extern_name": "rhodecode",
97 97 "extern_type": "rhodecode",
98 98 "firstname": "username",
99 99 "ip_addresses": [],
100 100 "language": null,
101 101 "last_login": "2015-09-16T17:16:35.854",
102 102 "lastname": "surname",
103 103 "user_id": <user_id>,
104 104 "username": "name"
105 105 }
106 106 ],
107 107 "fork_of": "parent-repo",
108 108 "landing_rev": [
109 109 "rev",
110 110 "tip"
111 111 ],
112 112 "last_changeset": {
113 113 "author": "User <user@example.com>",
114 114 "branch": "default",
115 115 "date": "timestamp",
116 116 "message": "last commit message",
117 117 "parents": [
118 118 {
119 119 "raw_id": "commit-id"
120 120 }
121 121 ],
122 122 "raw_id": "commit-id",
123 123 "revision": <revision number>,
124 124 "short_id": "short id"
125 125 },
126 126 "lock_reason": null,
127 127 "locked_by": null,
128 128 "locked_date": null,
129 129 "members": [
130 130 {
131 131 "name": "super-admin-name",
132 132 "origin": "super-admin",
133 133 "permission": "repository.admin",
134 134 "type": "user"
135 135 },
136 136 {
137 137 "name": "owner-name",
138 138 "origin": "owner",
139 139 "permission": "repository.admin",
140 140 "type": "user"
141 141 },
142 142 {
143 143 "name": "user-group-name",
144 144 "origin": "permission",
145 145 "permission": "repository.write",
146 146 "type": "user_group"
147 147 }
148 148 ],
149 149 "owner": "owner-name",
150 150 "permissions": [
151 151 {
152 152 "name": "super-admin-name",
153 153 "origin": "super-admin",
154 154 "permission": "repository.admin",
155 155 "type": "user"
156 156 },
157 157 {
158 158 "name": "owner-name",
159 159 "origin": "owner",
160 160 "permission": "repository.admin",
161 161 "type": "user"
162 162 },
163 163 {
164 164 "name": "user-group-name",
165 165 "origin": "permission",
166 166 "permission": "repository.write",
167 167 "type": "user_group"
168 168 }
169 169 ],
170 170 "private": true,
171 171 "repo_id": 676,
172 172 "repo_name": "user-group/repo-name",
173 173 "repo_type": "hg"
174 174 }
175 175 }
176 176 """
177 177
178 178 repo = get_repo_or_error(repoid)
179 179 cache = Optional.extract(cache)
180 180
181 181 include_secrets = False
182 182 if has_superadmin_permission(apiuser):
183 183 include_secrets = True
184 184 else:
185 185 # check if we have at least read permission for this repo !
186 186 _perms = (
187 187 'repository.admin', 'repository.write', 'repository.read',)
188 188 validate_repo_permissions(apiuser, repoid, repo, _perms)
189 189
190 190 permissions = []
191 191 for _user in repo.permissions():
192 192 user_data = {
193 193 'name': _user.username,
194 194 'permission': _user.permission,
195 195 'origin': get_origin(_user),
196 196 'type': "user",
197 197 }
198 198 permissions.append(user_data)
199 199
200 200 for _user_group in repo.permission_user_groups():
201 201 user_group_data = {
202 202 'name': _user_group.users_group_name,
203 203 'permission': _user_group.permission,
204 204 'origin': get_origin(_user_group),
205 205 'type': "user_group",
206 206 }
207 207 permissions.append(user_group_data)
208 208
209 209 following_users = [
210 210 user.user.get_api_data(include_secrets=include_secrets)
211 211 for user in repo.followers]
212 212
213 213 if not cache:
214 214 repo.update_commit_cache()
215 215 data = repo.get_api_data(include_secrets=include_secrets)
216 216 data['members'] = permissions # TODO: this should be deprecated soon
217 217 data['permissions'] = permissions
218 218 data['followers'] = following_users
219 219 return data
220 220
221 221
222 222 @jsonrpc_method()
223 223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
224 224 """
225 225 Lists all existing repositories.
226 226
227 227 This command can only be run using an |authtoken| with admin rights,
228 228 or users with at least read rights to |repos|.
229 229
230 230 :param apiuser: This is filled automatically from the |authtoken|.
231 231 :type apiuser: AuthUser
232 232 :param root: specify root repository group to fetch repositories.
233 233 filters the returned repositories to be members of given root group.
234 234 :type root: Optional(None)
235 235 :param traverse: traverse given root into subrepositories. With this flag
236 236 set to False, it will only return top-level repositories from `root`.
237 237 if root is empty it will return just top-level repositories.
238 238 :type traverse: Optional(True)
239 239
240 240
241 241 Example output:
242 242
243 243 .. code-block:: bash
244 244
245 245 id : <id_given_in_input>
246 246 result: [
247 247 {
248 248 "repo_id" : "<repo_id>",
249 249 "repo_name" : "<reponame>"
250 250 "repo_type" : "<repo_type>",
251 251 "clone_uri" : "<clone_uri>",
252 252 "private": : "<bool>",
253 253 "created_on" : "<datetimecreated>",
254 254 "description" : "<description>",
255 255 "landing_rev": "<landing_rev>",
256 256 "owner": "<repo_owner>",
257 257 "fork_of": "<name_of_fork_parent>",
258 258 "enable_downloads": "<bool>",
259 259 "enable_locking": "<bool>",
260 260 "enable_statistics": "<bool>",
261 261 },
262 262 ...
263 263 ]
264 264 error: null
265 265 """
266 266
267 267 include_secrets = has_superadmin_permission(apiuser)
268 268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
269 269 extras = {'user': apiuser}
270 270
271 271 root = Optional.extract(root)
272 272 traverse = Optional.extract(traverse, binary=True)
273 273
274 274 if root:
275 275 # verify parent existance, if it's empty return an error
276 276 parent = RepoGroup.get_by_group_name(root)
277 277 if not parent:
278 278 raise JSONRPCError(
279 279 'Root repository group `{}` does not exist'.format(root))
280 280
281 281 if traverse:
282 282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
283 283 else:
284 284 repos = RepoModel().get_repos_for_root(root=parent)
285 285 else:
286 286 if traverse:
287 287 repos = RepoModel().get_all()
288 288 else:
289 289 # return just top-level
290 290 repos = RepoModel().get_repos_for_root(root=None)
291 291
292 292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
293 293 return [repo.get_api_data(include_secrets=include_secrets)
294 294 for repo in repo_list]
295 295
296 296
297 297 @jsonrpc_method()
298 298 def get_repo_changeset(request, apiuser, repoid, revision,
299 299 details=Optional('basic')):
300 300 """
301 301 Returns information about a changeset.
302 302
303 303 Additionally parameters define the amount of details returned by
304 304 this function.
305 305
306 306 This command can only be run using an |authtoken| with admin rights,
307 307 or users with at least read rights to the |repo|.
308 308
309 309 :param apiuser: This is filled automatically from the |authtoken|.
310 310 :type apiuser: AuthUser
311 311 :param repoid: The repository name or repository id
312 312 :type repoid: str or int
313 313 :param revision: revision for which listing should be done
314 314 :type revision: str
315 315 :param details: details can be 'basic|extended|full' full gives diff
316 316 info details like the diff itself, and number of changed files etc.
317 317 :type details: Optional(str)
318 318
319 319 """
320 320 repo = get_repo_or_error(repoid)
321 321 if not has_superadmin_permission(apiuser):
322 322 _perms = (
323 323 'repository.admin', 'repository.write', 'repository.read',)
324 324 validate_repo_permissions(apiuser, repoid, repo, _perms)
325 325
326 326 changes_details = Optional.extract(details)
327 327 _changes_details_types = ['basic', 'extended', 'full']
328 328 if changes_details not in _changes_details_types:
329 329 raise JSONRPCError(
330 330 'ret_type must be one of %s' % (
331 331 ','.join(_changes_details_types)))
332 332
333 333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
334 334 'status', '_commit', '_file_paths']
335 335
336 336 try:
337 337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
338 338 except TypeError as e:
339 339 raise JSONRPCError(e.message)
340 340 _cs_json = cs.__json__()
341 341 _cs_json['diff'] = build_commit_data(cs, changes_details)
342 342 if changes_details == 'full':
343 343 _cs_json['refs'] = {
344 344 'branches': [cs.branch],
345 345 'bookmarks': getattr(cs, 'bookmarks', []),
346 346 'tags': cs.tags
347 347 }
348 348 return _cs_json
349 349
350 350
351 351 @jsonrpc_method()
352 352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
353 353 details=Optional('basic')):
354 354 """
355 355 Returns a set of commits limited by the number starting
356 356 from the `start_rev` option.
357 357
358 358 Additional parameters define the amount of details returned by this
359 359 function.
360 360
361 361 This command can only be run using an |authtoken| with admin rights,
362 362 or users with at least read rights to |repos|.
363 363
364 364 :param apiuser: This is filled automatically from the |authtoken|.
365 365 :type apiuser: AuthUser
366 366 :param repoid: The repository name or repository ID.
367 367 :type repoid: str or int
368 368 :param start_rev: The starting revision from where to get changesets.
369 369 :type start_rev: str
370 370 :param limit: Limit the number of commits to this amount
371 371 :type limit: str or int
372 372 :param details: Set the level of detail returned. Valid option are:
373 373 ``basic``, ``extended`` and ``full``.
374 374 :type details: Optional(str)
375 375
376 376 .. note::
377 377
378 378 Setting the parameter `details` to the value ``full`` is extensive
379 379 and returns details like the diff itself, and the number
380 380 of changed files.
381 381
382 382 """
383 383 repo = get_repo_or_error(repoid)
384 384 if not has_superadmin_permission(apiuser):
385 385 _perms = (
386 386 'repository.admin', 'repository.write', 'repository.read',)
387 387 validate_repo_permissions(apiuser, repoid, repo, _perms)
388 388
389 389 changes_details = Optional.extract(details)
390 390 _changes_details_types = ['basic', 'extended', 'full']
391 391 if changes_details not in _changes_details_types:
392 392 raise JSONRPCError(
393 393 'ret_type must be one of %s' % (
394 394 ','.join(_changes_details_types)))
395 395
396 396 limit = int(limit)
397 397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
398 398 'status', '_commit', '_file_paths']
399 399
400 400 vcs_repo = repo.scm_instance()
401 401 # SVN needs a special case to distinguish its index and commit id
402 402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
403 403 start_rev = vcs_repo.commit_ids[0]
404 404
405 405 try:
406 406 commits = vcs_repo.get_commits(
407 407 start_id=start_rev, pre_load=pre_load)
408 408 except TypeError as e:
409 409 raise JSONRPCError(e.message)
410 410 except Exception:
411 411 log.exception('Fetching of commits failed')
412 412 raise JSONRPCError('Error occurred during commit fetching')
413 413
414 414 ret = []
415 415 for cnt, commit in enumerate(commits):
416 416 if cnt >= limit != -1:
417 417 break
418 418 _cs_json = commit.__json__()
419 419 _cs_json['diff'] = build_commit_data(commit, changes_details)
420 420 if changes_details == 'full':
421 421 _cs_json['refs'] = {
422 422 'branches': [commit.branch],
423 423 'bookmarks': getattr(commit, 'bookmarks', []),
424 424 'tags': commit.tags
425 425 }
426 426 ret.append(_cs_json)
427 427 return ret
428 428
429 429
430 430 @jsonrpc_method()
431 431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
432 432 ret_type=Optional('all'), details=Optional('basic'),
433 433 max_file_bytes=Optional(None)):
434 434 """
435 435 Returns a list of nodes and children in a flat list for a given
436 436 path at given revision.
437 437
438 438 It's possible to specify ret_type to show only `files` or `dirs`.
439 439
440 440 This command can only be run using an |authtoken| with admin rights,
441 441 or users with at least read rights to |repos|.
442 442
443 443 :param apiuser: This is filled automatically from the |authtoken|.
444 444 :type apiuser: AuthUser
445 445 :param repoid: The repository name or repository ID.
446 446 :type repoid: str or int
447 447 :param revision: The revision for which listing should be done.
448 448 :type revision: str
449 449 :param root_path: The path from which to start displaying.
450 450 :type root_path: str
451 451 :param ret_type: Set the return type. Valid options are
452 452 ``all`` (default), ``files`` and ``dirs``.
453 453 :type ret_type: Optional(str)
454 454 :param details: Returns extended information about nodes, such as
455 455 md5, binary, and or content. The valid options are ``basic`` and
456 456 ``full``.
457 457 :type details: Optional(str)
458 458 :param max_file_bytes: Only return file content under this file size bytes
459 459 :type details: Optional(int)
460 460
461 461 Example output:
462 462
463 463 .. code-block:: bash
464 464
465 465 id : <id_given_in_input>
466 466 result: [
467 467 {
468 468 "name" : "<name>"
469 469 "type" : "<type>",
470 470 "binary": "<true|false>" (only in extended mode)
471 471 "md5" : "<md5 of file content>" (only in extended mode)
472 472 },
473 473 ...
474 474 ]
475 475 error: null
476 476 """
477 477
478 478 repo = get_repo_or_error(repoid)
479 479 if not has_superadmin_permission(apiuser):
480 480 _perms = (
481 481 'repository.admin', 'repository.write', 'repository.read',)
482 482 validate_repo_permissions(apiuser, repoid, repo, _perms)
483 483
484 484 ret_type = Optional.extract(ret_type)
485 485 details = Optional.extract(details)
486 486 _extended_types = ['basic', 'full']
487 487 if details not in _extended_types:
488 488 raise JSONRPCError(
489 489 'ret_type must be one of %s' % (','.join(_extended_types)))
490 490 extended_info = False
491 491 content = False
492 492 if details == 'basic':
493 493 extended_info = True
494 494
495 495 if details == 'full':
496 496 extended_info = content = True
497 497
498 498 _map = {}
499 499 try:
500 500 # check if repo is not empty by any chance, skip quicker if it is.
501 501 _scm = repo.scm_instance()
502 502 if _scm.is_empty():
503 503 return []
504 504
505 505 _d, _f = ScmModel().get_nodes(
506 506 repo, revision, root_path, flat=False,
507 507 extended_info=extended_info, content=content,
508 508 max_file_bytes=max_file_bytes)
509 509 _map = {
510 510 'all': _d + _f,
511 511 'files': _f,
512 512 'dirs': _d,
513 513 }
514 514 return _map[ret_type]
515 515 except KeyError:
516 516 raise JSONRPCError(
517 517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
518 518 except Exception:
519 519 log.exception("Exception occurred while trying to get repo nodes")
520 520 raise JSONRPCError(
521 521 'failed to get repo: `%s` nodes' % repo.repo_name
522 522 )
523 523
524 524
525 525 @jsonrpc_method()
526 526 def get_repo_refs(request, apiuser, repoid):
527 527 """
528 528 Returns a dictionary of current references. It returns
529 529 bookmarks, branches, closed_branches, and tags for given repository
530 530
531 531 It's possible to specify ret_type to show only `files` or `dirs`.
532 532
533 533 This command can only be run using an |authtoken| with admin rights,
534 534 or users with at least read rights to |repos|.
535 535
536 536 :param apiuser: This is filled automatically from the |authtoken|.
537 537 :type apiuser: AuthUser
538 538 :param repoid: The repository name or repository ID.
539 539 :type repoid: str or int
540 540
541 541 Example output:
542 542
543 543 .. code-block:: bash
544 544
545 545 id : <id_given_in_input>
546 546 "result": {
547 547 "bookmarks": {
548 548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 550 },
551 551 "branches": {
552 552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
553 553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
554 554 },
555 555 "branches_closed": {},
556 556 "tags": {
557 557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
558 558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
559 559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
560 560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
561 561 }
562 562 }
563 563 error: null
564 564 """
565 565
566 566 repo = get_repo_or_error(repoid)
567 567 if not has_superadmin_permission(apiuser):
568 568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
569 569 validate_repo_permissions(apiuser, repoid, repo, _perms)
570 570
571 571 try:
572 572 # check if repo is not empty by any chance, skip quicker if it is.
573 573 vcs_instance = repo.scm_instance()
574 574 refs = vcs_instance.refs()
575 575 return refs
576 576 except Exception:
577 577 log.exception("Exception occurred while trying to get repo refs")
578 578 raise JSONRPCError(
579 579 'failed to get repo: `%s` references' % repo.repo_name
580 580 )
581 581
582 582
583 583 @jsonrpc_method()
584 584 def create_repo(
585 585 request, apiuser, repo_name, repo_type,
586 586 owner=Optional(OAttr('apiuser')),
587 587 description=Optional(''),
588 588 private=Optional(False),
589 589 clone_uri=Optional(None),
590 590 landing_rev=Optional('rev:tip'),
591 591 enable_statistics=Optional(False),
592 592 enable_locking=Optional(False),
593 593 enable_downloads=Optional(False),
594 594 copy_permissions=Optional(False)):
595 595 """
596 596 Creates a repository.
597 597
598 598 * If the repository name contains "/", repository will be created inside
599 599 a repository group or nested repository groups
600 600
601 601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
602 602 group "foo/bar". You have to have permissions to access and write to
603 603 the last repository group ("bar" in this example)
604 604
605 605 This command can only be run using an |authtoken| with at least
606 606 permissions to create repositories, or write permissions to
607 607 parent repository groups.
608 608
609 609 :param apiuser: This is filled automatically from the |authtoken|.
610 610 :type apiuser: AuthUser
611 611 :param repo_name: Set the repository name.
612 612 :type repo_name: str
613 613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
614 614 :type repo_type: str
615 615 :param owner: user_id or username
616 616 :type owner: Optional(str)
617 617 :param description: Set the repository description.
618 618 :type description: Optional(str)
619 619 :param private: set repository as private
620 620 :type private: bool
621 621 :param clone_uri: set clone_uri
622 622 :type clone_uri: str
623 623 :param landing_rev: <rev_type>:<rev>
624 624 :type landing_rev: str
625 625 :param enable_locking:
626 626 :type enable_locking: bool
627 627 :param enable_downloads:
628 628 :type enable_downloads: bool
629 629 :param enable_statistics:
630 630 :type enable_statistics: bool
631 631 :param copy_permissions: Copy permission from group in which the
632 632 repository is being created.
633 633 :type copy_permissions: bool
634 634
635 635
636 636 Example output:
637 637
638 638 .. code-block:: bash
639 639
640 640 id : <id_given_in_input>
641 641 result: {
642 642 "msg": "Created new repository `<reponame>`",
643 643 "success": true,
644 644 "task": "<celery task id or None if done sync>"
645 645 }
646 646 error: null
647 647
648 648
649 649 Example error output:
650 650
651 651 .. code-block:: bash
652 652
653 653 id : <id_given_in_input>
654 654 result : null
655 655 error : {
656 656 'failed to create repository `<repo_name>`'
657 657 }
658 658
659 659 """
660 660
661 661 owner = validate_set_owner_permissions(apiuser, owner)
662 662
663 663 description = Optional.extract(description)
664 664 copy_permissions = Optional.extract(copy_permissions)
665 665 clone_uri = Optional.extract(clone_uri)
666 666 landing_commit_ref = Optional.extract(landing_rev)
667 667
668 668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
669 669 if isinstance(private, Optional):
670 670 private = defs.get('repo_private') or Optional.extract(private)
671 671 if isinstance(repo_type, Optional):
672 672 repo_type = defs.get('repo_type')
673 673 if isinstance(enable_statistics, Optional):
674 674 enable_statistics = defs.get('repo_enable_statistics')
675 675 if isinstance(enable_locking, Optional):
676 676 enable_locking = defs.get('repo_enable_locking')
677 677 if isinstance(enable_downloads, Optional):
678 678 enable_downloads = defs.get('repo_enable_downloads')
679 679
680 680 schema = repo_schema.RepoSchema().bind(
681 681 repo_type_options=rhodecode.BACKENDS.keys(),
682 682 # user caller
683 683 user=apiuser)
684 684
685 685 try:
686 686 schema_data = schema.deserialize(dict(
687 687 repo_name=repo_name,
688 688 repo_type=repo_type,
689 689 repo_owner=owner.username,
690 690 repo_description=description,
691 691 repo_landing_commit_ref=landing_commit_ref,
692 692 repo_clone_uri=clone_uri,
693 693 repo_private=private,
694 694 repo_copy_permissions=copy_permissions,
695 695 repo_enable_statistics=enable_statistics,
696 696 repo_enable_downloads=enable_downloads,
697 697 repo_enable_locking=enable_locking))
698 698 except validation_schema.Invalid as err:
699 699 raise JSONRPCValidationError(colander_exc=err)
700 700
701 701 try:
702 702 data = {
703 703 'owner': owner,
704 704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
705 705 'repo_name_full': schema_data['repo_name'],
706 706 'repo_group': schema_data['repo_group']['repo_group_id'],
707 707 'repo_type': schema_data['repo_type'],
708 708 'repo_description': schema_data['repo_description'],
709 709 'repo_private': schema_data['repo_private'],
710 710 'clone_uri': schema_data['repo_clone_uri'],
711 711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
712 712 'enable_statistics': schema_data['repo_enable_statistics'],
713 713 'enable_locking': schema_data['repo_enable_locking'],
714 714 'enable_downloads': schema_data['repo_enable_downloads'],
715 715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
716 716 }
717 717
718 718 task = RepoModel().create(form_data=data, cur_user=owner)
719 719 from celery.result import BaseAsyncResult
720 720 task_id = None
721 721 if isinstance(task, BaseAsyncResult):
722 722 task_id = task.task_id
723 723 # no commit, it's done in RepoModel, or async via celery
724 724 return {
725 725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
726 726 'success': True, # cannot return the repo data here since fork
727 727 # can be done async
728 728 'task': task_id
729 729 }
730 730 except Exception:
731 731 log.exception(
732 732 u"Exception while trying to create the repository %s",
733 733 schema_data['repo_name'])
734 734 raise JSONRPCError(
735 735 'failed to create repository `%s`' % (schema_data['repo_name'],))
736 736
737 737
738 738 @jsonrpc_method()
739 739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
740 740 description=Optional('')):
741 741 """
742 742 Adds an extra field to a repository.
743 743
744 744 This command can only be run using an |authtoken| with at least
745 745 write permissions to the |repo|.
746 746
747 747 :param apiuser: This is filled automatically from the |authtoken|.
748 748 :type apiuser: AuthUser
749 749 :param repoid: Set the repository name or repository id.
750 750 :type repoid: str or int
751 751 :param key: Create a unique field key for this repository.
752 752 :type key: str
753 753 :param label:
754 754 :type label: Optional(str)
755 755 :param description:
756 756 :type description: Optional(str)
757 757 """
758 758 repo = get_repo_or_error(repoid)
759 759 if not has_superadmin_permission(apiuser):
760 760 _perms = ('repository.admin',)
761 761 validate_repo_permissions(apiuser, repoid, repo, _perms)
762 762
763 763 label = Optional.extract(label) or key
764 764 description = Optional.extract(description)
765 765
766 766 field = RepositoryField.get_by_key_name(key, repo)
767 767 if field:
768 768 raise JSONRPCError('Field with key '
769 769 '`%s` exists for repo `%s`' % (key, repoid))
770 770
771 771 try:
772 772 RepoModel().add_repo_field(repo, key, field_label=label,
773 773 field_desc=description)
774 774 Session().commit()
775 775 return {
776 776 'msg': "Added new repository field `%s`" % (key,),
777 777 'success': True,
778 778 }
779 779 except Exception:
780 780 log.exception("Exception occurred while trying to add field to repo")
781 781 raise JSONRPCError(
782 782 'failed to create new field for repository `%s`' % (repoid,))
783 783
784 784
785 785 @jsonrpc_method()
786 786 def remove_field_from_repo(request, apiuser, repoid, key):
787 787 """
788 788 Removes an extra field from a repository.
789 789
790 790 This command can only be run using an |authtoken| with at least
791 791 write permissions to the |repo|.
792 792
793 793 :param apiuser: This is filled automatically from the |authtoken|.
794 794 :type apiuser: AuthUser
795 795 :param repoid: Set the repository name or repository ID.
796 796 :type repoid: str or int
797 797 :param key: Set the unique field key for this repository.
798 798 :type key: str
799 799 """
800 800
801 801 repo = get_repo_or_error(repoid)
802 802 if not has_superadmin_permission(apiuser):
803 803 _perms = ('repository.admin',)
804 804 validate_repo_permissions(apiuser, repoid, repo, _perms)
805 805
806 806 field = RepositoryField.get_by_key_name(key, repo)
807 807 if not field:
808 808 raise JSONRPCError('Field with key `%s` does not '
809 809 'exists for repo `%s`' % (key, repoid))
810 810
811 811 try:
812 812 RepoModel().delete_repo_field(repo, field_key=key)
813 813 Session().commit()
814 814 return {
815 815 'msg': "Deleted repository field `%s`" % (key,),
816 816 'success': True,
817 817 }
818 818 except Exception:
819 819 log.exception(
820 820 "Exception occurred while trying to delete field from repo")
821 821 raise JSONRPCError(
822 822 'failed to delete field for repository `%s`' % (repoid,))
823 823
824 824
825 825 @jsonrpc_method()
826 826 def update_repo(
827 827 request, apiuser, repoid, repo_name=Optional(None),
828 828 owner=Optional(OAttr('apiuser')), description=Optional(''),
829 829 private=Optional(False), clone_uri=Optional(None),
830 830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
831 831 enable_statistics=Optional(False),
832 832 enable_locking=Optional(False),
833 833 enable_downloads=Optional(False), fields=Optional('')):
834 834 """
835 835 Updates a repository with the given information.
836 836
837 837 This command can only be run using an |authtoken| with at least
838 838 admin permissions to the |repo|.
839 839
840 840 * If the repository name contains "/", repository will be updated
841 841 accordingly with a repository group or nested repository groups
842 842
843 843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
844 844 called "repo-test" and place it inside group "foo/bar".
845 845 You have to have permissions to access and write to the last repository
846 846 group ("bar" in this example)
847 847
848 848 :param apiuser: This is filled automatically from the |authtoken|.
849 849 :type apiuser: AuthUser
850 850 :param repoid: repository name or repository ID.
851 851 :type repoid: str or int
852 852 :param repo_name: Update the |repo| name, including the
853 853 repository group it's in.
854 854 :type repo_name: str
855 855 :param owner: Set the |repo| owner.
856 856 :type owner: str
857 857 :param fork_of: Set the |repo| as fork of another |repo|.
858 858 :type fork_of: str
859 859 :param description: Update the |repo| description.
860 860 :type description: str
861 861 :param private: Set the |repo| as private. (True | False)
862 862 :type private: bool
863 863 :param clone_uri: Update the |repo| clone URI.
864 864 :type clone_uri: str
865 865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
866 866 :type landing_rev: str
867 867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
868 868 :type enable_statistics: bool
869 869 :param enable_locking: Enable |repo| locking.
870 870 :type enable_locking: bool
871 871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
872 872 :type enable_downloads: bool
873 873 :param fields: Add extra fields to the |repo|. Use the following
874 874 example format: ``field_key=field_val,field_key2=fieldval2``.
875 875 Escape ', ' with \,
876 876 :type fields: str
877 877 """
878 878
879 879 repo = get_repo_or_error(repoid)
880 880
881 881 include_secrets = False
882 882 if not has_superadmin_permission(apiuser):
883 883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
884 884 else:
885 885 include_secrets = True
886 886
887 887 updates = dict(
888 888 repo_name=repo_name
889 889 if not isinstance(repo_name, Optional) else repo.repo_name,
890 890
891 891 fork_id=fork_of
892 892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
893 893
894 894 user=owner
895 895 if not isinstance(owner, Optional) else repo.user.username,
896 896
897 897 repo_description=description
898 898 if not isinstance(description, Optional) else repo.description,
899 899
900 900 repo_private=private
901 901 if not isinstance(private, Optional) else repo.private,
902 902
903 903 clone_uri=clone_uri
904 904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
905 905
906 906 repo_landing_rev=landing_rev
907 907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
908 908
909 909 repo_enable_statistics=enable_statistics
910 910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
911 911
912 912 repo_enable_locking=enable_locking
913 913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
914 914
915 915 repo_enable_downloads=enable_downloads
916 916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
917 917
918 918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
919 919
920 920 schema = repo_schema.RepoSchema().bind(
921 921 repo_type_options=rhodecode.BACKENDS.keys(),
922 922 repo_ref_options=ref_choices,
923 923 # user caller
924 924 user=apiuser,
925 925 old_values=repo.get_api_data())
926 926 try:
927 927 schema_data = schema.deserialize(dict(
928 928 # we save old value, users cannot change type
929 929 repo_type=repo.repo_type,
930 930
931 931 repo_name=updates['repo_name'],
932 932 repo_owner=updates['user'],
933 933 repo_description=updates['repo_description'],
934 934 repo_clone_uri=updates['clone_uri'],
935 935 repo_fork_of=updates['fork_id'],
936 936 repo_private=updates['repo_private'],
937 937 repo_landing_commit_ref=updates['repo_landing_rev'],
938 938 repo_enable_statistics=updates['repo_enable_statistics'],
939 939 repo_enable_downloads=updates['repo_enable_downloads'],
940 940 repo_enable_locking=updates['repo_enable_locking']))
941 941 except validation_schema.Invalid as err:
942 942 raise JSONRPCValidationError(colander_exc=err)
943 943
944 944 # save validated data back into the updates dict
945 945 validated_updates = dict(
946 946 repo_name=schema_data['repo_group']['repo_name_without_group'],
947 947 repo_group=schema_data['repo_group']['repo_group_id'],
948 948
949 949 user=schema_data['repo_owner'],
950 950 repo_description=schema_data['repo_description'],
951 951 repo_private=schema_data['repo_private'],
952 952 clone_uri=schema_data['repo_clone_uri'],
953 953 repo_landing_rev=schema_data['repo_landing_commit_ref'],
954 954 repo_enable_statistics=schema_data['repo_enable_statistics'],
955 955 repo_enable_locking=schema_data['repo_enable_locking'],
956 956 repo_enable_downloads=schema_data['repo_enable_downloads'],
957 957 )
958 958
959 959 if schema_data['repo_fork_of']:
960 960 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
961 961 validated_updates['fork_id'] = fork_repo.repo_id
962 962
963 963 # extra fields
964 964 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
965 965 if fields:
966 966 validated_updates.update(fields)
967 967
968 968 try:
969 969 RepoModel().update(repo, **validated_updates)
970 970 Session().commit()
971 971 return {
972 972 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
973 973 'repository': repo.get_api_data(include_secrets=include_secrets)
974 974 }
975 975 except Exception:
976 976 log.exception(
977 977 u"Exception while trying to update the repository %s",
978 978 repoid)
979 979 raise JSONRPCError('failed to update repo `%s`' % repoid)
980 980
981 981
982 982 @jsonrpc_method()
983 983 def fork_repo(request, apiuser, repoid, fork_name,
984 984 owner=Optional(OAttr('apiuser')),
985 985 description=Optional(''),
986 986 private=Optional(False),
987 987 clone_uri=Optional(None),
988 988 landing_rev=Optional('rev:tip'),
989 989 copy_permissions=Optional(False)):
990 990 """
991 991 Creates a fork of the specified |repo|.
992 992
993 993 * If the fork_name contains "/", fork will be created inside
994 994 a repository group or nested repository groups
995 995
996 996 For example "foo/bar/fork-repo" will create fork called "fork-repo"
997 997 inside group "foo/bar". You have to have permissions to access and
998 998 write to the last repository group ("bar" in this example)
999 999
1000 1000 This command can only be run using an |authtoken| with minimum
1001 1001 read permissions of the forked repo, create fork permissions for an user.
1002 1002
1003 1003 :param apiuser: This is filled automatically from the |authtoken|.
1004 1004 :type apiuser: AuthUser
1005 1005 :param repoid: Set repository name or repository ID.
1006 1006 :type repoid: str or int
1007 1007 :param fork_name: Set the fork name, including it's repository group membership.
1008 1008 :type fork_name: str
1009 1009 :param owner: Set the fork owner.
1010 1010 :type owner: str
1011 1011 :param description: Set the fork description.
1012 1012 :type description: str
1013 1013 :param copy_permissions: Copy permissions from parent |repo|. The
1014 1014 default is False.
1015 1015 :type copy_permissions: bool
1016 1016 :param private: Make the fork private. The default is False.
1017 1017 :type private: bool
1018 1018 :param landing_rev: Set the landing revision. The default is tip.
1019 1019
1020 1020 Example output:
1021 1021
1022 1022 .. code-block:: bash
1023 1023
1024 1024 id : <id_for_response>
1025 1025 api_key : "<api_key>"
1026 1026 args: {
1027 1027 "repoid" : "<reponame or repo_id>",
1028 1028 "fork_name": "<forkname>",
1029 1029 "owner": "<username or user_id = Optional(=apiuser)>",
1030 1030 "description": "<description>",
1031 1031 "copy_permissions": "<bool>",
1032 1032 "private": "<bool>",
1033 1033 "landing_rev": "<landing_rev>"
1034 1034 }
1035 1035
1036 1036 Example error output:
1037 1037
1038 1038 .. code-block:: bash
1039 1039
1040 1040 id : <id_given_in_input>
1041 1041 result: {
1042 1042 "msg": "Created fork of `<reponame>` as `<forkname>`",
1043 1043 "success": true,
1044 1044 "task": "<celery task id or None if done sync>"
1045 1045 }
1046 1046 error: null
1047 1047
1048 1048 """
1049 1049
1050 1050 repo = get_repo_or_error(repoid)
1051 1051 repo_name = repo.repo_name
1052 1052
1053 1053 if not has_superadmin_permission(apiuser):
1054 1054 # check if we have at least read permission for
1055 1055 # this repo that we fork !
1056 1056 _perms = (
1057 1057 'repository.admin', 'repository.write', 'repository.read')
1058 1058 validate_repo_permissions(apiuser, repoid, repo, _perms)
1059 1059
1060 1060 # check if the regular user has at least fork permissions as well
1061 1061 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1062 1062 raise JSONRPCForbidden()
1063 1063
1064 1064 # check if user can set owner parameter
1065 1065 owner = validate_set_owner_permissions(apiuser, owner)
1066 1066
1067 1067 description = Optional.extract(description)
1068 1068 copy_permissions = Optional.extract(copy_permissions)
1069 1069 clone_uri = Optional.extract(clone_uri)
1070 1070 landing_commit_ref = Optional.extract(landing_rev)
1071 1071 private = Optional.extract(private)
1072 1072
1073 1073 schema = repo_schema.RepoSchema().bind(
1074 1074 repo_type_options=rhodecode.BACKENDS.keys(),
1075 1075 # user caller
1076 1076 user=apiuser)
1077 1077
1078 1078 try:
1079 1079 schema_data = schema.deserialize(dict(
1080 1080 repo_name=fork_name,
1081 1081 repo_type=repo.repo_type,
1082 1082 repo_owner=owner.username,
1083 1083 repo_description=description,
1084 1084 repo_landing_commit_ref=landing_commit_ref,
1085 1085 repo_clone_uri=clone_uri,
1086 1086 repo_private=private,
1087 1087 repo_copy_permissions=copy_permissions))
1088 1088 except validation_schema.Invalid as err:
1089 1089 raise JSONRPCValidationError(colander_exc=err)
1090 1090
1091 1091 try:
1092 1092 data = {
1093 1093 'fork_parent_id': repo.repo_id,
1094 1094
1095 1095 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1096 1096 'repo_name_full': schema_data['repo_name'],
1097 1097 'repo_group': schema_data['repo_group']['repo_group_id'],
1098 1098 'repo_type': schema_data['repo_type'],
1099 1099 'description': schema_data['repo_description'],
1100 1100 'private': schema_data['repo_private'],
1101 1101 'copy_permissions': schema_data['repo_copy_permissions'],
1102 1102 'landing_rev': schema_data['repo_landing_commit_ref'],
1103 1103 }
1104 1104
1105 1105 task = RepoModel().create_fork(data, cur_user=owner)
1106 1106 # no commit, it's done in RepoModel, or async via celery
1107 1107 from celery.result import BaseAsyncResult
1108 1108 task_id = None
1109 1109 if isinstance(task, BaseAsyncResult):
1110 1110 task_id = task.task_id
1111 1111 return {
1112 1112 'msg': 'Created fork of `%s` as `%s`' % (
1113 1113 repo.repo_name, schema_data['repo_name']),
1114 1114 'success': True, # cannot return the repo data here since fork
1115 1115 # can be done async
1116 1116 'task': task_id
1117 1117 }
1118 1118 except Exception:
1119 1119 log.exception(
1120 1120 u"Exception while trying to create fork %s",
1121 1121 schema_data['repo_name'])
1122 1122 raise JSONRPCError(
1123 1123 'failed to fork repository `%s` as `%s`' % (
1124 1124 repo_name, schema_data['repo_name']))
1125 1125
1126 1126
1127 1127 @jsonrpc_method()
1128 1128 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1129 1129 """
1130 1130 Deletes a repository.
1131 1131
1132 1132 * When the `forks` parameter is set it's possible to detach or delete
1133 1133 forks of deleted repository.
1134 1134
1135 1135 This command can only be run using an |authtoken| with admin
1136 1136 permissions on the |repo|.
1137 1137
1138 1138 :param apiuser: This is filled automatically from the |authtoken|.
1139 1139 :type apiuser: AuthUser
1140 1140 :param repoid: Set the repository name or repository ID.
1141 1141 :type repoid: str or int
1142 1142 :param forks: Set to `detach` or `delete` forks from the |repo|.
1143 1143 :type forks: Optional(str)
1144 1144
1145 1145 Example error output:
1146 1146
1147 1147 .. code-block:: bash
1148 1148
1149 1149 id : <id_given_in_input>
1150 1150 result: {
1151 1151 "msg": "Deleted repository `<reponame>`",
1152 1152 "success": true
1153 1153 }
1154 1154 error: null
1155 1155 """
1156 1156
1157 1157 repo = get_repo_or_error(repoid)
1158 1158 repo_name = repo.repo_name
1159 1159 if not has_superadmin_permission(apiuser):
1160 1160 _perms = ('repository.admin',)
1161 1161 validate_repo_permissions(apiuser, repoid, repo, _perms)
1162 1162
1163 1163 try:
1164 1164 handle_forks = Optional.extract(forks)
1165 1165 _forks_msg = ''
1166 1166 _forks = [f for f in repo.forks]
1167 1167 if handle_forks == 'detach':
1168 1168 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1169 1169 elif handle_forks == 'delete':
1170 1170 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1171 1171 elif _forks:
1172 1172 raise JSONRPCError(
1173 1173 'Cannot delete `%s` it still contains attached forks' %
1174 1174 (repo.repo_name,)
1175 1175 )
1176 1176 repo_data = repo.get_api_data()
1177 1177 RepoModel().delete(repo, forks=forks)
1178 1178
1179 1179 repo = audit_logger.RepoWrap(repo_id=None,
1180 1180 repo_name=repo.repo_name)
1181 1181
1182 1182 audit_logger.store(
1183 1183 action='repo.delete',
1184 action_data={'repo_data': repo_data, 'source': 'api_call'},
1184 action_data={'repo_data': repo_data,
1185 'source': audit_logger.SOURCE_API},
1185 1186 user=apiuser, repo=repo, commit=False)
1186 1187
1187 1188 ScmModel().mark_for_invalidation(repo_name, delete=True)
1188 1189 Session().commit()
1189 1190 return {
1190 1191 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1191 1192 'success': True
1192 1193 }
1193 1194 except Exception:
1194 1195 log.exception("Exception occurred while trying to delete repo")
1195 1196 raise JSONRPCError(
1196 1197 'failed to delete repository `%s`' % (repo_name,)
1197 1198 )
1198 1199
1199 1200
1200 1201 #TODO: marcink, change name ?
1201 1202 @jsonrpc_method()
1202 1203 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1203 1204 """
1204 1205 Invalidates the cache for the specified repository.
1205 1206
1206 1207 This command can only be run using an |authtoken| with admin rights to
1207 1208 the specified repository.
1208 1209
1209 1210 This command takes the following options:
1210 1211
1211 1212 :param apiuser: This is filled automatically from |authtoken|.
1212 1213 :type apiuser: AuthUser
1213 1214 :param repoid: Sets the repository name or repository ID.
1214 1215 :type repoid: str or int
1215 1216 :param delete_keys: This deletes the invalidated keys instead of
1216 1217 just flagging them.
1217 1218 :type delete_keys: Optional(``True`` | ``False``)
1218 1219
1219 1220 Example output:
1220 1221
1221 1222 .. code-block:: bash
1222 1223
1223 1224 id : <id_given_in_input>
1224 1225 result : {
1225 1226 'msg': Cache for repository `<repository name>` was invalidated,
1226 1227 'repository': <repository name>
1227 1228 }
1228 1229 error : null
1229 1230
1230 1231 Example error output:
1231 1232
1232 1233 .. code-block:: bash
1233 1234
1234 1235 id : <id_given_in_input>
1235 1236 result : null
1236 1237 error : {
1237 1238 'Error occurred during cache invalidation action'
1238 1239 }
1239 1240
1240 1241 """
1241 1242
1242 1243 repo = get_repo_or_error(repoid)
1243 1244 if not has_superadmin_permission(apiuser):
1244 1245 _perms = ('repository.admin', 'repository.write',)
1245 1246 validate_repo_permissions(apiuser, repoid, repo, _perms)
1246 1247
1247 1248 delete = Optional.extract(delete_keys)
1248 1249 try:
1249 1250 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1250 1251 return {
1251 1252 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1252 1253 'repository': repo.repo_name
1253 1254 }
1254 1255 except Exception:
1255 1256 log.exception(
1256 1257 "Exception occurred while trying to invalidate repo cache")
1257 1258 raise JSONRPCError(
1258 1259 'Error occurred during cache invalidation action'
1259 1260 )
1260 1261
1261 1262
1262 1263 #TODO: marcink, change name ?
1263 1264 @jsonrpc_method()
1264 1265 def lock(request, apiuser, repoid, locked=Optional(None),
1265 1266 userid=Optional(OAttr('apiuser'))):
1266 1267 """
1267 1268 Sets the lock state of the specified |repo| by the given user.
1268 1269 From more information, see :ref:`repo-locking`.
1269 1270
1270 1271 * If the ``userid`` option is not set, the repository is locked to the
1271 1272 user who called the method.
1272 1273 * If the ``locked`` parameter is not set, the current lock state of the
1273 1274 repository is displayed.
1274 1275
1275 1276 This command can only be run using an |authtoken| with admin rights to
1276 1277 the specified repository.
1277 1278
1278 1279 This command takes the following options:
1279 1280
1280 1281 :param apiuser: This is filled automatically from the |authtoken|.
1281 1282 :type apiuser: AuthUser
1282 1283 :param repoid: Sets the repository name or repository ID.
1283 1284 :type repoid: str or int
1284 1285 :param locked: Sets the lock state.
1285 1286 :type locked: Optional(``True`` | ``False``)
1286 1287 :param userid: Set the repository lock to this user.
1287 1288 :type userid: Optional(str or int)
1288 1289
1289 1290 Example error output:
1290 1291
1291 1292 .. code-block:: bash
1292 1293
1293 1294 id : <id_given_in_input>
1294 1295 result : {
1295 1296 'repo': '<reponame>',
1296 1297 'locked': <bool: lock state>,
1297 1298 'locked_since': <int: lock timestamp>,
1298 1299 'locked_by': <username of person who made the lock>,
1299 1300 'lock_reason': <str: reason for locking>,
1300 1301 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1301 1302 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1302 1303 or
1303 1304 'msg': 'Repo `<repository name>` not locked.'
1304 1305 or
1305 1306 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1306 1307 }
1307 1308 error : null
1308 1309
1309 1310 Example error output:
1310 1311
1311 1312 .. code-block:: bash
1312 1313
1313 1314 id : <id_given_in_input>
1314 1315 result : null
1315 1316 error : {
1316 1317 'Error occurred locking repository `<reponame>`'
1317 1318 }
1318 1319 """
1319 1320
1320 1321 repo = get_repo_or_error(repoid)
1321 1322 if not has_superadmin_permission(apiuser):
1322 1323 # check if we have at least write permission for this repo !
1323 1324 _perms = ('repository.admin', 'repository.write',)
1324 1325 validate_repo_permissions(apiuser, repoid, repo, _perms)
1325 1326
1326 1327 # make sure normal user does not pass someone else userid,
1327 1328 # he is not allowed to do that
1328 1329 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1329 1330 raise JSONRPCError('userid is not the same as your user')
1330 1331
1331 1332 if isinstance(userid, Optional):
1332 1333 userid = apiuser.user_id
1333 1334
1334 1335 user = get_user_or_error(userid)
1335 1336
1336 1337 if isinstance(locked, Optional):
1337 1338 lockobj = repo.locked
1338 1339
1339 1340 if lockobj[0] is None:
1340 1341 _d = {
1341 1342 'repo': repo.repo_name,
1342 1343 'locked': False,
1343 1344 'locked_since': None,
1344 1345 'locked_by': None,
1345 1346 'lock_reason': None,
1346 1347 'lock_state_changed': False,
1347 1348 'msg': 'Repo `%s` not locked.' % repo.repo_name
1348 1349 }
1349 1350 return _d
1350 1351 else:
1351 1352 _user_id, _time, _reason = lockobj
1352 1353 lock_user = get_user_or_error(userid)
1353 1354 _d = {
1354 1355 'repo': repo.repo_name,
1355 1356 'locked': True,
1356 1357 'locked_since': _time,
1357 1358 'locked_by': lock_user.username,
1358 1359 'lock_reason': _reason,
1359 1360 'lock_state_changed': False,
1360 1361 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1361 1362 % (repo.repo_name, lock_user.username,
1362 1363 json.dumps(time_to_datetime(_time))))
1363 1364 }
1364 1365 return _d
1365 1366
1366 1367 # force locked state through a flag
1367 1368 else:
1368 1369 locked = str2bool(locked)
1369 1370 lock_reason = Repository.LOCK_API
1370 1371 try:
1371 1372 if locked:
1372 1373 lock_time = time.time()
1373 1374 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1374 1375 else:
1375 1376 lock_time = None
1376 1377 Repository.unlock(repo)
1377 1378 _d = {
1378 1379 'repo': repo.repo_name,
1379 1380 'locked': locked,
1380 1381 'locked_since': lock_time,
1381 1382 'locked_by': user.username,
1382 1383 'lock_reason': lock_reason,
1383 1384 'lock_state_changed': True,
1384 1385 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1385 1386 % (user.username, repo.repo_name, locked))
1386 1387 }
1387 1388 return _d
1388 1389 except Exception:
1389 1390 log.exception(
1390 1391 "Exception occurred while trying to lock repository")
1391 1392 raise JSONRPCError(
1392 1393 'Error occurred locking repository `%s`' % repo.repo_name
1393 1394 )
1394 1395
1395 1396
1396 1397 @jsonrpc_method()
1397 1398 def comment_commit(
1398 1399 request, apiuser, repoid, commit_id, message, status=Optional(None),
1399 1400 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1400 1401 resolves_comment_id=Optional(None),
1401 1402 userid=Optional(OAttr('apiuser'))):
1402 1403 """
1403 1404 Set a commit comment, and optionally change the status of the commit.
1404 1405
1405 1406 :param apiuser: This is filled automatically from the |authtoken|.
1406 1407 :type apiuser: AuthUser
1407 1408 :param repoid: Set the repository name or repository ID.
1408 1409 :type repoid: str or int
1409 1410 :param commit_id: Specify the commit_id for which to set a comment.
1410 1411 :type commit_id: str
1411 1412 :param message: The comment text.
1412 1413 :type message: str
1413 1414 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1414 1415 'approved', 'rejected', 'under_review'
1415 1416 :type status: str
1416 1417 :param comment_type: Comment type, one of: 'note', 'todo'
1417 1418 :type comment_type: Optional(str), default: 'note'
1418 1419 :param userid: Set the user name of the comment creator.
1419 1420 :type userid: Optional(str or int)
1420 1421
1421 1422 Example error output:
1422 1423
1423 1424 .. code-block:: bash
1424 1425
1425 1426 {
1426 1427 "id" : <id_given_in_input>,
1427 1428 "result" : {
1428 1429 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1429 1430 "status_change": null or <status>,
1430 1431 "success": true
1431 1432 },
1432 1433 "error" : null
1433 1434 }
1434 1435
1435 1436 """
1436 1437 repo = get_repo_or_error(repoid)
1437 1438 if not has_superadmin_permission(apiuser):
1438 1439 _perms = ('repository.read', 'repository.write', 'repository.admin')
1439 1440 validate_repo_permissions(apiuser, repoid, repo, _perms)
1440 1441
1441 1442 try:
1442 1443 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1443 1444 except Exception as e:
1444 1445 log.exception('Failed to fetch commit')
1445 1446 raise JSONRPCError(e.message)
1446 1447
1447 1448 if isinstance(userid, Optional):
1448 1449 userid = apiuser.user_id
1449 1450
1450 1451 user = get_user_or_error(userid)
1451 1452 status = Optional.extract(status)
1452 1453 comment_type = Optional.extract(comment_type)
1453 1454 resolves_comment_id = Optional.extract(resolves_comment_id)
1454 1455
1455 1456 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1456 1457 if status and status not in allowed_statuses:
1457 1458 raise JSONRPCError('Bad status, must be on '
1458 1459 'of %s got %s' % (allowed_statuses, status,))
1459 1460
1460 1461 if resolves_comment_id:
1461 1462 comment = ChangesetComment.get(resolves_comment_id)
1462 1463 if not comment:
1463 1464 raise JSONRPCError(
1464 1465 'Invalid resolves_comment_id `%s` for this commit.'
1465 1466 % resolves_comment_id)
1466 1467 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1467 1468 raise JSONRPCError(
1468 1469 'Comment `%s` is wrong type for setting status to resolved.'
1469 1470 % resolves_comment_id)
1470 1471
1471 1472 try:
1472 1473 rc_config = SettingsModel().get_all_settings()
1473 1474 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1474 1475 status_change_label = ChangesetStatus.get_status_lbl(status)
1475 1476 comm = CommentsModel().create(
1476 1477 message, repo, user, commit_id=commit_id,
1477 1478 status_change=status_change_label,
1478 1479 status_change_type=status,
1479 1480 renderer=renderer,
1480 1481 comment_type=comment_type,
1481 1482 resolves_comment_id=resolves_comment_id
1482 1483 )
1483 1484 if status:
1484 1485 # also do a status change
1485 1486 try:
1486 1487 ChangesetStatusModel().set_status(
1487 1488 repo, status, user, comm, revision=commit_id,
1488 1489 dont_allow_on_closed_pull_request=True
1489 1490 )
1490 1491 except StatusChangeOnClosedPullRequestError:
1491 1492 log.exception(
1492 1493 "Exception occurred while trying to change repo commit status")
1493 1494 msg = ('Changing status on a changeset associated with '
1494 1495 'a closed pull request is not allowed')
1495 1496 raise JSONRPCError(msg)
1496 1497
1497 1498 Session().commit()
1498 1499 return {
1499 1500 'msg': (
1500 1501 'Commented on commit `%s` for repository `%s`' % (
1501 1502 comm.revision, repo.repo_name)),
1502 1503 'status_change': status,
1503 1504 'success': True,
1504 1505 }
1505 1506 except JSONRPCError:
1506 1507 # catch any inside errors, and re-raise them to prevent from
1507 1508 # below global catch to silence them
1508 1509 raise
1509 1510 except Exception:
1510 1511 log.exception("Exception occurred while trying to comment on commit")
1511 1512 raise JSONRPCError(
1512 1513 'failed to set comment on repository `%s`' % (repo.repo_name,)
1513 1514 )
1514 1515
1515 1516
1516 1517 @jsonrpc_method()
1517 1518 def grant_user_permission(request, apiuser, repoid, userid, perm):
1518 1519 """
1519 1520 Grant permissions for the specified user on the given repository,
1520 1521 or update existing permissions if found.
1521 1522
1522 1523 This command can only be run using an |authtoken| with admin
1523 1524 permissions on the |repo|.
1524 1525
1525 1526 :param apiuser: This is filled automatically from the |authtoken|.
1526 1527 :type apiuser: AuthUser
1527 1528 :param repoid: Set the repository name or repository ID.
1528 1529 :type repoid: str or int
1529 1530 :param userid: Set the user name.
1530 1531 :type userid: str
1531 1532 :param perm: Set the user permissions, using the following format
1532 1533 ``(repository.(none|read|write|admin))``
1533 1534 :type perm: str
1534 1535
1535 1536 Example output:
1536 1537
1537 1538 .. code-block:: bash
1538 1539
1539 1540 id : <id_given_in_input>
1540 1541 result: {
1541 1542 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1542 1543 "success": true
1543 1544 }
1544 1545 error: null
1545 1546 """
1546 1547
1547 1548 repo = get_repo_or_error(repoid)
1548 1549 user = get_user_or_error(userid)
1549 1550 perm = get_perm_or_error(perm)
1550 1551 if not has_superadmin_permission(apiuser):
1551 1552 _perms = ('repository.admin',)
1552 1553 validate_repo_permissions(apiuser, repoid, repo, _perms)
1553 1554
1554 1555 try:
1555 1556
1556 1557 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1557 1558
1558 1559 Session().commit()
1559 1560 return {
1560 1561 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1561 1562 perm.permission_name, user.username, repo.repo_name
1562 1563 ),
1563 1564 'success': True
1564 1565 }
1565 1566 except Exception:
1566 1567 log.exception(
1567 1568 "Exception occurred while trying edit permissions for repo")
1568 1569 raise JSONRPCError(
1569 1570 'failed to edit permission for user: `%s` in repo: `%s`' % (
1570 1571 userid, repoid
1571 1572 )
1572 1573 )
1573 1574
1574 1575
1575 1576 @jsonrpc_method()
1576 1577 def revoke_user_permission(request, apiuser, repoid, userid):
1577 1578 """
1578 1579 Revoke permission for a user on the specified repository.
1579 1580
1580 1581 This command can only be run using an |authtoken| with admin
1581 1582 permissions on the |repo|.
1582 1583
1583 1584 :param apiuser: This is filled automatically from the |authtoken|.
1584 1585 :type apiuser: AuthUser
1585 1586 :param repoid: Set the repository name or repository ID.
1586 1587 :type repoid: str or int
1587 1588 :param userid: Set the user name of revoked user.
1588 1589 :type userid: str or int
1589 1590
1590 1591 Example error output:
1591 1592
1592 1593 .. code-block:: bash
1593 1594
1594 1595 id : <id_given_in_input>
1595 1596 result: {
1596 1597 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1597 1598 "success": true
1598 1599 }
1599 1600 error: null
1600 1601 """
1601 1602
1602 1603 repo = get_repo_or_error(repoid)
1603 1604 user = get_user_or_error(userid)
1604 1605 if not has_superadmin_permission(apiuser):
1605 1606 _perms = ('repository.admin',)
1606 1607 validate_repo_permissions(apiuser, repoid, repo, _perms)
1607 1608
1608 1609 try:
1609 1610 RepoModel().revoke_user_permission(repo=repo, user=user)
1610 1611 Session().commit()
1611 1612 return {
1612 1613 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1613 1614 user.username, repo.repo_name
1614 1615 ),
1615 1616 'success': True
1616 1617 }
1617 1618 except Exception:
1618 1619 log.exception(
1619 1620 "Exception occurred while trying revoke permissions to repo")
1620 1621 raise JSONRPCError(
1621 1622 'failed to edit permission for user: `%s` in repo: `%s`' % (
1622 1623 userid, repoid
1623 1624 )
1624 1625 )
1625 1626
1626 1627
1627 1628 @jsonrpc_method()
1628 1629 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1629 1630 """
1630 1631 Grant permission for a user group on the specified repository,
1631 1632 or update existing permissions.
1632 1633
1633 1634 This command can only be run using an |authtoken| with admin
1634 1635 permissions on the |repo|.
1635 1636
1636 1637 :param apiuser: This is filled automatically from the |authtoken|.
1637 1638 :type apiuser: AuthUser
1638 1639 :param repoid: Set the repository name or repository ID.
1639 1640 :type repoid: str or int
1640 1641 :param usergroupid: Specify the ID of the user group.
1641 1642 :type usergroupid: str or int
1642 1643 :param perm: Set the user group permissions using the following
1643 1644 format: (repository.(none|read|write|admin))
1644 1645 :type perm: str
1645 1646
1646 1647 Example output:
1647 1648
1648 1649 .. code-block:: bash
1649 1650
1650 1651 id : <id_given_in_input>
1651 1652 result : {
1652 1653 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1653 1654 "success": true
1654 1655
1655 1656 }
1656 1657 error : null
1657 1658
1658 1659 Example error output:
1659 1660
1660 1661 .. code-block:: bash
1661 1662
1662 1663 id : <id_given_in_input>
1663 1664 result : null
1664 1665 error : {
1665 1666 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1666 1667 }
1667 1668
1668 1669 """
1669 1670
1670 1671 repo = get_repo_or_error(repoid)
1671 1672 perm = get_perm_or_error(perm)
1672 1673 if not has_superadmin_permission(apiuser):
1673 1674 _perms = ('repository.admin',)
1674 1675 validate_repo_permissions(apiuser, repoid, repo, _perms)
1675 1676
1676 1677 user_group = get_user_group_or_error(usergroupid)
1677 1678 if not has_superadmin_permission(apiuser):
1678 1679 # check if we have at least read permission for this user group !
1679 1680 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1680 1681 if not HasUserGroupPermissionAnyApi(*_perms)(
1681 1682 user=apiuser, user_group_name=user_group.users_group_name):
1682 1683 raise JSONRPCError(
1683 1684 'user group `%s` does not exist' % (usergroupid,))
1684 1685
1685 1686 try:
1686 1687 RepoModel().grant_user_group_permission(
1687 1688 repo=repo, group_name=user_group, perm=perm)
1688 1689
1689 1690 Session().commit()
1690 1691 return {
1691 1692 'msg': 'Granted perm: `%s` for user group: `%s` in '
1692 1693 'repo: `%s`' % (
1693 1694 perm.permission_name, user_group.users_group_name,
1694 1695 repo.repo_name
1695 1696 ),
1696 1697 'success': True
1697 1698 }
1698 1699 except Exception:
1699 1700 log.exception(
1700 1701 "Exception occurred while trying change permission on repo")
1701 1702 raise JSONRPCError(
1702 1703 'failed to edit permission for user group: `%s` in '
1703 1704 'repo: `%s`' % (
1704 1705 usergroupid, repo.repo_name
1705 1706 )
1706 1707 )
1707 1708
1708 1709
1709 1710 @jsonrpc_method()
1710 1711 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1711 1712 """
1712 1713 Revoke the permissions of a user group on a given repository.
1713 1714
1714 1715 This command can only be run using an |authtoken| with admin
1715 1716 permissions on the |repo|.
1716 1717
1717 1718 :param apiuser: This is filled automatically from the |authtoken|.
1718 1719 :type apiuser: AuthUser
1719 1720 :param repoid: Set the repository name or repository ID.
1720 1721 :type repoid: str or int
1721 1722 :param usergroupid: Specify the user group ID.
1722 1723 :type usergroupid: str or int
1723 1724
1724 1725 Example output:
1725 1726
1726 1727 .. code-block:: bash
1727 1728
1728 1729 id : <id_given_in_input>
1729 1730 result: {
1730 1731 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1731 1732 "success": true
1732 1733 }
1733 1734 error: null
1734 1735 """
1735 1736
1736 1737 repo = get_repo_or_error(repoid)
1737 1738 if not has_superadmin_permission(apiuser):
1738 1739 _perms = ('repository.admin',)
1739 1740 validate_repo_permissions(apiuser, repoid, repo, _perms)
1740 1741
1741 1742 user_group = get_user_group_or_error(usergroupid)
1742 1743 if not has_superadmin_permission(apiuser):
1743 1744 # check if we have at least read permission for this user group !
1744 1745 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1745 1746 if not HasUserGroupPermissionAnyApi(*_perms)(
1746 1747 user=apiuser, user_group_name=user_group.users_group_name):
1747 1748 raise JSONRPCError(
1748 1749 'user group `%s` does not exist' % (usergroupid,))
1749 1750
1750 1751 try:
1751 1752 RepoModel().revoke_user_group_permission(
1752 1753 repo=repo, group_name=user_group)
1753 1754
1754 1755 Session().commit()
1755 1756 return {
1756 1757 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1757 1758 user_group.users_group_name, repo.repo_name
1758 1759 ),
1759 1760 'success': True
1760 1761 }
1761 1762 except Exception:
1762 1763 log.exception("Exception occurred while trying revoke "
1763 1764 "user group permission on repo")
1764 1765 raise JSONRPCError(
1765 1766 'failed to edit permission for user group: `%s` in '
1766 1767 'repo: `%s`' % (
1767 1768 user_group.users_group_name, repo.repo_name
1768 1769 )
1769 1770 )
1770 1771
1771 1772
1772 1773 @jsonrpc_method()
1773 1774 def pull(request, apiuser, repoid):
1774 1775 """
1775 1776 Triggers a pull on the given repository from a remote location. You
1776 1777 can use this to keep remote repositories up-to-date.
1777 1778
1778 1779 This command can only be run using an |authtoken| with admin
1779 1780 rights to the specified repository. For more information,
1780 1781 see :ref:`config-token-ref`.
1781 1782
1782 1783 This command takes the following options:
1783 1784
1784 1785 :param apiuser: This is filled automatically from the |authtoken|.
1785 1786 :type apiuser: AuthUser
1786 1787 :param repoid: The repository name or repository ID.
1787 1788 :type repoid: str or int
1788 1789
1789 1790 Example output:
1790 1791
1791 1792 .. code-block:: bash
1792 1793
1793 1794 id : <id_given_in_input>
1794 1795 result : {
1795 1796 "msg": "Pulled from `<repository name>`"
1796 1797 "repository": "<repository name>"
1797 1798 }
1798 1799 error : null
1799 1800
1800 1801 Example error output:
1801 1802
1802 1803 .. code-block:: bash
1803 1804
1804 1805 id : <id_given_in_input>
1805 1806 result : null
1806 1807 error : {
1807 1808 "Unable to pull changes from `<reponame>`"
1808 1809 }
1809 1810
1810 1811 """
1811 1812
1812 1813 repo = get_repo_or_error(repoid)
1813 1814 if not has_superadmin_permission(apiuser):
1814 1815 _perms = ('repository.admin',)
1815 1816 validate_repo_permissions(apiuser, repoid, repo, _perms)
1816 1817
1817 1818 try:
1818 1819 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1819 1820 return {
1820 1821 'msg': 'Pulled from `%s`' % repo.repo_name,
1821 1822 'repository': repo.repo_name
1822 1823 }
1823 1824 except Exception:
1824 1825 log.exception("Exception occurred while trying to "
1825 1826 "pull changes from remote location")
1826 1827 raise JSONRPCError(
1827 1828 'Unable to pull changes from `%s`' % repo.repo_name
1828 1829 )
1829 1830
1830 1831
1831 1832 @jsonrpc_method()
1832 1833 def strip(request, apiuser, repoid, revision, branch):
1833 1834 """
1834 1835 Strips the given revision from the specified repository.
1835 1836
1836 1837 * This will remove the revision and all of its decendants.
1837 1838
1838 1839 This command can only be run using an |authtoken| with admin rights to
1839 1840 the specified repository.
1840 1841
1841 1842 This command takes the following options:
1842 1843
1843 1844 :param apiuser: This is filled automatically from the |authtoken|.
1844 1845 :type apiuser: AuthUser
1845 1846 :param repoid: The repository name or repository ID.
1846 1847 :type repoid: str or int
1847 1848 :param revision: The revision you wish to strip.
1848 1849 :type revision: str
1849 1850 :param branch: The branch from which to strip the revision.
1850 1851 :type branch: str
1851 1852
1852 1853 Example output:
1853 1854
1854 1855 .. code-block:: bash
1855 1856
1856 1857 id : <id_given_in_input>
1857 1858 result : {
1858 1859 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1859 1860 "repository": "<repository name>"
1860 1861 }
1861 1862 error : null
1862 1863
1863 1864 Example error output:
1864 1865
1865 1866 .. code-block:: bash
1866 1867
1867 1868 id : <id_given_in_input>
1868 1869 result : null
1869 1870 error : {
1870 1871 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1871 1872 }
1872 1873
1873 1874 """
1874 1875
1875 1876 repo = get_repo_or_error(repoid)
1876 1877 if not has_superadmin_permission(apiuser):
1877 1878 _perms = ('repository.admin',)
1878 1879 validate_repo_permissions(apiuser, repoid, repo, _perms)
1879 1880
1880 1881 try:
1881 1882 ScmModel().strip(repo, revision, branch)
1882 1883 return {
1883 1884 'msg': 'Stripped commit %s from repo `%s`' % (
1884 1885 revision, repo.repo_name),
1885 1886 'repository': repo.repo_name
1886 1887 }
1887 1888 except Exception:
1888 1889 log.exception("Exception while trying to strip")
1889 1890 raise JSONRPCError(
1890 1891 'Unable to strip commit %s from repo `%s`' % (
1891 1892 revision, repo.repo_name)
1892 1893 )
1893 1894
1894 1895
1895 1896 @jsonrpc_method()
1896 1897 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1897 1898 """
1898 1899 Returns all settings for a repository. If key is given it only returns the
1899 1900 setting identified by the key or null.
1900 1901
1901 1902 :param apiuser: This is filled automatically from the |authtoken|.
1902 1903 :type apiuser: AuthUser
1903 1904 :param repoid: The repository name or repository id.
1904 1905 :type repoid: str or int
1905 1906 :param key: Key of the setting to return.
1906 1907 :type: key: Optional(str)
1907 1908
1908 1909 Example output:
1909 1910
1910 1911 .. code-block:: bash
1911 1912
1912 1913 {
1913 1914 "error": null,
1914 1915 "id": 237,
1915 1916 "result": {
1916 1917 "extensions_largefiles": true,
1917 1918 "extensions_evolve": true,
1918 1919 "hooks_changegroup_push_logger": true,
1919 1920 "hooks_changegroup_repo_size": false,
1920 1921 "hooks_outgoing_pull_logger": true,
1921 1922 "phases_publish": "True",
1922 1923 "rhodecode_hg_use_rebase_for_merging": true,
1923 1924 "rhodecode_pr_merge_enabled": true,
1924 1925 "rhodecode_use_outdated_comments": true
1925 1926 }
1926 1927 }
1927 1928 """
1928 1929
1929 1930 # Restrict access to this api method to admins only.
1930 1931 if not has_superadmin_permission(apiuser):
1931 1932 raise JSONRPCForbidden()
1932 1933
1933 1934 try:
1934 1935 repo = get_repo_or_error(repoid)
1935 1936 settings_model = VcsSettingsModel(repo=repo)
1936 1937 settings = settings_model.get_global_settings()
1937 1938 settings.update(settings_model.get_repo_settings())
1938 1939
1939 1940 # If only a single setting is requested fetch it from all settings.
1940 1941 key = Optional.extract(key)
1941 1942 if key is not None:
1942 1943 settings = settings.get(key, None)
1943 1944 except Exception:
1944 1945 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1945 1946 log.exception(msg)
1946 1947 raise JSONRPCError(msg)
1947 1948
1948 1949 return settings
1949 1950
1950 1951
1951 1952 @jsonrpc_method()
1952 1953 def set_repo_settings(request, apiuser, repoid, settings):
1953 1954 """
1954 1955 Update repository settings. Returns true on success.
1955 1956
1956 1957 :param apiuser: This is filled automatically from the |authtoken|.
1957 1958 :type apiuser: AuthUser
1958 1959 :param repoid: The repository name or repository id.
1959 1960 :type repoid: str or int
1960 1961 :param settings: The new settings for the repository.
1961 1962 :type: settings: dict
1962 1963
1963 1964 Example output:
1964 1965
1965 1966 .. code-block:: bash
1966 1967
1967 1968 {
1968 1969 "error": null,
1969 1970 "id": 237,
1970 1971 "result": true
1971 1972 }
1972 1973 """
1973 1974 # Restrict access to this api method to admins only.
1974 1975 if not has_superadmin_permission(apiuser):
1975 1976 raise JSONRPCForbidden()
1976 1977
1977 1978 if type(settings) is not dict:
1978 1979 raise JSONRPCError('Settings have to be a JSON Object.')
1979 1980
1980 1981 try:
1981 1982 settings_model = VcsSettingsModel(repo=repoid)
1982 1983
1983 1984 # Merge global, repo and incoming settings.
1984 1985 new_settings = settings_model.get_global_settings()
1985 1986 new_settings.update(settings_model.get_repo_settings())
1986 1987 new_settings.update(settings)
1987 1988
1988 1989 # Update the settings.
1989 1990 inherit_global_settings = new_settings.get(
1990 1991 'inherit_global_settings', False)
1991 1992 settings_model.create_or_update_repo_settings(
1992 1993 new_settings, inherit_global_settings=inherit_global_settings)
1993 1994 Session().commit()
1994 1995 except Exception:
1995 1996 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1996 1997 log.exception(msg)
1997 1998 raise JSONRPCError(msg)
1998 1999
1999 2000 # Indicate success.
2000 2001 return True
2001 2002
2002 2003
2003 2004 @jsonrpc_method()
2004 2005 def maintenance(request, apiuser, repoid):
2005 2006 """
2006 2007 Triggers a maintenance on the given repository.
2007 2008
2008 2009 This command can only be run using an |authtoken| with admin
2009 2010 rights to the specified repository. For more information,
2010 2011 see :ref:`config-token-ref`.
2011 2012
2012 2013 This command takes the following options:
2013 2014
2014 2015 :param apiuser: This is filled automatically from the |authtoken|.
2015 2016 :type apiuser: AuthUser
2016 2017 :param repoid: The repository name or repository ID.
2017 2018 :type repoid: str or int
2018 2019
2019 2020 Example output:
2020 2021
2021 2022 .. code-block:: bash
2022 2023
2023 2024 id : <id_given_in_input>
2024 2025 result : {
2025 2026 "msg": "executed maintenance command",
2026 2027 "executed_actions": [
2027 2028 <action_message>, <action_message2>...
2028 2029 ],
2029 2030 "repository": "<repository name>"
2030 2031 }
2031 2032 error : null
2032 2033
2033 2034 Example error output:
2034 2035
2035 2036 .. code-block:: bash
2036 2037
2037 2038 id : <id_given_in_input>
2038 2039 result : null
2039 2040 error : {
2040 2041 "Unable to execute maintenance on `<reponame>`"
2041 2042 }
2042 2043
2043 2044 """
2044 2045
2045 2046 repo = get_repo_or_error(repoid)
2046 2047 if not has_superadmin_permission(apiuser):
2047 2048 _perms = ('repository.admin',)
2048 2049 validate_repo_permissions(apiuser, repoid, repo, _perms)
2049 2050
2050 2051 try:
2051 2052 maintenance = repo_maintenance.RepoMaintenance()
2052 2053 executed_actions = maintenance.execute(repo)
2053 2054
2054 2055 return {
2055 2056 'msg': 'executed maintenance command',
2056 2057 'executed_actions': executed_actions,
2057 2058 'repository': repo.repo_name
2058 2059 }
2059 2060 except Exception:
2060 2061 log.exception("Exception occurred while trying to run maintenance")
2061 2062 raise JSONRPCError(
2062 2063 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,226 +1,227 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.view import view_config
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.apps._base import RepoAppView
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 31 from rhodecode.lib.exceptions import AttachedForksError
32 32 from rhodecode.lib.utils2 import safe_int
33 33 from rhodecode.lib.vcs import RepositoryError
34 34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 35 from rhodecode.model.repo import RepoModel
36 36 from rhodecode.model.scm import ScmModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class RepoSettingsView(RepoAppView):
42 42
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45
46 46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 47 c.repo_info = self.db_repo
48 48
49 49 self._register_global_c(c)
50 50 return c
51 51
52 52 @LoginRequired()
53 53 @HasRepoPermissionAnyDecorator('repository.admin')
54 54 @view_config(
55 55 route_name='edit_repo_advanced', request_method='GET',
56 56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 57 def edit_advanced(self):
58 58 c = self.load_default_context()
59 59 c.active = 'advanced'
60 60
61 61 c.default_user_id = User.get_default_user().user_id
62 62 c.in_public_journal = UserFollowing.query() \
63 63 .filter(UserFollowing.user_id == c.default_user_id) \
64 64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
65 65
66 66 c.has_origin_repo_read_perm = False
67 67 if self.db_repo.fork:
68 68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 69 'repository.write', 'repository.read', 'repository.admin')(
70 70 self.db_repo.fork.repo_name, 'repo set as fork page')
71 71
72 72 return self._get_template_context(c)
73 73
74 74 @LoginRequired()
75 75 @HasRepoPermissionAnyDecorator('repository.admin')
76 76 @view_config(
77 77 route_name='edit_repo_advanced_delete', request_method='POST',
78 78 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 79 def edit_advanced_delete(self):
80 80 """
81 81 Deletes the repository, or shows warnings if deletion is not possible
82 82 because of attached forks or other errors.
83 83 """
84 84 _ = self.request.translate
85 85 handle_forks = self.request.POST.get('forks', None)
86 86
87 87 try:
88 88 _forks = self.db_repo.forks.count()
89 89 if _forks and handle_forks:
90 90 if handle_forks == 'detach_forks':
91 91 handle_forks = 'detach'
92 92 h.flash(_('Detached %s forks') % _forks, category='success')
93 93 elif handle_forks == 'delete_forks':
94 94 handle_forks = 'delete'
95 95 h.flash(_('Deleted %s forks') % _forks, category='success')
96 96
97 97 repo_data = self.db_repo.get_api_data()
98 98 RepoModel().delete(self.db_repo, forks=handle_forks)
99 99
100 100 repo = audit_logger.RepoWrap(repo_id=None,
101 101 repo_name=self.db_repo.repo_name)
102 102 audit_logger.store(
103 103 action='repo.delete',
104 action_data={'repo_data': repo_data, 'source': 'web_action'},
104 action_data={'repo_data': repo_data,
105 'source': audit_logger.SOURCE_WEB},
105 106 user=self._rhodecode_user, repo=repo, commit=False)
106 107
107 108 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
108 109 h.flash(
109 110 _('Deleted repository `%s`') % self.db_repo_name,
110 111 category='success')
111 112 Session().commit()
112 113 except AttachedForksError:
113 114 repo_advanced_url = h.route_path(
114 115 'edit_repo_advanced', repo_name=self.db_repo_name,
115 116 _anchor='advanced-delete')
116 117 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
117 118 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
118 119 'Try using {delete_or_detach} option.')
119 120 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
120 121 category='warning')
121 122
122 123 # redirect to advanced for forks handle action ?
123 124 raise HTTPFound(repo_advanced_url)
124 125
125 126 except Exception:
126 127 log.exception("Exception during deletion of repository")
127 128 h.flash(_('An error occurred during deletion of `%s`')
128 129 % self.db_repo_name, category='error')
129 130 # redirect to advanced for more deletion options
130 131 raise HTTPFound(
131 132 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
132 133 _anchor='advanced-delete')
133 134
134 135 raise HTTPFound(h.route_path('home'))
135 136
136 137 @LoginRequired()
137 138 @HasRepoPermissionAnyDecorator('repository.admin')
138 139 @CSRFRequired()
139 140 @view_config(
140 141 route_name='edit_repo_advanced_journal', request_method='POST',
141 142 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
142 143 def edit_advanced_journal(self):
143 144 """
144 145 Set's this repository to be visible in public journal,
145 146 in other words making default user to follow this repo
146 147 """
147 148 _ = self.request.translate
148 149
149 150 try:
150 151 user_id = User.get_default_user().user_id
151 152 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
152 153 h.flash(_('Updated repository visibility in public journal'),
153 154 category='success')
154 155 Session().commit()
155 156 except Exception:
156 157 h.flash(_('An error occurred during setting this '
157 158 'repository in public journal'),
158 159 category='error')
159 160
160 161 raise HTTPFound(
161 162 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
162 163
163 164 @LoginRequired()
164 165 @HasRepoPermissionAnyDecorator('repository.admin')
165 166 @CSRFRequired()
166 167 @view_config(
167 168 route_name='edit_repo_advanced_fork', request_method='POST',
168 169 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
169 170 def edit_advanced_fork(self):
170 171 """
171 172 Mark given repository as a fork of another
172 173 """
173 174 _ = self.request.translate
174 175
175 176 new_fork_id = self.request.POST.get('id_fork_of')
176 177 try:
177 178
178 179 if new_fork_id and not new_fork_id.isdigit():
179 180 log.error('Given fork id %s is not an INT', new_fork_id)
180 181
181 182 fork_id = safe_int(new_fork_id)
182 183 repo = ScmModel().mark_as_fork(
183 184 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
184 185 fork = repo.fork.repo_name if repo.fork else _('Nothing')
185 186 Session().commit()
186 187 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
187 188 category='success')
188 189 except RepositoryError as e:
189 190 log.exception("Repository Error occurred")
190 191 h.flash(str(e), category='error')
191 192 except Exception as e:
192 193 log.exception("Exception while editing fork")
193 194 h.flash(_('An error occurred during this operation'),
194 195 category='error')
195 196
196 197 raise HTTPFound(
197 198 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
198 199
199 200 @LoginRequired()
200 201 @HasRepoPermissionAnyDecorator('repository.admin')
201 202 @CSRFRequired()
202 203 @view_config(
203 204 route_name='edit_repo_advanced_locking', request_method='POST',
204 205 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
205 206 def edit_advanced_locking(self):
206 207 """
207 208 Toggle locking of repository
208 209 """
209 210 _ = self.request.translate
210 211 set_lock = self.request.POST.get('set_lock')
211 212 set_unlock = self.request.POST.get('set_unlock')
212 213
213 214 try:
214 215 if set_lock:
215 216 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
216 217 lock_reason=Repository.LOCK_WEB)
217 218 h.flash(_('Locked repository'), category='success')
218 219 elif set_unlock:
219 220 Repository.unlock(self.db_repo)
220 221 h.flash(_('Unlocked repository'), category='success')
221 222 except Exception as e:
222 223 log.exception("Exception during unlocking")
223 224 h.flash(_('An error occurred during unlocking'), category='error')
224 225
225 226 raise HTTPFound(
226 227 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,184 +1,188 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23
24 24 from rhodecode.model import meta
25 25 from rhodecode.model.db import User, UserLog, Repository
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 ACTIONS = {
32 32 'user.login.success': {},
33 33 'user.login.failure': {},
34 34 'user.logout': {},
35 35 'user.password.reset_request': {},
36 36 'user.push': {},
37 37 'user.pull': {},
38 38
39 39 'repo.create': {},
40 40 'repo.edit': {},
41 41 'repo.edit.permissions': {},
42 42 'repo.delete': {},
43 43 'repo.commit.strip': {},
44 44 'repo.archive.download': {},
45 45 }
46 46
47 SOURCE_WEB = 'source_web'
48 SOURCE_API = 'source_api'
49
47 50
48 51 class UserWrap(object):
49 52 """
50 53 Fake object used to imitate AuthUser
51 54 """
52 55
53 56 def __init__(self, user_id=None, username=None, ip_addr=None):
54 57 self.user_id = user_id
55 58 self.username = username
56 59 self.ip_addr = ip_addr
57 60
58 61
59 62 class RepoWrap(object):
60 63 """
61 64 Fake object used to imitate RepoObject that audit logger requires
62 65 """
63 66
64 67 def __init__(self, repo_id=None, repo_name=None):
65 68 self.repo_id = repo_id
66 69 self.repo_name = repo_name
67 70
68 71
69 72 def _store_log(action_name, action_data, user_id, username, user_data,
70 73 ip_address, repository_id, repository_name):
71 74 user_log = UserLog()
72 75 user_log.version = UserLog.VERSION_2
73 76
74 77 user_log.action = action_name
75 78 user_log.action_data = action_data
76 79
77 80 user_log.user_ip = ip_address
78 81
79 82 user_log.user_id = user_id
80 83 user_log.username = username
81 84 user_log.user_data = user_data
82 85
83 86 user_log.repository_id = repository_id
84 87 user_log.repository_name = repository_name
85 88
86 89 user_log.action_date = datetime.datetime.now()
87 90
88 91 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
89 92 action_name, user_id, username, ip_address)
90 93
91 94 return user_log
92 95
93 96
94 97 def store(
95 98 action, user, action_data=None, user_data=None, ip_addr=None,
96 99 repo=None, sa_session=None, commit=False):
97 100 """
98 101 Audit logger for various actions made by users, typically this results in a call such::
99 102
100 103 from rhodecode.lib import audit_logger
101 104
102 105 audit_logger.store(
103 106 action='repo.edit', user=self._rhodecode_user)
104 107 audit_logger.store(
105 108 action='repo.delete', action_data={'repo_data': repo_data},
106 109 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
107 110
108 111 # repo action
109 112 audit_logger.store(
110 113 action='repo.delete',
111 114 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
112 115 repo=audit_logger.RepoWrap(repo_name='some-repo'))
113 116
114 117 # repo action, when we know and have the repository object already
115 118 audit_logger.store(
116 119 action='repo.delete',
117 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
120 action_data={'source': audit_logger.SOURCE_WEB, },
121 user=self._rhodecode_user,
118 122 repo=repo_object)
119 123
120 124 # without an user ?
121 125 audit_logger.store(
122 126 action='user.login.failure',
123 127 user=audit_logger.UserWrap(
124 128 username=self.request.params.get('username'),
125 129 ip_addr=self.request.remote_addr))
126 130
127 131 """
128 132 from rhodecode.lib.utils2 import safe_unicode
129 133 from rhodecode.lib.auth import AuthUser
130 134
131 135 if action not in ACTIONS:
132 136 raise ValueError('Action `{}` not in valid actions'.format(action))
133 137
134 138 if not sa_session:
135 139 sa_session = meta.Session()
136 140
137 141 try:
138 142 username = getattr(user, 'username', None)
139 143 if not username:
140 144 pass
141 145
142 146 user_id = getattr(user, 'user_id', None)
143 147 if not user_id:
144 148 # maybe we have username ? Try to figure user_id from username
145 149 if username:
146 150 user_id = getattr(
147 151 User.get_by_username(username), 'user_id', None)
148 152
149 153 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
150 154 if not ip_addr:
151 155 pass
152 156
153 157 if not user_data:
154 158 # try to get this from the auth user
155 159 if isinstance(user, AuthUser):
156 160 user_data = {
157 161 'username': user.username,
158 162 'email': user.email,
159 163 }
160 164
161 165 repository_name = getattr(repo, 'repo_name', None)
162 166 repository_id = getattr(repo, 'repo_id', None)
163 167 if not repository_id:
164 168 # maybe we have repo_name ? Try to figure repo_id from repo_name
165 169 if repository_name:
166 170 repository_id = getattr(
167 171 Repository.get_by_repo_name(repository_name), 'repo_id', None)
168 172
169 173 user_log = _store_log(
170 174 action_name=safe_unicode(action),
171 175 action_data=action_data or {},
172 176 user_id=user_id,
173 177 username=username,
174 178 user_data=user_data or {},
175 179 ip_address=safe_unicode(ip_addr),
176 180 repository_id=repository_id,
177 181 repository_name=repository_name
178 182 )
179 183 sa_session.add(user_log)
180 184 if commit:
181 185 sa_session.commit()
182 186
183 187 except Exception:
184 188 log.exception('AUDIT: failed to store audit log')
General Comments 0
You need to be logged in to leave comments. Login now