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