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