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