##// END OF EJS Templates
api: security, fix problem when absolute paths are specified with API call, that would allow...
marcink -
r2663:0777b16f default
parent child Browse files
Show More
@@ -1,195 +1,197 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 ({'push_uri': ''},
59 ({'push_uri': ''},
60 {'push_uri': ''}),
60 {'push_uri': ''}),
61
61
62 ({'landing_rev': 'rev:tip'},
62 ({'landing_rev': 'rev:tip'},
63 {'landing_rev': ['rev', 'tip']}),
63 {'landing_rev': ['rev', 'tip']}),
64
64
65 ({'enable_statistics': True},
65 ({'enable_statistics': True},
66 SAME_AS_UPDATES),
66 SAME_AS_UPDATES),
67
67
68 ({'enable_locking': True},
68 ({'enable_locking': True},
69 SAME_AS_UPDATES),
69 SAME_AS_UPDATES),
70
70
71 ({'enable_downloads': True},
71 ({'enable_downloads': True},
72 SAME_AS_UPDATES),
72 SAME_AS_UPDATES),
73
73
74 ({'repo_name': 'new_repo_name'},
74 ({'repo_name': 'new_repo_name'},
75 {
75 {
76 'repo_name': 'new_repo_name',
76 'repo_name': 'new_repo_name',
77 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
77 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
78 }),
78 }),
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 '_group': 'test_group_for_update'},
81 '_group': 'test_group_for_update'},
82 {
82 {
83 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
83 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
84 'url': 'http://{}/test_group_for_update/{}'.format(
84 'url': 'http://{}/test_group_for_update/{}'.format(
85 http_host_only_stub(), UPDATE_REPO_NAME)
85 http_host_only_stub(), UPDATE_REPO_NAME)
86 }),
86 }),
87 ])
87 ])
88 def test_api_update_repo(self, updates, expected, backend):
88 def test_api_update_repo(self, updates, expected, backend):
89 repo_name = UPDATE_REPO_NAME
89 repo_name = UPDATE_REPO_NAME
90 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
90 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
91 if updates.get('_group'):
91 if updates.get('_group'):
92 fixture.create_repo_group(updates['_group'])
92 fixture.create_repo_group(updates['_group'])
93
93
94 expected_api_data = repo.get_api_data(include_secrets=True)
94 expected_api_data = repo.get_api_data(include_secrets=True)
95 if expected is SAME_AS_UPDATES:
95 if expected is SAME_AS_UPDATES:
96 expected_api_data.update(updates)
96 expected_api_data.update(updates)
97 else:
97 else:
98 expected_api_data.update(expected)
98 expected_api_data.update(expected)
99
99
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey, 'update_repo', repoid=repo_name, **updates)
101 self.apikey, 'update_repo', repoid=repo_name, **updates)
102 response = api_call(self.app, params)
102
103 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
104 response = api_call(self.app, params)
103
105
104 if updates.get('repo_name'):
106 if updates.get('repo_name'):
105 repo_name = updates['repo_name']
107 repo_name = updates['repo_name']
106
108
107 try:
109 try:
108 expected = {
110 expected = {
109 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
111 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
110 'repository': jsonify(expected_api_data)
112 'repository': jsonify(expected_api_data)
111 }
113 }
112 assert_ok(id_, expected, given=response.body)
114 assert_ok(id_, expected, given=response.body)
113 finally:
115 finally:
114 fixture.destroy_repo(repo_name)
116 fixture.destroy_repo(repo_name)
115 if updates.get('_group'):
117 if updates.get('_group'):
116 fixture.destroy_repo_group(updates['_group'])
118 fixture.destroy_repo_group(updates['_group'])
117
119
118 def test_api_update_repo_fork_of_field(self, backend):
120 def test_api_update_repo_fork_of_field(self, backend):
119 master_repo = backend.create_repo()
121 master_repo = backend.create_repo()
120 repo = backend.create_repo()
122 repo = backend.create_repo()
121 updates = {
123 updates = {
122 'fork_of': master_repo.repo_name,
124 'fork_of': master_repo.repo_name,
123 'fork_of_id': master_repo.repo_id
125 'fork_of_id': master_repo.repo_id
124 }
126 }
125 expected_api_data = repo.get_api_data(include_secrets=True)
127 expected_api_data = repo.get_api_data(include_secrets=True)
126 expected_api_data.update(updates)
128 expected_api_data.update(updates)
127
129
128 id_, params = build_data(
130 id_, params = build_data(
129 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
131 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
130 response = api_call(self.app, params)
132 response = api_call(self.app, params)
131 expected = {
133 expected = {
132 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
134 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
133 'repository': jsonify(expected_api_data)
135 'repository': jsonify(expected_api_data)
134 }
136 }
135 assert_ok(id_, expected, given=response.body)
137 assert_ok(id_, expected, given=response.body)
136 result = response.json['result']['repository']
138 result = response.json['result']['repository']
137 assert result['fork_of'] == master_repo.repo_name
139 assert result['fork_of'] == master_repo.repo_name
138 assert result['fork_of_id'] == master_repo.repo_id
140 assert result['fork_of_id'] == master_repo.repo_id
139
141
140 def test_api_update_repo_fork_of_not_found(self, backend):
142 def test_api_update_repo_fork_of_not_found(self, backend):
141 master_repo_name = 'fake-parent-repo'
143 master_repo_name = 'fake-parent-repo'
142 repo = backend.create_repo()
144 repo = backend.create_repo()
143 updates = {
145 updates = {
144 'fork_of': master_repo_name
146 'fork_of': master_repo_name
145 }
147 }
146 id_, params = build_data(
148 id_, params = build_data(
147 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
149 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
148 response = api_call(self.app, params)
150 response = api_call(self.app, params)
149 expected = {
151 expected = {
150 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
152 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
151 master_repo_name)}
153 master_repo_name)}
152 assert_error(id_, expected, given=response.body)
154 assert_error(id_, expected, given=response.body)
153
155
154 def test_api_update_repo_with_repo_group_not_existing(self):
156 def test_api_update_repo_with_repo_group_not_existing(self):
155 repo_name = 'admin_owned'
157 repo_name = 'admin_owned'
156 fake_repo_group = 'test_group_for_update'
158 fake_repo_group = 'test_group_for_update'
157 fixture.create_repo(repo_name)
159 fixture.create_repo(repo_name)
158 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
160 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
159 id_, params = build_data(
161 id_, params = build_data(
160 self.apikey, 'update_repo', repoid=repo_name, **updates)
162 self.apikey, 'update_repo', repoid=repo_name, **updates)
161 response = api_call(self.app, params)
163 response = api_call(self.app, params)
162 try:
164 try:
163 expected = {
165 expected = {
164 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
166 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
165 }
167 }
166 assert_error(id_, expected, given=response.body)
168 assert_error(id_, expected, given=response.body)
167 finally:
169 finally:
168 fixture.destroy_repo(repo_name)
170 fixture.destroy_repo(repo_name)
169
171
170 def test_api_update_repo_regular_user_not_allowed(self):
172 def test_api_update_repo_regular_user_not_allowed(self):
171 repo_name = 'admin_owned'
173 repo_name = 'admin_owned'
172 fixture.create_repo(repo_name)
174 fixture.create_repo(repo_name)
173 updates = {'active': False}
175 updates = {'active': False}
174 id_, params = build_data(
176 id_, params = build_data(
175 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
177 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
176 response = api_call(self.app, params)
178 response = api_call(self.app, params)
177 try:
179 try:
178 expected = 'repository `%s` does not exist' % (repo_name,)
180 expected = 'repository `%s` does not exist' % (repo_name,)
179 assert_error(id_, expected, given=response.body)
181 assert_error(id_, expected, given=response.body)
180 finally:
182 finally:
181 fixture.destroy_repo(repo_name)
183 fixture.destroy_repo(repo_name)
182
184
183 @mock.patch.object(RepoModel, 'update', crash)
185 @mock.patch.object(RepoModel, 'update', crash)
184 def test_api_update_repo_exception_occurred(self, backend):
186 def test_api_update_repo_exception_occurred(self, backend):
185 repo_name = UPDATE_REPO_NAME
187 repo_name = UPDATE_REPO_NAME
186 fixture.create_repo(repo_name, repo_type=backend.alias)
188 fixture.create_repo(repo_name, repo_type=backend.alias)
187 id_, params = build_data(
189 id_, params = build_data(
188 self.apikey, 'update_repo', repoid=repo_name,
190 self.apikey, 'update_repo', repoid=repo_name,
189 owner=TEST_USER_ADMIN_LOGIN,)
191 owner=TEST_USER_ADMIN_LOGIN,)
190 response = api_call(self.app, params)
192 response = api_call(self.app, params)
191 try:
193 try:
192 expected = 'failed to update repo `%s`' % (repo_name,)
194 expected = 'failed to update repo `%s`' % (repo_name,)
193 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
194 finally:
196 finally:
195 fixture.destroy_repo(repo_name)
197 fixture.destroy_repo(repo_name)
@@ -1,2060 +1,2064 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 push_uri=Optional(None),
566 push_uri=Optional(None),
567 landing_rev=Optional('rev:tip'),
567 landing_rev=Optional('rev:tip'),
568 enable_statistics=Optional(False),
568 enable_statistics=Optional(False),
569 enable_locking=Optional(False),
569 enable_locking=Optional(False),
570 enable_downloads=Optional(False),
570 enable_downloads=Optional(False),
571 copy_permissions=Optional(False)):
571 copy_permissions=Optional(False)):
572 """
572 """
573 Creates a repository.
573 Creates a repository.
574
574
575 * If the repository name contains "/", repository will be created inside
575 * If the repository name contains "/", repository will be created inside
576 a repository group or nested repository groups
576 a repository group or nested repository groups
577
577
578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
579 group "foo/bar". You have to have permissions to access and write to
579 group "foo/bar". You have to have permissions to access and write to
580 the last repository group ("bar" in this example)
580 the last repository group ("bar" in this example)
581
581
582 This command can only be run using an |authtoken| with at least
582 This command can only be run using an |authtoken| with at least
583 permissions to create repositories, or write permissions to
583 permissions to create repositories, or write permissions to
584 parent repository groups.
584 parent repository groups.
585
585
586 :param apiuser: This is filled automatically from the |authtoken|.
586 :param apiuser: This is filled automatically from the |authtoken|.
587 :type apiuser: AuthUser
587 :type apiuser: AuthUser
588 :param repo_name: Set the repository name.
588 :param repo_name: Set the repository name.
589 :type repo_name: str
589 :type repo_name: str
590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
591 :type repo_type: str
591 :type repo_type: str
592 :param owner: user_id or username
592 :param owner: user_id or username
593 :type owner: Optional(str)
593 :type owner: Optional(str)
594 :param description: Set the repository description.
594 :param description: Set the repository description.
595 :type description: Optional(str)
595 :type description: Optional(str)
596 :param private: set repository as private
596 :param private: set repository as private
597 :type private: bool
597 :type private: bool
598 :param clone_uri: set clone_uri
598 :param clone_uri: set clone_uri
599 :type clone_uri: str
599 :type clone_uri: str
600 :param push_uri: set push_uri
600 :param push_uri: set push_uri
601 :type push_uri: str
601 :type push_uri: str
602 :param landing_rev: <rev_type>:<rev>
602 :param landing_rev: <rev_type>:<rev>
603 :type landing_rev: str
603 :type landing_rev: str
604 :param enable_locking:
604 :param enable_locking:
605 :type enable_locking: bool
605 :type enable_locking: bool
606 :param enable_downloads:
606 :param enable_downloads:
607 :type enable_downloads: bool
607 :type enable_downloads: bool
608 :param enable_statistics:
608 :param enable_statistics:
609 :type enable_statistics: bool
609 :type enable_statistics: bool
610 :param copy_permissions: Copy permission from group in which the
610 :param copy_permissions: Copy permission from group in which the
611 repository is being created.
611 repository is being created.
612 :type copy_permissions: bool
612 :type copy_permissions: bool
613
613
614
614
615 Example output:
615 Example output:
616
616
617 .. code-block:: bash
617 .. code-block:: bash
618
618
619 id : <id_given_in_input>
619 id : <id_given_in_input>
620 result: {
620 result: {
621 "msg": "Created new repository `<reponame>`",
621 "msg": "Created new repository `<reponame>`",
622 "success": true,
622 "success": true,
623 "task": "<celery task id or None if done sync>"
623 "task": "<celery task id or None if done sync>"
624 }
624 }
625 error: null
625 error: null
626
626
627
627
628 Example error output:
628 Example error output:
629
629
630 .. code-block:: bash
630 .. code-block:: bash
631
631
632 id : <id_given_in_input>
632 id : <id_given_in_input>
633 result : null
633 result : null
634 error : {
634 error : {
635 'failed to create repository `<repo_name>`'
635 'failed to create repository `<repo_name>`'
636 }
636 }
637
637
638 """
638 """
639
639
640 owner = validate_set_owner_permissions(apiuser, owner)
640 owner = validate_set_owner_permissions(apiuser, owner)
641
641
642 description = Optional.extract(description)
642 description = Optional.extract(description)
643 copy_permissions = Optional.extract(copy_permissions)
643 copy_permissions = Optional.extract(copy_permissions)
644 clone_uri = Optional.extract(clone_uri)
644 clone_uri = Optional.extract(clone_uri)
645 push_uri = Optional.extract(push_uri)
645 push_uri = Optional.extract(push_uri)
646 landing_commit_ref = Optional.extract(landing_rev)
646 landing_commit_ref = Optional.extract(landing_rev)
647
647
648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
649 if isinstance(private, Optional):
649 if isinstance(private, Optional):
650 private = defs.get('repo_private') or Optional.extract(private)
650 private = defs.get('repo_private') or Optional.extract(private)
651 if isinstance(repo_type, Optional):
651 if isinstance(repo_type, Optional):
652 repo_type = defs.get('repo_type')
652 repo_type = defs.get('repo_type')
653 if isinstance(enable_statistics, Optional):
653 if isinstance(enable_statistics, Optional):
654 enable_statistics = defs.get('repo_enable_statistics')
654 enable_statistics = defs.get('repo_enable_statistics')
655 if isinstance(enable_locking, Optional):
655 if isinstance(enable_locking, Optional):
656 enable_locking = defs.get('repo_enable_locking')
656 enable_locking = defs.get('repo_enable_locking')
657 if isinstance(enable_downloads, Optional):
657 if isinstance(enable_downloads, Optional):
658 enable_downloads = defs.get('repo_enable_downloads')
658 enable_downloads = defs.get('repo_enable_downloads')
659
659
660 schema = repo_schema.RepoSchema().bind(
660 schema = repo_schema.RepoSchema().bind(
661 repo_type_options=rhodecode.BACKENDS.keys(),
661 repo_type_options=rhodecode.BACKENDS.keys(),
662 repo_type=repo_type,
662 # user caller
663 # user caller
663 user=apiuser)
664 user=apiuser)
664
665
665 try:
666 try:
666 schema_data = schema.deserialize(dict(
667 schema_data = schema.deserialize(dict(
667 repo_name=repo_name,
668 repo_name=repo_name,
668 repo_type=repo_type,
669 repo_type=repo_type,
669 repo_owner=owner.username,
670 repo_owner=owner.username,
670 repo_description=description,
671 repo_description=description,
671 repo_landing_commit_ref=landing_commit_ref,
672 repo_landing_commit_ref=landing_commit_ref,
672 repo_clone_uri=clone_uri,
673 repo_clone_uri=clone_uri,
673 repo_push_uri=push_uri,
674 repo_push_uri=push_uri,
674 repo_private=private,
675 repo_private=private,
675 repo_copy_permissions=copy_permissions,
676 repo_copy_permissions=copy_permissions,
676 repo_enable_statistics=enable_statistics,
677 repo_enable_statistics=enable_statistics,
677 repo_enable_downloads=enable_downloads,
678 repo_enable_downloads=enable_downloads,
678 repo_enable_locking=enable_locking))
679 repo_enable_locking=enable_locking))
679 except validation_schema.Invalid as err:
680 except validation_schema.Invalid as err:
680 raise JSONRPCValidationError(colander_exc=err)
681 raise JSONRPCValidationError(colander_exc=err)
681
682
682 try:
683 try:
683 data = {
684 data = {
684 'owner': owner,
685 'owner': owner,
685 'repo_name': schema_data['repo_group']['repo_name_without_group'],
686 'repo_name': schema_data['repo_group']['repo_name_without_group'],
686 'repo_name_full': schema_data['repo_name'],
687 'repo_name_full': schema_data['repo_name'],
687 'repo_group': schema_data['repo_group']['repo_group_id'],
688 'repo_group': schema_data['repo_group']['repo_group_id'],
688 'repo_type': schema_data['repo_type'],
689 'repo_type': schema_data['repo_type'],
689 'repo_description': schema_data['repo_description'],
690 'repo_description': schema_data['repo_description'],
690 'repo_private': schema_data['repo_private'],
691 'repo_private': schema_data['repo_private'],
691 'clone_uri': schema_data['repo_clone_uri'],
692 'clone_uri': schema_data['repo_clone_uri'],
692 'push_uri': schema_data['repo_push_uri'],
693 'push_uri': schema_data['repo_push_uri'],
693 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
694 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
694 'enable_statistics': schema_data['repo_enable_statistics'],
695 'enable_statistics': schema_data['repo_enable_statistics'],
695 'enable_locking': schema_data['repo_enable_locking'],
696 'enable_locking': schema_data['repo_enable_locking'],
696 'enable_downloads': schema_data['repo_enable_downloads'],
697 'enable_downloads': schema_data['repo_enable_downloads'],
697 'repo_copy_permissions': schema_data['repo_copy_permissions'],
698 'repo_copy_permissions': schema_data['repo_copy_permissions'],
698 }
699 }
699
700
700 task = RepoModel().create(form_data=data, cur_user=owner)
701 task = RepoModel().create(form_data=data, cur_user=owner)
701 task_id = get_task_id(task)
702 task_id = get_task_id(task)
702 # no commit, it's done in RepoModel, or async via celery
703 # no commit, it's done in RepoModel, or async via celery
703 return {
704 return {
704 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
705 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
705 'success': True, # cannot return the repo data here since fork
706 'success': True, # cannot return the repo data here since fork
706 # can be done async
707 # can be done async
707 'task': task_id
708 'task': task_id
708 }
709 }
709 except Exception:
710 except Exception:
710 log.exception(
711 log.exception(
711 u"Exception while trying to create the repository %s",
712 u"Exception while trying to create the repository %s",
712 schema_data['repo_name'])
713 schema_data['repo_name'])
713 raise JSONRPCError(
714 raise JSONRPCError(
714 'failed to create repository `%s`' % (schema_data['repo_name'],))
715 'failed to create repository `%s`' % (schema_data['repo_name'],))
715
716
716
717
717 @jsonrpc_method()
718 @jsonrpc_method()
718 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
719 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
719 description=Optional('')):
720 description=Optional('')):
720 """
721 """
721 Adds an extra field to a repository.
722 Adds an extra field to a repository.
722
723
723 This command can only be run using an |authtoken| with at least
724 This command can only be run using an |authtoken| with at least
724 write permissions to the |repo|.
725 write permissions to the |repo|.
725
726
726 :param apiuser: This is filled automatically from the |authtoken|.
727 :param apiuser: This is filled automatically from the |authtoken|.
727 :type apiuser: AuthUser
728 :type apiuser: AuthUser
728 :param repoid: Set the repository name or repository id.
729 :param repoid: Set the repository name or repository id.
729 :type repoid: str or int
730 :type repoid: str or int
730 :param key: Create a unique field key for this repository.
731 :param key: Create a unique field key for this repository.
731 :type key: str
732 :type key: str
732 :param label:
733 :param label:
733 :type label: Optional(str)
734 :type label: Optional(str)
734 :param description:
735 :param description:
735 :type description: Optional(str)
736 :type description: Optional(str)
736 """
737 """
737 repo = get_repo_or_error(repoid)
738 repo = get_repo_or_error(repoid)
738 if not has_superadmin_permission(apiuser):
739 if not has_superadmin_permission(apiuser):
739 _perms = ('repository.admin',)
740 _perms = ('repository.admin',)
740 validate_repo_permissions(apiuser, repoid, repo, _perms)
741 validate_repo_permissions(apiuser, repoid, repo, _perms)
741
742
742 label = Optional.extract(label) or key
743 label = Optional.extract(label) or key
743 description = Optional.extract(description)
744 description = Optional.extract(description)
744
745
745 field = RepositoryField.get_by_key_name(key, repo)
746 field = RepositoryField.get_by_key_name(key, repo)
746 if field:
747 if field:
747 raise JSONRPCError('Field with key '
748 raise JSONRPCError('Field with key '
748 '`%s` exists for repo `%s`' % (key, repoid))
749 '`%s` exists for repo `%s`' % (key, repoid))
749
750
750 try:
751 try:
751 RepoModel().add_repo_field(repo, key, field_label=label,
752 RepoModel().add_repo_field(repo, key, field_label=label,
752 field_desc=description)
753 field_desc=description)
753 Session().commit()
754 Session().commit()
754 return {
755 return {
755 'msg': "Added new repository field `%s`" % (key,),
756 'msg': "Added new repository field `%s`" % (key,),
756 'success': True,
757 'success': True,
757 }
758 }
758 except Exception:
759 except Exception:
759 log.exception("Exception occurred while trying to add field to repo")
760 log.exception("Exception occurred while trying to add field to repo")
760 raise JSONRPCError(
761 raise JSONRPCError(
761 'failed to create new field for repository `%s`' % (repoid,))
762 'failed to create new field for repository `%s`' % (repoid,))
762
763
763
764
764 @jsonrpc_method()
765 @jsonrpc_method()
765 def remove_field_from_repo(request, apiuser, repoid, key):
766 def remove_field_from_repo(request, apiuser, repoid, key):
766 """
767 """
767 Removes an extra field from a repository.
768 Removes an extra field from a repository.
768
769
769 This command can only be run using an |authtoken| with at least
770 This command can only be run using an |authtoken| with at least
770 write permissions to the |repo|.
771 write permissions to the |repo|.
771
772
772 :param apiuser: This is filled automatically from the |authtoken|.
773 :param apiuser: This is filled automatically from the |authtoken|.
773 :type apiuser: AuthUser
774 :type apiuser: AuthUser
774 :param repoid: Set the repository name or repository ID.
775 :param repoid: Set the repository name or repository ID.
775 :type repoid: str or int
776 :type repoid: str or int
776 :param key: Set the unique field key for this repository.
777 :param key: Set the unique field key for this repository.
777 :type key: str
778 :type key: str
778 """
779 """
779
780
780 repo = get_repo_or_error(repoid)
781 repo = get_repo_or_error(repoid)
781 if not has_superadmin_permission(apiuser):
782 if not has_superadmin_permission(apiuser):
782 _perms = ('repository.admin',)
783 _perms = ('repository.admin',)
783 validate_repo_permissions(apiuser, repoid, repo, _perms)
784 validate_repo_permissions(apiuser, repoid, repo, _perms)
784
785
785 field = RepositoryField.get_by_key_name(key, repo)
786 field = RepositoryField.get_by_key_name(key, repo)
786 if not field:
787 if not field:
787 raise JSONRPCError('Field with key `%s` does not '
788 raise JSONRPCError('Field with key `%s` does not '
788 'exists for repo `%s`' % (key, repoid))
789 'exists for repo `%s`' % (key, repoid))
789
790
790 try:
791 try:
791 RepoModel().delete_repo_field(repo, field_key=key)
792 RepoModel().delete_repo_field(repo, field_key=key)
792 Session().commit()
793 Session().commit()
793 return {
794 return {
794 'msg': "Deleted repository field `%s`" % (key,),
795 'msg': "Deleted repository field `%s`" % (key,),
795 'success': True,
796 'success': True,
796 }
797 }
797 except Exception:
798 except Exception:
798 log.exception(
799 log.exception(
799 "Exception occurred while trying to delete field from repo")
800 "Exception occurred while trying to delete field from repo")
800 raise JSONRPCError(
801 raise JSONRPCError(
801 'failed to delete field for repository `%s`' % (repoid,))
802 'failed to delete field for repository `%s`' % (repoid,))
802
803
803
804
804 @jsonrpc_method()
805 @jsonrpc_method()
805 def update_repo(
806 def update_repo(
806 request, apiuser, repoid, repo_name=Optional(None),
807 request, apiuser, repoid, repo_name=Optional(None),
807 owner=Optional(OAttr('apiuser')), description=Optional(''),
808 owner=Optional(OAttr('apiuser')), description=Optional(''),
808 private=Optional(False),
809 private=Optional(False),
809 clone_uri=Optional(None), push_uri=Optional(None),
810 clone_uri=Optional(None), push_uri=Optional(None),
810 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
811 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
811 enable_statistics=Optional(False),
812 enable_statistics=Optional(False),
812 enable_locking=Optional(False),
813 enable_locking=Optional(False),
813 enable_downloads=Optional(False), fields=Optional('')):
814 enable_downloads=Optional(False), fields=Optional('')):
814 """
815 """
815 Updates a repository with the given information.
816 Updates a repository with the given information.
816
817
817 This command can only be run using an |authtoken| with at least
818 This command can only be run using an |authtoken| with at least
818 admin permissions to the |repo|.
819 admin permissions to the |repo|.
819
820
820 * If the repository name contains "/", repository will be updated
821 * If the repository name contains "/", repository will be updated
821 accordingly with a repository group or nested repository groups
822 accordingly with a repository group or nested repository groups
822
823
823 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
824 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
824 called "repo-test" and place it inside group "foo/bar".
825 called "repo-test" and place it inside group "foo/bar".
825 You have to have permissions to access and write to the last repository
826 You have to have permissions to access and write to the last repository
826 group ("bar" in this example)
827 group ("bar" in this example)
827
828
828 :param apiuser: This is filled automatically from the |authtoken|.
829 :param apiuser: This is filled automatically from the |authtoken|.
829 :type apiuser: AuthUser
830 :type apiuser: AuthUser
830 :param repoid: repository name or repository ID.
831 :param repoid: repository name or repository ID.
831 :type repoid: str or int
832 :type repoid: str or int
832 :param repo_name: Update the |repo| name, including the
833 :param repo_name: Update the |repo| name, including the
833 repository group it's in.
834 repository group it's in.
834 :type repo_name: str
835 :type repo_name: str
835 :param owner: Set the |repo| owner.
836 :param owner: Set the |repo| owner.
836 :type owner: str
837 :type owner: str
837 :param fork_of: Set the |repo| as fork of another |repo|.
838 :param fork_of: Set the |repo| as fork of another |repo|.
838 :type fork_of: str
839 :type fork_of: str
839 :param description: Update the |repo| description.
840 :param description: Update the |repo| description.
840 :type description: str
841 :type description: str
841 :param private: Set the |repo| as private. (True | False)
842 :param private: Set the |repo| as private. (True | False)
842 :type private: bool
843 :type private: bool
843 :param clone_uri: Update the |repo| clone URI.
844 :param clone_uri: Update the |repo| clone URI.
844 :type clone_uri: str
845 :type clone_uri: str
845 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
846 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
846 :type landing_rev: str
847 :type landing_rev: str
847 :param enable_statistics: Enable statistics on the |repo|, (True | False).
848 :param enable_statistics: Enable statistics on the |repo|, (True | False).
848 :type enable_statistics: bool
849 :type enable_statistics: bool
849 :param enable_locking: Enable |repo| locking.
850 :param enable_locking: Enable |repo| locking.
850 :type enable_locking: bool
851 :type enable_locking: bool
851 :param enable_downloads: Enable downloads from the |repo|, (True | False).
852 :param enable_downloads: Enable downloads from the |repo|, (True | False).
852 :type enable_downloads: bool
853 :type enable_downloads: bool
853 :param fields: Add extra fields to the |repo|. Use the following
854 :param fields: Add extra fields to the |repo|. Use the following
854 example format: ``field_key=field_val,field_key2=fieldval2``.
855 example format: ``field_key=field_val,field_key2=fieldval2``.
855 Escape ', ' with \,
856 Escape ', ' with \,
856 :type fields: str
857 :type fields: str
857 """
858 """
858
859
859 repo = get_repo_or_error(repoid)
860 repo = get_repo_or_error(repoid)
860
861
861 include_secrets = False
862 include_secrets = False
862 if not has_superadmin_permission(apiuser):
863 if not has_superadmin_permission(apiuser):
863 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
864 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
864 else:
865 else:
865 include_secrets = True
866 include_secrets = True
866
867
867 updates = dict(
868 updates = dict(
868 repo_name=repo_name
869 repo_name=repo_name
869 if not isinstance(repo_name, Optional) else repo.repo_name,
870 if not isinstance(repo_name, Optional) else repo.repo_name,
870
871
871 fork_id=fork_of
872 fork_id=fork_of
872 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
873 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
873
874
874 user=owner
875 user=owner
875 if not isinstance(owner, Optional) else repo.user.username,
876 if not isinstance(owner, Optional) else repo.user.username,
876
877
877 repo_description=description
878 repo_description=description
878 if not isinstance(description, Optional) else repo.description,
879 if not isinstance(description, Optional) else repo.description,
879
880
880 repo_private=private
881 repo_private=private
881 if not isinstance(private, Optional) else repo.private,
882 if not isinstance(private, Optional) else repo.private,
882
883
883 clone_uri=clone_uri
884 clone_uri=clone_uri
884 if not isinstance(clone_uri, Optional) else repo.clone_uri,
885 if not isinstance(clone_uri, Optional) else repo.clone_uri,
885
886
886 push_uri=push_uri
887 push_uri=push_uri
887 if not isinstance(push_uri, Optional) else repo.push_uri,
888 if not isinstance(push_uri, Optional) else repo.push_uri,
888
889
889 repo_landing_rev=landing_rev
890 repo_landing_rev=landing_rev
890 if not isinstance(landing_rev, Optional) else repo._landing_revision,
891 if not isinstance(landing_rev, Optional) else repo._landing_revision,
891
892
892 repo_enable_statistics=enable_statistics
893 repo_enable_statistics=enable_statistics
893 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
894 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
894
895
895 repo_enable_locking=enable_locking
896 repo_enable_locking=enable_locking
896 if not isinstance(enable_locking, Optional) else repo.enable_locking,
897 if not isinstance(enable_locking, Optional) else repo.enable_locking,
897
898
898 repo_enable_downloads=enable_downloads
899 repo_enable_downloads=enable_downloads
899 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
900 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
900
901
901 ref_choices, _labels = ScmModel().get_repo_landing_revs(
902 ref_choices, _labels = ScmModel().get_repo_landing_revs(
902 request.translate, repo=repo)
903 request.translate, repo=repo)
903
904
904 old_values = repo.get_api_data()
905 old_values = repo.get_api_data()
906 repo_type = repo.repo_type
905 schema = repo_schema.RepoSchema().bind(
907 schema = repo_schema.RepoSchema().bind(
906 repo_type_options=rhodecode.BACKENDS.keys(),
908 repo_type_options=rhodecode.BACKENDS.keys(),
907 repo_ref_options=ref_choices,
909 repo_ref_options=ref_choices,
910 repo_type=repo_type,
908 # user caller
911 # user caller
909 user=apiuser,
912 user=apiuser,
910 old_values=old_values)
913 old_values=old_values)
911 try:
914 try:
912 schema_data = schema.deserialize(dict(
915 schema_data = schema.deserialize(dict(
913 # we save old value, users cannot change type
916 # we save old value, users cannot change type
914 repo_type=repo.repo_type,
917 repo_type=repo_type,
915
918
916 repo_name=updates['repo_name'],
919 repo_name=updates['repo_name'],
917 repo_owner=updates['user'],
920 repo_owner=updates['user'],
918 repo_description=updates['repo_description'],
921 repo_description=updates['repo_description'],
919 repo_clone_uri=updates['clone_uri'],
922 repo_clone_uri=updates['clone_uri'],
920 repo_push_uri=updates['push_uri'],
923 repo_push_uri=updates['push_uri'],
921 repo_fork_of=updates['fork_id'],
924 repo_fork_of=updates['fork_id'],
922 repo_private=updates['repo_private'],
925 repo_private=updates['repo_private'],
923 repo_landing_commit_ref=updates['repo_landing_rev'],
926 repo_landing_commit_ref=updates['repo_landing_rev'],
924 repo_enable_statistics=updates['repo_enable_statistics'],
927 repo_enable_statistics=updates['repo_enable_statistics'],
925 repo_enable_downloads=updates['repo_enable_downloads'],
928 repo_enable_downloads=updates['repo_enable_downloads'],
926 repo_enable_locking=updates['repo_enable_locking']))
929 repo_enable_locking=updates['repo_enable_locking']))
927 except validation_schema.Invalid as err:
930 except validation_schema.Invalid as err:
928 raise JSONRPCValidationError(colander_exc=err)
931 raise JSONRPCValidationError(colander_exc=err)
929
932
930 # save validated data back into the updates dict
933 # save validated data back into the updates dict
931 validated_updates = dict(
934 validated_updates = dict(
932 repo_name=schema_data['repo_group']['repo_name_without_group'],
935 repo_name=schema_data['repo_group']['repo_name_without_group'],
933 repo_group=schema_data['repo_group']['repo_group_id'],
936 repo_group=schema_data['repo_group']['repo_group_id'],
934
937
935 user=schema_data['repo_owner'],
938 user=schema_data['repo_owner'],
936 repo_description=schema_data['repo_description'],
939 repo_description=schema_data['repo_description'],
937 repo_private=schema_data['repo_private'],
940 repo_private=schema_data['repo_private'],
938 clone_uri=schema_data['repo_clone_uri'],
941 clone_uri=schema_data['repo_clone_uri'],
939 push_uri=schema_data['repo_push_uri'],
942 push_uri=schema_data['repo_push_uri'],
940 repo_landing_rev=schema_data['repo_landing_commit_ref'],
943 repo_landing_rev=schema_data['repo_landing_commit_ref'],
941 repo_enable_statistics=schema_data['repo_enable_statistics'],
944 repo_enable_statistics=schema_data['repo_enable_statistics'],
942 repo_enable_locking=schema_data['repo_enable_locking'],
945 repo_enable_locking=schema_data['repo_enable_locking'],
943 repo_enable_downloads=schema_data['repo_enable_downloads'],
946 repo_enable_downloads=schema_data['repo_enable_downloads'],
944 )
947 )
945
948
946 if schema_data['repo_fork_of']:
949 if schema_data['repo_fork_of']:
947 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
950 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
948 validated_updates['fork_id'] = fork_repo.repo_id
951 validated_updates['fork_id'] = fork_repo.repo_id
949
952
950 # extra fields
953 # extra fields
951 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
954 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
952 if fields:
955 if fields:
953 validated_updates.update(fields)
956 validated_updates.update(fields)
954
957
955 try:
958 try:
956 RepoModel().update(repo, **validated_updates)
959 RepoModel().update(repo, **validated_updates)
957 audit_logger.store_api(
960 audit_logger.store_api(
958 'repo.edit', action_data={'old_data': old_values},
961 'repo.edit', action_data={'old_data': old_values},
959 user=apiuser, repo=repo)
962 user=apiuser, repo=repo)
960 Session().commit()
963 Session().commit()
961 return {
964 return {
962 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
965 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
963 'repository': repo.get_api_data(include_secrets=include_secrets)
966 'repository': repo.get_api_data(include_secrets=include_secrets)
964 }
967 }
965 except Exception:
968 except Exception:
966 log.exception(
969 log.exception(
967 u"Exception while trying to update the repository %s",
970 u"Exception while trying to update the repository %s",
968 repoid)
971 repoid)
969 raise JSONRPCError('failed to update repo `%s`' % repoid)
972 raise JSONRPCError('failed to update repo `%s`' % repoid)
970
973
971
974
972 @jsonrpc_method()
975 @jsonrpc_method()
973 def fork_repo(request, apiuser, repoid, fork_name,
976 def fork_repo(request, apiuser, repoid, fork_name,
974 owner=Optional(OAttr('apiuser')),
977 owner=Optional(OAttr('apiuser')),
975 description=Optional(''),
978 description=Optional(''),
976 private=Optional(False),
979 private=Optional(False),
977 clone_uri=Optional(None),
980 clone_uri=Optional(None),
978 landing_rev=Optional('rev:tip'),
981 landing_rev=Optional('rev:tip'),
979 copy_permissions=Optional(False)):
982 copy_permissions=Optional(False)):
980 """
983 """
981 Creates a fork of the specified |repo|.
984 Creates a fork of the specified |repo|.
982
985
983 * If the fork_name contains "/", fork will be created inside
986 * If the fork_name contains "/", fork will be created inside
984 a repository group or nested repository groups
987 a repository group or nested repository groups
985
988
986 For example "foo/bar/fork-repo" will create fork called "fork-repo"
989 For example "foo/bar/fork-repo" will create fork called "fork-repo"
987 inside group "foo/bar". You have to have permissions to access and
990 inside group "foo/bar". You have to have permissions to access and
988 write to the last repository group ("bar" in this example)
991 write to the last repository group ("bar" in this example)
989
992
990 This command can only be run using an |authtoken| with minimum
993 This command can only be run using an |authtoken| with minimum
991 read permissions of the forked repo, create fork permissions for an user.
994 read permissions of the forked repo, create fork permissions for an user.
992
995
993 :param apiuser: This is filled automatically from the |authtoken|.
996 :param apiuser: This is filled automatically from the |authtoken|.
994 :type apiuser: AuthUser
997 :type apiuser: AuthUser
995 :param repoid: Set repository name or repository ID.
998 :param repoid: Set repository name or repository ID.
996 :type repoid: str or int
999 :type repoid: str or int
997 :param fork_name: Set the fork name, including it's repository group membership.
1000 :param fork_name: Set the fork name, including it's repository group membership.
998 :type fork_name: str
1001 :type fork_name: str
999 :param owner: Set the fork owner.
1002 :param owner: Set the fork owner.
1000 :type owner: str
1003 :type owner: str
1001 :param description: Set the fork description.
1004 :param description: Set the fork description.
1002 :type description: str
1005 :type description: str
1003 :param copy_permissions: Copy permissions from parent |repo|. The
1006 :param copy_permissions: Copy permissions from parent |repo|. The
1004 default is False.
1007 default is False.
1005 :type copy_permissions: bool
1008 :type copy_permissions: bool
1006 :param private: Make the fork private. The default is False.
1009 :param private: Make the fork private. The default is False.
1007 :type private: bool
1010 :type private: bool
1008 :param landing_rev: Set the landing revision. The default is tip.
1011 :param landing_rev: Set the landing revision. The default is tip.
1009
1012
1010 Example output:
1013 Example output:
1011
1014
1012 .. code-block:: bash
1015 .. code-block:: bash
1013
1016
1014 id : <id_for_response>
1017 id : <id_for_response>
1015 api_key : "<api_key>"
1018 api_key : "<api_key>"
1016 args: {
1019 args: {
1017 "repoid" : "<reponame or repo_id>",
1020 "repoid" : "<reponame or repo_id>",
1018 "fork_name": "<forkname>",
1021 "fork_name": "<forkname>",
1019 "owner": "<username or user_id = Optional(=apiuser)>",
1022 "owner": "<username or user_id = Optional(=apiuser)>",
1020 "description": "<description>",
1023 "description": "<description>",
1021 "copy_permissions": "<bool>",
1024 "copy_permissions": "<bool>",
1022 "private": "<bool>",
1025 "private": "<bool>",
1023 "landing_rev": "<landing_rev>"
1026 "landing_rev": "<landing_rev>"
1024 }
1027 }
1025
1028
1026 Example error output:
1029 Example error output:
1027
1030
1028 .. code-block:: bash
1031 .. code-block:: bash
1029
1032
1030 id : <id_given_in_input>
1033 id : <id_given_in_input>
1031 result: {
1034 result: {
1032 "msg": "Created fork of `<reponame>` as `<forkname>`",
1035 "msg": "Created fork of `<reponame>` as `<forkname>`",
1033 "success": true,
1036 "success": true,
1034 "task": "<celery task id or None if done sync>"
1037 "task": "<celery task id or None if done sync>"
1035 }
1038 }
1036 error: null
1039 error: null
1037
1040
1038 """
1041 """
1039
1042
1040 repo = get_repo_or_error(repoid)
1043 repo = get_repo_or_error(repoid)
1041 repo_name = repo.repo_name
1044 repo_name = repo.repo_name
1042
1045
1043 if not has_superadmin_permission(apiuser):
1046 if not has_superadmin_permission(apiuser):
1044 # check if we have at least read permission for
1047 # check if we have at least read permission for
1045 # this repo that we fork !
1048 # this repo that we fork !
1046 _perms = (
1049 _perms = (
1047 'repository.admin', 'repository.write', 'repository.read')
1050 'repository.admin', 'repository.write', 'repository.read')
1048 validate_repo_permissions(apiuser, repoid, repo, _perms)
1051 validate_repo_permissions(apiuser, repoid, repo, _perms)
1049
1052
1050 # check if the regular user has at least fork permissions as well
1053 # check if the regular user has at least fork permissions as well
1051 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1054 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1052 raise JSONRPCForbidden()
1055 raise JSONRPCForbidden()
1053
1056
1054 # check if user can set owner parameter
1057 # check if user can set owner parameter
1055 owner = validate_set_owner_permissions(apiuser, owner)
1058 owner = validate_set_owner_permissions(apiuser, owner)
1056
1059
1057 description = Optional.extract(description)
1060 description = Optional.extract(description)
1058 copy_permissions = Optional.extract(copy_permissions)
1061 copy_permissions = Optional.extract(copy_permissions)
1059 clone_uri = Optional.extract(clone_uri)
1062 clone_uri = Optional.extract(clone_uri)
1060 landing_commit_ref = Optional.extract(landing_rev)
1063 landing_commit_ref = Optional.extract(landing_rev)
1061 private = Optional.extract(private)
1064 private = Optional.extract(private)
1062
1065
1063 schema = repo_schema.RepoSchema().bind(
1066 schema = repo_schema.RepoSchema().bind(
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1067 repo_type_options=rhodecode.BACKENDS.keys(),
1068 repo_type=repo.repo_type,
1065 # user caller
1069 # user caller
1066 user=apiuser)
1070 user=apiuser)
1067
1071
1068 try:
1072 try:
1069 schema_data = schema.deserialize(dict(
1073 schema_data = schema.deserialize(dict(
1070 repo_name=fork_name,
1074 repo_name=fork_name,
1071 repo_type=repo.repo_type,
1075 repo_type=repo.repo_type,
1072 repo_owner=owner.username,
1076 repo_owner=owner.username,
1073 repo_description=description,
1077 repo_description=description,
1074 repo_landing_commit_ref=landing_commit_ref,
1078 repo_landing_commit_ref=landing_commit_ref,
1075 repo_clone_uri=clone_uri,
1079 repo_clone_uri=clone_uri,
1076 repo_private=private,
1080 repo_private=private,
1077 repo_copy_permissions=copy_permissions))
1081 repo_copy_permissions=copy_permissions))
1078 except validation_schema.Invalid as err:
1082 except validation_schema.Invalid as err:
1079 raise JSONRPCValidationError(colander_exc=err)
1083 raise JSONRPCValidationError(colander_exc=err)
1080
1084
1081 try:
1085 try:
1082 data = {
1086 data = {
1083 'fork_parent_id': repo.repo_id,
1087 'fork_parent_id': repo.repo_id,
1084
1088
1085 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1089 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1086 'repo_name_full': schema_data['repo_name'],
1090 'repo_name_full': schema_data['repo_name'],
1087 'repo_group': schema_data['repo_group']['repo_group_id'],
1091 'repo_group': schema_data['repo_group']['repo_group_id'],
1088 'repo_type': schema_data['repo_type'],
1092 'repo_type': schema_data['repo_type'],
1089 'description': schema_data['repo_description'],
1093 'description': schema_data['repo_description'],
1090 'private': schema_data['repo_private'],
1094 'private': schema_data['repo_private'],
1091 'copy_permissions': schema_data['repo_copy_permissions'],
1095 'copy_permissions': schema_data['repo_copy_permissions'],
1092 'landing_rev': schema_data['repo_landing_commit_ref'],
1096 'landing_rev': schema_data['repo_landing_commit_ref'],
1093 }
1097 }
1094
1098
1095 task = RepoModel().create_fork(data, cur_user=owner)
1099 task = RepoModel().create_fork(data, cur_user=owner)
1096 # no commit, it's done in RepoModel, or async via celery
1100 # no commit, it's done in RepoModel, or async via celery
1097 task_id = get_task_id(task)
1101 task_id = get_task_id(task)
1098
1102
1099 return {
1103 return {
1100 'msg': 'Created fork of `%s` as `%s`' % (
1104 'msg': 'Created fork of `%s` as `%s`' % (
1101 repo.repo_name, schema_data['repo_name']),
1105 repo.repo_name, schema_data['repo_name']),
1102 'success': True, # cannot return the repo data here since fork
1106 'success': True, # cannot return the repo data here since fork
1103 # can be done async
1107 # can be done async
1104 'task': task_id
1108 'task': task_id
1105 }
1109 }
1106 except Exception:
1110 except Exception:
1107 log.exception(
1111 log.exception(
1108 u"Exception while trying to create fork %s",
1112 u"Exception while trying to create fork %s",
1109 schema_data['repo_name'])
1113 schema_data['repo_name'])
1110 raise JSONRPCError(
1114 raise JSONRPCError(
1111 'failed to fork repository `%s` as `%s`' % (
1115 'failed to fork repository `%s` as `%s`' % (
1112 repo_name, schema_data['repo_name']))
1116 repo_name, schema_data['repo_name']))
1113
1117
1114
1118
1115 @jsonrpc_method()
1119 @jsonrpc_method()
1116 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1120 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1117 """
1121 """
1118 Deletes a repository.
1122 Deletes a repository.
1119
1123
1120 * When the `forks` parameter is set it's possible to detach or delete
1124 * When the `forks` parameter is set it's possible to detach or delete
1121 forks of deleted repository.
1125 forks of deleted repository.
1122
1126
1123 This command can only be run using an |authtoken| with admin
1127 This command can only be run using an |authtoken| with admin
1124 permissions on the |repo|.
1128 permissions on the |repo|.
1125
1129
1126 :param apiuser: This is filled automatically from the |authtoken|.
1130 :param apiuser: This is filled automatically from the |authtoken|.
1127 :type apiuser: AuthUser
1131 :type apiuser: AuthUser
1128 :param repoid: Set the repository name or repository ID.
1132 :param repoid: Set the repository name or repository ID.
1129 :type repoid: str or int
1133 :type repoid: str or int
1130 :param forks: Set to `detach` or `delete` forks from the |repo|.
1134 :param forks: Set to `detach` or `delete` forks from the |repo|.
1131 :type forks: Optional(str)
1135 :type forks: Optional(str)
1132
1136
1133 Example error output:
1137 Example error output:
1134
1138
1135 .. code-block:: bash
1139 .. code-block:: bash
1136
1140
1137 id : <id_given_in_input>
1141 id : <id_given_in_input>
1138 result: {
1142 result: {
1139 "msg": "Deleted repository `<reponame>`",
1143 "msg": "Deleted repository `<reponame>`",
1140 "success": true
1144 "success": true
1141 }
1145 }
1142 error: null
1146 error: null
1143 """
1147 """
1144
1148
1145 repo = get_repo_or_error(repoid)
1149 repo = get_repo_or_error(repoid)
1146 repo_name = repo.repo_name
1150 repo_name = repo.repo_name
1147 if not has_superadmin_permission(apiuser):
1151 if not has_superadmin_permission(apiuser):
1148 _perms = ('repository.admin',)
1152 _perms = ('repository.admin',)
1149 validate_repo_permissions(apiuser, repoid, repo, _perms)
1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
1150
1154
1151 try:
1155 try:
1152 handle_forks = Optional.extract(forks)
1156 handle_forks = Optional.extract(forks)
1153 _forks_msg = ''
1157 _forks_msg = ''
1154 _forks = [f for f in repo.forks]
1158 _forks = [f for f in repo.forks]
1155 if handle_forks == 'detach':
1159 if handle_forks == 'detach':
1156 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1160 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1157 elif handle_forks == 'delete':
1161 elif handle_forks == 'delete':
1158 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1162 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1159 elif _forks:
1163 elif _forks:
1160 raise JSONRPCError(
1164 raise JSONRPCError(
1161 'Cannot delete `%s` it still contains attached forks' %
1165 'Cannot delete `%s` it still contains attached forks' %
1162 (repo.repo_name,)
1166 (repo.repo_name,)
1163 )
1167 )
1164 old_data = repo.get_api_data()
1168 old_data = repo.get_api_data()
1165 RepoModel().delete(repo, forks=forks)
1169 RepoModel().delete(repo, forks=forks)
1166
1170
1167 repo = audit_logger.RepoWrap(repo_id=None,
1171 repo = audit_logger.RepoWrap(repo_id=None,
1168 repo_name=repo.repo_name)
1172 repo_name=repo.repo_name)
1169
1173
1170 audit_logger.store_api(
1174 audit_logger.store_api(
1171 'repo.delete', action_data={'old_data': old_data},
1175 'repo.delete', action_data={'old_data': old_data},
1172 user=apiuser, repo=repo)
1176 user=apiuser, repo=repo)
1173
1177
1174 ScmModel().mark_for_invalidation(repo_name, delete=True)
1178 ScmModel().mark_for_invalidation(repo_name, delete=True)
1175 Session().commit()
1179 Session().commit()
1176 return {
1180 return {
1177 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1181 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1178 'success': True
1182 'success': True
1179 }
1183 }
1180 except Exception:
1184 except Exception:
1181 log.exception("Exception occurred while trying to delete repo")
1185 log.exception("Exception occurred while trying to delete repo")
1182 raise JSONRPCError(
1186 raise JSONRPCError(
1183 'failed to delete repository `%s`' % (repo_name,)
1187 'failed to delete repository `%s`' % (repo_name,)
1184 )
1188 )
1185
1189
1186
1190
1187 #TODO: marcink, change name ?
1191 #TODO: marcink, change name ?
1188 @jsonrpc_method()
1192 @jsonrpc_method()
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1193 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1190 """
1194 """
1191 Invalidates the cache for the specified repository.
1195 Invalidates the cache for the specified repository.
1192
1196
1193 This command can only be run using an |authtoken| with admin rights to
1197 This command can only be run using an |authtoken| with admin rights to
1194 the specified repository.
1198 the specified repository.
1195
1199
1196 This command takes the following options:
1200 This command takes the following options:
1197
1201
1198 :param apiuser: This is filled automatically from |authtoken|.
1202 :param apiuser: This is filled automatically from |authtoken|.
1199 :type apiuser: AuthUser
1203 :type apiuser: AuthUser
1200 :param repoid: Sets the repository name or repository ID.
1204 :param repoid: Sets the repository name or repository ID.
1201 :type repoid: str or int
1205 :type repoid: str or int
1202 :param delete_keys: This deletes the invalidated keys instead of
1206 :param delete_keys: This deletes the invalidated keys instead of
1203 just flagging them.
1207 just flagging them.
1204 :type delete_keys: Optional(``True`` | ``False``)
1208 :type delete_keys: Optional(``True`` | ``False``)
1205
1209
1206 Example output:
1210 Example output:
1207
1211
1208 .. code-block:: bash
1212 .. code-block:: bash
1209
1213
1210 id : <id_given_in_input>
1214 id : <id_given_in_input>
1211 result : {
1215 result : {
1212 'msg': Cache for repository `<repository name>` was invalidated,
1216 'msg': Cache for repository `<repository name>` was invalidated,
1213 'repository': <repository name>
1217 'repository': <repository name>
1214 }
1218 }
1215 error : null
1219 error : null
1216
1220
1217 Example error output:
1221 Example error output:
1218
1222
1219 .. code-block:: bash
1223 .. code-block:: bash
1220
1224
1221 id : <id_given_in_input>
1225 id : <id_given_in_input>
1222 result : null
1226 result : null
1223 error : {
1227 error : {
1224 'Error occurred during cache invalidation action'
1228 'Error occurred during cache invalidation action'
1225 }
1229 }
1226
1230
1227 """
1231 """
1228
1232
1229 repo = get_repo_or_error(repoid)
1233 repo = get_repo_or_error(repoid)
1230 if not has_superadmin_permission(apiuser):
1234 if not has_superadmin_permission(apiuser):
1231 _perms = ('repository.admin', 'repository.write',)
1235 _perms = ('repository.admin', 'repository.write',)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1236 validate_repo_permissions(apiuser, repoid, repo, _perms)
1233
1237
1234 delete = Optional.extract(delete_keys)
1238 delete = Optional.extract(delete_keys)
1235 try:
1239 try:
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1240 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1237 return {
1241 return {
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1242 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1239 'repository': repo.repo_name
1243 'repository': repo.repo_name
1240 }
1244 }
1241 except Exception:
1245 except Exception:
1242 log.exception(
1246 log.exception(
1243 "Exception occurred while trying to invalidate repo cache")
1247 "Exception occurred while trying to invalidate repo cache")
1244 raise JSONRPCError(
1248 raise JSONRPCError(
1245 'Error occurred during cache invalidation action'
1249 'Error occurred during cache invalidation action'
1246 )
1250 )
1247
1251
1248
1252
1249 #TODO: marcink, change name ?
1253 #TODO: marcink, change name ?
1250 @jsonrpc_method()
1254 @jsonrpc_method()
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1255 def lock(request, apiuser, repoid, locked=Optional(None),
1252 userid=Optional(OAttr('apiuser'))):
1256 userid=Optional(OAttr('apiuser'))):
1253 """
1257 """
1254 Sets the lock state of the specified |repo| by the given user.
1258 Sets the lock state of the specified |repo| by the given user.
1255 From more information, see :ref:`repo-locking`.
1259 From more information, see :ref:`repo-locking`.
1256
1260
1257 * If the ``userid`` option is not set, the repository is locked to the
1261 * If the ``userid`` option is not set, the repository is locked to the
1258 user who called the method.
1262 user who called the method.
1259 * If the ``locked`` parameter is not set, the current lock state of the
1263 * If the ``locked`` parameter is not set, the current lock state of the
1260 repository is displayed.
1264 repository is displayed.
1261
1265
1262 This command can only be run using an |authtoken| with admin rights to
1266 This command can only be run using an |authtoken| with admin rights to
1263 the specified repository.
1267 the specified repository.
1264
1268
1265 This command takes the following options:
1269 This command takes the following options:
1266
1270
1267 :param apiuser: This is filled automatically from the |authtoken|.
1271 :param apiuser: This is filled automatically from the |authtoken|.
1268 :type apiuser: AuthUser
1272 :type apiuser: AuthUser
1269 :param repoid: Sets the repository name or repository ID.
1273 :param repoid: Sets the repository name or repository ID.
1270 :type repoid: str or int
1274 :type repoid: str or int
1271 :param locked: Sets the lock state.
1275 :param locked: Sets the lock state.
1272 :type locked: Optional(``True`` | ``False``)
1276 :type locked: Optional(``True`` | ``False``)
1273 :param userid: Set the repository lock to this user.
1277 :param userid: Set the repository lock to this user.
1274 :type userid: Optional(str or int)
1278 :type userid: Optional(str or int)
1275
1279
1276 Example error output:
1280 Example error output:
1277
1281
1278 .. code-block:: bash
1282 .. code-block:: bash
1279
1283
1280 id : <id_given_in_input>
1284 id : <id_given_in_input>
1281 result : {
1285 result : {
1282 'repo': '<reponame>',
1286 'repo': '<reponame>',
1283 'locked': <bool: lock state>,
1287 'locked': <bool: lock state>,
1284 'locked_since': <int: lock timestamp>,
1288 'locked_since': <int: lock timestamp>,
1285 'locked_by': <username of person who made the lock>,
1289 'locked_by': <username of person who made the lock>,
1286 'lock_reason': <str: reason for locking>,
1290 'lock_reason': <str: reason for locking>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1291 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1292 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1289 or
1293 or
1290 'msg': 'Repo `<repository name>` not locked.'
1294 'msg': 'Repo `<repository name>` not locked.'
1291 or
1295 or
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1296 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1293 }
1297 }
1294 error : null
1298 error : null
1295
1299
1296 Example error output:
1300 Example error output:
1297
1301
1298 .. code-block:: bash
1302 .. code-block:: bash
1299
1303
1300 id : <id_given_in_input>
1304 id : <id_given_in_input>
1301 result : null
1305 result : null
1302 error : {
1306 error : {
1303 'Error occurred locking repository `<reponame>`'
1307 'Error occurred locking repository `<reponame>`'
1304 }
1308 }
1305 """
1309 """
1306
1310
1307 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1308 if not has_superadmin_permission(apiuser):
1312 if not has_superadmin_permission(apiuser):
1309 # check if we have at least write permission for this repo !
1313 # check if we have at least write permission for this repo !
1310 _perms = ('repository.admin', 'repository.write',)
1314 _perms = ('repository.admin', 'repository.write',)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1312
1316
1313 # make sure normal user does not pass someone else userid,
1317 # make sure normal user does not pass someone else userid,
1314 # he is not allowed to do that
1318 # he is not allowed to do that
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1319 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1316 raise JSONRPCError('userid is not the same as your user')
1320 raise JSONRPCError('userid is not the same as your user')
1317
1321
1318 if isinstance(userid, Optional):
1322 if isinstance(userid, Optional):
1319 userid = apiuser.user_id
1323 userid = apiuser.user_id
1320
1324
1321 user = get_user_or_error(userid)
1325 user = get_user_or_error(userid)
1322
1326
1323 if isinstance(locked, Optional):
1327 if isinstance(locked, Optional):
1324 lockobj = repo.locked
1328 lockobj = repo.locked
1325
1329
1326 if lockobj[0] is None:
1330 if lockobj[0] is None:
1327 _d = {
1331 _d = {
1328 'repo': repo.repo_name,
1332 'repo': repo.repo_name,
1329 'locked': False,
1333 'locked': False,
1330 'locked_since': None,
1334 'locked_since': None,
1331 'locked_by': None,
1335 'locked_by': None,
1332 'lock_reason': None,
1336 'lock_reason': None,
1333 'lock_state_changed': False,
1337 'lock_state_changed': False,
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1338 'msg': 'Repo `%s` not locked.' % repo.repo_name
1335 }
1339 }
1336 return _d
1340 return _d
1337 else:
1341 else:
1338 _user_id, _time, _reason = lockobj
1342 _user_id, _time, _reason = lockobj
1339 lock_user = get_user_or_error(userid)
1343 lock_user = get_user_or_error(userid)
1340 _d = {
1344 _d = {
1341 'repo': repo.repo_name,
1345 'repo': repo.repo_name,
1342 'locked': True,
1346 'locked': True,
1343 'locked_since': _time,
1347 'locked_since': _time,
1344 'locked_by': lock_user.username,
1348 'locked_by': lock_user.username,
1345 'lock_reason': _reason,
1349 'lock_reason': _reason,
1346 'lock_state_changed': False,
1350 'lock_state_changed': False,
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1351 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1348 % (repo.repo_name, lock_user.username,
1352 % (repo.repo_name, lock_user.username,
1349 json.dumps(time_to_datetime(_time))))
1353 json.dumps(time_to_datetime(_time))))
1350 }
1354 }
1351 return _d
1355 return _d
1352
1356
1353 # force locked state through a flag
1357 # force locked state through a flag
1354 else:
1358 else:
1355 locked = str2bool(locked)
1359 locked = str2bool(locked)
1356 lock_reason = Repository.LOCK_API
1360 lock_reason = Repository.LOCK_API
1357 try:
1361 try:
1358 if locked:
1362 if locked:
1359 lock_time = time.time()
1363 lock_time = time.time()
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1364 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1361 else:
1365 else:
1362 lock_time = None
1366 lock_time = None
1363 Repository.unlock(repo)
1367 Repository.unlock(repo)
1364 _d = {
1368 _d = {
1365 'repo': repo.repo_name,
1369 'repo': repo.repo_name,
1366 'locked': locked,
1370 'locked': locked,
1367 'locked_since': lock_time,
1371 'locked_since': lock_time,
1368 'locked_by': user.username,
1372 'locked_by': user.username,
1369 'lock_reason': lock_reason,
1373 'lock_reason': lock_reason,
1370 'lock_state_changed': True,
1374 'lock_state_changed': True,
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1375 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1372 % (user.username, repo.repo_name, locked))
1376 % (user.username, repo.repo_name, locked))
1373 }
1377 }
1374 return _d
1378 return _d
1375 except Exception:
1379 except Exception:
1376 log.exception(
1380 log.exception(
1377 "Exception occurred while trying to lock repository")
1381 "Exception occurred while trying to lock repository")
1378 raise JSONRPCError(
1382 raise JSONRPCError(
1379 'Error occurred locking repository `%s`' % repo.repo_name
1383 'Error occurred locking repository `%s`' % repo.repo_name
1380 )
1384 )
1381
1385
1382
1386
1383 @jsonrpc_method()
1387 @jsonrpc_method()
1384 def comment_commit(
1388 def comment_commit(
1385 request, apiuser, repoid, commit_id, message, status=Optional(None),
1389 request, apiuser, repoid, commit_id, message, status=Optional(None),
1386 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1390 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1387 resolves_comment_id=Optional(None),
1391 resolves_comment_id=Optional(None),
1388 userid=Optional(OAttr('apiuser'))):
1392 userid=Optional(OAttr('apiuser'))):
1389 """
1393 """
1390 Set a commit comment, and optionally change the status of the commit.
1394 Set a commit comment, and optionally change the status of the commit.
1391
1395
1392 :param apiuser: This is filled automatically from the |authtoken|.
1396 :param apiuser: This is filled automatically from the |authtoken|.
1393 :type apiuser: AuthUser
1397 :type apiuser: AuthUser
1394 :param repoid: Set the repository name or repository ID.
1398 :param repoid: Set the repository name or repository ID.
1395 :type repoid: str or int
1399 :type repoid: str or int
1396 :param commit_id: Specify the commit_id for which to set a comment.
1400 :param commit_id: Specify the commit_id for which to set a comment.
1397 :type commit_id: str
1401 :type commit_id: str
1398 :param message: The comment text.
1402 :param message: The comment text.
1399 :type message: str
1403 :type message: str
1400 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1404 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1401 'approved', 'rejected', 'under_review'
1405 'approved', 'rejected', 'under_review'
1402 :type status: str
1406 :type status: str
1403 :param comment_type: Comment type, one of: 'note', 'todo'
1407 :param comment_type: Comment type, one of: 'note', 'todo'
1404 :type comment_type: Optional(str), default: 'note'
1408 :type comment_type: Optional(str), default: 'note'
1405 :param userid: Set the user name of the comment creator.
1409 :param userid: Set the user name of the comment creator.
1406 :type userid: Optional(str or int)
1410 :type userid: Optional(str or int)
1407
1411
1408 Example error output:
1412 Example error output:
1409
1413
1410 .. code-block:: bash
1414 .. code-block:: bash
1411
1415
1412 {
1416 {
1413 "id" : <id_given_in_input>,
1417 "id" : <id_given_in_input>,
1414 "result" : {
1418 "result" : {
1415 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1419 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1416 "status_change": null or <status>,
1420 "status_change": null or <status>,
1417 "success": true
1421 "success": true
1418 },
1422 },
1419 "error" : null
1423 "error" : null
1420 }
1424 }
1421
1425
1422 """
1426 """
1423 repo = get_repo_or_error(repoid)
1427 repo = get_repo_or_error(repoid)
1424 if not has_superadmin_permission(apiuser):
1428 if not has_superadmin_permission(apiuser):
1425 _perms = ('repository.read', 'repository.write', 'repository.admin')
1429 _perms = ('repository.read', 'repository.write', 'repository.admin')
1426 validate_repo_permissions(apiuser, repoid, repo, _perms)
1430 validate_repo_permissions(apiuser, repoid, repo, _perms)
1427
1431
1428 try:
1432 try:
1429 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1433 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1430 except Exception as e:
1434 except Exception as e:
1431 log.exception('Failed to fetch commit')
1435 log.exception('Failed to fetch commit')
1432 raise JSONRPCError(e.message)
1436 raise JSONRPCError(e.message)
1433
1437
1434 if isinstance(userid, Optional):
1438 if isinstance(userid, Optional):
1435 userid = apiuser.user_id
1439 userid = apiuser.user_id
1436
1440
1437 user = get_user_or_error(userid)
1441 user = get_user_or_error(userid)
1438 status = Optional.extract(status)
1442 status = Optional.extract(status)
1439 comment_type = Optional.extract(comment_type)
1443 comment_type = Optional.extract(comment_type)
1440 resolves_comment_id = Optional.extract(resolves_comment_id)
1444 resolves_comment_id = Optional.extract(resolves_comment_id)
1441
1445
1442 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1446 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1443 if status and status not in allowed_statuses:
1447 if status and status not in allowed_statuses:
1444 raise JSONRPCError('Bad status, must be on '
1448 raise JSONRPCError('Bad status, must be on '
1445 'of %s got %s' % (allowed_statuses, status,))
1449 'of %s got %s' % (allowed_statuses, status,))
1446
1450
1447 if resolves_comment_id:
1451 if resolves_comment_id:
1448 comment = ChangesetComment.get(resolves_comment_id)
1452 comment = ChangesetComment.get(resolves_comment_id)
1449 if not comment:
1453 if not comment:
1450 raise JSONRPCError(
1454 raise JSONRPCError(
1451 'Invalid resolves_comment_id `%s` for this commit.'
1455 'Invalid resolves_comment_id `%s` for this commit.'
1452 % resolves_comment_id)
1456 % resolves_comment_id)
1453 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1454 raise JSONRPCError(
1458 raise JSONRPCError(
1455 'Comment `%s` is wrong type for setting status to resolved.'
1459 'Comment `%s` is wrong type for setting status to resolved.'
1456 % resolves_comment_id)
1460 % resolves_comment_id)
1457
1461
1458 try:
1462 try:
1459 rc_config = SettingsModel().get_all_settings()
1463 rc_config = SettingsModel().get_all_settings()
1460 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1464 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1461 status_change_label = ChangesetStatus.get_status_lbl(status)
1465 status_change_label = ChangesetStatus.get_status_lbl(status)
1462 comment = CommentsModel().create(
1466 comment = CommentsModel().create(
1463 message, repo, user, commit_id=commit_id,
1467 message, repo, user, commit_id=commit_id,
1464 status_change=status_change_label,
1468 status_change=status_change_label,
1465 status_change_type=status,
1469 status_change_type=status,
1466 renderer=renderer,
1470 renderer=renderer,
1467 comment_type=comment_type,
1471 comment_type=comment_type,
1468 resolves_comment_id=resolves_comment_id
1472 resolves_comment_id=resolves_comment_id
1469 )
1473 )
1470 if status:
1474 if status:
1471 # also do a status change
1475 # also do a status change
1472 try:
1476 try:
1473 ChangesetStatusModel().set_status(
1477 ChangesetStatusModel().set_status(
1474 repo, status, user, comment, revision=commit_id,
1478 repo, status, user, comment, revision=commit_id,
1475 dont_allow_on_closed_pull_request=True
1479 dont_allow_on_closed_pull_request=True
1476 )
1480 )
1477 except StatusChangeOnClosedPullRequestError:
1481 except StatusChangeOnClosedPullRequestError:
1478 log.exception(
1482 log.exception(
1479 "Exception occurred while trying to change repo commit status")
1483 "Exception occurred while trying to change repo commit status")
1480 msg = ('Changing status on a changeset associated with '
1484 msg = ('Changing status on a changeset associated with '
1481 'a closed pull request is not allowed')
1485 'a closed pull request is not allowed')
1482 raise JSONRPCError(msg)
1486 raise JSONRPCError(msg)
1483
1487
1484 Session().commit()
1488 Session().commit()
1485 return {
1489 return {
1486 'msg': (
1490 'msg': (
1487 'Commented on commit `%s` for repository `%s`' % (
1491 'Commented on commit `%s` for repository `%s`' % (
1488 comment.revision, repo.repo_name)),
1492 comment.revision, repo.repo_name)),
1489 'status_change': status,
1493 'status_change': status,
1490 'success': True,
1494 'success': True,
1491 }
1495 }
1492 except JSONRPCError:
1496 except JSONRPCError:
1493 # catch any inside errors, and re-raise them to prevent from
1497 # catch any inside errors, and re-raise them to prevent from
1494 # below global catch to silence them
1498 # below global catch to silence them
1495 raise
1499 raise
1496 except Exception:
1500 except Exception:
1497 log.exception("Exception occurred while trying to comment on commit")
1501 log.exception("Exception occurred while trying to comment on commit")
1498 raise JSONRPCError(
1502 raise JSONRPCError(
1499 'failed to set comment on repository `%s`' % (repo.repo_name,)
1503 'failed to set comment on repository `%s`' % (repo.repo_name,)
1500 )
1504 )
1501
1505
1502
1506
1503 @jsonrpc_method()
1507 @jsonrpc_method()
1504 def grant_user_permission(request, apiuser, repoid, userid, perm):
1508 def grant_user_permission(request, apiuser, repoid, userid, perm):
1505 """
1509 """
1506 Grant permissions for the specified user on the given repository,
1510 Grant permissions for the specified user on the given repository,
1507 or update existing permissions if found.
1511 or update existing permissions if found.
1508
1512
1509 This command can only be run using an |authtoken| with admin
1513 This command can only be run using an |authtoken| with admin
1510 permissions on the |repo|.
1514 permissions on the |repo|.
1511
1515
1512 :param apiuser: This is filled automatically from the |authtoken|.
1516 :param apiuser: This is filled automatically from the |authtoken|.
1513 :type apiuser: AuthUser
1517 :type apiuser: AuthUser
1514 :param repoid: Set the repository name or repository ID.
1518 :param repoid: Set the repository name or repository ID.
1515 :type repoid: str or int
1519 :type repoid: str or int
1516 :param userid: Set the user name.
1520 :param userid: Set the user name.
1517 :type userid: str
1521 :type userid: str
1518 :param perm: Set the user permissions, using the following format
1522 :param perm: Set the user permissions, using the following format
1519 ``(repository.(none|read|write|admin))``
1523 ``(repository.(none|read|write|admin))``
1520 :type perm: str
1524 :type perm: str
1521
1525
1522 Example output:
1526 Example output:
1523
1527
1524 .. code-block:: bash
1528 .. code-block:: bash
1525
1529
1526 id : <id_given_in_input>
1530 id : <id_given_in_input>
1527 result: {
1531 result: {
1528 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1532 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1529 "success": true
1533 "success": true
1530 }
1534 }
1531 error: null
1535 error: null
1532 """
1536 """
1533
1537
1534 repo = get_repo_or_error(repoid)
1538 repo = get_repo_or_error(repoid)
1535 user = get_user_or_error(userid)
1539 user = get_user_or_error(userid)
1536 perm = get_perm_or_error(perm)
1540 perm = get_perm_or_error(perm)
1537 if not has_superadmin_permission(apiuser):
1541 if not has_superadmin_permission(apiuser):
1538 _perms = ('repository.admin',)
1542 _perms = ('repository.admin',)
1539 validate_repo_permissions(apiuser, repoid, repo, _perms)
1543 validate_repo_permissions(apiuser, repoid, repo, _perms)
1540
1544
1541 try:
1545 try:
1542
1546
1543 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1547 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1544
1548
1545 Session().commit()
1549 Session().commit()
1546 return {
1550 return {
1547 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1551 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1548 perm.permission_name, user.username, repo.repo_name
1552 perm.permission_name, user.username, repo.repo_name
1549 ),
1553 ),
1550 'success': True
1554 'success': True
1551 }
1555 }
1552 except Exception:
1556 except Exception:
1553 log.exception(
1557 log.exception(
1554 "Exception occurred while trying edit permissions for repo")
1558 "Exception occurred while trying edit permissions for repo")
1555 raise JSONRPCError(
1559 raise JSONRPCError(
1556 'failed to edit permission for user: `%s` in repo: `%s`' % (
1560 'failed to edit permission for user: `%s` in repo: `%s`' % (
1557 userid, repoid
1561 userid, repoid
1558 )
1562 )
1559 )
1563 )
1560
1564
1561
1565
1562 @jsonrpc_method()
1566 @jsonrpc_method()
1563 def revoke_user_permission(request, apiuser, repoid, userid):
1567 def revoke_user_permission(request, apiuser, repoid, userid):
1564 """
1568 """
1565 Revoke permission for a user on the specified repository.
1569 Revoke permission for a user on the specified repository.
1566
1570
1567 This command can only be run using an |authtoken| with admin
1571 This command can only be run using an |authtoken| with admin
1568 permissions on the |repo|.
1572 permissions on the |repo|.
1569
1573
1570 :param apiuser: This is filled automatically from the |authtoken|.
1574 :param apiuser: This is filled automatically from the |authtoken|.
1571 :type apiuser: AuthUser
1575 :type apiuser: AuthUser
1572 :param repoid: Set the repository name or repository ID.
1576 :param repoid: Set the repository name or repository ID.
1573 :type repoid: str or int
1577 :type repoid: str or int
1574 :param userid: Set the user name of revoked user.
1578 :param userid: Set the user name of revoked user.
1575 :type userid: str or int
1579 :type userid: str or int
1576
1580
1577 Example error output:
1581 Example error output:
1578
1582
1579 .. code-block:: bash
1583 .. code-block:: bash
1580
1584
1581 id : <id_given_in_input>
1585 id : <id_given_in_input>
1582 result: {
1586 result: {
1583 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1587 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1584 "success": true
1588 "success": true
1585 }
1589 }
1586 error: null
1590 error: null
1587 """
1591 """
1588
1592
1589 repo = get_repo_or_error(repoid)
1593 repo = get_repo_or_error(repoid)
1590 user = get_user_or_error(userid)
1594 user = get_user_or_error(userid)
1591 if not has_superadmin_permission(apiuser):
1595 if not has_superadmin_permission(apiuser):
1592 _perms = ('repository.admin',)
1596 _perms = ('repository.admin',)
1593 validate_repo_permissions(apiuser, repoid, repo, _perms)
1597 validate_repo_permissions(apiuser, repoid, repo, _perms)
1594
1598
1595 try:
1599 try:
1596 RepoModel().revoke_user_permission(repo=repo, user=user)
1600 RepoModel().revoke_user_permission(repo=repo, user=user)
1597 Session().commit()
1601 Session().commit()
1598 return {
1602 return {
1599 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1603 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1600 user.username, repo.repo_name
1604 user.username, repo.repo_name
1601 ),
1605 ),
1602 'success': True
1606 'success': True
1603 }
1607 }
1604 except Exception:
1608 except Exception:
1605 log.exception(
1609 log.exception(
1606 "Exception occurred while trying revoke permissions to repo")
1610 "Exception occurred while trying revoke permissions to repo")
1607 raise JSONRPCError(
1611 raise JSONRPCError(
1608 'failed to edit permission for user: `%s` in repo: `%s`' % (
1612 'failed to edit permission for user: `%s` in repo: `%s`' % (
1609 userid, repoid
1613 userid, repoid
1610 )
1614 )
1611 )
1615 )
1612
1616
1613
1617
1614 @jsonrpc_method()
1618 @jsonrpc_method()
1615 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1619 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1616 """
1620 """
1617 Grant permission for a user group on the specified repository,
1621 Grant permission for a user group on the specified repository,
1618 or update existing permissions.
1622 or update existing permissions.
1619
1623
1620 This command can only be run using an |authtoken| with admin
1624 This command can only be run using an |authtoken| with admin
1621 permissions on the |repo|.
1625 permissions on the |repo|.
1622
1626
1623 :param apiuser: This is filled automatically from the |authtoken|.
1627 :param apiuser: This is filled automatically from the |authtoken|.
1624 :type apiuser: AuthUser
1628 :type apiuser: AuthUser
1625 :param repoid: Set the repository name or repository ID.
1629 :param repoid: Set the repository name or repository ID.
1626 :type repoid: str or int
1630 :type repoid: str or int
1627 :param usergroupid: Specify the ID of the user group.
1631 :param usergroupid: Specify the ID of the user group.
1628 :type usergroupid: str or int
1632 :type usergroupid: str or int
1629 :param perm: Set the user group permissions using the following
1633 :param perm: Set the user group permissions using the following
1630 format: (repository.(none|read|write|admin))
1634 format: (repository.(none|read|write|admin))
1631 :type perm: str
1635 :type perm: str
1632
1636
1633 Example output:
1637 Example 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 : {
1642 result : {
1639 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1643 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1640 "success": true
1644 "success": true
1641
1645
1642 }
1646 }
1643 error : null
1647 error : null
1644
1648
1645 Example error output:
1649 Example error output:
1646
1650
1647 .. code-block:: bash
1651 .. code-block:: bash
1648
1652
1649 id : <id_given_in_input>
1653 id : <id_given_in_input>
1650 result : null
1654 result : null
1651 error : {
1655 error : {
1652 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1656 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1653 }
1657 }
1654
1658
1655 """
1659 """
1656
1660
1657 repo = get_repo_or_error(repoid)
1661 repo = get_repo_or_error(repoid)
1658 perm = get_perm_or_error(perm)
1662 perm = get_perm_or_error(perm)
1659 if not has_superadmin_permission(apiuser):
1663 if not has_superadmin_permission(apiuser):
1660 _perms = ('repository.admin',)
1664 _perms = ('repository.admin',)
1661 validate_repo_permissions(apiuser, repoid, repo, _perms)
1665 validate_repo_permissions(apiuser, repoid, repo, _perms)
1662
1666
1663 user_group = get_user_group_or_error(usergroupid)
1667 user_group = get_user_group_or_error(usergroupid)
1664 if not has_superadmin_permission(apiuser):
1668 if not has_superadmin_permission(apiuser):
1665 # check if we have at least read permission for this user group !
1669 # check if we have at least read permission for this user group !
1666 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1670 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1667 if not HasUserGroupPermissionAnyApi(*_perms)(
1671 if not HasUserGroupPermissionAnyApi(*_perms)(
1668 user=apiuser, user_group_name=user_group.users_group_name):
1672 user=apiuser, user_group_name=user_group.users_group_name):
1669 raise JSONRPCError(
1673 raise JSONRPCError(
1670 'user group `%s` does not exist' % (usergroupid,))
1674 'user group `%s` does not exist' % (usergroupid,))
1671
1675
1672 try:
1676 try:
1673 RepoModel().grant_user_group_permission(
1677 RepoModel().grant_user_group_permission(
1674 repo=repo, group_name=user_group, perm=perm)
1678 repo=repo, group_name=user_group, perm=perm)
1675
1679
1676 Session().commit()
1680 Session().commit()
1677 return {
1681 return {
1678 'msg': 'Granted perm: `%s` for user group: `%s` in '
1682 'msg': 'Granted perm: `%s` for user group: `%s` in '
1679 'repo: `%s`' % (
1683 'repo: `%s`' % (
1680 perm.permission_name, user_group.users_group_name,
1684 perm.permission_name, user_group.users_group_name,
1681 repo.repo_name
1685 repo.repo_name
1682 ),
1686 ),
1683 'success': True
1687 'success': True
1684 }
1688 }
1685 except Exception:
1689 except Exception:
1686 log.exception(
1690 log.exception(
1687 "Exception occurred while trying change permission on repo")
1691 "Exception occurred while trying change permission on repo")
1688 raise JSONRPCError(
1692 raise JSONRPCError(
1689 'failed to edit permission for user group: `%s` in '
1693 'failed to edit permission for user group: `%s` in '
1690 'repo: `%s`' % (
1694 'repo: `%s`' % (
1691 usergroupid, repo.repo_name
1695 usergroupid, repo.repo_name
1692 )
1696 )
1693 )
1697 )
1694
1698
1695
1699
1696 @jsonrpc_method()
1700 @jsonrpc_method()
1697 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1701 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1698 """
1702 """
1699 Revoke the permissions of a user group on a given repository.
1703 Revoke the permissions of a user group on a given repository.
1700
1704
1701 This command can only be run using an |authtoken| with admin
1705 This command can only be run using an |authtoken| with admin
1702 permissions on the |repo|.
1706 permissions on the |repo|.
1703
1707
1704 :param apiuser: This is filled automatically from the |authtoken|.
1708 :param apiuser: This is filled automatically from the |authtoken|.
1705 :type apiuser: AuthUser
1709 :type apiuser: AuthUser
1706 :param repoid: Set the repository name or repository ID.
1710 :param repoid: Set the repository name or repository ID.
1707 :type repoid: str or int
1711 :type repoid: str or int
1708 :param usergroupid: Specify the user group ID.
1712 :param usergroupid: Specify the user group ID.
1709 :type usergroupid: str or int
1713 :type usergroupid: str or int
1710
1714
1711 Example output:
1715 Example output:
1712
1716
1713 .. code-block:: bash
1717 .. code-block:: bash
1714
1718
1715 id : <id_given_in_input>
1719 id : <id_given_in_input>
1716 result: {
1720 result: {
1717 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1721 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1718 "success": true
1722 "success": true
1719 }
1723 }
1720 error: null
1724 error: null
1721 """
1725 """
1722
1726
1723 repo = get_repo_or_error(repoid)
1727 repo = get_repo_or_error(repoid)
1724 if not has_superadmin_permission(apiuser):
1728 if not has_superadmin_permission(apiuser):
1725 _perms = ('repository.admin',)
1729 _perms = ('repository.admin',)
1726 validate_repo_permissions(apiuser, repoid, repo, _perms)
1730 validate_repo_permissions(apiuser, repoid, repo, _perms)
1727
1731
1728 user_group = get_user_group_or_error(usergroupid)
1732 user_group = get_user_group_or_error(usergroupid)
1729 if not has_superadmin_permission(apiuser):
1733 if not has_superadmin_permission(apiuser):
1730 # check if we have at least read permission for this user group !
1734 # check if we have at least read permission for this user group !
1731 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1735 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1732 if not HasUserGroupPermissionAnyApi(*_perms)(
1736 if not HasUserGroupPermissionAnyApi(*_perms)(
1733 user=apiuser, user_group_name=user_group.users_group_name):
1737 user=apiuser, user_group_name=user_group.users_group_name):
1734 raise JSONRPCError(
1738 raise JSONRPCError(
1735 'user group `%s` does not exist' % (usergroupid,))
1739 'user group `%s` does not exist' % (usergroupid,))
1736
1740
1737 try:
1741 try:
1738 RepoModel().revoke_user_group_permission(
1742 RepoModel().revoke_user_group_permission(
1739 repo=repo, group_name=user_group)
1743 repo=repo, group_name=user_group)
1740
1744
1741 Session().commit()
1745 Session().commit()
1742 return {
1746 return {
1743 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1747 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1744 user_group.users_group_name, repo.repo_name
1748 user_group.users_group_name, repo.repo_name
1745 ),
1749 ),
1746 'success': True
1750 'success': True
1747 }
1751 }
1748 except Exception:
1752 except Exception:
1749 log.exception("Exception occurred while trying revoke "
1753 log.exception("Exception occurred while trying revoke "
1750 "user group permission on repo")
1754 "user group permission on repo")
1751 raise JSONRPCError(
1755 raise JSONRPCError(
1752 'failed to edit permission for user group: `%s` in '
1756 'failed to edit permission for user group: `%s` in '
1753 'repo: `%s`' % (
1757 'repo: `%s`' % (
1754 user_group.users_group_name, repo.repo_name
1758 user_group.users_group_name, repo.repo_name
1755 )
1759 )
1756 )
1760 )
1757
1761
1758
1762
1759 @jsonrpc_method()
1763 @jsonrpc_method()
1760 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1764 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1761 """
1765 """
1762 Triggers a pull on the given repository from a remote location. You
1766 Triggers a pull on the given repository from a remote location. You
1763 can use this to keep remote repositories up-to-date.
1767 can use this to keep remote repositories up-to-date.
1764
1768
1765 This command can only be run using an |authtoken| with admin
1769 This command can only be run using an |authtoken| with admin
1766 rights to the specified repository. For more information,
1770 rights to the specified repository. For more information,
1767 see :ref:`config-token-ref`.
1771 see :ref:`config-token-ref`.
1768
1772
1769 This command takes the following options:
1773 This command takes the following options:
1770
1774
1771 :param apiuser: This is filled automatically from the |authtoken|.
1775 :param apiuser: This is filled automatically from the |authtoken|.
1772 :type apiuser: AuthUser
1776 :type apiuser: AuthUser
1773 :param repoid: The repository name or repository ID.
1777 :param repoid: The repository name or repository ID.
1774 :type repoid: str or int
1778 :type repoid: str or int
1775 :param remote_uri: Optional remote URI to pass in for pull
1779 :param remote_uri: Optional remote URI to pass in for pull
1776 :type remote_uri: str
1780 :type remote_uri: str
1777
1781
1778 Example output:
1782 Example output:
1779
1783
1780 .. code-block:: bash
1784 .. code-block:: bash
1781
1785
1782 id : <id_given_in_input>
1786 id : <id_given_in_input>
1783 result : {
1787 result : {
1784 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1788 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1785 "repository": "<repository name>"
1789 "repository": "<repository name>"
1786 }
1790 }
1787 error : null
1791 error : null
1788
1792
1789 Example error output:
1793 Example error output:
1790
1794
1791 .. code-block:: bash
1795 .. code-block:: bash
1792
1796
1793 id : <id_given_in_input>
1797 id : <id_given_in_input>
1794 result : null
1798 result : null
1795 error : {
1799 error : {
1796 "Unable to push changes from `<remote_url>`"
1800 "Unable to push changes from `<remote_url>`"
1797 }
1801 }
1798
1802
1799 """
1803 """
1800
1804
1801 repo = get_repo_or_error(repoid)
1805 repo = get_repo_or_error(repoid)
1802 remote_uri = Optional.extract(remote_uri)
1806 remote_uri = Optional.extract(remote_uri)
1803 remote_uri_display = remote_uri or repo.clone_uri_hidden
1807 remote_uri_display = remote_uri or repo.clone_uri_hidden
1804 if not has_superadmin_permission(apiuser):
1808 if not has_superadmin_permission(apiuser):
1805 _perms = ('repository.admin',)
1809 _perms = ('repository.admin',)
1806 validate_repo_permissions(apiuser, repoid, repo, _perms)
1810 validate_repo_permissions(apiuser, repoid, repo, _perms)
1807
1811
1808 try:
1812 try:
1809 ScmModel().pull_changes(
1813 ScmModel().pull_changes(
1810 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1814 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1811 return {
1815 return {
1812 'msg': 'Pulled from url `%s` on repo `%s`' % (
1816 'msg': 'Pulled from url `%s` on repo `%s`' % (
1813 remote_uri_display, repo.repo_name),
1817 remote_uri_display, repo.repo_name),
1814 'repository': repo.repo_name
1818 'repository': repo.repo_name
1815 }
1819 }
1816 except Exception:
1820 except Exception:
1817 log.exception("Exception occurred while trying to "
1821 log.exception("Exception occurred while trying to "
1818 "pull changes from remote location")
1822 "pull changes from remote location")
1819 raise JSONRPCError(
1823 raise JSONRPCError(
1820 'Unable to pull changes from `%s`' % remote_uri_display
1824 'Unable to pull changes from `%s`' % remote_uri_display
1821 )
1825 )
1822
1826
1823
1827
1824 @jsonrpc_method()
1828 @jsonrpc_method()
1825 def strip(request, apiuser, repoid, revision, branch):
1829 def strip(request, apiuser, repoid, revision, branch):
1826 """
1830 """
1827 Strips the given revision from the specified repository.
1831 Strips the given revision from the specified repository.
1828
1832
1829 * This will remove the revision and all of its decendants.
1833 * This will remove the revision and all of its decendants.
1830
1834
1831 This command can only be run using an |authtoken| with admin rights to
1835 This command can only be run using an |authtoken| with admin rights to
1832 the specified repository.
1836 the specified repository.
1833
1837
1834 This command takes the following options:
1838 This command takes the following options:
1835
1839
1836 :param apiuser: This is filled automatically from the |authtoken|.
1840 :param apiuser: This is filled automatically from the |authtoken|.
1837 :type apiuser: AuthUser
1841 :type apiuser: AuthUser
1838 :param repoid: The repository name or repository ID.
1842 :param repoid: The repository name or repository ID.
1839 :type repoid: str or int
1843 :type repoid: str or int
1840 :param revision: The revision you wish to strip.
1844 :param revision: The revision you wish to strip.
1841 :type revision: str
1845 :type revision: str
1842 :param branch: The branch from which to strip the revision.
1846 :param branch: The branch from which to strip the revision.
1843 :type branch: str
1847 :type branch: str
1844
1848
1845 Example output:
1849 Example output:
1846
1850
1847 .. code-block:: bash
1851 .. code-block:: bash
1848
1852
1849 id : <id_given_in_input>
1853 id : <id_given_in_input>
1850 result : {
1854 result : {
1851 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1855 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1852 "repository": "<repository name>"
1856 "repository": "<repository name>"
1853 }
1857 }
1854 error : null
1858 error : null
1855
1859
1856 Example error output:
1860 Example error output:
1857
1861
1858 .. code-block:: bash
1862 .. code-block:: bash
1859
1863
1860 id : <id_given_in_input>
1864 id : <id_given_in_input>
1861 result : null
1865 result : null
1862 error : {
1866 error : {
1863 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1867 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1864 }
1868 }
1865
1869
1866 """
1870 """
1867
1871
1868 repo = get_repo_or_error(repoid)
1872 repo = get_repo_or_error(repoid)
1869 if not has_superadmin_permission(apiuser):
1873 if not has_superadmin_permission(apiuser):
1870 _perms = ('repository.admin',)
1874 _perms = ('repository.admin',)
1871 validate_repo_permissions(apiuser, repoid, repo, _perms)
1875 validate_repo_permissions(apiuser, repoid, repo, _perms)
1872
1876
1873 try:
1877 try:
1874 ScmModel().strip(repo, revision, branch)
1878 ScmModel().strip(repo, revision, branch)
1875 audit_logger.store_api(
1879 audit_logger.store_api(
1876 'repo.commit.strip', action_data={'commit_id': revision},
1880 'repo.commit.strip', action_data={'commit_id': revision},
1877 repo=repo,
1881 repo=repo,
1878 user=apiuser, commit=True)
1882 user=apiuser, commit=True)
1879
1883
1880 return {
1884 return {
1881 'msg': 'Stripped commit %s from repo `%s`' % (
1885 'msg': 'Stripped commit %s from repo `%s`' % (
1882 revision, repo.repo_name),
1886 revision, repo.repo_name),
1883 'repository': repo.repo_name
1887 'repository': repo.repo_name
1884 }
1888 }
1885 except Exception:
1889 except Exception:
1886 log.exception("Exception while trying to strip")
1890 log.exception("Exception while trying to strip")
1887 raise JSONRPCError(
1891 raise JSONRPCError(
1888 'Unable to strip commit %s from repo `%s`' % (
1892 'Unable to strip commit %s from repo `%s`' % (
1889 revision, repo.repo_name)
1893 revision, repo.repo_name)
1890 )
1894 )
1891
1895
1892
1896
1893 @jsonrpc_method()
1897 @jsonrpc_method()
1894 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1898 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1895 """
1899 """
1896 Returns all settings for a repository. If key is given it only returns the
1900 Returns all settings for a repository. If key is given it only returns the
1897 setting identified by the key or null.
1901 setting identified by the key or null.
1898
1902
1899 :param apiuser: This is filled automatically from the |authtoken|.
1903 :param apiuser: This is filled automatically from the |authtoken|.
1900 :type apiuser: AuthUser
1904 :type apiuser: AuthUser
1901 :param repoid: The repository name or repository id.
1905 :param repoid: The repository name or repository id.
1902 :type repoid: str or int
1906 :type repoid: str or int
1903 :param key: Key of the setting to return.
1907 :param key: Key of the setting to return.
1904 :type: key: Optional(str)
1908 :type: key: Optional(str)
1905
1909
1906 Example output:
1910 Example output:
1907
1911
1908 .. code-block:: bash
1912 .. code-block:: bash
1909
1913
1910 {
1914 {
1911 "error": null,
1915 "error": null,
1912 "id": 237,
1916 "id": 237,
1913 "result": {
1917 "result": {
1914 "extensions_largefiles": true,
1918 "extensions_largefiles": true,
1915 "extensions_evolve": true,
1919 "extensions_evolve": true,
1916 "hooks_changegroup_push_logger": true,
1920 "hooks_changegroup_push_logger": true,
1917 "hooks_changegroup_repo_size": false,
1921 "hooks_changegroup_repo_size": false,
1918 "hooks_outgoing_pull_logger": true,
1922 "hooks_outgoing_pull_logger": true,
1919 "phases_publish": "True",
1923 "phases_publish": "True",
1920 "rhodecode_hg_use_rebase_for_merging": true,
1924 "rhodecode_hg_use_rebase_for_merging": true,
1921 "rhodecode_pr_merge_enabled": true,
1925 "rhodecode_pr_merge_enabled": true,
1922 "rhodecode_use_outdated_comments": true
1926 "rhodecode_use_outdated_comments": true
1923 }
1927 }
1924 }
1928 }
1925 """
1929 """
1926
1930
1927 # Restrict access to this api method to admins only.
1931 # Restrict access to this api method to admins only.
1928 if not has_superadmin_permission(apiuser):
1932 if not has_superadmin_permission(apiuser):
1929 raise JSONRPCForbidden()
1933 raise JSONRPCForbidden()
1930
1934
1931 try:
1935 try:
1932 repo = get_repo_or_error(repoid)
1936 repo = get_repo_or_error(repoid)
1933 settings_model = VcsSettingsModel(repo=repo)
1937 settings_model = VcsSettingsModel(repo=repo)
1934 settings = settings_model.get_global_settings()
1938 settings = settings_model.get_global_settings()
1935 settings.update(settings_model.get_repo_settings())
1939 settings.update(settings_model.get_repo_settings())
1936
1940
1937 # If only a single setting is requested fetch it from all settings.
1941 # If only a single setting is requested fetch it from all settings.
1938 key = Optional.extract(key)
1942 key = Optional.extract(key)
1939 if key is not None:
1943 if key is not None:
1940 settings = settings.get(key, None)
1944 settings = settings.get(key, None)
1941 except Exception:
1945 except Exception:
1942 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1946 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1943 log.exception(msg)
1947 log.exception(msg)
1944 raise JSONRPCError(msg)
1948 raise JSONRPCError(msg)
1945
1949
1946 return settings
1950 return settings
1947
1951
1948
1952
1949 @jsonrpc_method()
1953 @jsonrpc_method()
1950 def set_repo_settings(request, apiuser, repoid, settings):
1954 def set_repo_settings(request, apiuser, repoid, settings):
1951 """
1955 """
1952 Update repository settings. Returns true on success.
1956 Update repository settings. Returns true on success.
1953
1957
1954 :param apiuser: This is filled automatically from the |authtoken|.
1958 :param apiuser: This is filled automatically from the |authtoken|.
1955 :type apiuser: AuthUser
1959 :type apiuser: AuthUser
1956 :param repoid: The repository name or repository id.
1960 :param repoid: The repository name or repository id.
1957 :type repoid: str or int
1961 :type repoid: str or int
1958 :param settings: The new settings for the repository.
1962 :param settings: The new settings for the repository.
1959 :type: settings: dict
1963 :type: settings: dict
1960
1964
1961 Example output:
1965 Example output:
1962
1966
1963 .. code-block:: bash
1967 .. code-block:: bash
1964
1968
1965 {
1969 {
1966 "error": null,
1970 "error": null,
1967 "id": 237,
1971 "id": 237,
1968 "result": true
1972 "result": true
1969 }
1973 }
1970 """
1974 """
1971 # Restrict access to this api method to admins only.
1975 # Restrict access to this api method to admins only.
1972 if not has_superadmin_permission(apiuser):
1976 if not has_superadmin_permission(apiuser):
1973 raise JSONRPCForbidden()
1977 raise JSONRPCForbidden()
1974
1978
1975 if type(settings) is not dict:
1979 if type(settings) is not dict:
1976 raise JSONRPCError('Settings have to be a JSON Object.')
1980 raise JSONRPCError('Settings have to be a JSON Object.')
1977
1981
1978 try:
1982 try:
1979 settings_model = VcsSettingsModel(repo=repoid)
1983 settings_model = VcsSettingsModel(repo=repoid)
1980
1984
1981 # Merge global, repo and incoming settings.
1985 # Merge global, repo and incoming settings.
1982 new_settings = settings_model.get_global_settings()
1986 new_settings = settings_model.get_global_settings()
1983 new_settings.update(settings_model.get_repo_settings())
1987 new_settings.update(settings_model.get_repo_settings())
1984 new_settings.update(settings)
1988 new_settings.update(settings)
1985
1989
1986 # Update the settings.
1990 # Update the settings.
1987 inherit_global_settings = new_settings.get(
1991 inherit_global_settings = new_settings.get(
1988 'inherit_global_settings', False)
1992 'inherit_global_settings', False)
1989 settings_model.create_or_update_repo_settings(
1993 settings_model.create_or_update_repo_settings(
1990 new_settings, inherit_global_settings=inherit_global_settings)
1994 new_settings, inherit_global_settings=inherit_global_settings)
1991 Session().commit()
1995 Session().commit()
1992 except Exception:
1996 except Exception:
1993 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1997 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1994 log.exception(msg)
1998 log.exception(msg)
1995 raise JSONRPCError(msg)
1999 raise JSONRPCError(msg)
1996
2000
1997 # Indicate success.
2001 # Indicate success.
1998 return True
2002 return True
1999
2003
2000
2004
2001 @jsonrpc_method()
2005 @jsonrpc_method()
2002 def maintenance(request, apiuser, repoid):
2006 def maintenance(request, apiuser, repoid):
2003 """
2007 """
2004 Triggers a maintenance on the given repository.
2008 Triggers a maintenance on the given repository.
2005
2009
2006 This command can only be run using an |authtoken| with admin
2010 This command can only be run using an |authtoken| with admin
2007 rights to the specified repository. For more information,
2011 rights to the specified repository. For more information,
2008 see :ref:`config-token-ref`.
2012 see :ref:`config-token-ref`.
2009
2013
2010 This command takes the following options:
2014 This command takes the following options:
2011
2015
2012 :param apiuser: This is filled automatically from the |authtoken|.
2016 :param apiuser: This is filled automatically from the |authtoken|.
2013 :type apiuser: AuthUser
2017 :type apiuser: AuthUser
2014 :param repoid: The repository name or repository ID.
2018 :param repoid: The repository name or repository ID.
2015 :type repoid: str or int
2019 :type repoid: str or int
2016
2020
2017 Example output:
2021 Example output:
2018
2022
2019 .. code-block:: bash
2023 .. code-block:: bash
2020
2024
2021 id : <id_given_in_input>
2025 id : <id_given_in_input>
2022 result : {
2026 result : {
2023 "msg": "executed maintenance command",
2027 "msg": "executed maintenance command",
2024 "executed_actions": [
2028 "executed_actions": [
2025 <action_message>, <action_message2>...
2029 <action_message>, <action_message2>...
2026 ],
2030 ],
2027 "repository": "<repository name>"
2031 "repository": "<repository name>"
2028 }
2032 }
2029 error : null
2033 error : null
2030
2034
2031 Example error output:
2035 Example error output:
2032
2036
2033 .. code-block:: bash
2037 .. code-block:: bash
2034
2038
2035 id : <id_given_in_input>
2039 id : <id_given_in_input>
2036 result : null
2040 result : null
2037 error : {
2041 error : {
2038 "Unable to execute maintenance on `<reponame>`"
2042 "Unable to execute maintenance on `<reponame>`"
2039 }
2043 }
2040
2044
2041 """
2045 """
2042
2046
2043 repo = get_repo_or_error(repoid)
2047 repo = get_repo_or_error(repoid)
2044 if not has_superadmin_permission(apiuser):
2048 if not has_superadmin_permission(apiuser):
2045 _perms = ('repository.admin',)
2049 _perms = ('repository.admin',)
2046 validate_repo_permissions(apiuser, repoid, repo, _perms)
2050 validate_repo_permissions(apiuser, repoid, repo, _perms)
2047
2051
2048 try:
2052 try:
2049 maintenance = repo_maintenance.RepoMaintenance()
2053 maintenance = repo_maintenance.RepoMaintenance()
2050 executed_actions = maintenance.execute(repo)
2054 executed_actions = maintenance.execute(repo)
2051
2055
2052 return {
2056 return {
2053 'msg': 'executed maintenance command',
2057 'msg': 'executed maintenance command',
2054 'executed_actions': executed_actions,
2058 'executed_actions': executed_actions,
2055 'repository': repo.repo_name
2059 'repository': repo.repo_name
2056 }
2060 }
2057 except Exception:
2061 except Exception:
2058 log.exception("Exception occurred while trying to run maintenance")
2062 log.exception("Exception occurred while trying to run maintenance")
2059 raise JSONRPCError(
2063 raise JSONRPCError(
2060 'Unable to execute maintenance on `%s`' % repo.repo_name)
2064 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,430 +1,430 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_sync_uri_validator(node, kw):
69 def deferred_sync_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_sync_uri_validator,
323 preparers=[preparers.strip_preparer],
323 preparers=[preparers.strip_preparer],
324 missing='')
324 missing='')
325
325
326 repo_push_uri = colander.SchemaNode(
326 repo_push_uri = colander.SchemaNode(
327 colander.String(),
327 colander.String(),
328 validator=colander.All(colander.Length(min=1)),
328 validator=deferred_sync_uri_validator,
329 preparers=[preparers.strip_preparer],
329 preparers=[preparers.strip_preparer],
330 missing='')
330 missing='')
331
331
332 repo_fork_of = colander.SchemaNode(
332 repo_fork_of = colander.SchemaNode(
333 colander.String(),
333 colander.String(),
334 validator=deferred_fork_of_validator,
334 validator=deferred_fork_of_validator,
335 missing=None)
335 missing=None)
336
336
337 repo_private = colander.SchemaNode(
337 repo_private = colander.SchemaNode(
338 types.StringBooleanType(),
338 types.StringBooleanType(),
339 missing=False, widget=deform.widget.CheckboxWidget())
339 missing=False, widget=deform.widget.CheckboxWidget())
340 repo_copy_permissions = colander.SchemaNode(
340 repo_copy_permissions = 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_statistics = colander.SchemaNode(
343 repo_enable_statistics = colander.SchemaNode(
344 types.StringBooleanType(),
344 types.StringBooleanType(),
345 missing=False, widget=deform.widget.CheckboxWidget())
345 missing=False, widget=deform.widget.CheckboxWidget())
346 repo_enable_downloads = colander.SchemaNode(
346 repo_enable_downloads = colander.SchemaNode(
347 types.StringBooleanType(),
347 types.StringBooleanType(),
348 missing=False, widget=deform.widget.CheckboxWidget())
348 missing=False, widget=deform.widget.CheckboxWidget())
349 repo_enable_locking = colander.SchemaNode(
349 repo_enable_locking = colander.SchemaNode(
350 types.StringBooleanType(),
350 types.StringBooleanType(),
351 missing=False, widget=deform.widget.CheckboxWidget())
351 missing=False, widget=deform.widget.CheckboxWidget())
352
352
353 def deserialize(self, cstruct):
353 def deserialize(self, cstruct):
354 """
354 """
355 Custom deserialize that allows to chain validation, and verify
355 Custom deserialize that allows to chain validation, and verify
356 permissions, and as last step uniqueness
356 permissions, and as last step uniqueness
357 """
357 """
358
358
359 # first pass, to validate given data
359 # first pass, to validate given data
360 appstruct = super(RepoSchema, self).deserialize(cstruct)
360 appstruct = super(RepoSchema, self).deserialize(cstruct)
361 validated_name = appstruct['repo_name']
361 validated_name = appstruct['repo_name']
362
362
363 # second pass to validate permissions to repo_group
363 # second pass to validate permissions to repo_group
364 second = RepoGroupAccessSchema().bind(**self.bindings)
364 second = RepoGroupAccessSchema().bind(**self.bindings)
365 appstruct_second = second.deserialize({'repo_group': validated_name})
365 appstruct_second = second.deserialize({'repo_group': validated_name})
366 # save result
366 # save result
367 appstruct['repo_group'] = appstruct_second['repo_group']
367 appstruct['repo_group'] = appstruct_second['repo_group']
368
368
369 # thirds to validate uniqueness
369 # thirds to validate uniqueness
370 third = RepoNameUniqueSchema().bind(**self.bindings)
370 third = RepoNameUniqueSchema().bind(**self.bindings)
371 third.deserialize({'unique_repo_name': validated_name})
371 third.deserialize({'unique_repo_name': validated_name})
372
372
373 return appstruct
373 return appstruct
374
374
375
375
376 class RepoSettingsSchema(RepoSchema):
376 class RepoSettingsSchema(RepoSchema):
377 repo_group = colander.SchemaNode(
377 repo_group = colander.SchemaNode(
378 colander.Integer(),
378 colander.Integer(),
379 validator=deferred_repo_group_validator,
379 validator=deferred_repo_group_validator,
380 widget=deferred_repo_group_widget,
380 widget=deferred_repo_group_widget,
381 missing='')
381 missing='')
382
382
383 repo_clone_uri_change = colander.SchemaNode(
383 repo_clone_uri_change = colander.SchemaNode(
384 colander.String(),
384 colander.String(),
385 missing='NEW')
385 missing='NEW')
386
386
387 repo_clone_uri = colander.SchemaNode(
387 repo_clone_uri = colander.SchemaNode(
388 colander.String(),
388 colander.String(),
389 preparers=[preparers.strip_preparer],
389 preparers=[preparers.strip_preparer],
390 validator=deferred_sync_uri_validator,
390 validator=deferred_sync_uri_validator,
391 missing='')
391 missing='')
392
392
393 repo_push_uri_change = colander.SchemaNode(
393 repo_push_uri_change = colander.SchemaNode(
394 colander.String(),
394 colander.String(),
395 missing='NEW')
395 missing='NEW')
396
396
397 repo_push_uri = colander.SchemaNode(
397 repo_push_uri = colander.SchemaNode(
398 colander.String(),
398 colander.String(),
399 preparers=[preparers.strip_preparer],
399 preparers=[preparers.strip_preparer],
400 validator=deferred_sync_uri_validator,
400 validator=deferred_sync_uri_validator,
401 missing='')
401 missing='')
402
402
403 def deserialize(self, cstruct):
403 def deserialize(self, cstruct):
404 """
404 """
405 Custom deserialize that allows to chain validation, and verify
405 Custom deserialize that allows to chain validation, and verify
406 permissions, and as last step uniqueness
406 permissions, and as last step uniqueness
407 """
407 """
408
408
409 # first pass, to validate given data
409 # first pass, to validate given data
410 appstruct = super(RepoSchema, self).deserialize(cstruct)
410 appstruct = super(RepoSchema, self).deserialize(cstruct)
411 validated_name = appstruct['repo_name']
411 validated_name = appstruct['repo_name']
412 # because of repoSchema adds repo-group as an ID, we inject it as
412 # because of repoSchema adds repo-group as an ID, we inject it as
413 # full name here because validators require it, it's unwrapped later
413 # full name here because validators require it, it's unwrapped later
414 # so it's safe to use and final name is going to be without group anyway
414 # so it's safe to use and final name is going to be without group anyway
415
415
416 group, separator = get_repo_group(appstruct['repo_group'])
416 group, separator = get_repo_group(appstruct['repo_group'])
417 if group:
417 if group:
418 validated_name = separator.join([group.group_name, validated_name])
418 validated_name = separator.join([group.group_name, validated_name])
419
419
420 # second pass to validate permissions to repo_group
420 # second pass to validate permissions to repo_group
421 second = RepoGroupAccessSchema().bind(**self.bindings)
421 second = RepoGroupAccessSchema().bind(**self.bindings)
422 appstruct_second = second.deserialize({'repo_group': validated_name})
422 appstruct_second = second.deserialize({'repo_group': validated_name})
423 # save result
423 # save result
424 appstruct['repo_group'] = appstruct_second['repo_group']
424 appstruct['repo_group'] = appstruct_second['repo_group']
425
425
426 # thirds to validate uniqueness
426 # thirds to validate uniqueness
427 third = RepoNameUniqueSchema().bind(**self.bindings)
427 third = RepoNameUniqueSchema().bind(**self.bindings)
428 third.deserialize({'unique_repo_name': validated_name})
428 third.deserialize({'unique_repo_name': validated_name})
429
429
430 return appstruct
430 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