##// END OF EJS Templates
permissions: properly flush user cache permissions in more cases of permission changes....
marcink -
r3824:49be3910 stable
parent child Browse files
Show More
@@ -1,2315 +1,2320 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.lib.vcs import RepositoryError
39 from rhodecode.lib.vcs import RepositoryError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 ChangesetComment)
45 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.scm import ScmModel, RepoList
48 from rhodecode.model.scm import ScmModel, RepoList
48 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 from rhodecode.model import validation_schema
50 from rhodecode.model import validation_schema
50 from rhodecode.model.validation_schema.schemas import repo_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
51
52
52 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
53
54
54
55
55 @jsonrpc_method()
56 @jsonrpc_method()
56 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 """
58 """
58 Gets an existing repository by its name or repository_id.
59 Gets an existing repository by its name or repository_id.
59
60
60 The members section so the output returns users groups or users
61 The members section so the output returns users groups or users
61 associated with that repository.
62 associated with that repository.
62
63
63 This command can only be run using an |authtoken| with admin rights,
64 This command can only be run using an |authtoken| with admin rights,
64 or users with at least read rights to the |repo|.
65 or users with at least read rights to the |repo|.
65
66
66 :param apiuser: This is filled automatically from the |authtoken|.
67 :param apiuser: This is filled automatically from the |authtoken|.
67 :type apiuser: AuthUser
68 :type apiuser: AuthUser
68 :param repoid: The repository name or repository id.
69 :param repoid: The repository name or repository id.
69 :type repoid: str or int
70 :type repoid: str or int
70 :param cache: use the cached value for last changeset
71 :param cache: use the cached value for last changeset
71 :type: cache: Optional(bool)
72 :type: cache: Optional(bool)
72
73
73 Example output:
74 Example output:
74
75
75 .. code-block:: bash
76 .. code-block:: bash
76
77
77 {
78 {
78 "error": null,
79 "error": null,
79 "id": <repo_id>,
80 "id": <repo_id>,
80 "result": {
81 "result": {
81 "clone_uri": null,
82 "clone_uri": null,
82 "created_on": "timestamp",
83 "created_on": "timestamp",
83 "description": "repo description",
84 "description": "repo description",
84 "enable_downloads": false,
85 "enable_downloads": false,
85 "enable_locking": false,
86 "enable_locking": false,
86 "enable_statistics": false,
87 "enable_statistics": false,
87 "followers": [
88 "followers": [
88 {
89 {
89 "active": true,
90 "active": true,
90 "admin": false,
91 "admin": false,
91 "api_key": "****************************************",
92 "api_key": "****************************************",
92 "api_keys": [
93 "api_keys": [
93 "****************************************"
94 "****************************************"
94 ],
95 ],
95 "email": "user@example.com",
96 "email": "user@example.com",
96 "emails": [
97 "emails": [
97 "user@example.com"
98 "user@example.com"
98 ],
99 ],
99 "extern_name": "rhodecode",
100 "extern_name": "rhodecode",
100 "extern_type": "rhodecode",
101 "extern_type": "rhodecode",
101 "firstname": "username",
102 "firstname": "username",
102 "ip_addresses": [],
103 "ip_addresses": [],
103 "language": null,
104 "language": null,
104 "last_login": "2015-09-16T17:16:35.854",
105 "last_login": "2015-09-16T17:16:35.854",
105 "lastname": "surname",
106 "lastname": "surname",
106 "user_id": <user_id>,
107 "user_id": <user_id>,
107 "username": "name"
108 "username": "name"
108 }
109 }
109 ],
110 ],
110 "fork_of": "parent-repo",
111 "fork_of": "parent-repo",
111 "landing_rev": [
112 "landing_rev": [
112 "rev",
113 "rev",
113 "tip"
114 "tip"
114 ],
115 ],
115 "last_changeset": {
116 "last_changeset": {
116 "author": "User <user@example.com>",
117 "author": "User <user@example.com>",
117 "branch": "default",
118 "branch": "default",
118 "date": "timestamp",
119 "date": "timestamp",
119 "message": "last commit message",
120 "message": "last commit message",
120 "parents": [
121 "parents": [
121 {
122 {
122 "raw_id": "commit-id"
123 "raw_id": "commit-id"
123 }
124 }
124 ],
125 ],
125 "raw_id": "commit-id",
126 "raw_id": "commit-id",
126 "revision": <revision number>,
127 "revision": <revision number>,
127 "short_id": "short id"
128 "short_id": "short id"
128 },
129 },
129 "lock_reason": null,
130 "lock_reason": null,
130 "locked_by": null,
131 "locked_by": null,
131 "locked_date": null,
132 "locked_date": null,
132 "owner": "owner-name",
133 "owner": "owner-name",
133 "permissions": [
134 "permissions": [
134 {
135 {
135 "name": "super-admin-name",
136 "name": "super-admin-name",
136 "origin": "super-admin",
137 "origin": "super-admin",
137 "permission": "repository.admin",
138 "permission": "repository.admin",
138 "type": "user"
139 "type": "user"
139 },
140 },
140 {
141 {
141 "name": "owner-name",
142 "name": "owner-name",
142 "origin": "owner",
143 "origin": "owner",
143 "permission": "repository.admin",
144 "permission": "repository.admin",
144 "type": "user"
145 "type": "user"
145 },
146 },
146 {
147 {
147 "name": "user-group-name",
148 "name": "user-group-name",
148 "origin": "permission",
149 "origin": "permission",
149 "permission": "repository.write",
150 "permission": "repository.write",
150 "type": "user_group"
151 "type": "user_group"
151 }
152 }
152 ],
153 ],
153 "private": true,
154 "private": true,
154 "repo_id": 676,
155 "repo_id": 676,
155 "repo_name": "user-group/repo-name",
156 "repo_name": "user-group/repo-name",
156 "repo_type": "hg"
157 "repo_type": "hg"
157 }
158 }
158 }
159 }
159 """
160 """
160
161
161 repo = get_repo_or_error(repoid)
162 repo = get_repo_or_error(repoid)
162 cache = Optional.extract(cache)
163 cache = Optional.extract(cache)
163
164
164 include_secrets = False
165 include_secrets = False
165 if has_superadmin_permission(apiuser):
166 if has_superadmin_permission(apiuser):
166 include_secrets = True
167 include_secrets = True
167 else:
168 else:
168 # check if we have at least read permission for this repo !
169 # check if we have at least read permission for this repo !
169 _perms = (
170 _perms = (
170 'repository.admin', 'repository.write', 'repository.read',)
171 'repository.admin', 'repository.write', 'repository.read',)
171 validate_repo_permissions(apiuser, repoid, repo, _perms)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
172
173
173 permissions = []
174 permissions = []
174 for _user in repo.permissions():
175 for _user in repo.permissions():
175 user_data = {
176 user_data = {
176 'name': _user.username,
177 'name': _user.username,
177 'permission': _user.permission,
178 'permission': _user.permission,
178 'origin': get_origin(_user),
179 'origin': get_origin(_user),
179 'type': "user",
180 'type': "user",
180 }
181 }
181 permissions.append(user_data)
182 permissions.append(user_data)
182
183
183 for _user_group in repo.permission_user_groups():
184 for _user_group in repo.permission_user_groups():
184 user_group_data = {
185 user_group_data = {
185 'name': _user_group.users_group_name,
186 'name': _user_group.users_group_name,
186 'permission': _user_group.permission,
187 'permission': _user_group.permission,
187 'origin': get_origin(_user_group),
188 'origin': get_origin(_user_group),
188 'type': "user_group",
189 'type': "user_group",
189 }
190 }
190 permissions.append(user_group_data)
191 permissions.append(user_group_data)
191
192
192 following_users = [
193 following_users = [
193 user.user.get_api_data(include_secrets=include_secrets)
194 user.user.get_api_data(include_secrets=include_secrets)
194 for user in repo.followers]
195 for user in repo.followers]
195
196
196 if not cache:
197 if not cache:
197 repo.update_commit_cache()
198 repo.update_commit_cache()
198 data = repo.get_api_data(include_secrets=include_secrets)
199 data = repo.get_api_data(include_secrets=include_secrets)
199 data['permissions'] = permissions
200 data['permissions'] = permissions
200 data['followers'] = following_users
201 data['followers'] = following_users
201 return data
202 return data
202
203
203
204
204 @jsonrpc_method()
205 @jsonrpc_method()
205 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 """
207 """
207 Lists all existing repositories.
208 Lists all existing repositories.
208
209
209 This command can only be run using an |authtoken| with admin rights,
210 This command can only be run using an |authtoken| with admin rights,
210 or users with at least read rights to |repos|.
211 or users with at least read rights to |repos|.
211
212
212 :param apiuser: This is filled automatically from the |authtoken|.
213 :param apiuser: This is filled automatically from the |authtoken|.
213 :type apiuser: AuthUser
214 :type apiuser: AuthUser
214 :param root: specify root repository group to fetch repositories.
215 :param root: specify root repository group to fetch repositories.
215 filters the returned repositories to be members of given root group.
216 filters the returned repositories to be members of given root group.
216 :type root: Optional(None)
217 :type root: Optional(None)
217 :param traverse: traverse given root into subrepositories. With this flag
218 :param traverse: traverse given root into subrepositories. With this flag
218 set to False, it will only return top-level repositories from `root`.
219 set to False, it will only return top-level repositories from `root`.
219 if root is empty it will return just top-level repositories.
220 if root is empty it will return just top-level repositories.
220 :type traverse: Optional(True)
221 :type traverse: Optional(True)
221
222
222
223
223 Example output:
224 Example output:
224
225
225 .. code-block:: bash
226 .. code-block:: bash
226
227
227 id : <id_given_in_input>
228 id : <id_given_in_input>
228 result: [
229 result: [
229 {
230 {
230 "repo_id" : "<repo_id>",
231 "repo_id" : "<repo_id>",
231 "repo_name" : "<reponame>"
232 "repo_name" : "<reponame>"
232 "repo_type" : "<repo_type>",
233 "repo_type" : "<repo_type>",
233 "clone_uri" : "<clone_uri>",
234 "clone_uri" : "<clone_uri>",
234 "private": : "<bool>",
235 "private": : "<bool>",
235 "created_on" : "<datetimecreated>",
236 "created_on" : "<datetimecreated>",
236 "description" : "<description>",
237 "description" : "<description>",
237 "landing_rev": "<landing_rev>",
238 "landing_rev": "<landing_rev>",
238 "owner": "<repo_owner>",
239 "owner": "<repo_owner>",
239 "fork_of": "<name_of_fork_parent>",
240 "fork_of": "<name_of_fork_parent>",
240 "enable_downloads": "<bool>",
241 "enable_downloads": "<bool>",
241 "enable_locking": "<bool>",
242 "enable_locking": "<bool>",
242 "enable_statistics": "<bool>",
243 "enable_statistics": "<bool>",
243 },
244 },
244 ...
245 ...
245 ]
246 ]
246 error: null
247 error: null
247 """
248 """
248
249
249 include_secrets = has_superadmin_permission(apiuser)
250 include_secrets = has_superadmin_permission(apiuser)
250 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 extras = {'user': apiuser}
252 extras = {'user': apiuser}
252
253
253 root = Optional.extract(root)
254 root = Optional.extract(root)
254 traverse = Optional.extract(traverse, binary=True)
255 traverse = Optional.extract(traverse, binary=True)
255
256
256 if root:
257 if root:
257 # verify parent existance, if it's empty return an error
258 # verify parent existance, if it's empty return an error
258 parent = RepoGroup.get_by_group_name(root)
259 parent = RepoGroup.get_by_group_name(root)
259 if not parent:
260 if not parent:
260 raise JSONRPCError(
261 raise JSONRPCError(
261 'Root repository group `{}` does not exist'.format(root))
262 'Root repository group `{}` does not exist'.format(root))
262
263
263 if traverse:
264 if traverse:
264 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 else:
266 else:
266 repos = RepoModel().get_repos_for_root(root=parent)
267 repos = RepoModel().get_repos_for_root(root=parent)
267 else:
268 else:
268 if traverse:
269 if traverse:
269 repos = RepoModel().get_all()
270 repos = RepoModel().get_all()
270 else:
271 else:
271 # return just top-level
272 # return just top-level
272 repos = RepoModel().get_repos_for_root(root=None)
273 repos = RepoModel().get_repos_for_root(root=None)
273
274
274 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 return [repo.get_api_data(include_secrets=include_secrets)
276 return [repo.get_api_data(include_secrets=include_secrets)
276 for repo in repo_list]
277 for repo in repo_list]
277
278
278
279
279 @jsonrpc_method()
280 @jsonrpc_method()
280 def get_repo_changeset(request, apiuser, repoid, revision,
281 def get_repo_changeset(request, apiuser, repoid, revision,
281 details=Optional('basic')):
282 details=Optional('basic')):
282 """
283 """
283 Returns information about a changeset.
284 Returns information about a changeset.
284
285
285 Additionally parameters define the amount of details returned by
286 Additionally parameters define the amount of details returned by
286 this function.
287 this function.
287
288
288 This command can only be run using an |authtoken| with admin rights,
289 This command can only be run using an |authtoken| with admin rights,
289 or users with at least read rights to the |repo|.
290 or users with at least read rights to the |repo|.
290
291
291 :param apiuser: This is filled automatically from the |authtoken|.
292 :param apiuser: This is filled automatically from the |authtoken|.
292 :type apiuser: AuthUser
293 :type apiuser: AuthUser
293 :param repoid: The repository name or repository id
294 :param repoid: The repository name or repository id
294 :type repoid: str or int
295 :type repoid: str or int
295 :param revision: revision for which listing should be done
296 :param revision: revision for which listing should be done
296 :type revision: str
297 :type revision: str
297 :param details: details can be 'basic|extended|full' full gives diff
298 :param details: details can be 'basic|extended|full' full gives diff
298 info details like the diff itself, and number of changed files etc.
299 info details like the diff itself, and number of changed files etc.
299 :type details: Optional(str)
300 :type details: Optional(str)
300
301
301 """
302 """
302 repo = get_repo_or_error(repoid)
303 repo = get_repo_or_error(repoid)
303 if not has_superadmin_permission(apiuser):
304 if not has_superadmin_permission(apiuser):
304 _perms = (
305 _perms = (
305 'repository.admin', 'repository.write', 'repository.read',)
306 'repository.admin', 'repository.write', 'repository.read',)
306 validate_repo_permissions(apiuser, repoid, repo, _perms)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
307
308
308 changes_details = Optional.extract(details)
309 changes_details = Optional.extract(details)
309 _changes_details_types = ['basic', 'extended', 'full']
310 _changes_details_types = ['basic', 'extended', 'full']
310 if changes_details not in _changes_details_types:
311 if changes_details not in _changes_details_types:
311 raise JSONRPCError(
312 raise JSONRPCError(
312 'ret_type must be one of %s' % (
313 'ret_type must be one of %s' % (
313 ','.join(_changes_details_types)))
314 ','.join(_changes_details_types)))
314
315
315 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 'status', '_commit', '_file_paths']
317 'status', '_commit', '_file_paths']
317
318
318 try:
319 try:
319 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 except TypeError as e:
321 except TypeError as e:
321 raise JSONRPCError(safe_str(e))
322 raise JSONRPCError(safe_str(e))
322 _cs_json = cs.__json__()
323 _cs_json = cs.__json__()
323 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 if changes_details == 'full':
325 if changes_details == 'full':
325 _cs_json['refs'] = cs._get_refs()
326 _cs_json['refs'] = cs._get_refs()
326 return _cs_json
327 return _cs_json
327
328
328
329
329 @jsonrpc_method()
330 @jsonrpc_method()
330 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 details=Optional('basic')):
332 details=Optional('basic')):
332 """
333 """
333 Returns a set of commits limited by the number starting
334 Returns a set of commits limited by the number starting
334 from the `start_rev` option.
335 from the `start_rev` option.
335
336
336 Additional parameters define the amount of details returned by this
337 Additional parameters define the amount of details returned by this
337 function.
338 function.
338
339
339 This command can only be run using an |authtoken| with admin rights,
340 This command can only be run using an |authtoken| with admin rights,
340 or users with at least read rights to |repos|.
341 or users with at least read rights to |repos|.
341
342
342 :param apiuser: This is filled automatically from the |authtoken|.
343 :param apiuser: This is filled automatically from the |authtoken|.
343 :type apiuser: AuthUser
344 :type apiuser: AuthUser
344 :param repoid: The repository name or repository ID.
345 :param repoid: The repository name or repository ID.
345 :type repoid: str or int
346 :type repoid: str or int
346 :param start_rev: The starting revision from where to get changesets.
347 :param start_rev: The starting revision from where to get changesets.
347 :type start_rev: str
348 :type start_rev: str
348 :param limit: Limit the number of commits to this amount
349 :param limit: Limit the number of commits to this amount
349 :type limit: str or int
350 :type limit: str or int
350 :param details: Set the level of detail returned. Valid option are:
351 :param details: Set the level of detail returned. Valid option are:
351 ``basic``, ``extended`` and ``full``.
352 ``basic``, ``extended`` and ``full``.
352 :type details: Optional(str)
353 :type details: Optional(str)
353
354
354 .. note::
355 .. note::
355
356
356 Setting the parameter `details` to the value ``full`` is extensive
357 Setting the parameter `details` to the value ``full`` is extensive
357 and returns details like the diff itself, and the number
358 and returns details like the diff itself, and the number
358 of changed files.
359 of changed files.
359
360
360 """
361 """
361 repo = get_repo_or_error(repoid)
362 repo = get_repo_or_error(repoid)
362 if not has_superadmin_permission(apiuser):
363 if not has_superadmin_permission(apiuser):
363 _perms = (
364 _perms = (
364 'repository.admin', 'repository.write', 'repository.read',)
365 'repository.admin', 'repository.write', 'repository.read',)
365 validate_repo_permissions(apiuser, repoid, repo, _perms)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
366
367
367 changes_details = Optional.extract(details)
368 changes_details = Optional.extract(details)
368 _changes_details_types = ['basic', 'extended', 'full']
369 _changes_details_types = ['basic', 'extended', 'full']
369 if changes_details not in _changes_details_types:
370 if changes_details not in _changes_details_types:
370 raise JSONRPCError(
371 raise JSONRPCError(
371 'ret_type must be one of %s' % (
372 'ret_type must be one of %s' % (
372 ','.join(_changes_details_types)))
373 ','.join(_changes_details_types)))
373
374
374 limit = int(limit)
375 limit = int(limit)
375 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 'status', '_commit', '_file_paths']
377 'status', '_commit', '_file_paths']
377
378
378 vcs_repo = repo.scm_instance()
379 vcs_repo = repo.scm_instance()
379 # SVN needs a special case to distinguish its index and commit id
380 # SVN needs a special case to distinguish its index and commit id
380 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 start_rev = vcs_repo.commit_ids[0]
382 start_rev = vcs_repo.commit_ids[0]
382
383
383 try:
384 try:
384 commits = vcs_repo.get_commits(
385 commits = vcs_repo.get_commits(
385 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 except TypeError as e:
387 except TypeError as e:
387 raise JSONRPCError(safe_str(e))
388 raise JSONRPCError(safe_str(e))
388 except Exception:
389 except Exception:
389 log.exception('Fetching of commits failed')
390 log.exception('Fetching of commits failed')
390 raise JSONRPCError('Error occurred during commit fetching')
391 raise JSONRPCError('Error occurred during commit fetching')
391
392
392 ret = []
393 ret = []
393 for cnt, commit in enumerate(commits):
394 for cnt, commit in enumerate(commits):
394 if cnt >= limit != -1:
395 if cnt >= limit != -1:
395 break
396 break
396 _cs_json = commit.__json__()
397 _cs_json = commit.__json__()
397 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 if changes_details == 'full':
399 if changes_details == 'full':
399 _cs_json['refs'] = {
400 _cs_json['refs'] = {
400 'branches': [commit.branch],
401 'branches': [commit.branch],
401 'bookmarks': getattr(commit, 'bookmarks', []),
402 'bookmarks': getattr(commit, 'bookmarks', []),
402 'tags': commit.tags
403 'tags': commit.tags
403 }
404 }
404 ret.append(_cs_json)
405 ret.append(_cs_json)
405 return ret
406 return ret
406
407
407
408
408 @jsonrpc_method()
409 @jsonrpc_method()
409 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 ret_type=Optional('all'), details=Optional('basic'),
411 ret_type=Optional('all'), details=Optional('basic'),
411 max_file_bytes=Optional(None)):
412 max_file_bytes=Optional(None)):
412 """
413 """
413 Returns a list of nodes and children in a flat list for a given
414 Returns a list of nodes and children in a flat list for a given
414 path at given revision.
415 path at given revision.
415
416
416 It's possible to specify ret_type to show only `files` or `dirs`.
417 It's possible to specify ret_type to show only `files` or `dirs`.
417
418
418 This command can only be run using an |authtoken| with admin rights,
419 This command can only be run using an |authtoken| with admin rights,
419 or users with at least read rights to |repos|.
420 or users with at least read rights to |repos|.
420
421
421 :param apiuser: This is filled automatically from the |authtoken|.
422 :param apiuser: This is filled automatically from the |authtoken|.
422 :type apiuser: AuthUser
423 :type apiuser: AuthUser
423 :param repoid: The repository name or repository ID.
424 :param repoid: The repository name or repository ID.
424 :type repoid: str or int
425 :type repoid: str or int
425 :param revision: The revision for which listing should be done.
426 :param revision: The revision for which listing should be done.
426 :type revision: str
427 :type revision: str
427 :param root_path: The path from which to start displaying.
428 :param root_path: The path from which to start displaying.
428 :type root_path: str
429 :type root_path: str
429 :param ret_type: Set the return type. Valid options are
430 :param ret_type: Set the return type. Valid options are
430 ``all`` (default), ``files`` and ``dirs``.
431 ``all`` (default), ``files`` and ``dirs``.
431 :type ret_type: Optional(str)
432 :type ret_type: Optional(str)
432 :param details: Returns extended information about nodes, such as
433 :param details: Returns extended information about nodes, such as
433 md5, binary, and or content.
434 md5, binary, and or content.
434 The valid options are ``basic`` and ``full``.
435 The valid options are ``basic`` and ``full``.
435 :type details: Optional(str)
436 :type details: Optional(str)
436 :param max_file_bytes: Only return file content under this file size bytes
437 :param max_file_bytes: Only return file content under this file size bytes
437 :type details: Optional(int)
438 :type details: Optional(int)
438
439
439 Example output:
440 Example output:
440
441
441 .. code-block:: bash
442 .. code-block:: bash
442
443
443 id : <id_given_in_input>
444 id : <id_given_in_input>
444 result: [
445 result: [
445 {
446 {
446 "binary": false,
447 "binary": false,
447 "content": "File line",
448 "content": "File line",
448 "extension": "md",
449 "extension": "md",
449 "lines": 2,
450 "lines": 2,
450 "md5": "059fa5d29b19c0657e384749480f6422",
451 "md5": "059fa5d29b19c0657e384749480f6422",
451 "mimetype": "text/x-minidsrc",
452 "mimetype": "text/x-minidsrc",
452 "name": "file.md",
453 "name": "file.md",
453 "size": 580,
454 "size": 580,
454 "type": "file"
455 "type": "file"
455 },
456 },
456 ...
457 ...
457 ]
458 ]
458 error: null
459 error: null
459 """
460 """
460
461
461 repo = get_repo_or_error(repoid)
462 repo = get_repo_or_error(repoid)
462 if not has_superadmin_permission(apiuser):
463 if not has_superadmin_permission(apiuser):
463 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 validate_repo_permissions(apiuser, repoid, repo, _perms)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
465
466
466 ret_type = Optional.extract(ret_type)
467 ret_type = Optional.extract(ret_type)
467 details = Optional.extract(details)
468 details = Optional.extract(details)
468 _extended_types = ['basic', 'full']
469 _extended_types = ['basic', 'full']
469 if details not in _extended_types:
470 if details not in _extended_types:
470 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 extended_info = False
472 extended_info = False
472 content = False
473 content = False
473 if details == 'basic':
474 if details == 'basic':
474 extended_info = True
475 extended_info = True
475
476
476 if details == 'full':
477 if details == 'full':
477 extended_info = content = True
478 extended_info = content = True
478
479
479 _map = {}
480 _map = {}
480 try:
481 try:
481 # check if repo is not empty by any chance, skip quicker if it is.
482 # check if repo is not empty by any chance, skip quicker if it is.
482 _scm = repo.scm_instance()
483 _scm = repo.scm_instance()
483 if _scm.is_empty():
484 if _scm.is_empty():
484 return []
485 return []
485
486
486 _d, _f = ScmModel().get_nodes(
487 _d, _f = ScmModel().get_nodes(
487 repo, revision, root_path, flat=False,
488 repo, revision, root_path, flat=False,
488 extended_info=extended_info, content=content,
489 extended_info=extended_info, content=content,
489 max_file_bytes=max_file_bytes)
490 max_file_bytes=max_file_bytes)
490 _map = {
491 _map = {
491 'all': _d + _f,
492 'all': _d + _f,
492 'files': _f,
493 'files': _f,
493 'dirs': _d,
494 'dirs': _d,
494 }
495 }
495 return _map[ret_type]
496 return _map[ret_type]
496 except KeyError:
497 except KeyError:
497 raise JSONRPCError(
498 raise JSONRPCError(
498 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 except Exception:
500 except Exception:
500 log.exception("Exception occurred while trying to get repo nodes")
501 log.exception("Exception occurred while trying to get repo nodes")
501 raise JSONRPCError(
502 raise JSONRPCError(
502 'failed to get repo: `%s` nodes' % repo.repo_name
503 'failed to get repo: `%s` nodes' % repo.repo_name
503 )
504 )
504
505
505
506
506 @jsonrpc_method()
507 @jsonrpc_method()
507 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 max_file_bytes=Optional(None), details=Optional('basic'),
509 max_file_bytes=Optional(None), details=Optional('basic'),
509 cache=Optional(True)):
510 cache=Optional(True)):
510 """
511 """
511 Returns a single file from repository at given revision.
512 Returns a single file from repository at given revision.
512
513
513 This command can only be run using an |authtoken| with admin rights,
514 This command can only be run using an |authtoken| with admin rights,
514 or users with at least read rights to |repos|.
515 or users with at least read rights to |repos|.
515
516
516 :param apiuser: This is filled automatically from the |authtoken|.
517 :param apiuser: This is filled automatically from the |authtoken|.
517 :type apiuser: AuthUser
518 :type apiuser: AuthUser
518 :param repoid: The repository name or repository ID.
519 :param repoid: The repository name or repository ID.
519 :type repoid: str or int
520 :type repoid: str or int
520 :param commit_id: The revision for which listing should be done.
521 :param commit_id: The revision for which listing should be done.
521 :type commit_id: str
522 :type commit_id: str
522 :param file_path: The path from which to start displaying.
523 :param file_path: The path from which to start displaying.
523 :type file_path: str
524 :type file_path: str
524 :param details: Returns different set of information about nodes.
525 :param details: Returns different set of information about nodes.
525 The valid options are ``minimal`` ``basic`` and ``full``.
526 The valid options are ``minimal`` ``basic`` and ``full``.
526 :type details: Optional(str)
527 :type details: Optional(str)
527 :param max_file_bytes: Only return file content under this file size bytes
528 :param max_file_bytes: Only return file content under this file size bytes
528 :type max_file_bytes: Optional(int)
529 :type max_file_bytes: Optional(int)
529 :param cache: Use internal caches for fetching files. If disabled fetching
530 :param cache: Use internal caches for fetching files. If disabled fetching
530 files is slower but more memory efficient
531 files is slower but more memory efficient
531 :type cache: Optional(bool)
532 :type cache: Optional(bool)
532
533
533 Example output:
534 Example output:
534
535
535 .. code-block:: bash
536 .. code-block:: bash
536
537
537 id : <id_given_in_input>
538 id : <id_given_in_input>
538 result: {
539 result: {
539 "binary": false,
540 "binary": false,
540 "extension": "py",
541 "extension": "py",
541 "lines": 35,
542 "lines": 35,
542 "content": "....",
543 "content": "....",
543 "md5": "76318336366b0f17ee249e11b0c99c41",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
544 "mimetype": "text/x-python",
545 "mimetype": "text/x-python",
545 "name": "python.py",
546 "name": "python.py",
546 "size": 817,
547 "size": 817,
547 "type": "file",
548 "type": "file",
548 }
549 }
549 error: null
550 error: null
550 """
551 """
551
552
552 repo = get_repo_or_error(repoid)
553 repo = get_repo_or_error(repoid)
553 if not has_superadmin_permission(apiuser):
554 if not has_superadmin_permission(apiuser):
554 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 validate_repo_permissions(apiuser, repoid, repo, _perms)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
556
557
557 cache = Optional.extract(cache, binary=True)
558 cache = Optional.extract(cache, binary=True)
558 details = Optional.extract(details)
559 details = Optional.extract(details)
559 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 if details not in _extended_types:
561 if details not in _extended_types:
561 raise JSONRPCError(
562 raise JSONRPCError(
562 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 extended_info = False
564 extended_info = False
564 content = False
565 content = False
565
566
566 if details == 'minimal':
567 if details == 'minimal':
567 extended_info = False
568 extended_info = False
568
569
569 elif details == 'basic':
570 elif details == 'basic':
570 extended_info = True
571 extended_info = True
571
572
572 elif details == 'full':
573 elif details == 'full':
573 extended_info = content = True
574 extended_info = content = True
574
575
575 try:
576 try:
576 # check if repo is not empty by any chance, skip quicker if it is.
577 # check if repo is not empty by any chance, skip quicker if it is.
577 _scm = repo.scm_instance()
578 _scm = repo.scm_instance()
578 if _scm.is_empty():
579 if _scm.is_empty():
579 return None
580 return None
580
581
581 node = ScmModel().get_node(
582 node = ScmModel().get_node(
582 repo, commit_id, file_path, extended_info=extended_info,
583 repo, commit_id, file_path, extended_info=extended_info,
583 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 except NodeDoesNotExistError:
585 except NodeDoesNotExistError:
585 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 repo.repo_name, file_path, commit_id))
587 repo.repo_name, file_path, commit_id))
587 except Exception:
588 except Exception:
588 log.exception("Exception occurred while trying to get repo %s file",
589 log.exception("Exception occurred while trying to get repo %s file",
589 repo.repo_name)
590 repo.repo_name)
590 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 repo.repo_name, file_path))
592 repo.repo_name, file_path))
592
593
593 return node
594 return node
594
595
595
596
596 @jsonrpc_method()
597 @jsonrpc_method()
597 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 """
599 """
599 Returns a list of tree nodes for path at given revision. This api is built
600 Returns a list of tree nodes for path at given revision. This api is built
600 strictly for usage in full text search building, and shouldn't be consumed
601 strictly for usage in full text search building, and shouldn't be consumed
601
602
602 This command can only be run using an |authtoken| with admin rights,
603 This command can only be run using an |authtoken| with admin rights,
603 or users with at least read rights to |repos|.
604 or users with at least read rights to |repos|.
604
605
605 """
606 """
606
607
607 repo = get_repo_or_error(repoid)
608 repo = get_repo_or_error(repoid)
608 if not has_superadmin_permission(apiuser):
609 if not has_superadmin_permission(apiuser):
609 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 validate_repo_permissions(apiuser, repoid, repo, _perms)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
611
612
612 repo_id = repo.repo_id
613 repo_id = repo.repo_id
613 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 cache_on = cache_seconds > 0
615 cache_on = cache_seconds > 0
615
616
616 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618
619
619 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
620 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
620 condition=cache_on)
621 condition=cache_on)
621 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
622 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
622 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
623 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
623
624
624 try:
625 try:
625 # check if repo is not empty by any chance, skip quicker if it is.
626 # check if repo is not empty by any chance, skip quicker if it is.
626 _scm = repo.scm_instance()
627 _scm = repo.scm_instance()
627 if _scm.is_empty():
628 if _scm.is_empty():
628 return []
629 return []
629 except RepositoryError:
630 except RepositoryError:
630 log.exception("Exception occurred while trying to get repo nodes")
631 log.exception("Exception occurred while trying to get repo nodes")
631 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
632 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
632
633
633 try:
634 try:
634 # we need to resolve commit_id to a FULL sha for cache to work correctly.
635 # we need to resolve commit_id to a FULL sha for cache to work correctly.
635 # sending 'master' is a pointer that needs to be translated to current commit.
636 # sending 'master' is a pointer that needs to be translated to current commit.
636 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
637 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
637 log.debug(
638 log.debug(
638 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
639 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
639 'with caching: %s[TTL: %ss]' % (
640 'with caching: %s[TTL: %ss]' % (
640 repo_id, commit_id, cache_on, cache_seconds or 0))
641 repo_id, commit_id, cache_on, cache_seconds or 0))
641
642
642 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
643 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
643 return tree_files
644 return tree_files
644
645
645 except Exception:
646 except Exception:
646 log.exception("Exception occurred while trying to get repo nodes")
647 log.exception("Exception occurred while trying to get repo nodes")
647 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
648 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
648
649
649
650
650 @jsonrpc_method()
651 @jsonrpc_method()
651 def get_repo_refs(request, apiuser, repoid):
652 def get_repo_refs(request, apiuser, repoid):
652 """
653 """
653 Returns a dictionary of current references. It returns
654 Returns a dictionary of current references. It returns
654 bookmarks, branches, closed_branches, and tags for given repository
655 bookmarks, branches, closed_branches, and tags for given repository
655
656
656 It's possible to specify ret_type to show only `files` or `dirs`.
657 It's possible to specify ret_type to show only `files` or `dirs`.
657
658
658 This command can only be run using an |authtoken| with admin rights,
659 This command can only be run using an |authtoken| with admin rights,
659 or users with at least read rights to |repos|.
660 or users with at least read rights to |repos|.
660
661
661 :param apiuser: This is filled automatically from the |authtoken|.
662 :param apiuser: This is filled automatically from the |authtoken|.
662 :type apiuser: AuthUser
663 :type apiuser: AuthUser
663 :param repoid: The repository name or repository ID.
664 :param repoid: The repository name or repository ID.
664 :type repoid: str or int
665 :type repoid: str or int
665
666
666 Example output:
667 Example output:
667
668
668 .. code-block:: bash
669 .. code-block:: bash
669
670
670 id : <id_given_in_input>
671 id : <id_given_in_input>
671 "result": {
672 "result": {
672 "bookmarks": {
673 "bookmarks": {
673 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
674 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
674 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
675 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
675 },
676 },
676 "branches": {
677 "branches": {
677 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
678 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
679 },
680 },
680 "branches_closed": {},
681 "branches_closed": {},
681 "tags": {
682 "tags": {
682 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
683 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
684 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
684 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
685 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
685 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
686 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
686 }
687 }
687 }
688 }
688 error: null
689 error: null
689 """
690 """
690
691
691 repo = get_repo_or_error(repoid)
692 repo = get_repo_or_error(repoid)
692 if not has_superadmin_permission(apiuser):
693 if not has_superadmin_permission(apiuser):
693 _perms = ('repository.admin', 'repository.write', 'repository.read',)
694 _perms = ('repository.admin', 'repository.write', 'repository.read',)
694 validate_repo_permissions(apiuser, repoid, repo, _perms)
695 validate_repo_permissions(apiuser, repoid, repo, _perms)
695
696
696 try:
697 try:
697 # check if repo is not empty by any chance, skip quicker if it is.
698 # check if repo is not empty by any chance, skip quicker if it is.
698 vcs_instance = repo.scm_instance()
699 vcs_instance = repo.scm_instance()
699 refs = vcs_instance.refs()
700 refs = vcs_instance.refs()
700 return refs
701 return refs
701 except Exception:
702 except Exception:
702 log.exception("Exception occurred while trying to get repo refs")
703 log.exception("Exception occurred while trying to get repo refs")
703 raise JSONRPCError(
704 raise JSONRPCError(
704 'failed to get repo: `%s` references' % repo.repo_name
705 'failed to get repo: `%s` references' % repo.repo_name
705 )
706 )
706
707
707
708
708 @jsonrpc_method()
709 @jsonrpc_method()
709 def create_repo(
710 def create_repo(
710 request, apiuser, repo_name, repo_type,
711 request, apiuser, repo_name, repo_type,
711 owner=Optional(OAttr('apiuser')),
712 owner=Optional(OAttr('apiuser')),
712 description=Optional(''),
713 description=Optional(''),
713 private=Optional(False),
714 private=Optional(False),
714 clone_uri=Optional(None),
715 clone_uri=Optional(None),
715 push_uri=Optional(None),
716 push_uri=Optional(None),
716 landing_rev=Optional('rev:tip'),
717 landing_rev=Optional('rev:tip'),
717 enable_statistics=Optional(False),
718 enable_statistics=Optional(False),
718 enable_locking=Optional(False),
719 enable_locking=Optional(False),
719 enable_downloads=Optional(False),
720 enable_downloads=Optional(False),
720 copy_permissions=Optional(False)):
721 copy_permissions=Optional(False)):
721 """
722 """
722 Creates a repository.
723 Creates a repository.
723
724
724 * If the repository name contains "/", repository will be created inside
725 * If the repository name contains "/", repository will be created inside
725 a repository group or nested repository groups
726 a repository group or nested repository groups
726
727
727 For example "foo/bar/repo1" will create |repo| called "repo1" inside
728 For example "foo/bar/repo1" will create |repo| called "repo1" inside
728 group "foo/bar". You have to have permissions to access and write to
729 group "foo/bar". You have to have permissions to access and write to
729 the last repository group ("bar" in this example)
730 the last repository group ("bar" in this example)
730
731
731 This command can only be run using an |authtoken| with at least
732 This command can only be run using an |authtoken| with at least
732 permissions to create repositories, or write permissions to
733 permissions to create repositories, or write permissions to
733 parent repository groups.
734 parent repository groups.
734
735
735 :param apiuser: This is filled automatically from the |authtoken|.
736 :param apiuser: This is filled automatically from the |authtoken|.
736 :type apiuser: AuthUser
737 :type apiuser: AuthUser
737 :param repo_name: Set the repository name.
738 :param repo_name: Set the repository name.
738 :type repo_name: str
739 :type repo_name: str
739 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
740 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
740 :type repo_type: str
741 :type repo_type: str
741 :param owner: user_id or username
742 :param owner: user_id or username
742 :type owner: Optional(str)
743 :type owner: Optional(str)
743 :param description: Set the repository description.
744 :param description: Set the repository description.
744 :type description: Optional(str)
745 :type description: Optional(str)
745 :param private: set repository as private
746 :param private: set repository as private
746 :type private: bool
747 :type private: bool
747 :param clone_uri: set clone_uri
748 :param clone_uri: set clone_uri
748 :type clone_uri: str
749 :type clone_uri: str
749 :param push_uri: set push_uri
750 :param push_uri: set push_uri
750 :type push_uri: str
751 :type push_uri: str
751 :param landing_rev: <rev_type>:<rev>
752 :param landing_rev: <rev_type>:<rev>
752 :type landing_rev: str
753 :type landing_rev: str
753 :param enable_locking:
754 :param enable_locking:
754 :type enable_locking: bool
755 :type enable_locking: bool
755 :param enable_downloads:
756 :param enable_downloads:
756 :type enable_downloads: bool
757 :type enable_downloads: bool
757 :param enable_statistics:
758 :param enable_statistics:
758 :type enable_statistics: bool
759 :type enable_statistics: bool
759 :param copy_permissions: Copy permission from group in which the
760 :param copy_permissions: Copy permission from group in which the
760 repository is being created.
761 repository is being created.
761 :type copy_permissions: bool
762 :type copy_permissions: bool
762
763
763
764
764 Example output:
765 Example output:
765
766
766 .. code-block:: bash
767 .. code-block:: bash
767
768
768 id : <id_given_in_input>
769 id : <id_given_in_input>
769 result: {
770 result: {
770 "msg": "Created new repository `<reponame>`",
771 "msg": "Created new repository `<reponame>`",
771 "success": true,
772 "success": true,
772 "task": "<celery task id or None if done sync>"
773 "task": "<celery task id or None if done sync>"
773 }
774 }
774 error: null
775 error: null
775
776
776
777
777 Example error output:
778 Example error output:
778
779
779 .. code-block:: bash
780 .. code-block:: bash
780
781
781 id : <id_given_in_input>
782 id : <id_given_in_input>
782 result : null
783 result : null
783 error : {
784 error : {
784 'failed to create repository `<repo_name>`'
785 'failed to create repository `<repo_name>`'
785 }
786 }
786
787
787 """
788 """
788
789
789 owner = validate_set_owner_permissions(apiuser, owner)
790 owner = validate_set_owner_permissions(apiuser, owner)
790
791
791 description = Optional.extract(description)
792 description = Optional.extract(description)
792 copy_permissions = Optional.extract(copy_permissions)
793 copy_permissions = Optional.extract(copy_permissions)
793 clone_uri = Optional.extract(clone_uri)
794 clone_uri = Optional.extract(clone_uri)
794 push_uri = Optional.extract(push_uri)
795 push_uri = Optional.extract(push_uri)
795 landing_commit_ref = Optional.extract(landing_rev)
796 landing_commit_ref = Optional.extract(landing_rev)
796
797
797 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
798 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
798 if isinstance(private, Optional):
799 if isinstance(private, Optional):
799 private = defs.get('repo_private') or Optional.extract(private)
800 private = defs.get('repo_private') or Optional.extract(private)
800 if isinstance(repo_type, Optional):
801 if isinstance(repo_type, Optional):
801 repo_type = defs.get('repo_type')
802 repo_type = defs.get('repo_type')
802 if isinstance(enable_statistics, Optional):
803 if isinstance(enable_statistics, Optional):
803 enable_statistics = defs.get('repo_enable_statistics')
804 enable_statistics = defs.get('repo_enable_statistics')
804 if isinstance(enable_locking, Optional):
805 if isinstance(enable_locking, Optional):
805 enable_locking = defs.get('repo_enable_locking')
806 enable_locking = defs.get('repo_enable_locking')
806 if isinstance(enable_downloads, Optional):
807 if isinstance(enable_downloads, Optional):
807 enable_downloads = defs.get('repo_enable_downloads')
808 enable_downloads = defs.get('repo_enable_downloads')
808
809
809 schema = repo_schema.RepoSchema().bind(
810 schema = repo_schema.RepoSchema().bind(
810 repo_type_options=rhodecode.BACKENDS.keys(),
811 repo_type_options=rhodecode.BACKENDS.keys(),
811 repo_type=repo_type,
812 repo_type=repo_type,
812 # user caller
813 # user caller
813 user=apiuser)
814 user=apiuser)
814
815
815 try:
816 try:
816 schema_data = schema.deserialize(dict(
817 schema_data = schema.deserialize(dict(
817 repo_name=repo_name,
818 repo_name=repo_name,
818 repo_type=repo_type,
819 repo_type=repo_type,
819 repo_owner=owner.username,
820 repo_owner=owner.username,
820 repo_description=description,
821 repo_description=description,
821 repo_landing_commit_ref=landing_commit_ref,
822 repo_landing_commit_ref=landing_commit_ref,
822 repo_clone_uri=clone_uri,
823 repo_clone_uri=clone_uri,
823 repo_push_uri=push_uri,
824 repo_push_uri=push_uri,
824 repo_private=private,
825 repo_private=private,
825 repo_copy_permissions=copy_permissions,
826 repo_copy_permissions=copy_permissions,
826 repo_enable_statistics=enable_statistics,
827 repo_enable_statistics=enable_statistics,
827 repo_enable_downloads=enable_downloads,
828 repo_enable_downloads=enable_downloads,
828 repo_enable_locking=enable_locking))
829 repo_enable_locking=enable_locking))
829 except validation_schema.Invalid as err:
830 except validation_schema.Invalid as err:
830 raise JSONRPCValidationError(colander_exc=err)
831 raise JSONRPCValidationError(colander_exc=err)
831
832
832 try:
833 try:
833 data = {
834 data = {
834 'owner': owner,
835 'owner': owner,
835 'repo_name': schema_data['repo_group']['repo_name_without_group'],
836 'repo_name': schema_data['repo_group']['repo_name_without_group'],
836 'repo_name_full': schema_data['repo_name'],
837 'repo_name_full': schema_data['repo_name'],
837 'repo_group': schema_data['repo_group']['repo_group_id'],
838 'repo_group': schema_data['repo_group']['repo_group_id'],
838 'repo_type': schema_data['repo_type'],
839 'repo_type': schema_data['repo_type'],
839 'repo_description': schema_data['repo_description'],
840 'repo_description': schema_data['repo_description'],
840 'repo_private': schema_data['repo_private'],
841 'repo_private': schema_data['repo_private'],
841 'clone_uri': schema_data['repo_clone_uri'],
842 'clone_uri': schema_data['repo_clone_uri'],
842 'push_uri': schema_data['repo_push_uri'],
843 'push_uri': schema_data['repo_push_uri'],
843 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
844 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
844 'enable_statistics': schema_data['repo_enable_statistics'],
845 'enable_statistics': schema_data['repo_enable_statistics'],
845 'enable_locking': schema_data['repo_enable_locking'],
846 'enable_locking': schema_data['repo_enable_locking'],
846 'enable_downloads': schema_data['repo_enable_downloads'],
847 'enable_downloads': schema_data['repo_enable_downloads'],
847 'repo_copy_permissions': schema_data['repo_copy_permissions'],
848 'repo_copy_permissions': schema_data['repo_copy_permissions'],
848 }
849 }
849
850
850 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
851 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
851 task_id = get_task_id(task)
852 task_id = get_task_id(task)
852 # no commit, it's done in RepoModel, or async via celery
853 # no commit, it's done in RepoModel, or async via celery
853 return {
854 return {
854 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
855 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
855 'success': True, # cannot return the repo data here since fork
856 'success': True, # cannot return the repo data here since fork
856 # can be done async
857 # can be done async
857 'task': task_id
858 'task': task_id
858 }
859 }
859 except Exception:
860 except Exception:
860 log.exception(
861 log.exception(
861 u"Exception while trying to create the repository %s",
862 u"Exception while trying to create the repository %s",
862 schema_data['repo_name'])
863 schema_data['repo_name'])
863 raise JSONRPCError(
864 raise JSONRPCError(
864 'failed to create repository `%s`' % (schema_data['repo_name'],))
865 'failed to create repository `%s`' % (schema_data['repo_name'],))
865
866
866
867
867 @jsonrpc_method()
868 @jsonrpc_method()
868 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
869 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
869 description=Optional('')):
870 description=Optional('')):
870 """
871 """
871 Adds an extra field to a repository.
872 Adds an extra field to a repository.
872
873
873 This command can only be run using an |authtoken| with at least
874 This command can only be run using an |authtoken| with at least
874 write permissions to the |repo|.
875 write permissions to the |repo|.
875
876
876 :param apiuser: This is filled automatically from the |authtoken|.
877 :param apiuser: This is filled automatically from the |authtoken|.
877 :type apiuser: AuthUser
878 :type apiuser: AuthUser
878 :param repoid: Set the repository name or repository id.
879 :param repoid: Set the repository name or repository id.
879 :type repoid: str or int
880 :type repoid: str or int
880 :param key: Create a unique field key for this repository.
881 :param key: Create a unique field key for this repository.
881 :type key: str
882 :type key: str
882 :param label:
883 :param label:
883 :type label: Optional(str)
884 :type label: Optional(str)
884 :param description:
885 :param description:
885 :type description: Optional(str)
886 :type description: Optional(str)
886 """
887 """
887 repo = get_repo_or_error(repoid)
888 repo = get_repo_or_error(repoid)
888 if not has_superadmin_permission(apiuser):
889 if not has_superadmin_permission(apiuser):
889 _perms = ('repository.admin',)
890 _perms = ('repository.admin',)
890 validate_repo_permissions(apiuser, repoid, repo, _perms)
891 validate_repo_permissions(apiuser, repoid, repo, _perms)
891
892
892 label = Optional.extract(label) or key
893 label = Optional.extract(label) or key
893 description = Optional.extract(description)
894 description = Optional.extract(description)
894
895
895 field = RepositoryField.get_by_key_name(key, repo)
896 field = RepositoryField.get_by_key_name(key, repo)
896 if field:
897 if field:
897 raise JSONRPCError('Field with key '
898 raise JSONRPCError('Field with key '
898 '`%s` exists for repo `%s`' % (key, repoid))
899 '`%s` exists for repo `%s`' % (key, repoid))
899
900
900 try:
901 try:
901 RepoModel().add_repo_field(repo, key, field_label=label,
902 RepoModel().add_repo_field(repo, key, field_label=label,
902 field_desc=description)
903 field_desc=description)
903 Session().commit()
904 Session().commit()
904 return {
905 return {
905 'msg': "Added new repository field `%s`" % (key,),
906 'msg': "Added new repository field `%s`" % (key,),
906 'success': True,
907 'success': True,
907 }
908 }
908 except Exception:
909 except Exception:
909 log.exception("Exception occurred while trying to add field to repo")
910 log.exception("Exception occurred while trying to add field to repo")
910 raise JSONRPCError(
911 raise JSONRPCError(
911 'failed to create new field for repository `%s`' % (repoid,))
912 'failed to create new field for repository `%s`' % (repoid,))
912
913
913
914
914 @jsonrpc_method()
915 @jsonrpc_method()
915 def remove_field_from_repo(request, apiuser, repoid, key):
916 def remove_field_from_repo(request, apiuser, repoid, key):
916 """
917 """
917 Removes an extra field from a repository.
918 Removes an extra field from a repository.
918
919
919 This command can only be run using an |authtoken| with at least
920 This command can only be run using an |authtoken| with at least
920 write permissions to the |repo|.
921 write permissions to the |repo|.
921
922
922 :param apiuser: This is filled automatically from the |authtoken|.
923 :param apiuser: This is filled automatically from the |authtoken|.
923 :type apiuser: AuthUser
924 :type apiuser: AuthUser
924 :param repoid: Set the repository name or repository ID.
925 :param repoid: Set the repository name or repository ID.
925 :type repoid: str or int
926 :type repoid: str or int
926 :param key: Set the unique field key for this repository.
927 :param key: Set the unique field key for this repository.
927 :type key: str
928 :type key: str
928 """
929 """
929
930
930 repo = get_repo_or_error(repoid)
931 repo = get_repo_or_error(repoid)
931 if not has_superadmin_permission(apiuser):
932 if not has_superadmin_permission(apiuser):
932 _perms = ('repository.admin',)
933 _perms = ('repository.admin',)
933 validate_repo_permissions(apiuser, repoid, repo, _perms)
934 validate_repo_permissions(apiuser, repoid, repo, _perms)
934
935
935 field = RepositoryField.get_by_key_name(key, repo)
936 field = RepositoryField.get_by_key_name(key, repo)
936 if not field:
937 if not field:
937 raise JSONRPCError('Field with key `%s` does not '
938 raise JSONRPCError('Field with key `%s` does not '
938 'exists for repo `%s`' % (key, repoid))
939 'exists for repo `%s`' % (key, repoid))
939
940
940 try:
941 try:
941 RepoModel().delete_repo_field(repo, field_key=key)
942 RepoModel().delete_repo_field(repo, field_key=key)
942 Session().commit()
943 Session().commit()
943 return {
944 return {
944 'msg': "Deleted repository field `%s`" % (key,),
945 'msg': "Deleted repository field `%s`" % (key,),
945 'success': True,
946 'success': True,
946 }
947 }
947 except Exception:
948 except Exception:
948 log.exception(
949 log.exception(
949 "Exception occurred while trying to delete field from repo")
950 "Exception occurred while trying to delete field from repo")
950 raise JSONRPCError(
951 raise JSONRPCError(
951 'failed to delete field for repository `%s`' % (repoid,))
952 'failed to delete field for repository `%s`' % (repoid,))
952
953
953
954
954 @jsonrpc_method()
955 @jsonrpc_method()
955 def update_repo(
956 def update_repo(
956 request, apiuser, repoid, repo_name=Optional(None),
957 request, apiuser, repoid, repo_name=Optional(None),
957 owner=Optional(OAttr('apiuser')), description=Optional(''),
958 owner=Optional(OAttr('apiuser')), description=Optional(''),
958 private=Optional(False),
959 private=Optional(False),
959 clone_uri=Optional(None), push_uri=Optional(None),
960 clone_uri=Optional(None), push_uri=Optional(None),
960 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
961 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
961 enable_statistics=Optional(False),
962 enable_statistics=Optional(False),
962 enable_locking=Optional(False),
963 enable_locking=Optional(False),
963 enable_downloads=Optional(False), fields=Optional('')):
964 enable_downloads=Optional(False), fields=Optional('')):
964 """
965 """
965 Updates a repository with the given information.
966 Updates a repository with the given information.
966
967
967 This command can only be run using an |authtoken| with at least
968 This command can only be run using an |authtoken| with at least
968 admin permissions to the |repo|.
969 admin permissions to the |repo|.
969
970
970 * If the repository name contains "/", repository will be updated
971 * If the repository name contains "/", repository will be updated
971 accordingly with a repository group or nested repository groups
972 accordingly with a repository group or nested repository groups
972
973
973 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
974 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
974 called "repo-test" and place it inside group "foo/bar".
975 called "repo-test" and place it inside group "foo/bar".
975 You have to have permissions to access and write to the last repository
976 You have to have permissions to access and write to the last repository
976 group ("bar" in this example)
977 group ("bar" in this example)
977
978
978 :param apiuser: This is filled automatically from the |authtoken|.
979 :param apiuser: This is filled automatically from the |authtoken|.
979 :type apiuser: AuthUser
980 :type apiuser: AuthUser
980 :param repoid: repository name or repository ID.
981 :param repoid: repository name or repository ID.
981 :type repoid: str or int
982 :type repoid: str or int
982 :param repo_name: Update the |repo| name, including the
983 :param repo_name: Update the |repo| name, including the
983 repository group it's in.
984 repository group it's in.
984 :type repo_name: str
985 :type repo_name: str
985 :param owner: Set the |repo| owner.
986 :param owner: Set the |repo| owner.
986 :type owner: str
987 :type owner: str
987 :param fork_of: Set the |repo| as fork of another |repo|.
988 :param fork_of: Set the |repo| as fork of another |repo|.
988 :type fork_of: str
989 :type fork_of: str
989 :param description: Update the |repo| description.
990 :param description: Update the |repo| description.
990 :type description: str
991 :type description: str
991 :param private: Set the |repo| as private. (True | False)
992 :param private: Set the |repo| as private. (True | False)
992 :type private: bool
993 :type private: bool
993 :param clone_uri: Update the |repo| clone URI.
994 :param clone_uri: Update the |repo| clone URI.
994 :type clone_uri: str
995 :type clone_uri: str
995 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
996 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
996 :type landing_rev: str
997 :type landing_rev: str
997 :param enable_statistics: Enable statistics on the |repo|, (True | False).
998 :param enable_statistics: Enable statistics on the |repo|, (True | False).
998 :type enable_statistics: bool
999 :type enable_statistics: bool
999 :param enable_locking: Enable |repo| locking.
1000 :param enable_locking: Enable |repo| locking.
1000 :type enable_locking: bool
1001 :type enable_locking: bool
1001 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1002 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1002 :type enable_downloads: bool
1003 :type enable_downloads: bool
1003 :param fields: Add extra fields to the |repo|. Use the following
1004 :param fields: Add extra fields to the |repo|. Use the following
1004 example format: ``field_key=field_val,field_key2=fieldval2``.
1005 example format: ``field_key=field_val,field_key2=fieldval2``.
1005 Escape ', ' with \,
1006 Escape ', ' with \,
1006 :type fields: str
1007 :type fields: str
1007 """
1008 """
1008
1009
1009 repo = get_repo_or_error(repoid)
1010 repo = get_repo_or_error(repoid)
1010
1011
1011 include_secrets = False
1012 include_secrets = False
1012 if not has_superadmin_permission(apiuser):
1013 if not has_superadmin_permission(apiuser):
1013 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1014 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1014 else:
1015 else:
1015 include_secrets = True
1016 include_secrets = True
1016
1017
1017 updates = dict(
1018 updates = dict(
1018 repo_name=repo_name
1019 repo_name=repo_name
1019 if not isinstance(repo_name, Optional) else repo.repo_name,
1020 if not isinstance(repo_name, Optional) else repo.repo_name,
1020
1021
1021 fork_id=fork_of
1022 fork_id=fork_of
1022 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1023 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1023
1024
1024 user=owner
1025 user=owner
1025 if not isinstance(owner, Optional) else repo.user.username,
1026 if not isinstance(owner, Optional) else repo.user.username,
1026
1027
1027 repo_description=description
1028 repo_description=description
1028 if not isinstance(description, Optional) else repo.description,
1029 if not isinstance(description, Optional) else repo.description,
1029
1030
1030 repo_private=private
1031 repo_private=private
1031 if not isinstance(private, Optional) else repo.private,
1032 if not isinstance(private, Optional) else repo.private,
1032
1033
1033 clone_uri=clone_uri
1034 clone_uri=clone_uri
1034 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1035 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1035
1036
1036 push_uri=push_uri
1037 push_uri=push_uri
1037 if not isinstance(push_uri, Optional) else repo.push_uri,
1038 if not isinstance(push_uri, Optional) else repo.push_uri,
1038
1039
1039 repo_landing_rev=landing_rev
1040 repo_landing_rev=landing_rev
1040 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1041 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1041
1042
1042 repo_enable_statistics=enable_statistics
1043 repo_enable_statistics=enable_statistics
1043 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1044 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1044
1045
1045 repo_enable_locking=enable_locking
1046 repo_enable_locking=enable_locking
1046 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1047 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1047
1048
1048 repo_enable_downloads=enable_downloads
1049 repo_enable_downloads=enable_downloads
1049 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1050 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1050
1051
1051 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1052 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1052 request.translate, repo=repo)
1053 request.translate, repo=repo)
1053
1054
1054 old_values = repo.get_api_data()
1055 old_values = repo.get_api_data()
1055 repo_type = repo.repo_type
1056 repo_type = repo.repo_type
1056 schema = repo_schema.RepoSchema().bind(
1057 schema = repo_schema.RepoSchema().bind(
1057 repo_type_options=rhodecode.BACKENDS.keys(),
1058 repo_type_options=rhodecode.BACKENDS.keys(),
1058 repo_ref_options=ref_choices,
1059 repo_ref_options=ref_choices,
1059 repo_type=repo_type,
1060 repo_type=repo_type,
1060 # user caller
1061 # user caller
1061 user=apiuser,
1062 user=apiuser,
1062 old_values=old_values)
1063 old_values=old_values)
1063 try:
1064 try:
1064 schema_data = schema.deserialize(dict(
1065 schema_data = schema.deserialize(dict(
1065 # we save old value, users cannot change type
1066 # we save old value, users cannot change type
1066 repo_type=repo_type,
1067 repo_type=repo_type,
1067
1068
1068 repo_name=updates['repo_name'],
1069 repo_name=updates['repo_name'],
1069 repo_owner=updates['user'],
1070 repo_owner=updates['user'],
1070 repo_description=updates['repo_description'],
1071 repo_description=updates['repo_description'],
1071 repo_clone_uri=updates['clone_uri'],
1072 repo_clone_uri=updates['clone_uri'],
1072 repo_push_uri=updates['push_uri'],
1073 repo_push_uri=updates['push_uri'],
1073 repo_fork_of=updates['fork_id'],
1074 repo_fork_of=updates['fork_id'],
1074 repo_private=updates['repo_private'],
1075 repo_private=updates['repo_private'],
1075 repo_landing_commit_ref=updates['repo_landing_rev'],
1076 repo_landing_commit_ref=updates['repo_landing_rev'],
1076 repo_enable_statistics=updates['repo_enable_statistics'],
1077 repo_enable_statistics=updates['repo_enable_statistics'],
1077 repo_enable_downloads=updates['repo_enable_downloads'],
1078 repo_enable_downloads=updates['repo_enable_downloads'],
1078 repo_enable_locking=updates['repo_enable_locking']))
1079 repo_enable_locking=updates['repo_enable_locking']))
1079 except validation_schema.Invalid as err:
1080 except validation_schema.Invalid as err:
1080 raise JSONRPCValidationError(colander_exc=err)
1081 raise JSONRPCValidationError(colander_exc=err)
1081
1082
1082 # save validated data back into the updates dict
1083 # save validated data back into the updates dict
1083 validated_updates = dict(
1084 validated_updates = dict(
1084 repo_name=schema_data['repo_group']['repo_name_without_group'],
1085 repo_name=schema_data['repo_group']['repo_name_without_group'],
1085 repo_group=schema_data['repo_group']['repo_group_id'],
1086 repo_group=schema_data['repo_group']['repo_group_id'],
1086
1087
1087 user=schema_data['repo_owner'],
1088 user=schema_data['repo_owner'],
1088 repo_description=schema_data['repo_description'],
1089 repo_description=schema_data['repo_description'],
1089 repo_private=schema_data['repo_private'],
1090 repo_private=schema_data['repo_private'],
1090 clone_uri=schema_data['repo_clone_uri'],
1091 clone_uri=schema_data['repo_clone_uri'],
1091 push_uri=schema_data['repo_push_uri'],
1092 push_uri=schema_data['repo_push_uri'],
1092 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1093 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1093 repo_enable_statistics=schema_data['repo_enable_statistics'],
1094 repo_enable_statistics=schema_data['repo_enable_statistics'],
1094 repo_enable_locking=schema_data['repo_enable_locking'],
1095 repo_enable_locking=schema_data['repo_enable_locking'],
1095 repo_enable_downloads=schema_data['repo_enable_downloads'],
1096 repo_enable_downloads=schema_data['repo_enable_downloads'],
1096 )
1097 )
1097
1098
1098 if schema_data['repo_fork_of']:
1099 if schema_data['repo_fork_of']:
1099 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1100 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1100 validated_updates['fork_id'] = fork_repo.repo_id
1101 validated_updates['fork_id'] = fork_repo.repo_id
1101
1102
1102 # extra fields
1103 # extra fields
1103 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1104 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1104 if fields:
1105 if fields:
1105 validated_updates.update(fields)
1106 validated_updates.update(fields)
1106
1107
1107 try:
1108 try:
1108 RepoModel().update(repo, **validated_updates)
1109 RepoModel().update(repo, **validated_updates)
1109 audit_logger.store_api(
1110 audit_logger.store_api(
1110 'repo.edit', action_data={'old_data': old_values},
1111 'repo.edit', action_data={'old_data': old_values},
1111 user=apiuser, repo=repo)
1112 user=apiuser, repo=repo)
1112 Session().commit()
1113 Session().commit()
1113 return {
1114 return {
1114 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1115 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1115 'repository': repo.get_api_data(include_secrets=include_secrets)
1116 'repository': repo.get_api_data(include_secrets=include_secrets)
1116 }
1117 }
1117 except Exception:
1118 except Exception:
1118 log.exception(
1119 log.exception(
1119 u"Exception while trying to update the repository %s",
1120 u"Exception while trying to update the repository %s",
1120 repoid)
1121 repoid)
1121 raise JSONRPCError('failed to update repo `%s`' % repoid)
1122 raise JSONRPCError('failed to update repo `%s`' % repoid)
1122
1123
1123
1124
1124 @jsonrpc_method()
1125 @jsonrpc_method()
1125 def fork_repo(request, apiuser, repoid, fork_name,
1126 def fork_repo(request, apiuser, repoid, fork_name,
1126 owner=Optional(OAttr('apiuser')),
1127 owner=Optional(OAttr('apiuser')),
1127 description=Optional(''),
1128 description=Optional(''),
1128 private=Optional(False),
1129 private=Optional(False),
1129 clone_uri=Optional(None),
1130 clone_uri=Optional(None),
1130 landing_rev=Optional('rev:tip'),
1131 landing_rev=Optional('rev:tip'),
1131 copy_permissions=Optional(False)):
1132 copy_permissions=Optional(False)):
1132 """
1133 """
1133 Creates a fork of the specified |repo|.
1134 Creates a fork of the specified |repo|.
1134
1135
1135 * If the fork_name contains "/", fork will be created inside
1136 * If the fork_name contains "/", fork will be created inside
1136 a repository group or nested repository groups
1137 a repository group or nested repository groups
1137
1138
1138 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1139 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1139 inside group "foo/bar". You have to have permissions to access and
1140 inside group "foo/bar". You have to have permissions to access and
1140 write to the last repository group ("bar" in this example)
1141 write to the last repository group ("bar" in this example)
1141
1142
1142 This command can only be run using an |authtoken| with minimum
1143 This command can only be run using an |authtoken| with minimum
1143 read permissions of the forked repo, create fork permissions for an user.
1144 read permissions of the forked repo, create fork permissions for an user.
1144
1145
1145 :param apiuser: This is filled automatically from the |authtoken|.
1146 :param apiuser: This is filled automatically from the |authtoken|.
1146 :type apiuser: AuthUser
1147 :type apiuser: AuthUser
1147 :param repoid: Set repository name or repository ID.
1148 :param repoid: Set repository name or repository ID.
1148 :type repoid: str or int
1149 :type repoid: str or int
1149 :param fork_name: Set the fork name, including it's repository group membership.
1150 :param fork_name: Set the fork name, including it's repository group membership.
1150 :type fork_name: str
1151 :type fork_name: str
1151 :param owner: Set the fork owner.
1152 :param owner: Set the fork owner.
1152 :type owner: str
1153 :type owner: str
1153 :param description: Set the fork description.
1154 :param description: Set the fork description.
1154 :type description: str
1155 :type description: str
1155 :param copy_permissions: Copy permissions from parent |repo|. The
1156 :param copy_permissions: Copy permissions from parent |repo|. The
1156 default is False.
1157 default is False.
1157 :type copy_permissions: bool
1158 :type copy_permissions: bool
1158 :param private: Make the fork private. The default is False.
1159 :param private: Make the fork private. The default is False.
1159 :type private: bool
1160 :type private: bool
1160 :param landing_rev: Set the landing revision. The default is tip.
1161 :param landing_rev: Set the landing revision. The default is tip.
1161
1162
1162 Example output:
1163 Example output:
1163
1164
1164 .. code-block:: bash
1165 .. code-block:: bash
1165
1166
1166 id : <id_for_response>
1167 id : <id_for_response>
1167 api_key : "<api_key>"
1168 api_key : "<api_key>"
1168 args: {
1169 args: {
1169 "repoid" : "<reponame or repo_id>",
1170 "repoid" : "<reponame or repo_id>",
1170 "fork_name": "<forkname>",
1171 "fork_name": "<forkname>",
1171 "owner": "<username or user_id = Optional(=apiuser)>",
1172 "owner": "<username or user_id = Optional(=apiuser)>",
1172 "description": "<description>",
1173 "description": "<description>",
1173 "copy_permissions": "<bool>",
1174 "copy_permissions": "<bool>",
1174 "private": "<bool>",
1175 "private": "<bool>",
1175 "landing_rev": "<landing_rev>"
1176 "landing_rev": "<landing_rev>"
1176 }
1177 }
1177
1178
1178 Example error output:
1179 Example error output:
1179
1180
1180 .. code-block:: bash
1181 .. code-block:: bash
1181
1182
1182 id : <id_given_in_input>
1183 id : <id_given_in_input>
1183 result: {
1184 result: {
1184 "msg": "Created fork of `<reponame>` as `<forkname>`",
1185 "msg": "Created fork of `<reponame>` as `<forkname>`",
1185 "success": true,
1186 "success": true,
1186 "task": "<celery task id or None if done sync>"
1187 "task": "<celery task id or None if done sync>"
1187 }
1188 }
1188 error: null
1189 error: null
1189
1190
1190 """
1191 """
1191
1192
1192 repo = get_repo_or_error(repoid)
1193 repo = get_repo_or_error(repoid)
1193 repo_name = repo.repo_name
1194 repo_name = repo.repo_name
1194
1195
1195 if not has_superadmin_permission(apiuser):
1196 if not has_superadmin_permission(apiuser):
1196 # check if we have at least read permission for
1197 # check if we have at least read permission for
1197 # this repo that we fork !
1198 # this repo that we fork !
1198 _perms = (
1199 _perms = (
1199 'repository.admin', 'repository.write', 'repository.read')
1200 'repository.admin', 'repository.write', 'repository.read')
1200 validate_repo_permissions(apiuser, repoid, repo, _perms)
1201 validate_repo_permissions(apiuser, repoid, repo, _perms)
1201
1202
1202 # check if the regular user has at least fork permissions as well
1203 # check if the regular user has at least fork permissions as well
1203 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1204 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1204 raise JSONRPCForbidden()
1205 raise JSONRPCForbidden()
1205
1206
1206 # check if user can set owner parameter
1207 # check if user can set owner parameter
1207 owner = validate_set_owner_permissions(apiuser, owner)
1208 owner = validate_set_owner_permissions(apiuser, owner)
1208
1209
1209 description = Optional.extract(description)
1210 description = Optional.extract(description)
1210 copy_permissions = Optional.extract(copy_permissions)
1211 copy_permissions = Optional.extract(copy_permissions)
1211 clone_uri = Optional.extract(clone_uri)
1212 clone_uri = Optional.extract(clone_uri)
1212 landing_commit_ref = Optional.extract(landing_rev)
1213 landing_commit_ref = Optional.extract(landing_rev)
1213 private = Optional.extract(private)
1214 private = Optional.extract(private)
1214
1215
1215 schema = repo_schema.RepoSchema().bind(
1216 schema = repo_schema.RepoSchema().bind(
1216 repo_type_options=rhodecode.BACKENDS.keys(),
1217 repo_type_options=rhodecode.BACKENDS.keys(),
1217 repo_type=repo.repo_type,
1218 repo_type=repo.repo_type,
1218 # user caller
1219 # user caller
1219 user=apiuser)
1220 user=apiuser)
1220
1221
1221 try:
1222 try:
1222 schema_data = schema.deserialize(dict(
1223 schema_data = schema.deserialize(dict(
1223 repo_name=fork_name,
1224 repo_name=fork_name,
1224 repo_type=repo.repo_type,
1225 repo_type=repo.repo_type,
1225 repo_owner=owner.username,
1226 repo_owner=owner.username,
1226 repo_description=description,
1227 repo_description=description,
1227 repo_landing_commit_ref=landing_commit_ref,
1228 repo_landing_commit_ref=landing_commit_ref,
1228 repo_clone_uri=clone_uri,
1229 repo_clone_uri=clone_uri,
1229 repo_private=private,
1230 repo_private=private,
1230 repo_copy_permissions=copy_permissions))
1231 repo_copy_permissions=copy_permissions))
1231 except validation_schema.Invalid as err:
1232 except validation_schema.Invalid as err:
1232 raise JSONRPCValidationError(colander_exc=err)
1233 raise JSONRPCValidationError(colander_exc=err)
1233
1234
1234 try:
1235 try:
1235 data = {
1236 data = {
1236 'fork_parent_id': repo.repo_id,
1237 'fork_parent_id': repo.repo_id,
1237
1238
1238 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1239 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1239 'repo_name_full': schema_data['repo_name'],
1240 'repo_name_full': schema_data['repo_name'],
1240 'repo_group': schema_data['repo_group']['repo_group_id'],
1241 'repo_group': schema_data['repo_group']['repo_group_id'],
1241 'repo_type': schema_data['repo_type'],
1242 'repo_type': schema_data['repo_type'],
1242 'description': schema_data['repo_description'],
1243 'description': schema_data['repo_description'],
1243 'private': schema_data['repo_private'],
1244 'private': schema_data['repo_private'],
1244 'copy_permissions': schema_data['repo_copy_permissions'],
1245 'copy_permissions': schema_data['repo_copy_permissions'],
1245 'landing_rev': schema_data['repo_landing_commit_ref'],
1246 'landing_rev': schema_data['repo_landing_commit_ref'],
1246 }
1247 }
1247
1248
1248 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1249 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1249 # no commit, it's done in RepoModel, or async via celery
1250 # no commit, it's done in RepoModel, or async via celery
1250 task_id = get_task_id(task)
1251 task_id = get_task_id(task)
1251
1252
1252 return {
1253 return {
1253 'msg': 'Created fork of `%s` as `%s`' % (
1254 'msg': 'Created fork of `%s` as `%s`' % (
1254 repo.repo_name, schema_data['repo_name']),
1255 repo.repo_name, schema_data['repo_name']),
1255 'success': True, # cannot return the repo data here since fork
1256 'success': True, # cannot return the repo data here since fork
1256 # can be done async
1257 # can be done async
1257 'task': task_id
1258 'task': task_id
1258 }
1259 }
1259 except Exception:
1260 except Exception:
1260 log.exception(
1261 log.exception(
1261 u"Exception while trying to create fork %s",
1262 u"Exception while trying to create fork %s",
1262 schema_data['repo_name'])
1263 schema_data['repo_name'])
1263 raise JSONRPCError(
1264 raise JSONRPCError(
1264 'failed to fork repository `%s` as `%s`' % (
1265 'failed to fork repository `%s` as `%s`' % (
1265 repo_name, schema_data['repo_name']))
1266 repo_name, schema_data['repo_name']))
1266
1267
1267
1268
1268 @jsonrpc_method()
1269 @jsonrpc_method()
1269 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1270 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1270 """
1271 """
1271 Deletes a repository.
1272 Deletes a repository.
1272
1273
1273 * When the `forks` parameter is set it's possible to detach or delete
1274 * When the `forks` parameter is set it's possible to detach or delete
1274 forks of deleted repository.
1275 forks of deleted repository.
1275
1276
1276 This command can only be run using an |authtoken| with admin
1277 This command can only be run using an |authtoken| with admin
1277 permissions on the |repo|.
1278 permissions on the |repo|.
1278
1279
1279 :param apiuser: This is filled automatically from the |authtoken|.
1280 :param apiuser: This is filled automatically from the |authtoken|.
1280 :type apiuser: AuthUser
1281 :type apiuser: AuthUser
1281 :param repoid: Set the repository name or repository ID.
1282 :param repoid: Set the repository name or repository ID.
1282 :type repoid: str or int
1283 :type repoid: str or int
1283 :param forks: Set to `detach` or `delete` forks from the |repo|.
1284 :param forks: Set to `detach` or `delete` forks from the |repo|.
1284 :type forks: Optional(str)
1285 :type forks: Optional(str)
1285
1286
1286 Example error output:
1287 Example error output:
1287
1288
1288 .. code-block:: bash
1289 .. code-block:: bash
1289
1290
1290 id : <id_given_in_input>
1291 id : <id_given_in_input>
1291 result: {
1292 result: {
1292 "msg": "Deleted repository `<reponame>`",
1293 "msg": "Deleted repository `<reponame>`",
1293 "success": true
1294 "success": true
1294 }
1295 }
1295 error: null
1296 error: null
1296 """
1297 """
1297
1298
1298 repo = get_repo_or_error(repoid)
1299 repo = get_repo_or_error(repoid)
1299 repo_name = repo.repo_name
1300 repo_name = repo.repo_name
1300 if not has_superadmin_permission(apiuser):
1301 if not has_superadmin_permission(apiuser):
1301 _perms = ('repository.admin',)
1302 _perms = ('repository.admin',)
1302 validate_repo_permissions(apiuser, repoid, repo, _perms)
1303 validate_repo_permissions(apiuser, repoid, repo, _perms)
1303
1304
1304 try:
1305 try:
1305 handle_forks = Optional.extract(forks)
1306 handle_forks = Optional.extract(forks)
1306 _forks_msg = ''
1307 _forks_msg = ''
1307 _forks = [f for f in repo.forks]
1308 _forks = [f for f in repo.forks]
1308 if handle_forks == 'detach':
1309 if handle_forks == 'detach':
1309 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1310 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1310 elif handle_forks == 'delete':
1311 elif handle_forks == 'delete':
1311 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1312 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1312 elif _forks:
1313 elif _forks:
1313 raise JSONRPCError(
1314 raise JSONRPCError(
1314 'Cannot delete `%s` it still contains attached forks' %
1315 'Cannot delete `%s` it still contains attached forks' %
1315 (repo.repo_name,)
1316 (repo.repo_name,)
1316 )
1317 )
1317 old_data = repo.get_api_data()
1318 old_data = repo.get_api_data()
1318 RepoModel().delete(repo, forks=forks)
1319 RepoModel().delete(repo, forks=forks)
1319
1320
1320 repo = audit_logger.RepoWrap(repo_id=None,
1321 repo = audit_logger.RepoWrap(repo_id=None,
1321 repo_name=repo.repo_name)
1322 repo_name=repo.repo_name)
1322
1323
1323 audit_logger.store_api(
1324 audit_logger.store_api(
1324 'repo.delete', action_data={'old_data': old_data},
1325 'repo.delete', action_data={'old_data': old_data},
1325 user=apiuser, repo=repo)
1326 user=apiuser, repo=repo)
1326
1327
1327 ScmModel().mark_for_invalidation(repo_name, delete=True)
1328 ScmModel().mark_for_invalidation(repo_name, delete=True)
1328 Session().commit()
1329 Session().commit()
1329 return {
1330 return {
1330 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1331 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1331 'success': True
1332 'success': True
1332 }
1333 }
1333 except Exception:
1334 except Exception:
1334 log.exception("Exception occurred while trying to delete repo")
1335 log.exception("Exception occurred while trying to delete repo")
1335 raise JSONRPCError(
1336 raise JSONRPCError(
1336 'failed to delete repository `%s`' % (repo_name,)
1337 'failed to delete repository `%s`' % (repo_name,)
1337 )
1338 )
1338
1339
1339
1340
1340 #TODO: marcink, change name ?
1341 #TODO: marcink, change name ?
1341 @jsonrpc_method()
1342 @jsonrpc_method()
1342 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1343 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1343 """
1344 """
1344 Invalidates the cache for the specified repository.
1345 Invalidates the cache for the specified repository.
1345
1346
1346 This command can only be run using an |authtoken| with admin rights to
1347 This command can only be run using an |authtoken| with admin rights to
1347 the specified repository.
1348 the specified repository.
1348
1349
1349 This command takes the following options:
1350 This command takes the following options:
1350
1351
1351 :param apiuser: This is filled automatically from |authtoken|.
1352 :param apiuser: This is filled automatically from |authtoken|.
1352 :type apiuser: AuthUser
1353 :type apiuser: AuthUser
1353 :param repoid: Sets the repository name or repository ID.
1354 :param repoid: Sets the repository name or repository ID.
1354 :type repoid: str or int
1355 :type repoid: str or int
1355 :param delete_keys: This deletes the invalidated keys instead of
1356 :param delete_keys: This deletes the invalidated keys instead of
1356 just flagging them.
1357 just flagging them.
1357 :type delete_keys: Optional(``True`` | ``False``)
1358 :type delete_keys: Optional(``True`` | ``False``)
1358
1359
1359 Example output:
1360 Example output:
1360
1361
1361 .. code-block:: bash
1362 .. code-block:: bash
1362
1363
1363 id : <id_given_in_input>
1364 id : <id_given_in_input>
1364 result : {
1365 result : {
1365 'msg': Cache for repository `<repository name>` was invalidated,
1366 'msg': Cache for repository `<repository name>` was invalidated,
1366 'repository': <repository name>
1367 'repository': <repository name>
1367 }
1368 }
1368 error : null
1369 error : null
1369
1370
1370 Example error output:
1371 Example error output:
1371
1372
1372 .. code-block:: bash
1373 .. code-block:: bash
1373
1374
1374 id : <id_given_in_input>
1375 id : <id_given_in_input>
1375 result : null
1376 result : null
1376 error : {
1377 error : {
1377 'Error occurred during cache invalidation action'
1378 'Error occurred during cache invalidation action'
1378 }
1379 }
1379
1380
1380 """
1381 """
1381
1382
1382 repo = get_repo_or_error(repoid)
1383 repo = get_repo_or_error(repoid)
1383 if not has_superadmin_permission(apiuser):
1384 if not has_superadmin_permission(apiuser):
1384 _perms = ('repository.admin', 'repository.write',)
1385 _perms = ('repository.admin', 'repository.write',)
1385 validate_repo_permissions(apiuser, repoid, repo, _perms)
1386 validate_repo_permissions(apiuser, repoid, repo, _perms)
1386
1387
1387 delete = Optional.extract(delete_keys)
1388 delete = Optional.extract(delete_keys)
1388 try:
1389 try:
1389 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1390 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1390 return {
1391 return {
1391 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1392 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1392 'repository': repo.repo_name
1393 'repository': repo.repo_name
1393 }
1394 }
1394 except Exception:
1395 except Exception:
1395 log.exception(
1396 log.exception(
1396 "Exception occurred while trying to invalidate repo cache")
1397 "Exception occurred while trying to invalidate repo cache")
1397 raise JSONRPCError(
1398 raise JSONRPCError(
1398 'Error occurred during cache invalidation action'
1399 'Error occurred during cache invalidation action'
1399 )
1400 )
1400
1401
1401
1402
1402 #TODO: marcink, change name ?
1403 #TODO: marcink, change name ?
1403 @jsonrpc_method()
1404 @jsonrpc_method()
1404 def lock(request, apiuser, repoid, locked=Optional(None),
1405 def lock(request, apiuser, repoid, locked=Optional(None),
1405 userid=Optional(OAttr('apiuser'))):
1406 userid=Optional(OAttr('apiuser'))):
1406 """
1407 """
1407 Sets the lock state of the specified |repo| by the given user.
1408 Sets the lock state of the specified |repo| by the given user.
1408 From more information, see :ref:`repo-locking`.
1409 From more information, see :ref:`repo-locking`.
1409
1410
1410 * If the ``userid`` option is not set, the repository is locked to the
1411 * If the ``userid`` option is not set, the repository is locked to the
1411 user who called the method.
1412 user who called the method.
1412 * If the ``locked`` parameter is not set, the current lock state of the
1413 * If the ``locked`` parameter is not set, the current lock state of the
1413 repository is displayed.
1414 repository is displayed.
1414
1415
1415 This command can only be run using an |authtoken| with admin rights to
1416 This command can only be run using an |authtoken| with admin rights to
1416 the specified repository.
1417 the specified repository.
1417
1418
1418 This command takes the following options:
1419 This command takes the following options:
1419
1420
1420 :param apiuser: This is filled automatically from the |authtoken|.
1421 :param apiuser: This is filled automatically from the |authtoken|.
1421 :type apiuser: AuthUser
1422 :type apiuser: AuthUser
1422 :param repoid: Sets the repository name or repository ID.
1423 :param repoid: Sets the repository name or repository ID.
1423 :type repoid: str or int
1424 :type repoid: str or int
1424 :param locked: Sets the lock state.
1425 :param locked: Sets the lock state.
1425 :type locked: Optional(``True`` | ``False``)
1426 :type locked: Optional(``True`` | ``False``)
1426 :param userid: Set the repository lock to this user.
1427 :param userid: Set the repository lock to this user.
1427 :type userid: Optional(str or int)
1428 :type userid: Optional(str or int)
1428
1429
1429 Example error output:
1430 Example error output:
1430
1431
1431 .. code-block:: bash
1432 .. code-block:: bash
1432
1433
1433 id : <id_given_in_input>
1434 id : <id_given_in_input>
1434 result : {
1435 result : {
1435 'repo': '<reponame>',
1436 'repo': '<reponame>',
1436 'locked': <bool: lock state>,
1437 'locked': <bool: lock state>,
1437 'locked_since': <int: lock timestamp>,
1438 'locked_since': <int: lock timestamp>,
1438 'locked_by': <username of person who made the lock>,
1439 'locked_by': <username of person who made the lock>,
1439 'lock_reason': <str: reason for locking>,
1440 'lock_reason': <str: reason for locking>,
1440 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1441 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1441 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1442 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1442 or
1443 or
1443 'msg': 'Repo `<repository name>` not locked.'
1444 'msg': 'Repo `<repository name>` not locked.'
1444 or
1445 or
1445 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1446 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1446 }
1447 }
1447 error : null
1448 error : null
1448
1449
1449 Example error output:
1450 Example error output:
1450
1451
1451 .. code-block:: bash
1452 .. code-block:: bash
1452
1453
1453 id : <id_given_in_input>
1454 id : <id_given_in_input>
1454 result : null
1455 result : null
1455 error : {
1456 error : {
1456 'Error occurred locking repository `<reponame>`'
1457 'Error occurred locking repository `<reponame>`'
1457 }
1458 }
1458 """
1459 """
1459
1460
1460 repo = get_repo_or_error(repoid)
1461 repo = get_repo_or_error(repoid)
1461 if not has_superadmin_permission(apiuser):
1462 if not has_superadmin_permission(apiuser):
1462 # check if we have at least write permission for this repo !
1463 # check if we have at least write permission for this repo !
1463 _perms = ('repository.admin', 'repository.write',)
1464 _perms = ('repository.admin', 'repository.write',)
1464 validate_repo_permissions(apiuser, repoid, repo, _perms)
1465 validate_repo_permissions(apiuser, repoid, repo, _perms)
1465
1466
1466 # make sure normal user does not pass someone else userid,
1467 # make sure normal user does not pass someone else userid,
1467 # he is not allowed to do that
1468 # he is not allowed to do that
1468 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1469 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1469 raise JSONRPCError('userid is not the same as your user')
1470 raise JSONRPCError('userid is not the same as your user')
1470
1471
1471 if isinstance(userid, Optional):
1472 if isinstance(userid, Optional):
1472 userid = apiuser.user_id
1473 userid = apiuser.user_id
1473
1474
1474 user = get_user_or_error(userid)
1475 user = get_user_or_error(userid)
1475
1476
1476 if isinstance(locked, Optional):
1477 if isinstance(locked, Optional):
1477 lockobj = repo.locked
1478 lockobj = repo.locked
1478
1479
1479 if lockobj[0] is None:
1480 if lockobj[0] is None:
1480 _d = {
1481 _d = {
1481 'repo': repo.repo_name,
1482 'repo': repo.repo_name,
1482 'locked': False,
1483 'locked': False,
1483 'locked_since': None,
1484 'locked_since': None,
1484 'locked_by': None,
1485 'locked_by': None,
1485 'lock_reason': None,
1486 'lock_reason': None,
1486 'lock_state_changed': False,
1487 'lock_state_changed': False,
1487 'msg': 'Repo `%s` not locked.' % repo.repo_name
1488 'msg': 'Repo `%s` not locked.' % repo.repo_name
1488 }
1489 }
1489 return _d
1490 return _d
1490 else:
1491 else:
1491 _user_id, _time, _reason = lockobj
1492 _user_id, _time, _reason = lockobj
1492 lock_user = get_user_or_error(userid)
1493 lock_user = get_user_or_error(userid)
1493 _d = {
1494 _d = {
1494 'repo': repo.repo_name,
1495 'repo': repo.repo_name,
1495 'locked': True,
1496 'locked': True,
1496 'locked_since': _time,
1497 'locked_since': _time,
1497 'locked_by': lock_user.username,
1498 'locked_by': lock_user.username,
1498 'lock_reason': _reason,
1499 'lock_reason': _reason,
1499 'lock_state_changed': False,
1500 'lock_state_changed': False,
1500 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1501 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1501 % (repo.repo_name, lock_user.username,
1502 % (repo.repo_name, lock_user.username,
1502 json.dumps(time_to_datetime(_time))))
1503 json.dumps(time_to_datetime(_time))))
1503 }
1504 }
1504 return _d
1505 return _d
1505
1506
1506 # force locked state through a flag
1507 # force locked state through a flag
1507 else:
1508 else:
1508 locked = str2bool(locked)
1509 locked = str2bool(locked)
1509 lock_reason = Repository.LOCK_API
1510 lock_reason = Repository.LOCK_API
1510 try:
1511 try:
1511 if locked:
1512 if locked:
1512 lock_time = time.time()
1513 lock_time = time.time()
1513 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1514 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1514 else:
1515 else:
1515 lock_time = None
1516 lock_time = None
1516 Repository.unlock(repo)
1517 Repository.unlock(repo)
1517 _d = {
1518 _d = {
1518 'repo': repo.repo_name,
1519 'repo': repo.repo_name,
1519 'locked': locked,
1520 'locked': locked,
1520 'locked_since': lock_time,
1521 'locked_since': lock_time,
1521 'locked_by': user.username,
1522 'locked_by': user.username,
1522 'lock_reason': lock_reason,
1523 'lock_reason': lock_reason,
1523 'lock_state_changed': True,
1524 'lock_state_changed': True,
1524 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1525 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1525 % (user.username, repo.repo_name, locked))
1526 % (user.username, repo.repo_name, locked))
1526 }
1527 }
1527 return _d
1528 return _d
1528 except Exception:
1529 except Exception:
1529 log.exception(
1530 log.exception(
1530 "Exception occurred while trying to lock repository")
1531 "Exception occurred while trying to lock repository")
1531 raise JSONRPCError(
1532 raise JSONRPCError(
1532 'Error occurred locking repository `%s`' % repo.repo_name
1533 'Error occurred locking repository `%s`' % repo.repo_name
1533 )
1534 )
1534
1535
1535
1536
1536 @jsonrpc_method()
1537 @jsonrpc_method()
1537 def comment_commit(
1538 def comment_commit(
1538 request, apiuser, repoid, commit_id, message, status=Optional(None),
1539 request, apiuser, repoid, commit_id, message, status=Optional(None),
1539 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1540 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1540 resolves_comment_id=Optional(None),
1541 resolves_comment_id=Optional(None),
1541 userid=Optional(OAttr('apiuser'))):
1542 userid=Optional(OAttr('apiuser'))):
1542 """
1543 """
1543 Set a commit comment, and optionally change the status of the commit.
1544 Set a commit comment, and optionally change the status of the commit.
1544
1545
1545 :param apiuser: This is filled automatically from the |authtoken|.
1546 :param apiuser: This is filled automatically from the |authtoken|.
1546 :type apiuser: AuthUser
1547 :type apiuser: AuthUser
1547 :param repoid: Set the repository name or repository ID.
1548 :param repoid: Set the repository name or repository ID.
1548 :type repoid: str or int
1549 :type repoid: str or int
1549 :param commit_id: Specify the commit_id for which to set a comment.
1550 :param commit_id: Specify the commit_id for which to set a comment.
1550 :type commit_id: str
1551 :type commit_id: str
1551 :param message: The comment text.
1552 :param message: The comment text.
1552 :type message: str
1553 :type message: str
1553 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1554 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1554 'approved', 'rejected', 'under_review'
1555 'approved', 'rejected', 'under_review'
1555 :type status: str
1556 :type status: str
1556 :param comment_type: Comment type, one of: 'note', 'todo'
1557 :param comment_type: Comment type, one of: 'note', 'todo'
1557 :type comment_type: Optional(str), default: 'note'
1558 :type comment_type: Optional(str), default: 'note'
1558 :param userid: Set the user name of the comment creator.
1559 :param userid: Set the user name of the comment creator.
1559 :type userid: Optional(str or int)
1560 :type userid: Optional(str or int)
1560
1561
1561 Example error output:
1562 Example error output:
1562
1563
1563 .. code-block:: bash
1564 .. code-block:: bash
1564
1565
1565 {
1566 {
1566 "id" : <id_given_in_input>,
1567 "id" : <id_given_in_input>,
1567 "result" : {
1568 "result" : {
1568 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1569 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1569 "status_change": null or <status>,
1570 "status_change": null or <status>,
1570 "success": true
1571 "success": true
1571 },
1572 },
1572 "error" : null
1573 "error" : null
1573 }
1574 }
1574
1575
1575 """
1576 """
1576 repo = get_repo_or_error(repoid)
1577 repo = get_repo_or_error(repoid)
1577 if not has_superadmin_permission(apiuser):
1578 if not has_superadmin_permission(apiuser):
1578 _perms = ('repository.read', 'repository.write', 'repository.admin')
1579 _perms = ('repository.read', 'repository.write', 'repository.admin')
1579 validate_repo_permissions(apiuser, repoid, repo, _perms)
1580 validate_repo_permissions(apiuser, repoid, repo, _perms)
1580
1581
1581 try:
1582 try:
1582 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1583 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1583 except Exception as e:
1584 except Exception as e:
1584 log.exception('Failed to fetch commit')
1585 log.exception('Failed to fetch commit')
1585 raise JSONRPCError(safe_str(e))
1586 raise JSONRPCError(safe_str(e))
1586
1587
1587 if isinstance(userid, Optional):
1588 if isinstance(userid, Optional):
1588 userid = apiuser.user_id
1589 userid = apiuser.user_id
1589
1590
1590 user = get_user_or_error(userid)
1591 user = get_user_or_error(userid)
1591 status = Optional.extract(status)
1592 status = Optional.extract(status)
1592 comment_type = Optional.extract(comment_type)
1593 comment_type = Optional.extract(comment_type)
1593 resolves_comment_id = Optional.extract(resolves_comment_id)
1594 resolves_comment_id = Optional.extract(resolves_comment_id)
1594
1595
1595 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1596 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1596 if status and status not in allowed_statuses:
1597 if status and status not in allowed_statuses:
1597 raise JSONRPCError('Bad status, must be on '
1598 raise JSONRPCError('Bad status, must be on '
1598 'of %s got %s' % (allowed_statuses, status,))
1599 'of %s got %s' % (allowed_statuses, status,))
1599
1600
1600 if resolves_comment_id:
1601 if resolves_comment_id:
1601 comment = ChangesetComment.get(resolves_comment_id)
1602 comment = ChangesetComment.get(resolves_comment_id)
1602 if not comment:
1603 if not comment:
1603 raise JSONRPCError(
1604 raise JSONRPCError(
1604 'Invalid resolves_comment_id `%s` for this commit.'
1605 'Invalid resolves_comment_id `%s` for this commit.'
1605 % resolves_comment_id)
1606 % resolves_comment_id)
1606 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1607 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1607 raise JSONRPCError(
1608 raise JSONRPCError(
1608 'Comment `%s` is wrong type for setting status to resolved.'
1609 'Comment `%s` is wrong type for setting status to resolved.'
1609 % resolves_comment_id)
1610 % resolves_comment_id)
1610
1611
1611 try:
1612 try:
1612 rc_config = SettingsModel().get_all_settings()
1613 rc_config = SettingsModel().get_all_settings()
1613 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1614 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1614 status_change_label = ChangesetStatus.get_status_lbl(status)
1615 status_change_label = ChangesetStatus.get_status_lbl(status)
1615 comment = CommentsModel().create(
1616 comment = CommentsModel().create(
1616 message, repo, user, commit_id=commit_id,
1617 message, repo, user, commit_id=commit_id,
1617 status_change=status_change_label,
1618 status_change=status_change_label,
1618 status_change_type=status,
1619 status_change_type=status,
1619 renderer=renderer,
1620 renderer=renderer,
1620 comment_type=comment_type,
1621 comment_type=comment_type,
1621 resolves_comment_id=resolves_comment_id,
1622 resolves_comment_id=resolves_comment_id,
1622 auth_user=apiuser
1623 auth_user=apiuser
1623 )
1624 )
1624 if status:
1625 if status:
1625 # also do a status change
1626 # also do a status change
1626 try:
1627 try:
1627 ChangesetStatusModel().set_status(
1628 ChangesetStatusModel().set_status(
1628 repo, status, user, comment, revision=commit_id,
1629 repo, status, user, comment, revision=commit_id,
1629 dont_allow_on_closed_pull_request=True
1630 dont_allow_on_closed_pull_request=True
1630 )
1631 )
1631 except StatusChangeOnClosedPullRequestError:
1632 except StatusChangeOnClosedPullRequestError:
1632 log.exception(
1633 log.exception(
1633 "Exception occurred while trying to change repo commit status")
1634 "Exception occurred while trying to change repo commit status")
1634 msg = ('Changing status on a changeset associated with '
1635 msg = ('Changing status on a changeset associated with '
1635 'a closed pull request is not allowed')
1636 'a closed pull request is not allowed')
1636 raise JSONRPCError(msg)
1637 raise JSONRPCError(msg)
1637
1638
1638 Session().commit()
1639 Session().commit()
1639 return {
1640 return {
1640 'msg': (
1641 'msg': (
1641 'Commented on commit `%s` for repository `%s`' % (
1642 'Commented on commit `%s` for repository `%s`' % (
1642 comment.revision, repo.repo_name)),
1643 comment.revision, repo.repo_name)),
1643 'status_change': status,
1644 'status_change': status,
1644 'success': True,
1645 'success': True,
1645 }
1646 }
1646 except JSONRPCError:
1647 except JSONRPCError:
1647 # catch any inside errors, and re-raise them to prevent from
1648 # catch any inside errors, and re-raise them to prevent from
1648 # below global catch to silence them
1649 # below global catch to silence them
1649 raise
1650 raise
1650 except Exception:
1651 except Exception:
1651 log.exception("Exception occurred while trying to comment on commit")
1652 log.exception("Exception occurred while trying to comment on commit")
1652 raise JSONRPCError(
1653 raise JSONRPCError(
1653 'failed to set comment on repository `%s`' % (repo.repo_name,)
1654 'failed to set comment on repository `%s`' % (repo.repo_name,)
1654 )
1655 )
1655
1656
1656
1657
1657 @jsonrpc_method()
1658 @jsonrpc_method()
1658 def get_repo_comments(request, apiuser, repoid,
1659 def get_repo_comments(request, apiuser, repoid,
1659 commit_id=Optional(None), comment_type=Optional(None),
1660 commit_id=Optional(None), comment_type=Optional(None),
1660 userid=Optional(None)):
1661 userid=Optional(None)):
1661 """
1662 """
1662 Get all comments for a repository
1663 Get all comments for a repository
1663
1664
1664 :param apiuser: This is filled automatically from the |authtoken|.
1665 :param apiuser: This is filled automatically from the |authtoken|.
1665 :type apiuser: AuthUser
1666 :type apiuser: AuthUser
1666 :param repoid: Set the repository name or repository ID.
1667 :param repoid: Set the repository name or repository ID.
1667 :type repoid: str or int
1668 :type repoid: str or int
1668 :param commit_id: Optionally filter the comments by the commit_id
1669 :param commit_id: Optionally filter the comments by the commit_id
1669 :type commit_id: Optional(str), default: None
1670 :type commit_id: Optional(str), default: None
1670 :param comment_type: Optionally filter the comments by the comment_type
1671 :param comment_type: Optionally filter the comments by the comment_type
1671 one of: 'note', 'todo'
1672 one of: 'note', 'todo'
1672 :type comment_type: Optional(str), default: None
1673 :type comment_type: Optional(str), default: None
1673 :param userid: Optionally filter the comments by the author of comment
1674 :param userid: Optionally filter the comments by the author of comment
1674 :type userid: Optional(str or int), Default: None
1675 :type userid: Optional(str or int), Default: None
1675
1676
1676 Example error output:
1677 Example error output:
1677
1678
1678 .. code-block:: bash
1679 .. code-block:: bash
1679
1680
1680 {
1681 {
1681 "id" : <id_given_in_input>,
1682 "id" : <id_given_in_input>,
1682 "result" : [
1683 "result" : [
1683 {
1684 {
1684 "comment_author": <USER_DETAILS>,
1685 "comment_author": <USER_DETAILS>,
1685 "comment_created_on": "2017-02-01T14:38:16.309",
1686 "comment_created_on": "2017-02-01T14:38:16.309",
1686 "comment_f_path": "file.txt",
1687 "comment_f_path": "file.txt",
1687 "comment_id": 282,
1688 "comment_id": 282,
1688 "comment_lineno": "n1",
1689 "comment_lineno": "n1",
1689 "comment_resolved_by": null,
1690 "comment_resolved_by": null,
1690 "comment_status": [],
1691 "comment_status": [],
1691 "comment_text": "This file needs a header",
1692 "comment_text": "This file needs a header",
1692 "comment_type": "todo"
1693 "comment_type": "todo"
1693 }
1694 }
1694 ],
1695 ],
1695 "error" : null
1696 "error" : null
1696 }
1697 }
1697
1698
1698 """
1699 """
1699 repo = get_repo_or_error(repoid)
1700 repo = get_repo_or_error(repoid)
1700 if not has_superadmin_permission(apiuser):
1701 if not has_superadmin_permission(apiuser):
1701 _perms = ('repository.read', 'repository.write', 'repository.admin')
1702 _perms = ('repository.read', 'repository.write', 'repository.admin')
1702 validate_repo_permissions(apiuser, repoid, repo, _perms)
1703 validate_repo_permissions(apiuser, repoid, repo, _perms)
1703
1704
1704 commit_id = Optional.extract(commit_id)
1705 commit_id = Optional.extract(commit_id)
1705
1706
1706 userid = Optional.extract(userid)
1707 userid = Optional.extract(userid)
1707 if userid:
1708 if userid:
1708 user = get_user_or_error(userid)
1709 user = get_user_or_error(userid)
1709 else:
1710 else:
1710 user = None
1711 user = None
1711
1712
1712 comment_type = Optional.extract(comment_type)
1713 comment_type = Optional.extract(comment_type)
1713 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1714 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1714 raise JSONRPCError(
1715 raise JSONRPCError(
1715 'comment_type must be one of `{}` got {}'.format(
1716 'comment_type must be one of `{}` got {}'.format(
1716 ChangesetComment.COMMENT_TYPES, comment_type)
1717 ChangesetComment.COMMENT_TYPES, comment_type)
1717 )
1718 )
1718
1719
1719 comments = CommentsModel().get_repository_comments(
1720 comments = CommentsModel().get_repository_comments(
1720 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1721 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1721 return comments
1722 return comments
1722
1723
1723
1724
1724 @jsonrpc_method()
1725 @jsonrpc_method()
1725 def grant_user_permission(request, apiuser, repoid, userid, perm):
1726 def grant_user_permission(request, apiuser, repoid, userid, perm):
1726 """
1727 """
1727 Grant permissions for the specified user on the given repository,
1728 Grant permissions for the specified user on the given repository,
1728 or update existing permissions if found.
1729 or update existing permissions if found.
1729
1730
1730 This command can only be run using an |authtoken| with admin
1731 This command can only be run using an |authtoken| with admin
1731 permissions on the |repo|.
1732 permissions on the |repo|.
1732
1733
1733 :param apiuser: This is filled automatically from the |authtoken|.
1734 :param apiuser: This is filled automatically from the |authtoken|.
1734 :type apiuser: AuthUser
1735 :type apiuser: AuthUser
1735 :param repoid: Set the repository name or repository ID.
1736 :param repoid: Set the repository name or repository ID.
1736 :type repoid: str or int
1737 :type repoid: str or int
1737 :param userid: Set the user name.
1738 :param userid: Set the user name.
1738 :type userid: str
1739 :type userid: str
1739 :param perm: Set the user permissions, using the following format
1740 :param perm: Set the user permissions, using the following format
1740 ``(repository.(none|read|write|admin))``
1741 ``(repository.(none|read|write|admin))``
1741 :type perm: str
1742 :type perm: str
1742
1743
1743 Example output:
1744 Example output:
1744
1745
1745 .. code-block:: bash
1746 .. code-block:: bash
1746
1747
1747 id : <id_given_in_input>
1748 id : <id_given_in_input>
1748 result: {
1749 result: {
1749 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1750 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1750 "success": true
1751 "success": true
1751 }
1752 }
1752 error: null
1753 error: null
1753 """
1754 """
1754
1755
1755 repo = get_repo_or_error(repoid)
1756 repo = get_repo_or_error(repoid)
1756 user = get_user_or_error(userid)
1757 user = get_user_or_error(userid)
1757 perm = get_perm_or_error(perm)
1758 perm = get_perm_or_error(perm)
1758 if not has_superadmin_permission(apiuser):
1759 if not has_superadmin_permission(apiuser):
1759 _perms = ('repository.admin',)
1760 _perms = ('repository.admin',)
1760 validate_repo_permissions(apiuser, repoid, repo, _perms)
1761 validate_repo_permissions(apiuser, repoid, repo, _perms)
1761
1762
1762 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1763 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1763 try:
1764 try:
1764 changes = RepoModel().update_permissions(
1765 changes = RepoModel().update_permissions(
1765 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1766 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1766
1767
1767 action_data = {
1768 action_data = {
1768 'added': changes['added'],
1769 'added': changes['added'],
1769 'updated': changes['updated'],
1770 'updated': changes['updated'],
1770 'deleted': changes['deleted'],
1771 'deleted': changes['deleted'],
1771 }
1772 }
1772 audit_logger.store_api(
1773 audit_logger.store_api(
1773 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1774 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1775 Session().commit()
1776 PermissionModel().flush_user_permission_caches(changes)
1774
1777
1775 Session().commit()
1776 return {
1778 return {
1777 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1779 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1778 perm.permission_name, user.username, repo.repo_name
1780 perm.permission_name, user.username, repo.repo_name
1779 ),
1781 ),
1780 'success': True
1782 'success': True
1781 }
1783 }
1782 except Exception:
1784 except Exception:
1783 log.exception("Exception occurred while trying edit permissions for repo")
1785 log.exception("Exception occurred while trying edit permissions for repo")
1784 raise JSONRPCError(
1786 raise JSONRPCError(
1785 'failed to edit permission for user: `%s` in repo: `%s`' % (
1787 'failed to edit permission for user: `%s` in repo: `%s`' % (
1786 userid, repoid
1788 userid, repoid
1787 )
1789 )
1788 )
1790 )
1789
1791
1790
1792
1791 @jsonrpc_method()
1793 @jsonrpc_method()
1792 def revoke_user_permission(request, apiuser, repoid, userid):
1794 def revoke_user_permission(request, apiuser, repoid, userid):
1793 """
1795 """
1794 Revoke permission for a user on the specified repository.
1796 Revoke permission for a user on the specified repository.
1795
1797
1796 This command can only be run using an |authtoken| with admin
1798 This command can only be run using an |authtoken| with admin
1797 permissions on the |repo|.
1799 permissions on the |repo|.
1798
1800
1799 :param apiuser: This is filled automatically from the |authtoken|.
1801 :param apiuser: This is filled automatically from the |authtoken|.
1800 :type apiuser: AuthUser
1802 :type apiuser: AuthUser
1801 :param repoid: Set the repository name or repository ID.
1803 :param repoid: Set the repository name or repository ID.
1802 :type repoid: str or int
1804 :type repoid: str or int
1803 :param userid: Set the user name of revoked user.
1805 :param userid: Set the user name of revoked user.
1804 :type userid: str or int
1806 :type userid: str or int
1805
1807
1806 Example error output:
1808 Example error output:
1807
1809
1808 .. code-block:: bash
1810 .. code-block:: bash
1809
1811
1810 id : <id_given_in_input>
1812 id : <id_given_in_input>
1811 result: {
1813 result: {
1812 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1814 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1813 "success": true
1815 "success": true
1814 }
1816 }
1815 error: null
1817 error: null
1816 """
1818 """
1817
1819
1818 repo = get_repo_or_error(repoid)
1820 repo = get_repo_or_error(repoid)
1819 user = get_user_or_error(userid)
1821 user = get_user_or_error(userid)
1820 if not has_superadmin_permission(apiuser):
1822 if not has_superadmin_permission(apiuser):
1821 _perms = ('repository.admin',)
1823 _perms = ('repository.admin',)
1822 validate_repo_permissions(apiuser, repoid, repo, _perms)
1824 validate_repo_permissions(apiuser, repoid, repo, _perms)
1823
1825
1824 perm_deletions = [[user.user_id, None, "user"]]
1826 perm_deletions = [[user.user_id, None, "user"]]
1825 try:
1827 try:
1826 changes = RepoModel().update_permissions(
1828 changes = RepoModel().update_permissions(
1827 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1829 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1828
1830
1829 action_data = {
1831 action_data = {
1830 'added': changes['added'],
1832 'added': changes['added'],
1831 'updated': changes['updated'],
1833 'updated': changes['updated'],
1832 'deleted': changes['deleted'],
1834 'deleted': changes['deleted'],
1833 }
1835 }
1834 audit_logger.store_api(
1836 audit_logger.store_api(
1835 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1837 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1838 Session().commit()
1839 PermissionModel().flush_user_permission_caches(changes)
1836
1840
1837 Session().commit()
1838 return {
1841 return {
1839 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1842 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1840 user.username, repo.repo_name
1843 user.username, repo.repo_name
1841 ),
1844 ),
1842 'success': True
1845 'success': True
1843 }
1846 }
1844 except Exception:
1847 except Exception:
1845 log.exception("Exception occurred while trying revoke permissions to repo")
1848 log.exception("Exception occurred while trying revoke permissions to repo")
1846 raise JSONRPCError(
1849 raise JSONRPCError(
1847 'failed to edit permission for user: `%s` in repo: `%s`' % (
1850 'failed to edit permission for user: `%s` in repo: `%s`' % (
1848 userid, repoid
1851 userid, repoid
1849 )
1852 )
1850 )
1853 )
1851
1854
1852
1855
1853 @jsonrpc_method()
1856 @jsonrpc_method()
1854 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1857 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1855 """
1858 """
1856 Grant permission for a user group on the specified repository,
1859 Grant permission for a user group on the specified repository,
1857 or update existing permissions.
1860 or update existing permissions.
1858
1861
1859 This command can only be run using an |authtoken| with admin
1862 This command can only be run using an |authtoken| with admin
1860 permissions on the |repo|.
1863 permissions on the |repo|.
1861
1864
1862 :param apiuser: This is filled automatically from the |authtoken|.
1865 :param apiuser: This is filled automatically from the |authtoken|.
1863 :type apiuser: AuthUser
1866 :type apiuser: AuthUser
1864 :param repoid: Set the repository name or repository ID.
1867 :param repoid: Set the repository name or repository ID.
1865 :type repoid: str or int
1868 :type repoid: str or int
1866 :param usergroupid: Specify the ID of the user group.
1869 :param usergroupid: Specify the ID of the user group.
1867 :type usergroupid: str or int
1870 :type usergroupid: str or int
1868 :param perm: Set the user group permissions using the following
1871 :param perm: Set the user group permissions using the following
1869 format: (repository.(none|read|write|admin))
1872 format: (repository.(none|read|write|admin))
1870 :type perm: str
1873 :type perm: str
1871
1874
1872 Example output:
1875 Example output:
1873
1876
1874 .. code-block:: bash
1877 .. code-block:: bash
1875
1878
1876 id : <id_given_in_input>
1879 id : <id_given_in_input>
1877 result : {
1880 result : {
1878 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1881 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1879 "success": true
1882 "success": true
1880
1883
1881 }
1884 }
1882 error : null
1885 error : null
1883
1886
1884 Example error output:
1887 Example error output:
1885
1888
1886 .. code-block:: bash
1889 .. code-block:: bash
1887
1890
1888 id : <id_given_in_input>
1891 id : <id_given_in_input>
1889 result : null
1892 result : null
1890 error : {
1893 error : {
1891 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1894 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1892 }
1895 }
1893
1896
1894 """
1897 """
1895
1898
1896 repo = get_repo_or_error(repoid)
1899 repo = get_repo_or_error(repoid)
1897 perm = get_perm_or_error(perm)
1900 perm = get_perm_or_error(perm)
1898 if not has_superadmin_permission(apiuser):
1901 if not has_superadmin_permission(apiuser):
1899 _perms = ('repository.admin',)
1902 _perms = ('repository.admin',)
1900 validate_repo_permissions(apiuser, repoid, repo, _perms)
1903 validate_repo_permissions(apiuser, repoid, repo, _perms)
1901
1904
1902 user_group = get_user_group_or_error(usergroupid)
1905 user_group = get_user_group_or_error(usergroupid)
1903 if not has_superadmin_permission(apiuser):
1906 if not has_superadmin_permission(apiuser):
1904 # check if we have at least read permission for this user group !
1907 # check if we have at least read permission for this user group !
1905 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1908 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1906 if not HasUserGroupPermissionAnyApi(*_perms)(
1909 if not HasUserGroupPermissionAnyApi(*_perms)(
1907 user=apiuser, user_group_name=user_group.users_group_name):
1910 user=apiuser, user_group_name=user_group.users_group_name):
1908 raise JSONRPCError(
1911 raise JSONRPCError(
1909 'user group `%s` does not exist' % (usergroupid,))
1912 'user group `%s` does not exist' % (usergroupid,))
1910
1913
1911 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1914 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1912 try:
1915 try:
1913 changes = RepoModel().update_permissions(
1916 changes = RepoModel().update_permissions(
1914 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1917 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1915 action_data = {
1918 action_data = {
1916 'added': changes['added'],
1919 'added': changes['added'],
1917 'updated': changes['updated'],
1920 'updated': changes['updated'],
1918 'deleted': changes['deleted'],
1921 'deleted': changes['deleted'],
1919 }
1922 }
1920 audit_logger.store_api(
1923 audit_logger.store_api(
1921 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1924 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1925 Session().commit()
1926 PermissionModel().flush_user_permission_caches(changes)
1922
1927
1923 Session().commit()
1924 return {
1928 return {
1925 'msg': 'Granted perm: `%s` for user group: `%s` in '
1929 'msg': 'Granted perm: `%s` for user group: `%s` in '
1926 'repo: `%s`' % (
1930 'repo: `%s`' % (
1927 perm.permission_name, user_group.users_group_name,
1931 perm.permission_name, user_group.users_group_name,
1928 repo.repo_name
1932 repo.repo_name
1929 ),
1933 ),
1930 'success': True
1934 'success': True
1931 }
1935 }
1932 except Exception:
1936 except Exception:
1933 log.exception(
1937 log.exception(
1934 "Exception occurred while trying change permission on repo")
1938 "Exception occurred while trying change permission on repo")
1935 raise JSONRPCError(
1939 raise JSONRPCError(
1936 'failed to edit permission for user group: `%s` in '
1940 'failed to edit permission for user group: `%s` in '
1937 'repo: `%s`' % (
1941 'repo: `%s`' % (
1938 usergroupid, repo.repo_name
1942 usergroupid, repo.repo_name
1939 )
1943 )
1940 )
1944 )
1941
1945
1942
1946
1943 @jsonrpc_method()
1947 @jsonrpc_method()
1944 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1948 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1945 """
1949 """
1946 Revoke the permissions of a user group on a given repository.
1950 Revoke the permissions of a user group on a given repository.
1947
1951
1948 This command can only be run using an |authtoken| with admin
1952 This command can only be run using an |authtoken| with admin
1949 permissions on the |repo|.
1953 permissions on the |repo|.
1950
1954
1951 :param apiuser: This is filled automatically from the |authtoken|.
1955 :param apiuser: This is filled automatically from the |authtoken|.
1952 :type apiuser: AuthUser
1956 :type apiuser: AuthUser
1953 :param repoid: Set the repository name or repository ID.
1957 :param repoid: Set the repository name or repository ID.
1954 :type repoid: str or int
1958 :type repoid: str or int
1955 :param usergroupid: Specify the user group ID.
1959 :param usergroupid: Specify the user group ID.
1956 :type usergroupid: str or int
1960 :type usergroupid: str or int
1957
1961
1958 Example output:
1962 Example output:
1959
1963
1960 .. code-block:: bash
1964 .. code-block:: bash
1961
1965
1962 id : <id_given_in_input>
1966 id : <id_given_in_input>
1963 result: {
1967 result: {
1964 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1968 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1965 "success": true
1969 "success": true
1966 }
1970 }
1967 error: null
1971 error: null
1968 """
1972 """
1969
1973
1970 repo = get_repo_or_error(repoid)
1974 repo = get_repo_or_error(repoid)
1971 if not has_superadmin_permission(apiuser):
1975 if not has_superadmin_permission(apiuser):
1972 _perms = ('repository.admin',)
1976 _perms = ('repository.admin',)
1973 validate_repo_permissions(apiuser, repoid, repo, _perms)
1977 validate_repo_permissions(apiuser, repoid, repo, _perms)
1974
1978
1975 user_group = get_user_group_or_error(usergroupid)
1979 user_group = get_user_group_or_error(usergroupid)
1976 if not has_superadmin_permission(apiuser):
1980 if not has_superadmin_permission(apiuser):
1977 # check if we have at least read permission for this user group !
1981 # check if we have at least read permission for this user group !
1978 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1982 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1979 if not HasUserGroupPermissionAnyApi(*_perms)(
1983 if not HasUserGroupPermissionAnyApi(*_perms)(
1980 user=apiuser, user_group_name=user_group.users_group_name):
1984 user=apiuser, user_group_name=user_group.users_group_name):
1981 raise JSONRPCError(
1985 raise JSONRPCError(
1982 'user group `%s` does not exist' % (usergroupid,))
1986 'user group `%s` does not exist' % (usergroupid,))
1983
1987
1984 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1988 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1985 try:
1989 try:
1986 changes = RepoModel().update_permissions(
1990 changes = RepoModel().update_permissions(
1987 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1991 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1988 action_data = {
1992 action_data = {
1989 'added': changes['added'],
1993 'added': changes['added'],
1990 'updated': changes['updated'],
1994 'updated': changes['updated'],
1991 'deleted': changes['deleted'],
1995 'deleted': changes['deleted'],
1992 }
1996 }
1993 audit_logger.store_api(
1997 audit_logger.store_api(
1994 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1998 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1999 Session().commit()
2000 PermissionModel().flush_user_permission_caches(changes)
1995
2001
1996 Session().commit()
1997 return {
2002 return {
1998 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2003 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1999 user_group.users_group_name, repo.repo_name
2004 user_group.users_group_name, repo.repo_name
2000 ),
2005 ),
2001 'success': True
2006 'success': True
2002 }
2007 }
2003 except Exception:
2008 except Exception:
2004 log.exception("Exception occurred while trying revoke "
2009 log.exception("Exception occurred while trying revoke "
2005 "user group permission on repo")
2010 "user group permission on repo")
2006 raise JSONRPCError(
2011 raise JSONRPCError(
2007 'failed to edit permission for user group: `%s` in '
2012 'failed to edit permission for user group: `%s` in '
2008 'repo: `%s`' % (
2013 'repo: `%s`' % (
2009 user_group.users_group_name, repo.repo_name
2014 user_group.users_group_name, repo.repo_name
2010 )
2015 )
2011 )
2016 )
2012
2017
2013
2018
2014 @jsonrpc_method()
2019 @jsonrpc_method()
2015 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2020 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2016 """
2021 """
2017 Triggers a pull on the given repository from a remote location. You
2022 Triggers a pull on the given repository from a remote location. You
2018 can use this to keep remote repositories up-to-date.
2023 can use this to keep remote repositories up-to-date.
2019
2024
2020 This command can only be run using an |authtoken| with admin
2025 This command can only be run using an |authtoken| with admin
2021 rights to the specified repository. For more information,
2026 rights to the specified repository. For more information,
2022 see :ref:`config-token-ref`.
2027 see :ref:`config-token-ref`.
2023
2028
2024 This command takes the following options:
2029 This command takes the following options:
2025
2030
2026 :param apiuser: This is filled automatically from the |authtoken|.
2031 :param apiuser: This is filled automatically from the |authtoken|.
2027 :type apiuser: AuthUser
2032 :type apiuser: AuthUser
2028 :param repoid: The repository name or repository ID.
2033 :param repoid: The repository name or repository ID.
2029 :type repoid: str or int
2034 :type repoid: str or int
2030 :param remote_uri: Optional remote URI to pass in for pull
2035 :param remote_uri: Optional remote URI to pass in for pull
2031 :type remote_uri: str
2036 :type remote_uri: str
2032
2037
2033 Example output:
2038 Example output:
2034
2039
2035 .. code-block:: bash
2040 .. code-block:: bash
2036
2041
2037 id : <id_given_in_input>
2042 id : <id_given_in_input>
2038 result : {
2043 result : {
2039 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2044 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2040 "repository": "<repository name>"
2045 "repository": "<repository name>"
2041 }
2046 }
2042 error : null
2047 error : null
2043
2048
2044 Example error output:
2049 Example error output:
2045
2050
2046 .. code-block:: bash
2051 .. code-block:: bash
2047
2052
2048 id : <id_given_in_input>
2053 id : <id_given_in_input>
2049 result : null
2054 result : null
2050 error : {
2055 error : {
2051 "Unable to push changes from `<remote_url>`"
2056 "Unable to push changes from `<remote_url>`"
2052 }
2057 }
2053
2058
2054 """
2059 """
2055
2060
2056 repo = get_repo_or_error(repoid)
2061 repo = get_repo_or_error(repoid)
2057 remote_uri = Optional.extract(remote_uri)
2062 remote_uri = Optional.extract(remote_uri)
2058 remote_uri_display = remote_uri or repo.clone_uri_hidden
2063 remote_uri_display = remote_uri or repo.clone_uri_hidden
2059 if not has_superadmin_permission(apiuser):
2064 if not has_superadmin_permission(apiuser):
2060 _perms = ('repository.admin',)
2065 _perms = ('repository.admin',)
2061 validate_repo_permissions(apiuser, repoid, repo, _perms)
2066 validate_repo_permissions(apiuser, repoid, repo, _perms)
2062
2067
2063 try:
2068 try:
2064 ScmModel().pull_changes(
2069 ScmModel().pull_changes(
2065 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2070 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2066 return {
2071 return {
2067 'msg': 'Pulled from url `%s` on repo `%s`' % (
2072 'msg': 'Pulled from url `%s` on repo `%s`' % (
2068 remote_uri_display, repo.repo_name),
2073 remote_uri_display, repo.repo_name),
2069 'repository': repo.repo_name
2074 'repository': repo.repo_name
2070 }
2075 }
2071 except Exception:
2076 except Exception:
2072 log.exception("Exception occurred while trying to "
2077 log.exception("Exception occurred while trying to "
2073 "pull changes from remote location")
2078 "pull changes from remote location")
2074 raise JSONRPCError(
2079 raise JSONRPCError(
2075 'Unable to pull changes from `%s`' % remote_uri_display
2080 'Unable to pull changes from `%s`' % remote_uri_display
2076 )
2081 )
2077
2082
2078
2083
2079 @jsonrpc_method()
2084 @jsonrpc_method()
2080 def strip(request, apiuser, repoid, revision, branch):
2085 def strip(request, apiuser, repoid, revision, branch):
2081 """
2086 """
2082 Strips the given revision from the specified repository.
2087 Strips the given revision from the specified repository.
2083
2088
2084 * This will remove the revision and all of its decendants.
2089 * This will remove the revision and all of its decendants.
2085
2090
2086 This command can only be run using an |authtoken| with admin rights to
2091 This command can only be run using an |authtoken| with admin rights to
2087 the specified repository.
2092 the specified repository.
2088
2093
2089 This command takes the following options:
2094 This command takes the following options:
2090
2095
2091 :param apiuser: This is filled automatically from the |authtoken|.
2096 :param apiuser: This is filled automatically from the |authtoken|.
2092 :type apiuser: AuthUser
2097 :type apiuser: AuthUser
2093 :param repoid: The repository name or repository ID.
2098 :param repoid: The repository name or repository ID.
2094 :type repoid: str or int
2099 :type repoid: str or int
2095 :param revision: The revision you wish to strip.
2100 :param revision: The revision you wish to strip.
2096 :type revision: str
2101 :type revision: str
2097 :param branch: The branch from which to strip the revision.
2102 :param branch: The branch from which to strip the revision.
2098 :type branch: str
2103 :type branch: str
2099
2104
2100 Example output:
2105 Example output:
2101
2106
2102 .. code-block:: bash
2107 .. code-block:: bash
2103
2108
2104 id : <id_given_in_input>
2109 id : <id_given_in_input>
2105 result : {
2110 result : {
2106 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2111 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2107 "repository": "<repository name>"
2112 "repository": "<repository name>"
2108 }
2113 }
2109 error : null
2114 error : null
2110
2115
2111 Example error output:
2116 Example error output:
2112
2117
2113 .. code-block:: bash
2118 .. code-block:: bash
2114
2119
2115 id : <id_given_in_input>
2120 id : <id_given_in_input>
2116 result : null
2121 result : null
2117 error : {
2122 error : {
2118 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2123 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2119 }
2124 }
2120
2125
2121 """
2126 """
2122
2127
2123 repo = get_repo_or_error(repoid)
2128 repo = get_repo_or_error(repoid)
2124 if not has_superadmin_permission(apiuser):
2129 if not has_superadmin_permission(apiuser):
2125 _perms = ('repository.admin',)
2130 _perms = ('repository.admin',)
2126 validate_repo_permissions(apiuser, repoid, repo, _perms)
2131 validate_repo_permissions(apiuser, repoid, repo, _perms)
2127
2132
2128 try:
2133 try:
2129 ScmModel().strip(repo, revision, branch)
2134 ScmModel().strip(repo, revision, branch)
2130 audit_logger.store_api(
2135 audit_logger.store_api(
2131 'repo.commit.strip', action_data={'commit_id': revision},
2136 'repo.commit.strip', action_data={'commit_id': revision},
2132 repo=repo,
2137 repo=repo,
2133 user=apiuser, commit=True)
2138 user=apiuser, commit=True)
2134
2139
2135 return {
2140 return {
2136 'msg': 'Stripped commit %s from repo `%s`' % (
2141 'msg': 'Stripped commit %s from repo `%s`' % (
2137 revision, repo.repo_name),
2142 revision, repo.repo_name),
2138 'repository': repo.repo_name
2143 'repository': repo.repo_name
2139 }
2144 }
2140 except Exception:
2145 except Exception:
2141 log.exception("Exception while trying to strip")
2146 log.exception("Exception while trying to strip")
2142 raise JSONRPCError(
2147 raise JSONRPCError(
2143 'Unable to strip commit %s from repo `%s`' % (
2148 'Unable to strip commit %s from repo `%s`' % (
2144 revision, repo.repo_name)
2149 revision, repo.repo_name)
2145 )
2150 )
2146
2151
2147
2152
2148 @jsonrpc_method()
2153 @jsonrpc_method()
2149 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2154 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2150 """
2155 """
2151 Returns all settings for a repository. If key is given it only returns the
2156 Returns all settings for a repository. If key is given it only returns the
2152 setting identified by the key or null.
2157 setting identified by the key or null.
2153
2158
2154 :param apiuser: This is filled automatically from the |authtoken|.
2159 :param apiuser: This is filled automatically from the |authtoken|.
2155 :type apiuser: AuthUser
2160 :type apiuser: AuthUser
2156 :param repoid: The repository name or repository id.
2161 :param repoid: The repository name or repository id.
2157 :type repoid: str or int
2162 :type repoid: str or int
2158 :param key: Key of the setting to return.
2163 :param key: Key of the setting to return.
2159 :type: key: Optional(str)
2164 :type: key: Optional(str)
2160
2165
2161 Example output:
2166 Example output:
2162
2167
2163 .. code-block:: bash
2168 .. code-block:: bash
2164
2169
2165 {
2170 {
2166 "error": null,
2171 "error": null,
2167 "id": 237,
2172 "id": 237,
2168 "result": {
2173 "result": {
2169 "extensions_largefiles": true,
2174 "extensions_largefiles": true,
2170 "extensions_evolve": true,
2175 "extensions_evolve": true,
2171 "hooks_changegroup_push_logger": true,
2176 "hooks_changegroup_push_logger": true,
2172 "hooks_changegroup_repo_size": false,
2177 "hooks_changegroup_repo_size": false,
2173 "hooks_outgoing_pull_logger": true,
2178 "hooks_outgoing_pull_logger": true,
2174 "phases_publish": "True",
2179 "phases_publish": "True",
2175 "rhodecode_hg_use_rebase_for_merging": true,
2180 "rhodecode_hg_use_rebase_for_merging": true,
2176 "rhodecode_pr_merge_enabled": true,
2181 "rhodecode_pr_merge_enabled": true,
2177 "rhodecode_use_outdated_comments": true
2182 "rhodecode_use_outdated_comments": true
2178 }
2183 }
2179 }
2184 }
2180 """
2185 """
2181
2186
2182 # Restrict access to this api method to admins only.
2187 # Restrict access to this api method to admins only.
2183 if not has_superadmin_permission(apiuser):
2188 if not has_superadmin_permission(apiuser):
2184 raise JSONRPCForbidden()
2189 raise JSONRPCForbidden()
2185
2190
2186 try:
2191 try:
2187 repo = get_repo_or_error(repoid)
2192 repo = get_repo_or_error(repoid)
2188 settings_model = VcsSettingsModel(repo=repo)
2193 settings_model = VcsSettingsModel(repo=repo)
2189 settings = settings_model.get_global_settings()
2194 settings = settings_model.get_global_settings()
2190 settings.update(settings_model.get_repo_settings())
2195 settings.update(settings_model.get_repo_settings())
2191
2196
2192 # If only a single setting is requested fetch it from all settings.
2197 # If only a single setting is requested fetch it from all settings.
2193 key = Optional.extract(key)
2198 key = Optional.extract(key)
2194 if key is not None:
2199 if key is not None:
2195 settings = settings.get(key, None)
2200 settings = settings.get(key, None)
2196 except Exception:
2201 except Exception:
2197 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2202 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2198 log.exception(msg)
2203 log.exception(msg)
2199 raise JSONRPCError(msg)
2204 raise JSONRPCError(msg)
2200
2205
2201 return settings
2206 return settings
2202
2207
2203
2208
2204 @jsonrpc_method()
2209 @jsonrpc_method()
2205 def set_repo_settings(request, apiuser, repoid, settings):
2210 def set_repo_settings(request, apiuser, repoid, settings):
2206 """
2211 """
2207 Update repository settings. Returns true on success.
2212 Update repository settings. Returns true on success.
2208
2213
2209 :param apiuser: This is filled automatically from the |authtoken|.
2214 :param apiuser: This is filled automatically from the |authtoken|.
2210 :type apiuser: AuthUser
2215 :type apiuser: AuthUser
2211 :param repoid: The repository name or repository id.
2216 :param repoid: The repository name or repository id.
2212 :type repoid: str or int
2217 :type repoid: str or int
2213 :param settings: The new settings for the repository.
2218 :param settings: The new settings for the repository.
2214 :type: settings: dict
2219 :type: settings: dict
2215
2220
2216 Example output:
2221 Example output:
2217
2222
2218 .. code-block:: bash
2223 .. code-block:: bash
2219
2224
2220 {
2225 {
2221 "error": null,
2226 "error": null,
2222 "id": 237,
2227 "id": 237,
2223 "result": true
2228 "result": true
2224 }
2229 }
2225 """
2230 """
2226 # Restrict access to this api method to admins only.
2231 # Restrict access to this api method to admins only.
2227 if not has_superadmin_permission(apiuser):
2232 if not has_superadmin_permission(apiuser):
2228 raise JSONRPCForbidden()
2233 raise JSONRPCForbidden()
2229
2234
2230 if type(settings) is not dict:
2235 if type(settings) is not dict:
2231 raise JSONRPCError('Settings have to be a JSON Object.')
2236 raise JSONRPCError('Settings have to be a JSON Object.')
2232
2237
2233 try:
2238 try:
2234 settings_model = VcsSettingsModel(repo=repoid)
2239 settings_model = VcsSettingsModel(repo=repoid)
2235
2240
2236 # Merge global, repo and incoming settings.
2241 # Merge global, repo and incoming settings.
2237 new_settings = settings_model.get_global_settings()
2242 new_settings = settings_model.get_global_settings()
2238 new_settings.update(settings_model.get_repo_settings())
2243 new_settings.update(settings_model.get_repo_settings())
2239 new_settings.update(settings)
2244 new_settings.update(settings)
2240
2245
2241 # Update the settings.
2246 # Update the settings.
2242 inherit_global_settings = new_settings.get(
2247 inherit_global_settings = new_settings.get(
2243 'inherit_global_settings', False)
2248 'inherit_global_settings', False)
2244 settings_model.create_or_update_repo_settings(
2249 settings_model.create_or_update_repo_settings(
2245 new_settings, inherit_global_settings=inherit_global_settings)
2250 new_settings, inherit_global_settings=inherit_global_settings)
2246 Session().commit()
2251 Session().commit()
2247 except Exception:
2252 except Exception:
2248 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2253 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2249 log.exception(msg)
2254 log.exception(msg)
2250 raise JSONRPCError(msg)
2255 raise JSONRPCError(msg)
2251
2256
2252 # Indicate success.
2257 # Indicate success.
2253 return True
2258 return True
2254
2259
2255
2260
2256 @jsonrpc_method()
2261 @jsonrpc_method()
2257 def maintenance(request, apiuser, repoid):
2262 def maintenance(request, apiuser, repoid):
2258 """
2263 """
2259 Triggers a maintenance on the given repository.
2264 Triggers a maintenance on the given repository.
2260
2265
2261 This command can only be run using an |authtoken| with admin
2266 This command can only be run using an |authtoken| with admin
2262 rights to the specified repository. For more information,
2267 rights to the specified repository. For more information,
2263 see :ref:`config-token-ref`.
2268 see :ref:`config-token-ref`.
2264
2269
2265 This command takes the following options:
2270 This command takes the following options:
2266
2271
2267 :param apiuser: This is filled automatically from the |authtoken|.
2272 :param apiuser: This is filled automatically from the |authtoken|.
2268 :type apiuser: AuthUser
2273 :type apiuser: AuthUser
2269 :param repoid: The repository name or repository ID.
2274 :param repoid: The repository name or repository ID.
2270 :type repoid: str or int
2275 :type repoid: str or int
2271
2276
2272 Example output:
2277 Example output:
2273
2278
2274 .. code-block:: bash
2279 .. code-block:: bash
2275
2280
2276 id : <id_given_in_input>
2281 id : <id_given_in_input>
2277 result : {
2282 result : {
2278 "msg": "executed maintenance command",
2283 "msg": "executed maintenance command",
2279 "executed_actions": [
2284 "executed_actions": [
2280 <action_message>, <action_message2>...
2285 <action_message>, <action_message2>...
2281 ],
2286 ],
2282 "repository": "<repository name>"
2287 "repository": "<repository name>"
2283 }
2288 }
2284 error : null
2289 error : null
2285
2290
2286 Example error output:
2291 Example error output:
2287
2292
2288 .. code-block:: bash
2293 .. code-block:: bash
2289
2294
2290 id : <id_given_in_input>
2295 id : <id_given_in_input>
2291 result : null
2296 result : null
2292 error : {
2297 error : {
2293 "Unable to execute maintenance on `<reponame>`"
2298 "Unable to execute maintenance on `<reponame>`"
2294 }
2299 }
2295
2300
2296 """
2301 """
2297
2302
2298 repo = get_repo_or_error(repoid)
2303 repo = get_repo_or_error(repoid)
2299 if not has_superadmin_permission(apiuser):
2304 if not has_superadmin_permission(apiuser):
2300 _perms = ('repository.admin',)
2305 _perms = ('repository.admin',)
2301 validate_repo_permissions(apiuser, repoid, repo, _perms)
2306 validate_repo_permissions(apiuser, repoid, repo, _perms)
2302
2307
2303 try:
2308 try:
2304 maintenance = repo_maintenance.RepoMaintenance()
2309 maintenance = repo_maintenance.RepoMaintenance()
2305 executed_actions = maintenance.execute(repo)
2310 executed_actions = maintenance.execute(repo)
2306
2311
2307 return {
2312 return {
2308 'msg': 'executed maintenance command',
2313 'msg': 'executed maintenance command',
2309 'executed_actions': executed_actions,
2314 'executed_actions': executed_actions,
2310 'repository': repo.repo_name
2315 'repository': repo.repo_name
2311 }
2316 }
2312 except Exception:
2317 except Exception:
2313 log.exception("Exception occurred while trying to run maintenance")
2318 log.exception("Exception occurred while trying to run maintenance")
2314 raise JSONRPCError(
2319 raise JSONRPCError(
2315 'Unable to execute maintenance on `%s`' % repo.repo_name)
2320 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,754 +1,759 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import logging
22 import logging
23
23
24 from rhodecode.api import JSONRPCValidationError
24 from rhodecode.api import JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
33 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
34 from rhodecode.model.permission import PermissionModel
34 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model import validation_schema
37 from rhodecode.model import validation_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 from rhodecode.model.validation_schema.schemas import repo_group_schema
38
39
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 @jsonrpc_method()
44 @jsonrpc_method()
44 def get_repo_group(request, apiuser, repogroupid):
45 def get_repo_group(request, apiuser, repogroupid):
45 """
46 """
46 Return the specified |repo| group, along with permissions,
47 Return the specified |repo| group, along with permissions,
47 and repositories inside the group
48 and repositories inside the group
48
49
49 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :type apiuser: AuthUser
51 :type apiuser: AuthUser
51 :param repogroupid: Specify the name of ID of the repository group.
52 :param repogroupid: Specify the name of ID of the repository group.
52 :type repogroupid: str or int
53 :type repogroupid: str or int
53
54
54
55
55 Example output:
56 Example output:
56
57
57 .. code-block:: bash
58 .. code-block:: bash
58
59
59 {
60 {
60 "error": null,
61 "error": null,
61 "id": repo-group-id,
62 "id": repo-group-id,
62 "result": {
63 "result": {
63 "group_description": "repo group description",
64 "group_description": "repo group description",
64 "group_id": 14,
65 "group_id": 14,
65 "group_name": "group name",
66 "group_name": "group name",
66 "permissions": [
67 "permissions": [
67 {
68 {
68 "name": "super-admin-username",
69 "name": "super-admin-username",
69 "origin": "super-admin",
70 "origin": "super-admin",
70 "permission": "group.admin",
71 "permission": "group.admin",
71 "type": "user"
72 "type": "user"
72 },
73 },
73 {
74 {
74 "name": "owner-name",
75 "name": "owner-name",
75 "origin": "owner",
76 "origin": "owner",
76 "permission": "group.admin",
77 "permission": "group.admin",
77 "type": "user"
78 "type": "user"
78 },
79 },
79 {
80 {
80 "name": "user-group-name",
81 "name": "user-group-name",
81 "origin": "permission",
82 "origin": "permission",
82 "permission": "group.write",
83 "permission": "group.write",
83 "type": "user_group"
84 "type": "user_group"
84 }
85 }
85 ],
86 ],
86 "owner": "owner-name",
87 "owner": "owner-name",
87 "parent_group": null,
88 "parent_group": null,
88 "repositories": [ repo-list ]
89 "repositories": [ repo-list ]
89 }
90 }
90 }
91 }
91 """
92 """
92
93
93 repo_group = get_repo_group_or_error(repogroupid)
94 repo_group = get_repo_group_or_error(repogroupid)
94 if not has_superadmin_permission(apiuser):
95 if not has_superadmin_permission(apiuser):
95 # check if we have at least read permission for this repo group !
96 # check if we have at least read permission for this repo group !
96 _perms = ('group.admin', 'group.write', 'group.read',)
97 _perms = ('group.admin', 'group.write', 'group.read',)
97 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 user=apiuser, group_name=repo_group.group_name):
99 user=apiuser, group_name=repo_group.group_name):
99 raise JSONRPCError(
100 raise JSONRPCError(
100 'repository group `%s` does not exist' % (repogroupid,))
101 'repository group `%s` does not exist' % (repogroupid,))
101
102
102 permissions = []
103 permissions = []
103 for _user in repo_group.permissions():
104 for _user in repo_group.permissions():
104 user_data = {
105 user_data = {
105 'name': _user.username,
106 'name': _user.username,
106 'permission': _user.permission,
107 'permission': _user.permission,
107 'origin': get_origin(_user),
108 'origin': get_origin(_user),
108 'type': "user",
109 'type': "user",
109 }
110 }
110 permissions.append(user_data)
111 permissions.append(user_data)
111
112
112 for _user_group in repo_group.permission_user_groups():
113 for _user_group in repo_group.permission_user_groups():
113 user_group_data = {
114 user_group_data = {
114 'name': _user_group.users_group_name,
115 'name': _user_group.users_group_name,
115 'permission': _user_group.permission,
116 'permission': _user_group.permission,
116 'origin': get_origin(_user_group),
117 'origin': get_origin(_user_group),
117 'type': "user_group",
118 'type': "user_group",
118 }
119 }
119 permissions.append(user_group_data)
120 permissions.append(user_group_data)
120
121
121 data = repo_group.get_api_data()
122 data = repo_group.get_api_data()
122 data["permissions"] = permissions
123 data["permissions"] = permissions
123 return data
124 return data
124
125
125
126
126 @jsonrpc_method()
127 @jsonrpc_method()
127 def get_repo_groups(request, apiuser):
128 def get_repo_groups(request, apiuser):
128 """
129 """
129 Returns all repository groups.
130 Returns all repository groups.
130
131
131 :param apiuser: This is filled automatically from the |authtoken|.
132 :param apiuser: This is filled automatically from the |authtoken|.
132 :type apiuser: AuthUser
133 :type apiuser: AuthUser
133 """
134 """
134
135
135 result = []
136 result = []
136 _perms = ('group.read', 'group.write', 'group.admin',)
137 _perms = ('group.read', 'group.write', 'group.admin',)
137 extras = {'user': apiuser}
138 extras = {'user': apiuser}
138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 perm_set=_perms, extra_kwargs=extras):
140 perm_set=_perms, extra_kwargs=extras):
140 result.append(repo_group.get_api_data())
141 result.append(repo_group.get_api_data())
141 return result
142 return result
142
143
143
144
144 @jsonrpc_method()
145 @jsonrpc_method()
145 def create_repo_group(
146 def create_repo_group(
146 request, apiuser, group_name,
147 request, apiuser, group_name,
147 owner=Optional(OAttr('apiuser')),
148 owner=Optional(OAttr('apiuser')),
148 description=Optional(''),
149 description=Optional(''),
149 copy_permissions=Optional(False)):
150 copy_permissions=Optional(False)):
150 """
151 """
151 Creates a repository group.
152 Creates a repository group.
152
153
153 * If the repository group name contains "/", repository group will be
154 * If the repository group name contains "/", repository group will be
154 created inside a repository group or nested repository groups
155 created inside a repository group or nested repository groups
155
156
156 For example "foo/bar/group1" will create repository group called "group1"
157 For example "foo/bar/group1" will create repository group called "group1"
157 inside group "foo/bar". You have to have permissions to access and
158 inside group "foo/bar". You have to have permissions to access and
158 write to the last repository group ("bar" in this example)
159 write to the last repository group ("bar" in this example)
159
160
160 This command can only be run using an |authtoken| with at least
161 This command can only be run using an |authtoken| with at least
161 permissions to create repository groups, or admin permissions to
162 permissions to create repository groups, or admin permissions to
162 parent repository groups.
163 parent repository groups.
163
164
164 :param apiuser: This is filled automatically from the |authtoken|.
165 :param apiuser: This is filled automatically from the |authtoken|.
165 :type apiuser: AuthUser
166 :type apiuser: AuthUser
166 :param group_name: Set the repository group name.
167 :param group_name: Set the repository group name.
167 :type group_name: str
168 :type group_name: str
168 :param description: Set the |repo| group description.
169 :param description: Set the |repo| group description.
169 :type description: str
170 :type description: str
170 :param owner: Set the |repo| group owner.
171 :param owner: Set the |repo| group owner.
171 :type owner: str
172 :type owner: str
172 :param copy_permissions:
173 :param copy_permissions:
173 :type copy_permissions:
174 :type copy_permissions:
174
175
175 Example output:
176 Example output:
176
177
177 .. code-block:: bash
178 .. code-block:: bash
178
179
179 id : <id_given_in_input>
180 id : <id_given_in_input>
180 result : {
181 result : {
181 "msg": "Created new repo group `<repo_group_name>`"
182 "msg": "Created new repo group `<repo_group_name>`"
182 "repo_group": <repogroup_object>
183 "repo_group": <repogroup_object>
183 }
184 }
184 error : null
185 error : null
185
186
186
187
187 Example error output:
188 Example error output:
188
189
189 .. code-block:: bash
190 .. code-block:: bash
190
191
191 id : <id_given_in_input>
192 id : <id_given_in_input>
192 result : null
193 result : null
193 error : {
194 error : {
194 failed to create repo group `<repogroupid>`
195 failed to create repo group `<repogroupid>`
195 }
196 }
196
197
197 """
198 """
198
199
199 owner = validate_set_owner_permissions(apiuser, owner)
200 owner = validate_set_owner_permissions(apiuser, owner)
200
201
201 description = Optional.extract(description)
202 description = Optional.extract(description)
202 copy_permissions = Optional.extract(copy_permissions)
203 copy_permissions = Optional.extract(copy_permissions)
203
204
204 schema = repo_group_schema.RepoGroupSchema().bind(
205 schema = repo_group_schema.RepoGroupSchema().bind(
205 # user caller
206 # user caller
206 user=apiuser)
207 user=apiuser)
207
208
208 try:
209 try:
209 schema_data = schema.deserialize(dict(
210 schema_data = schema.deserialize(dict(
210 repo_group_name=group_name,
211 repo_group_name=group_name,
211 repo_group_owner=owner.username,
212 repo_group_owner=owner.username,
212 repo_group_description=description,
213 repo_group_description=description,
213 repo_group_copy_permissions=copy_permissions,
214 repo_group_copy_permissions=copy_permissions,
214 ))
215 ))
215 except validation_schema.Invalid as err:
216 except validation_schema.Invalid as err:
216 raise JSONRPCValidationError(colander_exc=err)
217 raise JSONRPCValidationError(colander_exc=err)
217
218
218 validated_group_name = schema_data['repo_group_name']
219 validated_group_name = schema_data['repo_group_name']
219
220
220 try:
221 try:
221 repo_group = RepoGroupModel().create(
222 repo_group = RepoGroupModel().create(
222 owner=owner,
223 owner=owner,
223 group_name=validated_group_name,
224 group_name=validated_group_name,
224 group_description=schema_data['repo_group_description'],
225 group_description=schema_data['repo_group_description'],
225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 copy_permissions=schema_data['repo_group_copy_permissions'])
226 Session().flush()
227 Session().flush()
227
228
228 repo_group_data = repo_group.get_api_data()
229 repo_group_data = repo_group.get_api_data()
229 audit_logger.store_api(
230 audit_logger.store_api(
230 'repo_group.create', action_data={'data': repo_group_data},
231 'repo_group.create', action_data={'data': repo_group_data},
231 user=apiuser)
232 user=apiuser)
232
233
233 Session().commit()
234 Session().commit()
234 return {
235 return {
235 'msg': 'Created new repo group `%s`' % validated_group_name,
236 'msg': 'Created new repo group `%s`' % validated_group_name,
236 'repo_group': repo_group.get_api_data()
237 'repo_group': repo_group.get_api_data()
237 }
238 }
238 except Exception:
239 except Exception:
239 log.exception("Exception occurred while trying create repo group")
240 log.exception("Exception occurred while trying create repo group")
240 raise JSONRPCError(
241 raise JSONRPCError(
241 'failed to create repo group `%s`' % (validated_group_name,))
242 'failed to create repo group `%s`' % (validated_group_name,))
242
243
243
244
244 @jsonrpc_method()
245 @jsonrpc_method()
245 def update_repo_group(
246 def update_repo_group(
246 request, apiuser, repogroupid, group_name=Optional(''),
247 request, apiuser, repogroupid, group_name=Optional(''),
247 description=Optional(''), owner=Optional(OAttr('apiuser')),
248 description=Optional(''), owner=Optional(OAttr('apiuser')),
248 enable_locking=Optional(False)):
249 enable_locking=Optional(False)):
249 """
250 """
250 Updates repository group with the details given.
251 Updates repository group with the details given.
251
252
252 This command can only be run using an |authtoken| with admin
253 This command can only be run using an |authtoken| with admin
253 permissions.
254 permissions.
254
255
255 * If the group_name name contains "/", repository group will be updated
256 * If the group_name name contains "/", repository group will be updated
256 accordingly with a repository group or nested repository groups
257 accordingly with a repository group or nested repository groups
257
258
258 For example repogroupid=group-test group_name="foo/bar/group-test"
259 For example repogroupid=group-test group_name="foo/bar/group-test"
259 will update repository group called "group-test" and place it
260 will update repository group called "group-test" and place it
260 inside group "foo/bar".
261 inside group "foo/bar".
261 You have to have permissions to access and write to the last repository
262 You have to have permissions to access and write to the last repository
262 group ("bar" in this example)
263 group ("bar" in this example)
263
264
264 :param apiuser: This is filled automatically from the |authtoken|.
265 :param apiuser: This is filled automatically from the |authtoken|.
265 :type apiuser: AuthUser
266 :type apiuser: AuthUser
266 :param repogroupid: Set the ID of repository group.
267 :param repogroupid: Set the ID of repository group.
267 :type repogroupid: str or int
268 :type repogroupid: str or int
268 :param group_name: Set the name of the |repo| group.
269 :param group_name: Set the name of the |repo| group.
269 :type group_name: str
270 :type group_name: str
270 :param description: Set a description for the group.
271 :param description: Set a description for the group.
271 :type description: str
272 :type description: str
272 :param owner: Set the |repo| group owner.
273 :param owner: Set the |repo| group owner.
273 :type owner: str
274 :type owner: str
274 :param enable_locking: Enable |repo| locking. The default is false.
275 :param enable_locking: Enable |repo| locking. The default is false.
275 :type enable_locking: bool
276 :type enable_locking: bool
276 """
277 """
277
278
278 repo_group = get_repo_group_or_error(repogroupid)
279 repo_group = get_repo_group_or_error(repogroupid)
279
280
280 if not has_superadmin_permission(apiuser):
281 if not has_superadmin_permission(apiuser):
281 validate_repo_group_permissions(
282 validate_repo_group_permissions(
282 apiuser, repogroupid, repo_group, ('group.admin',))
283 apiuser, repogroupid, repo_group, ('group.admin',))
283
284
284 updates = dict(
285 updates = dict(
285 group_name=group_name
286 group_name=group_name
286 if not isinstance(group_name, Optional) else repo_group.group_name,
287 if not isinstance(group_name, Optional) else repo_group.group_name,
287
288
288 group_description=description
289 group_description=description
289 if not isinstance(description, Optional) else repo_group.group_description,
290 if not isinstance(description, Optional) else repo_group.group_description,
290
291
291 user=owner
292 user=owner
292 if not isinstance(owner, Optional) else repo_group.user.username,
293 if not isinstance(owner, Optional) else repo_group.user.username,
293
294
294 enable_locking=enable_locking
295 enable_locking=enable_locking
295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
296 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
296 )
297 )
297
298
298 schema = repo_group_schema.RepoGroupSchema().bind(
299 schema = repo_group_schema.RepoGroupSchema().bind(
299 # user caller
300 # user caller
300 user=apiuser,
301 user=apiuser,
301 old_values=repo_group.get_api_data())
302 old_values=repo_group.get_api_data())
302
303
303 try:
304 try:
304 schema_data = schema.deserialize(dict(
305 schema_data = schema.deserialize(dict(
305 repo_group_name=updates['group_name'],
306 repo_group_name=updates['group_name'],
306 repo_group_owner=updates['user'],
307 repo_group_owner=updates['user'],
307 repo_group_description=updates['group_description'],
308 repo_group_description=updates['group_description'],
308 repo_group_enable_locking=updates['enable_locking'],
309 repo_group_enable_locking=updates['enable_locking'],
309 ))
310 ))
310 except validation_schema.Invalid as err:
311 except validation_schema.Invalid as err:
311 raise JSONRPCValidationError(colander_exc=err)
312 raise JSONRPCValidationError(colander_exc=err)
312
313
313 validated_updates = dict(
314 validated_updates = dict(
314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
315 group_name=schema_data['repo_group']['repo_group_name_without_group'],
315 group_parent_id=schema_data['repo_group']['repo_group_id'],
316 group_parent_id=schema_data['repo_group']['repo_group_id'],
316 user=schema_data['repo_group_owner'],
317 user=schema_data['repo_group_owner'],
317 group_description=schema_data['repo_group_description'],
318 group_description=schema_data['repo_group_description'],
318 enable_locking=schema_data['repo_group_enable_locking'],
319 enable_locking=schema_data['repo_group_enable_locking'],
319 )
320 )
320
321
321 old_data = repo_group.get_api_data()
322 old_data = repo_group.get_api_data()
322 try:
323 try:
323 RepoGroupModel().update(repo_group, validated_updates)
324 RepoGroupModel().update(repo_group, validated_updates)
324 audit_logger.store_api(
325 audit_logger.store_api(
325 'repo_group.edit', action_data={'old_data': old_data},
326 'repo_group.edit', action_data={'old_data': old_data},
326 user=apiuser)
327 user=apiuser)
327
328
328 Session().commit()
329 Session().commit()
329 return {
330 return {
330 'msg': 'updated repository group ID:%s %s' % (
331 'msg': 'updated repository group ID:%s %s' % (
331 repo_group.group_id, repo_group.group_name),
332 repo_group.group_id, repo_group.group_name),
332 'repo_group': repo_group.get_api_data()
333 'repo_group': repo_group.get_api_data()
333 }
334 }
334 except Exception:
335 except Exception:
335 log.exception(
336 log.exception(
336 u"Exception occurred while trying update repo group %s",
337 u"Exception occurred while trying update repo group %s",
337 repogroupid)
338 repogroupid)
338 raise JSONRPCError('failed to update repository group `%s`'
339 raise JSONRPCError('failed to update repository group `%s`'
339 % (repogroupid,))
340 % (repogroupid,))
340
341
341
342
342 @jsonrpc_method()
343 @jsonrpc_method()
343 def delete_repo_group(request, apiuser, repogroupid):
344 def delete_repo_group(request, apiuser, repogroupid):
344 """
345 """
345 Deletes a |repo| group.
346 Deletes a |repo| group.
346
347
347 :param apiuser: This is filled automatically from the |authtoken|.
348 :param apiuser: This is filled automatically from the |authtoken|.
348 :type apiuser: AuthUser
349 :type apiuser: AuthUser
349 :param repogroupid: Set the name or ID of repository group to be
350 :param repogroupid: Set the name or ID of repository group to be
350 deleted.
351 deleted.
351 :type repogroupid: str or int
352 :type repogroupid: str or int
352
353
353 Example output:
354 Example output:
354
355
355 .. code-block:: bash
356 .. code-block:: bash
356
357
357 id : <id_given_in_input>
358 id : <id_given_in_input>
358 result : {
359 result : {
359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
360 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
360 'repo_group': null
361 'repo_group': null
361 }
362 }
362 error : null
363 error : null
363
364
364 Example error output:
365 Example error output:
365
366
366 .. code-block:: bash
367 .. code-block:: bash
367
368
368 id : <id_given_in_input>
369 id : <id_given_in_input>
369 result : null
370 result : null
370 error : {
371 error : {
371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
372 "failed to delete repo group ID:<repogroupid> <repogroupname>"
372 }
373 }
373
374
374 """
375 """
375
376
376 repo_group = get_repo_group_or_error(repogroupid)
377 repo_group = get_repo_group_or_error(repogroupid)
377 if not has_superadmin_permission(apiuser):
378 if not has_superadmin_permission(apiuser):
378 validate_repo_group_permissions(
379 validate_repo_group_permissions(
379 apiuser, repogroupid, repo_group, ('group.admin',))
380 apiuser, repogroupid, repo_group, ('group.admin',))
380
381
381 old_data = repo_group.get_api_data()
382 old_data = repo_group.get_api_data()
382 try:
383 try:
383 RepoGroupModel().delete(repo_group)
384 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
385 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
386 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
387 user=apiuser)
387 Session().commit()
388 Session().commit()
388 return {
389 return {
389 'msg': 'deleted repo group ID:%s %s' %
390 'msg': 'deleted repo group ID:%s %s' %
390 (repo_group.group_id, repo_group.group_name),
391 (repo_group.group_id, repo_group.group_name),
391 'repo_group': None
392 'repo_group': None
392 }
393 }
393 except Exception:
394 except Exception:
394 log.exception("Exception occurred while trying to delete repo group")
395 log.exception("Exception occurred while trying to delete repo group")
395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
396 raise JSONRPCError('failed to delete repo group ID:%s %s' %
396 (repo_group.group_id, repo_group.group_name))
397 (repo_group.group_id, repo_group.group_name))
397
398
398
399
399 @jsonrpc_method()
400 @jsonrpc_method()
400 def grant_user_permission_to_repo_group(
401 def grant_user_permission_to_repo_group(
401 request, apiuser, repogroupid, userid, perm,
402 request, apiuser, repogroupid, userid, perm,
402 apply_to_children=Optional('none')):
403 apply_to_children=Optional('none')):
403 """
404 """
404 Grant permission for a user on the given repository group, or update
405 Grant permission for a user on the given repository group, or update
405 existing permissions if found.
406 existing permissions if found.
406
407
407 This command can only be run using an |authtoken| with admin
408 This command can only be run using an |authtoken| with admin
408 permissions.
409 permissions.
409
410
410 :param apiuser: This is filled automatically from the |authtoken|.
411 :param apiuser: This is filled automatically from the |authtoken|.
411 :type apiuser: AuthUser
412 :type apiuser: AuthUser
412 :param repogroupid: Set the name or ID of repository group.
413 :param repogroupid: Set the name or ID of repository group.
413 :type repogroupid: str or int
414 :type repogroupid: str or int
414 :param userid: Set the user name.
415 :param userid: Set the user name.
415 :type userid: str
416 :type userid: str
416 :param perm: (group.(none|read|write|admin))
417 :param perm: (group.(none|read|write|admin))
417 :type perm: str
418 :type perm: str
418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
419 :param apply_to_children: 'none', 'repos', 'groups', 'all'
419 :type apply_to_children: str
420 :type apply_to_children: str
420
421
421 Example output:
422 Example output:
422
423
423 .. code-block:: bash
424 .. code-block:: bash
424
425
425 id : <id_given_in_input>
426 id : <id_given_in_input>
426 result: {
427 result: {
427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
428 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
428 "success": true
429 "success": true
429 }
430 }
430 error: null
431 error: null
431
432
432 Example error output:
433 Example error output:
433
434
434 .. code-block:: bash
435 .. code-block:: bash
435
436
436 id : <id_given_in_input>
437 id : <id_given_in_input>
437 result : null
438 result : null
438 error : {
439 error : {
439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
440 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
440 }
441 }
441
442
442 """
443 """
443
444
444 repo_group = get_repo_group_or_error(repogroupid)
445 repo_group = get_repo_group_or_error(repogroupid)
445
446
446 if not has_superadmin_permission(apiuser):
447 if not has_superadmin_permission(apiuser):
447 validate_repo_group_permissions(
448 validate_repo_group_permissions(
448 apiuser, repogroupid, repo_group, ('group.admin',))
449 apiuser, repogroupid, repo_group, ('group.admin',))
449
450
450 user = get_user_or_error(userid)
451 user = get_user_or_error(userid)
451 perm = get_perm_or_error(perm, prefix='group.')
452 perm = get_perm_or_error(perm, prefix='group.')
452 apply_to_children = Optional.extract(apply_to_children)
453 apply_to_children = Optional.extract(apply_to_children)
453
454
454 perm_additions = [[user.user_id, perm.permission_name, "user"]]
455 perm_additions = [[user.user_id, perm.permission_name, "user"]]
455 try:
456 try:
456 changes = RepoGroupModel().update_permissions(
457 changes = RepoGroupModel().update_permissions(
457 repo_group=repo_group, perm_additions=perm_additions,
458 repo_group=repo_group, perm_additions=perm_additions,
458 recursive=apply_to_children, cur_user=apiuser)
459 recursive=apply_to_children, cur_user=apiuser)
459
460
460 action_data = {
461 action_data = {
461 'added': changes['added'],
462 'added': changes['added'],
462 'updated': changes['updated'],
463 'updated': changes['updated'],
463 'deleted': changes['deleted'],
464 'deleted': changes['deleted'],
464 }
465 }
465 audit_logger.store_api(
466 audit_logger.store_api(
466 'repo_group.edit.permissions', action_data=action_data,
467 'repo_group.edit.permissions', action_data=action_data,
467 user=apiuser)
468 user=apiuser)
469 Session().commit()
470 PermissionModel().flush_user_permission_caches(changes)
468
471
469 Session().commit()
470 return {
472 return {
471 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
473 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
472 '`%s` in repo group: `%s`' % (
474 '`%s` in repo group: `%s`' % (
473 perm.permission_name, apply_to_children, user.username,
475 perm.permission_name, apply_to_children, user.username,
474 repo_group.name
476 repo_group.name
475 ),
477 ),
476 'success': True
478 'success': True
477 }
479 }
478 except Exception:
480 except Exception:
479 log.exception("Exception occurred while trying to grant "
481 log.exception("Exception occurred while trying to grant "
480 "user permissions to repo group")
482 "user permissions to repo group")
481 raise JSONRPCError(
483 raise JSONRPCError(
482 'failed to edit permission for user: '
484 'failed to edit permission for user: '
483 '`%s` in repo group: `%s`' % (userid, repo_group.name))
485 '`%s` in repo group: `%s`' % (userid, repo_group.name))
484
486
485
487
486 @jsonrpc_method()
488 @jsonrpc_method()
487 def revoke_user_permission_from_repo_group(
489 def revoke_user_permission_from_repo_group(
488 request, apiuser, repogroupid, userid,
490 request, apiuser, repogroupid, userid,
489 apply_to_children=Optional('none')):
491 apply_to_children=Optional('none')):
490 """
492 """
491 Revoke permission for a user in a given repository group.
493 Revoke permission for a user in a given repository group.
492
494
493 This command can only be run using an |authtoken| with admin
495 This command can only be run using an |authtoken| with admin
494 permissions on the |repo| group.
496 permissions on the |repo| group.
495
497
496 :param apiuser: This is filled automatically from the |authtoken|.
498 :param apiuser: This is filled automatically from the |authtoken|.
497 :type apiuser: AuthUser
499 :type apiuser: AuthUser
498 :param repogroupid: Set the name or ID of the repository group.
500 :param repogroupid: Set the name or ID of the repository group.
499 :type repogroupid: str or int
501 :type repogroupid: str or int
500 :param userid: Set the user name to revoke.
502 :param userid: Set the user name to revoke.
501 :type userid: str
503 :type userid: str
502 :param apply_to_children: 'none', 'repos', 'groups', 'all'
504 :param apply_to_children: 'none', 'repos', 'groups', 'all'
503 :type apply_to_children: str
505 :type apply_to_children: str
504
506
505 Example output:
507 Example output:
506
508
507 .. code-block:: bash
509 .. code-block:: bash
508
510
509 id : <id_given_in_input>
511 id : <id_given_in_input>
510 result: {
512 result: {
511 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
513 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
512 "success": true
514 "success": true
513 }
515 }
514 error: null
516 error: null
515
517
516 Example error output:
518 Example error output:
517
519
518 .. code-block:: bash
520 .. code-block:: bash
519
521
520 id : <id_given_in_input>
522 id : <id_given_in_input>
521 result : null
523 result : null
522 error : {
524 error : {
523 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
525 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
524 }
526 }
525
527
526 """
528 """
527
529
528 repo_group = get_repo_group_or_error(repogroupid)
530 repo_group = get_repo_group_or_error(repogroupid)
529
531
530 if not has_superadmin_permission(apiuser):
532 if not has_superadmin_permission(apiuser):
531 validate_repo_group_permissions(
533 validate_repo_group_permissions(
532 apiuser, repogroupid, repo_group, ('group.admin',))
534 apiuser, repogroupid, repo_group, ('group.admin',))
533
535
534 user = get_user_or_error(userid)
536 user = get_user_or_error(userid)
535 apply_to_children = Optional.extract(apply_to_children)
537 apply_to_children = Optional.extract(apply_to_children)
536
538
537 perm_deletions = [[user.user_id, None, "user"]]
539 perm_deletions = [[user.user_id, None, "user"]]
538 try:
540 try:
539 changes = RepoGroupModel().update_permissions(
541 changes = RepoGroupModel().update_permissions(
540 repo_group=repo_group, perm_deletions=perm_deletions,
542 repo_group=repo_group, perm_deletions=perm_deletions,
541 recursive=apply_to_children, cur_user=apiuser)
543 recursive=apply_to_children, cur_user=apiuser)
542
544
543 action_data = {
545 action_data = {
544 'added': changes['added'],
546 'added': changes['added'],
545 'updated': changes['updated'],
547 'updated': changes['updated'],
546 'deleted': changes['deleted'],
548 'deleted': changes['deleted'],
547 }
549 }
548 audit_logger.store_api(
550 audit_logger.store_api(
549 'repo_group.edit.permissions', action_data=action_data,
551 'repo_group.edit.permissions', action_data=action_data,
550 user=apiuser)
552 user=apiuser)
553 Session().commit()
554 PermissionModel().flush_user_permission_caches(changes)
551
555
552 Session().commit()
553 return {
556 return {
554 'msg': 'Revoked perm (recursive:%s) for user: '
557 'msg': 'Revoked perm (recursive:%s) for user: '
555 '`%s` in repo group: `%s`' % (
558 '`%s` in repo group: `%s`' % (
556 apply_to_children, user.username, repo_group.name
559 apply_to_children, user.username, repo_group.name
557 ),
560 ),
558 'success': True
561 'success': True
559 }
562 }
560 except Exception:
563 except Exception:
561 log.exception("Exception occurred while trying revoke user "
564 log.exception("Exception occurred while trying revoke user "
562 "permission from repo group")
565 "permission from repo group")
563 raise JSONRPCError(
566 raise JSONRPCError(
564 'failed to edit permission for user: '
567 'failed to edit permission for user: '
565 '`%s` in repo group: `%s`' % (userid, repo_group.name))
568 '`%s` in repo group: `%s`' % (userid, repo_group.name))
566
569
567
570
568 @jsonrpc_method()
571 @jsonrpc_method()
569 def grant_user_group_permission_to_repo_group(
572 def grant_user_group_permission_to_repo_group(
570 request, apiuser, repogroupid, usergroupid, perm,
573 request, apiuser, repogroupid, usergroupid, perm,
571 apply_to_children=Optional('none'), ):
574 apply_to_children=Optional('none'), ):
572 """
575 """
573 Grant permission for a user group on given repository group, or update
576 Grant permission for a user group on given repository group, or update
574 existing permissions if found.
577 existing permissions if found.
575
578
576 This command can only be run using an |authtoken| with admin
579 This command can only be run using an |authtoken| with admin
577 permissions on the |repo| group.
580 permissions on the |repo| group.
578
581
579 :param apiuser: This is filled automatically from the |authtoken|.
582 :param apiuser: This is filled automatically from the |authtoken|.
580 :type apiuser: AuthUser
583 :type apiuser: AuthUser
581 :param repogroupid: Set the name or id of repository group
584 :param repogroupid: Set the name or id of repository group
582 :type repogroupid: str or int
585 :type repogroupid: str or int
583 :param usergroupid: id of usergroup
586 :param usergroupid: id of usergroup
584 :type usergroupid: str or int
587 :type usergroupid: str or int
585 :param perm: (group.(none|read|write|admin))
588 :param perm: (group.(none|read|write|admin))
586 :type perm: str
589 :type perm: str
587 :param apply_to_children: 'none', 'repos', 'groups', 'all'
590 :param apply_to_children: 'none', 'repos', 'groups', 'all'
588 :type apply_to_children: str
591 :type apply_to_children: str
589
592
590 Example output:
593 Example output:
591
594
592 .. code-block:: bash
595 .. code-block:: bash
593
596
594 id : <id_given_in_input>
597 id : <id_given_in_input>
595 result : {
598 result : {
596 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
599 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
597 "success": true
600 "success": true
598
601
599 }
602 }
600 error : null
603 error : null
601
604
602 Example error output:
605 Example error output:
603
606
604 .. code-block:: bash
607 .. code-block:: bash
605
608
606 id : <id_given_in_input>
609 id : <id_given_in_input>
607 result : null
610 result : null
608 error : {
611 error : {
609 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
612 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
610 }
613 }
611
614
612 """
615 """
613
616
614 repo_group = get_repo_group_or_error(repogroupid)
617 repo_group = get_repo_group_or_error(repogroupid)
615 perm = get_perm_or_error(perm, prefix='group.')
618 perm = get_perm_or_error(perm, prefix='group.')
616 user_group = get_user_group_or_error(usergroupid)
619 user_group = get_user_group_or_error(usergroupid)
617 if not has_superadmin_permission(apiuser):
620 if not has_superadmin_permission(apiuser):
618 validate_repo_group_permissions(
621 validate_repo_group_permissions(
619 apiuser, repogroupid, repo_group, ('group.admin',))
622 apiuser, repogroupid, repo_group, ('group.admin',))
620
623
621 # check if we have at least read permission for this user group !
624 # check if we have at least read permission for this user group !
622 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
625 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
623 if not HasUserGroupPermissionAnyApi(*_perms)(
626 if not HasUserGroupPermissionAnyApi(*_perms)(
624 user=apiuser, user_group_name=user_group.users_group_name):
627 user=apiuser, user_group_name=user_group.users_group_name):
625 raise JSONRPCError(
628 raise JSONRPCError(
626 'user group `%s` does not exist' % (usergroupid,))
629 'user group `%s` does not exist' % (usergroupid,))
627
630
628 apply_to_children = Optional.extract(apply_to_children)
631 apply_to_children = Optional.extract(apply_to_children)
629
632
630 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
633 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
631 try:
634 try:
632 changes = RepoGroupModel().update_permissions(
635 changes = RepoGroupModel().update_permissions(
633 repo_group=repo_group, perm_additions=perm_additions,
636 repo_group=repo_group, perm_additions=perm_additions,
634 recursive=apply_to_children, cur_user=apiuser)
637 recursive=apply_to_children, cur_user=apiuser)
635
638
636 action_data = {
639 action_data = {
637 'added': changes['added'],
640 'added': changes['added'],
638 'updated': changes['updated'],
641 'updated': changes['updated'],
639 'deleted': changes['deleted'],
642 'deleted': changes['deleted'],
640 }
643 }
641 audit_logger.store_api(
644 audit_logger.store_api(
642 'repo_group.edit.permissions', action_data=action_data,
645 'repo_group.edit.permissions', action_data=action_data,
643 user=apiuser)
646 user=apiuser)
647 Session().commit()
648 PermissionModel().flush_user_permission_caches(changes)
644
649
645 Session().commit()
646 return {
650 return {
647 'msg': 'Granted perm: `%s` (recursive:%s) '
651 'msg': 'Granted perm: `%s` (recursive:%s) '
648 'for user group: `%s` in repo group: `%s`' % (
652 'for user group: `%s` in repo group: `%s`' % (
649 perm.permission_name, apply_to_children,
653 perm.permission_name, apply_to_children,
650 user_group.users_group_name, repo_group.name
654 user_group.users_group_name, repo_group.name
651 ),
655 ),
652 'success': True
656 'success': True
653 }
657 }
654 except Exception:
658 except Exception:
655 log.exception("Exception occurred while trying to grant user "
659 log.exception("Exception occurred while trying to grant user "
656 "group permissions to repo group")
660 "group permissions to repo group")
657 raise JSONRPCError(
661 raise JSONRPCError(
658 'failed to edit permission for user group: `%s` in '
662 'failed to edit permission for user group: `%s` in '
659 'repo group: `%s`' % (
663 'repo group: `%s`' % (
660 usergroupid, repo_group.name
664 usergroupid, repo_group.name
661 )
665 )
662 )
666 )
663
667
664
668
665 @jsonrpc_method()
669 @jsonrpc_method()
666 def revoke_user_group_permission_from_repo_group(
670 def revoke_user_group_permission_from_repo_group(
667 request, apiuser, repogroupid, usergroupid,
671 request, apiuser, repogroupid, usergroupid,
668 apply_to_children=Optional('none')):
672 apply_to_children=Optional('none')):
669 """
673 """
670 Revoke permission for user group on given repository.
674 Revoke permission for user group on given repository.
671
675
672 This command can only be run using an |authtoken| with admin
676 This command can only be run using an |authtoken| with admin
673 permissions on the |repo| group.
677 permissions on the |repo| group.
674
678
675 :param apiuser: This is filled automatically from the |authtoken|.
679 :param apiuser: This is filled automatically from the |authtoken|.
676 :type apiuser: AuthUser
680 :type apiuser: AuthUser
677 :param repogroupid: name or id of repository group
681 :param repogroupid: name or id of repository group
678 :type repogroupid: str or int
682 :type repogroupid: str or int
679 :param usergroupid:
683 :param usergroupid:
680 :param apply_to_children: 'none', 'repos', 'groups', 'all'
684 :param apply_to_children: 'none', 'repos', 'groups', 'all'
681 :type apply_to_children: str
685 :type apply_to_children: str
682
686
683 Example output:
687 Example output:
684
688
685 .. code-block:: bash
689 .. code-block:: bash
686
690
687 id : <id_given_in_input>
691 id : <id_given_in_input>
688 result: {
692 result: {
689 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
693 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
690 "success": true
694 "success": true
691 }
695 }
692 error: null
696 error: null
693
697
694 Example error output:
698 Example error output:
695
699
696 .. code-block:: bash
700 .. code-block:: bash
697
701
698 id : <id_given_in_input>
702 id : <id_given_in_input>
699 result : null
703 result : null
700 error : {
704 error : {
701 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
705 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
702 }
706 }
703
707
704
708
705 """
709 """
706
710
707 repo_group = get_repo_group_or_error(repogroupid)
711 repo_group = get_repo_group_or_error(repogroupid)
708 user_group = get_user_group_or_error(usergroupid)
712 user_group = get_user_group_or_error(usergroupid)
709 if not has_superadmin_permission(apiuser):
713 if not has_superadmin_permission(apiuser):
710 validate_repo_group_permissions(
714 validate_repo_group_permissions(
711 apiuser, repogroupid, repo_group, ('group.admin',))
715 apiuser, repogroupid, repo_group, ('group.admin',))
712
716
713 # check if we have at least read permission for this user group !
717 # check if we have at least read permission for this user group !
714 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
718 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
715 if not HasUserGroupPermissionAnyApi(*_perms)(
719 if not HasUserGroupPermissionAnyApi(*_perms)(
716 user=apiuser, user_group_name=user_group.users_group_name):
720 user=apiuser, user_group_name=user_group.users_group_name):
717 raise JSONRPCError(
721 raise JSONRPCError(
718 'user group `%s` does not exist' % (usergroupid,))
722 'user group `%s` does not exist' % (usergroupid,))
719
723
720 apply_to_children = Optional.extract(apply_to_children)
724 apply_to_children = Optional.extract(apply_to_children)
721
725
722 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
726 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
723 try:
727 try:
724 changes = RepoGroupModel().update_permissions(
728 changes = RepoGroupModel().update_permissions(
725 repo_group=repo_group, perm_deletions=perm_deletions,
729 repo_group=repo_group, perm_deletions=perm_deletions,
726 recursive=apply_to_children, cur_user=apiuser)
730 recursive=apply_to_children, cur_user=apiuser)
727
731
728 action_data = {
732 action_data = {
729 'added': changes['added'],
733 'added': changes['added'],
730 'updated': changes['updated'],
734 'updated': changes['updated'],
731 'deleted': changes['deleted'],
735 'deleted': changes['deleted'],
732 }
736 }
733 audit_logger.store_api(
737 audit_logger.store_api(
734 'repo_group.edit.permissions', action_data=action_data,
738 'repo_group.edit.permissions', action_data=action_data,
735 user=apiuser)
739 user=apiuser)
740 Session().commit()
741 PermissionModel().flush_user_permission_caches(changes)
736
742
737 Session().commit()
738 return {
743 return {
739 'msg': 'Revoked perm (recursive:%s) for user group: '
744 'msg': 'Revoked perm (recursive:%s) for user group: '
740 '`%s` in repo group: `%s`' % (
745 '`%s` in repo group: `%s`' % (
741 apply_to_children, user_group.users_group_name,
746 apply_to_children, user_group.users_group_name,
742 repo_group.name
747 repo_group.name
743 ),
748 ),
744 'success': True
749 'success': True
745 }
750 }
746 except Exception:
751 except Exception:
747 log.exception("Exception occurred while trying revoke user group "
752 log.exception("Exception occurred while trying revoke user group "
748 "permissions from repo group")
753 "permissions from repo group")
749 raise JSONRPCError(
754 raise JSONRPCError(
750 'failed to edit permission for user group: '
755 'failed to edit permission for user group: '
751 '`%s` in repo group: `%s`' % (
756 '`%s` in repo group: `%s`' % (
752 user_group.users_group_name, repo_group.name
757 user_group.users_group_name, repo_group.name
753 )
758 )
754 )
759 )
@@ -1,895 +1,903 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 from rhodecode.api import (
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
27 get_user_or_error, get_user_group_or_error, get_perm_or_error)
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
29 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
30 from rhodecode.lib.exceptions import UserGroupAssignedException
30 from rhodecode.lib.exceptions import UserGroupAssignedException
31 from rhodecode.model.db import Session
31 from rhodecode.model.db import Session
32 from rhodecode.model.permission import PermissionModel
32 from rhodecode.model.scm import UserGroupList
33 from rhodecode.model.scm import UserGroupList
33 from rhodecode.model.user_group import UserGroupModel
34 from rhodecode.model.user_group import UserGroupModel
34 from rhodecode.model import validation_schema
35 from rhodecode.model import validation_schema
35 from rhodecode.model.validation_schema.schemas import user_group_schema
36 from rhodecode.model.validation_schema.schemas import user_group_schema
36
37
37 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
38
39
39
40
40 @jsonrpc_method()
41 @jsonrpc_method()
41 def get_user_group(request, apiuser, usergroupid):
42 def get_user_group(request, apiuser, usergroupid):
42 """
43 """
43 Returns the data of an existing user group.
44 Returns the data of an existing user group.
44
45
45 This command can only be run using an |authtoken| with admin rights to
46 This command can only be run using an |authtoken| with admin rights to
46 the specified repository.
47 the specified repository.
47
48
48 :param apiuser: This is filled automatically from the |authtoken|.
49 :param apiuser: This is filled automatically from the |authtoken|.
49 :type apiuser: AuthUser
50 :type apiuser: AuthUser
50 :param usergroupid: Set the user group from which to return data.
51 :param usergroupid: Set the user group from which to return data.
51 :type usergroupid: str or int
52 :type usergroupid: str or int
52
53
53 Example error output:
54 Example error output:
54
55
55 .. code-block:: bash
56 .. code-block:: bash
56
57
57 {
58 {
58 "error": null,
59 "error": null,
59 "id": <id>,
60 "id": <id>,
60 "result": {
61 "result": {
61 "active": true,
62 "active": true,
62 "group_description": "group description",
63 "group_description": "group description",
63 "group_name": "group name",
64 "group_name": "group name",
64 "permissions": [
65 "permissions": [
65 {
66 {
66 "name": "owner-name",
67 "name": "owner-name",
67 "origin": "owner",
68 "origin": "owner",
68 "permission": "usergroup.admin",
69 "permission": "usergroup.admin",
69 "type": "user"
70 "type": "user"
70 },
71 },
71 {
72 {
72 {
73 {
73 "name": "user name",
74 "name": "user name",
74 "origin": "permission",
75 "origin": "permission",
75 "permission": "usergroup.admin",
76 "permission": "usergroup.admin",
76 "type": "user"
77 "type": "user"
77 },
78 },
78 {
79 {
79 "name": "user group name",
80 "name": "user group name",
80 "origin": "permission",
81 "origin": "permission",
81 "permission": "usergroup.write",
82 "permission": "usergroup.write",
82 "type": "user_group"
83 "type": "user_group"
83 }
84 }
84 ],
85 ],
85 "permissions_summary": {
86 "permissions_summary": {
86 "repositories": {
87 "repositories": {
87 "aa-root-level-repo-1": "repository.admin"
88 "aa-root-level-repo-1": "repository.admin"
88 },
89 },
89 "repositories_groups": {}
90 "repositories_groups": {}
90 },
91 },
91 "owner": "owner name",
92 "owner": "owner name",
92 "users": [],
93 "users": [],
93 "users_group_id": 2
94 "users_group_id": 2
94 }
95 }
95 }
96 }
96
97
97 """
98 """
98
99
99 user_group = get_user_group_or_error(usergroupid)
100 user_group = get_user_group_or_error(usergroupid)
100 if not has_superadmin_permission(apiuser):
101 if not has_superadmin_permission(apiuser):
101 # check if we have at least read permission for this user group !
102 # check if we have at least read permission for this user group !
102 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
103 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
103 if not HasUserGroupPermissionAnyApi(*_perms)(
104 if not HasUserGroupPermissionAnyApi(*_perms)(
104 user=apiuser, user_group_name=user_group.users_group_name):
105 user=apiuser, user_group_name=user_group.users_group_name):
105 raise JSONRPCError('user group `%s` does not exist' % (
106 raise JSONRPCError('user group `%s` does not exist' % (
106 usergroupid,))
107 usergroupid,))
107
108
108 permissions = []
109 permissions = []
109 for _user in user_group.permissions():
110 for _user in user_group.permissions():
110 user_data = {
111 user_data = {
111 'name': _user.username,
112 'name': _user.username,
112 'permission': _user.permission,
113 'permission': _user.permission,
113 'origin': get_origin(_user),
114 'origin': get_origin(_user),
114 'type': "user",
115 'type': "user",
115 }
116 }
116 permissions.append(user_data)
117 permissions.append(user_data)
117
118
118 for _user_group in user_group.permission_user_groups():
119 for _user_group in user_group.permission_user_groups():
119 user_group_data = {
120 user_group_data = {
120 'name': _user_group.users_group_name,
121 'name': _user_group.users_group_name,
121 'permission': _user_group.permission,
122 'permission': _user_group.permission,
122 'origin': get_origin(_user_group),
123 'origin': get_origin(_user_group),
123 'type': "user_group",
124 'type': "user_group",
124 }
125 }
125 permissions.append(user_group_data)
126 permissions.append(user_group_data)
126
127
127 data = user_group.get_api_data()
128 data = user_group.get_api_data()
128 data["permissions"] = permissions
129 data["permissions"] = permissions
129 data["permissions_summary"] = UserGroupModel().get_perms_summary(
130 data["permissions_summary"] = UserGroupModel().get_perms_summary(
130 user_group.users_group_id)
131 user_group.users_group_id)
131 return data
132 return data
132
133
133
134
134 @jsonrpc_method()
135 @jsonrpc_method()
135 def get_user_groups(request, apiuser):
136 def get_user_groups(request, apiuser):
136 """
137 """
137 Lists all the existing user groups within RhodeCode.
138 Lists all the existing user groups within RhodeCode.
138
139
139 This command can only be run using an |authtoken| with admin rights to
140 This command can only be run using an |authtoken| with admin rights to
140 the specified repository.
141 the specified repository.
141
142
142 This command takes the following options:
143 This command takes the following options:
143
144
144 :param apiuser: This is filled automatically from the |authtoken|.
145 :param apiuser: This is filled automatically from the |authtoken|.
145 :type apiuser: AuthUser
146 :type apiuser: AuthUser
146
147
147 Example error output:
148 Example error output:
148
149
149 .. code-block:: bash
150 .. code-block:: bash
150
151
151 id : <id_given_in_input>
152 id : <id_given_in_input>
152 result : [<user_group_obj>,...]
153 result : [<user_group_obj>,...]
153 error : null
154 error : null
154 """
155 """
155
156
156 include_secrets = has_superadmin_permission(apiuser)
157 include_secrets = has_superadmin_permission(apiuser)
157
158
158 result = []
159 result = []
159 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
160 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
160 extras = {'user': apiuser}
161 extras = {'user': apiuser}
161 for user_group in UserGroupList(UserGroupModel().get_all(),
162 for user_group in UserGroupList(UserGroupModel().get_all(),
162 perm_set=_perms, extra_kwargs=extras):
163 perm_set=_perms, extra_kwargs=extras):
163 result.append(
164 result.append(
164 user_group.get_api_data(include_secrets=include_secrets))
165 user_group.get_api_data(include_secrets=include_secrets))
165 return result
166 return result
166
167
167
168
168 @jsonrpc_method()
169 @jsonrpc_method()
169 def create_user_group(
170 def create_user_group(
170 request, apiuser, group_name, description=Optional(''),
171 request, apiuser, group_name, description=Optional(''),
171 owner=Optional(OAttr('apiuser')), active=Optional(True),
172 owner=Optional(OAttr('apiuser')), active=Optional(True),
172 sync=Optional(None)):
173 sync=Optional(None)):
173 """
174 """
174 Creates a new user group.
175 Creates a new user group.
175
176
176 This command can only be run using an |authtoken| with admin rights to
177 This command can only be run using an |authtoken| with admin rights to
177 the specified repository.
178 the specified repository.
178
179
179 This command takes the following options:
180 This command takes the following options:
180
181
181 :param apiuser: This is filled automatically from the |authtoken|.
182 :param apiuser: This is filled automatically from the |authtoken|.
182 :type apiuser: AuthUser
183 :type apiuser: AuthUser
183 :param group_name: Set the name of the new user group.
184 :param group_name: Set the name of the new user group.
184 :type group_name: str
185 :type group_name: str
185 :param description: Give a description of the new user group.
186 :param description: Give a description of the new user group.
186 :type description: str
187 :type description: str
187 :param owner: Set the owner of the new user group.
188 :param owner: Set the owner of the new user group.
188 If not set, the owner is the |authtoken| user.
189 If not set, the owner is the |authtoken| user.
189 :type owner: Optional(str or int)
190 :type owner: Optional(str or int)
190 :param active: Set this group as active.
191 :param active: Set this group as active.
191 :type active: Optional(``True`` | ``False``)
192 :type active: Optional(``True`` | ``False``)
192 :param sync: Set enabled or disabled the automatically sync from
193 :param sync: Set enabled or disabled the automatically sync from
193 external authentication types like ldap.
194 external authentication types like ldap.
194 :type sync: Optional(``True`` | ``False``)
195 :type sync: Optional(``True`` | ``False``)
195
196
196 Example output:
197 Example output:
197
198
198 .. code-block:: bash
199 .. code-block:: bash
199
200
200 id : <id_given_in_input>
201 id : <id_given_in_input>
201 result: {
202 result: {
202 "msg": "created new user group `<groupname>`",
203 "msg": "created new user group `<groupname>`",
203 "user_group": <user_group_object>
204 "user_group": <user_group_object>
204 }
205 }
205 error: null
206 error: null
206
207
207 Example error output:
208 Example error output:
208
209
209 .. code-block:: bash
210 .. code-block:: bash
210
211
211 id : <id_given_in_input>
212 id : <id_given_in_input>
212 result : null
213 result : null
213 error : {
214 error : {
214 "user group `<group name>` already exist"
215 "user group `<group name>` already exist"
215 or
216 or
216 "failed to create group `<group name>`"
217 "failed to create group `<group name>`"
217 }
218 }
218
219
219 """
220 """
220
221
221 if not has_superadmin_permission(apiuser):
222 if not has_superadmin_permission(apiuser):
222 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
223 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
223 raise JSONRPCForbidden()
224 raise JSONRPCForbidden()
224
225
225 if UserGroupModel().get_by_name(group_name):
226 if UserGroupModel().get_by_name(group_name):
226 raise JSONRPCError("user group `%s` already exist" % (group_name,))
227 raise JSONRPCError("user group `%s` already exist" % (group_name,))
227
228
228 if isinstance(owner, Optional):
229 if isinstance(owner, Optional):
229 owner = apiuser.user_id
230 owner = apiuser.user_id
230
231
231 owner = get_user_or_error(owner)
232 owner = get_user_or_error(owner)
232 active = Optional.extract(active)
233 active = Optional.extract(active)
233 description = Optional.extract(description)
234 description = Optional.extract(description)
234 sync = Optional.extract(sync)
235 sync = Optional.extract(sync)
235
236
236 # set the sync option based on group_data
237 # set the sync option based on group_data
237 group_data = None
238 group_data = None
238 if sync:
239 if sync:
239 group_data = {
240 group_data = {
240 'extern_type': 'manual_api',
241 'extern_type': 'manual_api',
241 'extern_type_set_by': apiuser.username
242 'extern_type_set_by': apiuser.username
242 }
243 }
243
244
244 schema = user_group_schema.UserGroupSchema().bind(
245 schema = user_group_schema.UserGroupSchema().bind(
245 # user caller
246 # user caller
246 user=apiuser)
247 user=apiuser)
247 try:
248 try:
248 schema_data = schema.deserialize(dict(
249 schema_data = schema.deserialize(dict(
249 user_group_name=group_name,
250 user_group_name=group_name,
250 user_group_description=description,
251 user_group_description=description,
251 user_group_owner=owner.username,
252 user_group_owner=owner.username,
252 user_group_active=active,
253 user_group_active=active,
253 ))
254 ))
254 except validation_schema.Invalid as err:
255 except validation_schema.Invalid as err:
255 raise JSONRPCValidationError(colander_exc=err)
256 raise JSONRPCValidationError(colander_exc=err)
256
257
257 try:
258 try:
258 user_group = UserGroupModel().create(
259 user_group = UserGroupModel().create(
259 name=schema_data['user_group_name'],
260 name=schema_data['user_group_name'],
260 description=schema_data['user_group_description'],
261 description=schema_data['user_group_description'],
261 owner=owner,
262 owner=owner,
262 active=schema_data['user_group_active'], group_data=group_data)
263 active=schema_data['user_group_active'], group_data=group_data)
263 Session().flush()
264 Session().flush()
264 creation_data = user_group.get_api_data()
265 creation_data = user_group.get_api_data()
265 audit_logger.store_api(
266 audit_logger.store_api(
266 'user_group.create', action_data={'data': creation_data},
267 'user_group.create', action_data={'data': creation_data},
267 user=apiuser)
268 user=apiuser)
268 Session().commit()
269 Session().commit()
270
271 affected_user_ids = [apiuser.user_id, owner.user_id]
272 PermissionModel().trigger_permission_flush(affected_user_ids)
273
269 return {
274 return {
270 'msg': 'created new user group `%s`' % group_name,
275 'msg': 'created new user group `%s`' % group_name,
271 'user_group': creation_data
276 'user_group': creation_data
272 }
277 }
273 except Exception:
278 except Exception:
274 log.exception("Error occurred during creation of user group")
279 log.exception("Error occurred during creation of user group")
275 raise JSONRPCError('failed to create group `%s`' % (group_name,))
280 raise JSONRPCError('failed to create group `%s`' % (group_name,))
276
281
277
282
278 @jsonrpc_method()
283 @jsonrpc_method()
279 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
284 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
280 description=Optional(''), owner=Optional(None),
285 description=Optional(''), owner=Optional(None),
281 active=Optional(True), sync=Optional(None)):
286 active=Optional(True), sync=Optional(None)):
282 """
287 """
283 Updates the specified `user group` with the details provided.
288 Updates the specified `user group` with the details provided.
284
289
285 This command can only be run using an |authtoken| with admin rights to
290 This command can only be run using an |authtoken| with admin rights to
286 the specified repository.
291 the specified repository.
287
292
288 :param apiuser: This is filled automatically from the |authtoken|.
293 :param apiuser: This is filled automatically from the |authtoken|.
289 :type apiuser: AuthUser
294 :type apiuser: AuthUser
290 :param usergroupid: Set the id of the `user group` to update.
295 :param usergroupid: Set the id of the `user group` to update.
291 :type usergroupid: str or int
296 :type usergroupid: str or int
292 :param group_name: Set the new name the `user group`
297 :param group_name: Set the new name the `user group`
293 :type group_name: str
298 :type group_name: str
294 :param description: Give a description for the `user group`
299 :param description: Give a description for the `user group`
295 :type description: str
300 :type description: str
296 :param owner: Set the owner of the `user group`.
301 :param owner: Set the owner of the `user group`.
297 :type owner: Optional(str or int)
302 :type owner: Optional(str or int)
298 :param active: Set the group as active.
303 :param active: Set the group as active.
299 :type active: Optional(``True`` | ``False``)
304 :type active: Optional(``True`` | ``False``)
300 :param sync: Set enabled or disabled the automatically sync from
305 :param sync: Set enabled or disabled the automatically sync from
301 external authentication types like ldap.
306 external authentication types like ldap.
302 :type sync: Optional(``True`` | ``False``)
307 :type sync: Optional(``True`` | ``False``)
303
308
304 Example output:
309 Example output:
305
310
306 .. code-block:: bash
311 .. code-block:: bash
307
312
308 id : <id_given_in_input>
313 id : <id_given_in_input>
309 result : {
314 result : {
310 "msg": 'updated user group ID:<user group id> <user group name>',
315 "msg": 'updated user group ID:<user group id> <user group name>',
311 "user_group": <user_group_object>
316 "user_group": <user_group_object>
312 }
317 }
313 error : null
318 error : null
314
319
315 Example error output:
320 Example error output:
316
321
317 .. code-block:: bash
322 .. code-block:: bash
318
323
319 id : <id_given_in_input>
324 id : <id_given_in_input>
320 result : null
325 result : null
321 error : {
326 error : {
322 "failed to update user group `<user group name>`"
327 "failed to update user group `<user group name>`"
323 }
328 }
324
329
325 """
330 """
326
331
327 user_group = get_user_group_or_error(usergroupid)
332 user_group = get_user_group_or_error(usergroupid)
328 include_secrets = False
333 include_secrets = False
329 if not has_superadmin_permission(apiuser):
334 if not has_superadmin_permission(apiuser):
330 # check if we have admin permission for this user group !
335 # check if we have admin permission for this user group !
331 _perms = ('usergroup.admin',)
336 _perms = ('usergroup.admin',)
332 if not HasUserGroupPermissionAnyApi(*_perms)(
337 if not HasUserGroupPermissionAnyApi(*_perms)(
333 user=apiuser, user_group_name=user_group.users_group_name):
338 user=apiuser, user_group_name=user_group.users_group_name):
334 raise JSONRPCError(
339 raise JSONRPCError(
335 'user group `%s` does not exist' % (usergroupid,))
340 'user group `%s` does not exist' % (usergroupid,))
336 else:
341 else:
337 include_secrets = True
342 include_secrets = True
338
343
339 if not isinstance(owner, Optional):
344 if not isinstance(owner, Optional):
340 owner = get_user_or_error(owner)
345 owner = get_user_or_error(owner)
341
346
342 old_data = user_group.get_api_data()
347 old_data = user_group.get_api_data()
343 updates = {}
348 updates = {}
344 store_update(updates, group_name, 'users_group_name')
349 store_update(updates, group_name, 'users_group_name')
345 store_update(updates, description, 'user_group_description')
350 store_update(updates, description, 'user_group_description')
346 store_update(updates, owner, 'user')
351 store_update(updates, owner, 'user')
347 store_update(updates, active, 'users_group_active')
352 store_update(updates, active, 'users_group_active')
348
353
349 sync = Optional.extract(sync)
354 sync = Optional.extract(sync)
350 group_data = None
355 group_data = None
351 if sync is True:
356 if sync is True:
352 group_data = {
357 group_data = {
353 'extern_type': 'manual_api',
358 'extern_type': 'manual_api',
354 'extern_type_set_by': apiuser.username
359 'extern_type_set_by': apiuser.username
355 }
360 }
356 if sync is False:
361 if sync is False:
357 group_data = user_group.group_data
362 group_data = user_group.group_data
358 if group_data and "extern_type" in group_data:
363 if group_data and "extern_type" in group_data:
359 del group_data["extern_type"]
364 del group_data["extern_type"]
360
365
361 try:
366 try:
362 UserGroupModel().update(user_group, updates, group_data=group_data)
367 UserGroupModel().update(user_group, updates, group_data=group_data)
363 audit_logger.store_api(
368 audit_logger.store_api(
364 'user_group.edit', action_data={'old_data': old_data},
369 'user_group.edit', action_data={'old_data': old_data},
365 user=apiuser)
370 user=apiuser)
366 Session().commit()
371 Session().commit()
367 return {
372 return {
368 'msg': 'updated user group ID:%s %s' % (
373 'msg': 'updated user group ID:%s %s' % (
369 user_group.users_group_id, user_group.users_group_name),
374 user_group.users_group_id, user_group.users_group_name),
370 'user_group': user_group.get_api_data(
375 'user_group': user_group.get_api_data(
371 include_secrets=include_secrets)
376 include_secrets=include_secrets)
372 }
377 }
373 except Exception:
378 except Exception:
374 log.exception("Error occurred during update of user group")
379 log.exception("Error occurred during update of user group")
375 raise JSONRPCError(
380 raise JSONRPCError(
376 'failed to update user group `%s`' % (usergroupid,))
381 'failed to update user group `%s`' % (usergroupid,))
377
382
378
383
379 @jsonrpc_method()
384 @jsonrpc_method()
380 def delete_user_group(request, apiuser, usergroupid):
385 def delete_user_group(request, apiuser, usergroupid):
381 """
386 """
382 Deletes the specified `user group`.
387 Deletes the specified `user group`.
383
388
384 This command can only be run using an |authtoken| with admin rights to
389 This command can only be run using an |authtoken| with admin rights to
385 the specified repository.
390 the specified repository.
386
391
387 This command takes the following options:
392 This command takes the following options:
388
393
389 :param apiuser: filled automatically from apikey
394 :param apiuser: filled automatically from apikey
390 :type apiuser: AuthUser
395 :type apiuser: AuthUser
391 :param usergroupid:
396 :param usergroupid:
392 :type usergroupid: int
397 :type usergroupid: int
393
398
394 Example output:
399 Example output:
395
400
396 .. code-block:: bash
401 .. code-block:: bash
397
402
398 id : <id_given_in_input>
403 id : <id_given_in_input>
399 result : {
404 result : {
400 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
405 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
401 }
406 }
402 error : null
407 error : null
403
408
404 Example error output:
409 Example error output:
405
410
406 .. code-block:: bash
411 .. code-block:: bash
407
412
408 id : <id_given_in_input>
413 id : <id_given_in_input>
409 result : null
414 result : null
410 error : {
415 error : {
411 "failed to delete user group ID:<user_group_id> <user_group_name>"
416 "failed to delete user group ID:<user_group_id> <user_group_name>"
412 or
417 or
413 "RepoGroup assigned to <repo_groups_list>"
418 "RepoGroup assigned to <repo_groups_list>"
414 }
419 }
415
420
416 """
421 """
417
422
418 user_group = get_user_group_or_error(usergroupid)
423 user_group = get_user_group_or_error(usergroupid)
419 if not has_superadmin_permission(apiuser):
424 if not has_superadmin_permission(apiuser):
420 # check if we have admin permission for this user group !
425 # check if we have admin permission for this user group !
421 _perms = ('usergroup.admin',)
426 _perms = ('usergroup.admin',)
422 if not HasUserGroupPermissionAnyApi(*_perms)(
427 if not HasUserGroupPermissionAnyApi(*_perms)(
423 user=apiuser, user_group_name=user_group.users_group_name):
428 user=apiuser, user_group_name=user_group.users_group_name):
424 raise JSONRPCError(
429 raise JSONRPCError(
425 'user group `%s` does not exist' % (usergroupid,))
430 'user group `%s` does not exist' % (usergroupid,))
426
431
427 old_data = user_group.get_api_data()
432 old_data = user_group.get_api_data()
428 try:
433 try:
429 UserGroupModel().delete(user_group)
434 UserGroupModel().delete(user_group)
430 audit_logger.store_api(
435 audit_logger.store_api(
431 'user_group.delete', action_data={'old_data': old_data},
436 'user_group.delete', action_data={'old_data': old_data},
432 user=apiuser)
437 user=apiuser)
433 Session().commit()
438 Session().commit()
434 return {
439 return {
435 'msg': 'deleted user group ID:%s %s' % (
440 'msg': 'deleted user group ID:%s %s' % (
436 user_group.users_group_id, user_group.users_group_name),
441 user_group.users_group_id, user_group.users_group_name),
437 'user_group': None
442 'user_group': None
438 }
443 }
439 except UserGroupAssignedException as e:
444 except UserGroupAssignedException as e:
440 log.exception("UserGroupAssigned error")
445 log.exception("UserGroupAssigned error")
441 raise JSONRPCError(str(e))
446 raise JSONRPCError(str(e))
442 except Exception:
447 except Exception:
443 log.exception("Error occurred during deletion of user group")
448 log.exception("Error occurred during deletion of user group")
444 raise JSONRPCError(
449 raise JSONRPCError(
445 'failed to delete user group ID:%s %s' %(
450 'failed to delete user group ID:%s %s' %(
446 user_group.users_group_id, user_group.users_group_name))
451 user_group.users_group_id, user_group.users_group_name))
447
452
448
453
449 @jsonrpc_method()
454 @jsonrpc_method()
450 def add_user_to_user_group(request, apiuser, usergroupid, userid):
455 def add_user_to_user_group(request, apiuser, usergroupid, userid):
451 """
456 """
452 Adds a user to a `user group`. If the user already exists in the group
457 Adds a user to a `user group`. If the user already exists in the group
453 this command will return false.
458 this command will return false.
454
459
455 This command can only be run using an |authtoken| with admin rights to
460 This command can only be run using an |authtoken| with admin rights to
456 the specified user group.
461 the specified user group.
457
462
458 This command takes the following options:
463 This command takes the following options:
459
464
460 :param apiuser: This is filled automatically from the |authtoken|.
465 :param apiuser: This is filled automatically from the |authtoken|.
461 :type apiuser: AuthUser
466 :type apiuser: AuthUser
462 :param usergroupid: Set the name of the `user group` to which a
467 :param usergroupid: Set the name of the `user group` to which a
463 user will be added.
468 user will be added.
464 :type usergroupid: int
469 :type usergroupid: int
465 :param userid: Set the `user_id` of the user to add to the group.
470 :param userid: Set the `user_id` of the user to add to the group.
466 :type userid: int
471 :type userid: int
467
472
468 Example output:
473 Example output:
469
474
470 .. code-block:: bash
475 .. code-block:: bash
471
476
472 id : <id_given_in_input>
477 id : <id_given_in_input>
473 result : {
478 result : {
474 "success": True|False # depends on if member is in group
479 "success": True|False # depends on if member is in group
475 "msg": "added member `<username>` to user group `<groupname>` |
480 "msg": "added member `<username>` to user group `<groupname>` |
476 User is already in that group"
481 User is already in that group"
477
482
478 }
483 }
479 error : null
484 error : null
480
485
481 Example error output:
486 Example error output:
482
487
483 .. code-block:: bash
488 .. code-block:: bash
484
489
485 id : <id_given_in_input>
490 id : <id_given_in_input>
486 result : null
491 result : null
487 error : {
492 error : {
488 "failed to add member to user group `<user_group_name>`"
493 "failed to add member to user group `<user_group_name>`"
489 }
494 }
490
495
491 """
496 """
492
497
493 user = get_user_or_error(userid)
498 user = get_user_or_error(userid)
494 user_group = get_user_group_or_error(usergroupid)
499 user_group = get_user_group_or_error(usergroupid)
495 if not has_superadmin_permission(apiuser):
500 if not has_superadmin_permission(apiuser):
496 # check if we have admin permission for this user group !
501 # check if we have admin permission for this user group !
497 _perms = ('usergroup.admin',)
502 _perms = ('usergroup.admin',)
498 if not HasUserGroupPermissionAnyApi(*_perms)(
503 if not HasUserGroupPermissionAnyApi(*_perms)(
499 user=apiuser, user_group_name=user_group.users_group_name):
504 user=apiuser, user_group_name=user_group.users_group_name):
500 raise JSONRPCError('user group `%s` does not exist' % (
505 raise JSONRPCError('user group `%s` does not exist' % (
501 usergroupid,))
506 usergroupid,))
502
507
503 old_values = user_group.get_api_data()
508 old_values = user_group.get_api_data()
504 try:
509 try:
505 ugm = UserGroupModel().add_user_to_group(user_group, user)
510 ugm = UserGroupModel().add_user_to_group(user_group, user)
506 success = True if ugm is not True else False
511 success = True if ugm is not True else False
507 msg = 'added member `%s` to user group `%s`' % (
512 msg = 'added member `%s` to user group `%s`' % (
508 user.username, user_group.users_group_name
513 user.username, user_group.users_group_name
509 )
514 )
510 msg = msg if success else 'User is already in that group'
515 msg = msg if success else 'User is already in that group'
511 if success:
516 if success:
512 user_data = user.get_api_data()
517 user_data = user.get_api_data()
513 audit_logger.store_api(
518 audit_logger.store_api(
514 'user_group.edit.member.add',
519 'user_group.edit.member.add',
515 action_data={'user': user_data, 'old_data': old_values},
520 action_data={'user': user_data, 'old_data': old_values},
516 user=apiuser)
521 user=apiuser)
517
522
518 Session().commit()
523 Session().commit()
519
524
520 return {
525 return {
521 'success': success,
526 'success': success,
522 'msg': msg
527 'msg': msg
523 }
528 }
524 except Exception:
529 except Exception:
525 log.exception("Error occurred during adding a member to user group")
530 log.exception("Error occurred during adding a member to user group")
526 raise JSONRPCError(
531 raise JSONRPCError(
527 'failed to add member to user group `%s`' % (
532 'failed to add member to user group `%s`' % (
528 user_group.users_group_name,
533 user_group.users_group_name,
529 )
534 )
530 )
535 )
531
536
532
537
533 @jsonrpc_method()
538 @jsonrpc_method()
534 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
539 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
535 """
540 """
536 Removes a user from a user group.
541 Removes a user from a user group.
537
542
538 * If the specified user is not in the group, this command will return
543 * If the specified user is not in the group, this command will return
539 `false`.
544 `false`.
540
545
541 This command can only be run using an |authtoken| with admin rights to
546 This command can only be run using an |authtoken| with admin rights to
542 the specified user group.
547 the specified user group.
543
548
544 :param apiuser: This is filled automatically from the |authtoken|.
549 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
550 :type apiuser: AuthUser
546 :param usergroupid: Sets the user group name.
551 :param usergroupid: Sets the user group name.
547 :type usergroupid: str or int
552 :type usergroupid: str or int
548 :param userid: The user you wish to remove from |RCE|.
553 :param userid: The user you wish to remove from |RCE|.
549 :type userid: str or int
554 :type userid: str or int
550
555
551 Example output:
556 Example output:
552
557
553 .. code-block:: bash
558 .. code-block:: bash
554
559
555 id : <id_given_in_input>
560 id : <id_given_in_input>
556 result: {
561 result: {
557 "success": True|False, # depends on if member is in group
562 "success": True|False, # depends on if member is in group
558 "msg": "removed member <username> from user group <groupname> |
563 "msg": "removed member <username> from user group <groupname> |
559 User wasn't in group"
564 User wasn't in group"
560 }
565 }
561 error: null
566 error: null
562
567
563 """
568 """
564
569
565 user = get_user_or_error(userid)
570 user = get_user_or_error(userid)
566 user_group = get_user_group_or_error(usergroupid)
571 user_group = get_user_group_or_error(usergroupid)
567 if not has_superadmin_permission(apiuser):
572 if not has_superadmin_permission(apiuser):
568 # check if we have admin permission for this user group !
573 # check if we have admin permission for this user group !
569 _perms = ('usergroup.admin',)
574 _perms = ('usergroup.admin',)
570 if not HasUserGroupPermissionAnyApi(*_perms)(
575 if not HasUserGroupPermissionAnyApi(*_perms)(
571 user=apiuser, user_group_name=user_group.users_group_name):
576 user=apiuser, user_group_name=user_group.users_group_name):
572 raise JSONRPCError(
577 raise JSONRPCError(
573 'user group `%s` does not exist' % (usergroupid,))
578 'user group `%s` does not exist' % (usergroupid,))
574
579
575 old_values = user_group.get_api_data()
580 old_values = user_group.get_api_data()
576 try:
581 try:
577 success = UserGroupModel().remove_user_from_group(user_group, user)
582 success = UserGroupModel().remove_user_from_group(user_group, user)
578 msg = 'removed member `%s` from user group `%s`' % (
583 msg = 'removed member `%s` from user group `%s`' % (
579 user.username, user_group.users_group_name
584 user.username, user_group.users_group_name
580 )
585 )
581 msg = msg if success else "User wasn't in group"
586 msg = msg if success else "User wasn't in group"
582 if success:
587 if success:
583 user_data = user.get_api_data()
588 user_data = user.get_api_data()
584 audit_logger.store_api(
589 audit_logger.store_api(
585 'user_group.edit.member.delete',
590 'user_group.edit.member.delete',
586 action_data={'user': user_data, 'old_data': old_values},
591 action_data={'user': user_data, 'old_data': old_values},
587 user=apiuser)
592 user=apiuser)
588
593
589 Session().commit()
594 Session().commit()
590 return {'success': success, 'msg': msg}
595 return {'success': success, 'msg': msg}
591 except Exception:
596 except Exception:
592 log.exception("Error occurred during removing an member from user group")
597 log.exception("Error occurred during removing an member from user group")
593 raise JSONRPCError(
598 raise JSONRPCError(
594 'failed to remove member from user group `%s`' % (
599 'failed to remove member from user group `%s`' % (
595 user_group.users_group_name,
600 user_group.users_group_name,
596 )
601 )
597 )
602 )
598
603
599
604
600 @jsonrpc_method()
605 @jsonrpc_method()
601 def grant_user_permission_to_user_group(
606 def grant_user_permission_to_user_group(
602 request, apiuser, usergroupid, userid, perm):
607 request, apiuser, usergroupid, userid, perm):
603 """
608 """
604 Set permissions for a user in a user group.
609 Set permissions for a user in a user group.
605
610
606 :param apiuser: This is filled automatically from the |authtoken|.
611 :param apiuser: This is filled automatically from the |authtoken|.
607 :type apiuser: AuthUser
612 :type apiuser: AuthUser
608 :param usergroupid: Set the user group to edit permissions on.
613 :param usergroupid: Set the user group to edit permissions on.
609 :type usergroupid: str or int
614 :type usergroupid: str or int
610 :param userid: Set the user from whom you wish to set permissions.
615 :param userid: Set the user from whom you wish to set permissions.
611 :type userid: str
616 :type userid: str
612 :param perm: (usergroup.(none|read|write|admin))
617 :param perm: (usergroup.(none|read|write|admin))
613 :type perm: str
618 :type perm: str
614
619
615 Example output:
620 Example output:
616
621
617 .. code-block:: bash
622 .. code-block:: bash
618
623
619 id : <id_given_in_input>
624 id : <id_given_in_input>
620 result : {
625 result : {
621 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
626 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
622 "success": true
627 "success": true
623 }
628 }
624 error : null
629 error : null
625 """
630 """
626
631
627 user_group = get_user_group_or_error(usergroupid)
632 user_group = get_user_group_or_error(usergroupid)
628
633
629 if not has_superadmin_permission(apiuser):
634 if not has_superadmin_permission(apiuser):
630 # check if we have admin permission for this user group !
635 # check if we have admin permission for this user group !
631 _perms = ('usergroup.admin',)
636 _perms = ('usergroup.admin',)
632 if not HasUserGroupPermissionAnyApi(*_perms)(
637 if not HasUserGroupPermissionAnyApi(*_perms)(
633 user=apiuser, user_group_name=user_group.users_group_name):
638 user=apiuser, user_group_name=user_group.users_group_name):
634 raise JSONRPCError(
639 raise JSONRPCError(
635 'user group `%s` does not exist' % (usergroupid,))
640 'user group `%s` does not exist' % (usergroupid,))
636
641
637 user = get_user_or_error(userid)
642 user = get_user_or_error(userid)
638 perm = get_perm_or_error(perm, prefix='usergroup.')
643 perm = get_perm_or_error(perm, prefix='usergroup.')
639
644
640 try:
645 try:
641 changes = UserGroupModel().grant_user_permission(
646 changes = UserGroupModel().grant_user_permission(
642 user_group=user_group, user=user, perm=perm)
647 user_group=user_group, user=user, perm=perm)
643
648
644 action_data = {
649 action_data = {
645 'added': changes['added'],
650 'added': changes['added'],
646 'updated': changes['updated'],
651 'updated': changes['updated'],
647 'deleted': changes['deleted'],
652 'deleted': changes['deleted'],
648 }
653 }
649 audit_logger.store_api(
654 audit_logger.store_api(
650 'user_group.edit.permissions', action_data=action_data,
655 'user_group.edit.permissions', action_data=action_data,
651 user=apiuser)
656 user=apiuser)
657 Session().commit()
658 PermissionModel().flush_user_permission_caches(changes)
652
659
653 Session().commit()
654 return {
660 return {
655 'msg':
661 'msg':
656 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
662 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
657 perm.permission_name, user.username,
663 perm.permission_name, user.username,
658 user_group.users_group_name
664 user_group.users_group_name
659 ),
665 ),
660 'success': True
666 'success': True
661 }
667 }
662 except Exception:
668 except Exception:
663 log.exception("Error occurred during editing permissions "
669 log.exception("Error occurred during editing permissions "
664 "for user in user group")
670 "for user in user group")
665 raise JSONRPCError(
671 raise JSONRPCError(
666 'failed to edit permission for user: '
672 'failed to edit permission for user: '
667 '`%s` in user group: `%s`' % (
673 '`%s` in user group: `%s`' % (
668 userid, user_group.users_group_name))
674 userid, user_group.users_group_name))
669
675
670
676
671 @jsonrpc_method()
677 @jsonrpc_method()
672 def revoke_user_permission_from_user_group(
678 def revoke_user_permission_from_user_group(
673 request, apiuser, usergroupid, userid):
679 request, apiuser, usergroupid, userid):
674 """
680 """
675 Revoke a users permissions in a user group.
681 Revoke a users permissions in a user group.
676
682
677 :param apiuser: This is filled automatically from the |authtoken|.
683 :param apiuser: This is filled automatically from the |authtoken|.
678 :type apiuser: AuthUser
684 :type apiuser: AuthUser
679 :param usergroupid: Set the user group from which to revoke the user
685 :param usergroupid: Set the user group from which to revoke the user
680 permissions.
686 permissions.
681 :type: usergroupid: str or int
687 :type: usergroupid: str or int
682 :param userid: Set the userid of the user whose permissions will be
688 :param userid: Set the userid of the user whose permissions will be
683 revoked.
689 revoked.
684 :type userid: str
690 :type userid: str
685
691
686 Example output:
692 Example output:
687
693
688 .. code-block:: bash
694 .. code-block:: bash
689
695
690 id : <id_given_in_input>
696 id : <id_given_in_input>
691 result : {
697 result : {
692 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
698 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
693 "success": true
699 "success": true
694 }
700 }
695 error : null
701 error : null
696 """
702 """
697
703
698 user_group = get_user_group_or_error(usergroupid)
704 user_group = get_user_group_or_error(usergroupid)
699
705
700 if not has_superadmin_permission(apiuser):
706 if not has_superadmin_permission(apiuser):
701 # check if we have admin permission for this user group !
707 # check if we have admin permission for this user group !
702 _perms = ('usergroup.admin',)
708 _perms = ('usergroup.admin',)
703 if not HasUserGroupPermissionAnyApi(*_perms)(
709 if not HasUserGroupPermissionAnyApi(*_perms)(
704 user=apiuser, user_group_name=user_group.users_group_name):
710 user=apiuser, user_group_name=user_group.users_group_name):
705 raise JSONRPCError(
711 raise JSONRPCError(
706 'user group `%s` does not exist' % (usergroupid,))
712 'user group `%s` does not exist' % (usergroupid,))
707
713
708 user = get_user_or_error(userid)
714 user = get_user_or_error(userid)
709
715
710 try:
716 try:
711 changes = UserGroupModel().revoke_user_permission(
717 changes = UserGroupModel().revoke_user_permission(
712 user_group=user_group, user=user)
718 user_group=user_group, user=user)
713 action_data = {
719 action_data = {
714 'added': changes['added'],
720 'added': changes['added'],
715 'updated': changes['updated'],
721 'updated': changes['updated'],
716 'deleted': changes['deleted'],
722 'deleted': changes['deleted'],
717 }
723 }
718 audit_logger.store_api(
724 audit_logger.store_api(
719 'user_group.edit.permissions', action_data=action_data,
725 'user_group.edit.permissions', action_data=action_data,
720 user=apiuser)
726 user=apiuser)
727 Session().commit()
728 PermissionModel().flush_user_permission_caches(changes)
721
729
722 Session().commit()
723 return {
730 return {
724 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
731 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
725 user.username, user_group.users_group_name
732 user.username, user_group.users_group_name
726 ),
733 ),
727 'success': True
734 'success': True
728 }
735 }
729 except Exception:
736 except Exception:
730 log.exception("Error occurred during editing permissions "
737 log.exception("Error occurred during editing permissions "
731 "for user in user group")
738 "for user in user group")
732 raise JSONRPCError(
739 raise JSONRPCError(
733 'failed to edit permission for user: `%s` in user group: `%s`'
740 'failed to edit permission for user: `%s` in user group: `%s`'
734 % (userid, user_group.users_group_name))
741 % (userid, user_group.users_group_name))
735
742
736
743
737 @jsonrpc_method()
744 @jsonrpc_method()
738 def grant_user_group_permission_to_user_group(
745 def grant_user_group_permission_to_user_group(
739 request, apiuser, usergroupid, sourceusergroupid, perm):
746 request, apiuser, usergroupid, sourceusergroupid, perm):
740 """
747 """
741 Give one user group permissions to another user group.
748 Give one user group permissions to another user group.
742
749
743 :param apiuser: This is filled automatically from the |authtoken|.
750 :param apiuser: This is filled automatically from the |authtoken|.
744 :type apiuser: AuthUser
751 :type apiuser: AuthUser
745 :param usergroupid: Set the user group on which to edit permissions.
752 :param usergroupid: Set the user group on which to edit permissions.
746 :type usergroupid: str or int
753 :type usergroupid: str or int
747 :param sourceusergroupid: Set the source user group to which
754 :param sourceusergroupid: Set the source user group to which
748 access/permissions will be granted.
755 access/permissions will be granted.
749 :type sourceusergroupid: str or int
756 :type sourceusergroupid: str or int
750 :param perm: (usergroup.(none|read|write|admin))
757 :param perm: (usergroup.(none|read|write|admin))
751 :type perm: str
758 :type perm: str
752
759
753 Example output:
760 Example output:
754
761
755 .. code-block:: bash
762 .. code-block:: bash
756
763
757 id : <id_given_in_input>
764 id : <id_given_in_input>
758 result : {
765 result : {
759 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
766 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
760 "success": true
767 "success": true
761 }
768 }
762 error : null
769 error : null
763 """
770 """
764
771
765 user_group = get_user_group_or_error(sourceusergroupid)
772 user_group = get_user_group_or_error(sourceusergroupid)
766 target_user_group = get_user_group_or_error(usergroupid)
773 target_user_group = get_user_group_or_error(usergroupid)
767 perm = get_perm_or_error(perm, prefix='usergroup.')
774 perm = get_perm_or_error(perm, prefix='usergroup.')
768
775
769 if not has_superadmin_permission(apiuser):
776 if not has_superadmin_permission(apiuser):
770 # check if we have admin permission for this user group !
777 # check if we have admin permission for this user group !
771 _perms = ('usergroup.admin',)
778 _perms = ('usergroup.admin',)
772 if not HasUserGroupPermissionAnyApi(*_perms)(
779 if not HasUserGroupPermissionAnyApi(*_perms)(
773 user=apiuser,
780 user=apiuser,
774 user_group_name=target_user_group.users_group_name):
781 user_group_name=target_user_group.users_group_name):
775 raise JSONRPCError(
782 raise JSONRPCError(
776 'to user group `%s` does not exist' % (usergroupid,))
783 'to user group `%s` does not exist' % (usergroupid,))
777
784
778 # check if we have at least read permission for source user group !
785 # check if we have at least read permission for source user group !
779 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
786 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
780 if not HasUserGroupPermissionAnyApi(*_perms)(
787 if not HasUserGroupPermissionAnyApi(*_perms)(
781 user=apiuser, user_group_name=user_group.users_group_name):
788 user=apiuser, user_group_name=user_group.users_group_name):
782 raise JSONRPCError(
789 raise JSONRPCError(
783 'user group `%s` does not exist' % (sourceusergroupid,))
790 'user group `%s` does not exist' % (sourceusergroupid,))
784
791
785 try:
792 try:
786 changes = UserGroupModel().grant_user_group_permission(
793 changes = UserGroupModel().grant_user_group_permission(
787 target_user_group=target_user_group,
794 target_user_group=target_user_group,
788 user_group=user_group, perm=perm)
795 user_group=user_group, perm=perm)
789
796
790 action_data = {
797 action_data = {
791 'added': changes['added'],
798 'added': changes['added'],
792 'updated': changes['updated'],
799 'updated': changes['updated'],
793 'deleted': changes['deleted'],
800 'deleted': changes['deleted'],
794 }
801 }
795 audit_logger.store_api(
802 audit_logger.store_api(
796 'user_group.edit.permissions', action_data=action_data,
803 'user_group.edit.permissions', action_data=action_data,
797 user=apiuser)
804 user=apiuser)
805 Session().commit()
806 PermissionModel().flush_user_permission_caches(changes)
798
807
799 Session().commit()
800 return {
808 return {
801 'msg': 'Granted perm: `%s` for user group: `%s` '
809 'msg': 'Granted perm: `%s` for user group: `%s` '
802 'in user group: `%s`' % (
810 'in user group: `%s`' % (
803 perm.permission_name, user_group.users_group_name,
811 perm.permission_name, user_group.users_group_name,
804 target_user_group.users_group_name
812 target_user_group.users_group_name
805 ),
813 ),
806 'success': True
814 'success': True
807 }
815 }
808 except Exception:
816 except Exception:
809 log.exception("Error occurred during editing permissions "
817 log.exception("Error occurred during editing permissions "
810 "for user group in user group")
818 "for user group in user group")
811 raise JSONRPCError(
819 raise JSONRPCError(
812 'failed to edit permission for user group: `%s` in '
820 'failed to edit permission for user group: `%s` in '
813 'user group: `%s`' % (
821 'user group: `%s`' % (
814 sourceusergroupid, target_user_group.users_group_name
822 sourceusergroupid, target_user_group.users_group_name
815 )
823 )
816 )
824 )
817
825
818
826
819 @jsonrpc_method()
827 @jsonrpc_method()
820 def revoke_user_group_permission_from_user_group(
828 def revoke_user_group_permission_from_user_group(
821 request, apiuser, usergroupid, sourceusergroupid):
829 request, apiuser, usergroupid, sourceusergroupid):
822 """
830 """
823 Revoke the permissions that one user group has to another.
831 Revoke the permissions that one user group has to another.
824
832
825 :param apiuser: This is filled automatically from the |authtoken|.
833 :param apiuser: This is filled automatically from the |authtoken|.
826 :type apiuser: AuthUser
834 :type apiuser: AuthUser
827 :param usergroupid: Set the user group on which to edit permissions.
835 :param usergroupid: Set the user group on which to edit permissions.
828 :type usergroupid: str or int
836 :type usergroupid: str or int
829 :param sourceusergroupid: Set the user group from which permissions
837 :param sourceusergroupid: Set the user group from which permissions
830 are revoked.
838 are revoked.
831 :type sourceusergroupid: str or int
839 :type sourceusergroupid: str or int
832
840
833 Example output:
841 Example output:
834
842
835 .. code-block:: bash
843 .. code-block:: bash
836
844
837 id : <id_given_in_input>
845 id : <id_given_in_input>
838 result : {
846 result : {
839 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
847 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
840 "success": true
848 "success": true
841 }
849 }
842 error : null
850 error : null
843 """
851 """
844
852
845 user_group = get_user_group_or_error(sourceusergroupid)
853 user_group = get_user_group_or_error(sourceusergroupid)
846 target_user_group = get_user_group_or_error(usergroupid)
854 target_user_group = get_user_group_or_error(usergroupid)
847
855
848 if not has_superadmin_permission(apiuser):
856 if not has_superadmin_permission(apiuser):
849 # check if we have admin permission for this user group !
857 # check if we have admin permission for this user group !
850 _perms = ('usergroup.admin',)
858 _perms = ('usergroup.admin',)
851 if not HasUserGroupPermissionAnyApi(*_perms)(
859 if not HasUserGroupPermissionAnyApi(*_perms)(
852 user=apiuser,
860 user=apiuser,
853 user_group_name=target_user_group.users_group_name):
861 user_group_name=target_user_group.users_group_name):
854 raise JSONRPCError(
862 raise JSONRPCError(
855 'to user group `%s` does not exist' % (usergroupid,))
863 'to user group `%s` does not exist' % (usergroupid,))
856
864
857 # check if we have at least read permission
865 # check if we have at least read permission
858 # for the source user group !
866 # for the source user group !
859 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
867 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
860 if not HasUserGroupPermissionAnyApi(*_perms)(
868 if not HasUserGroupPermissionAnyApi(*_perms)(
861 user=apiuser, user_group_name=user_group.users_group_name):
869 user=apiuser, user_group_name=user_group.users_group_name):
862 raise JSONRPCError(
870 raise JSONRPCError(
863 'user group `%s` does not exist' % (sourceusergroupid,))
871 'user group `%s` does not exist' % (sourceusergroupid,))
864
872
865 try:
873 try:
866 changes = UserGroupModel().revoke_user_group_permission(
874 changes = UserGroupModel().revoke_user_group_permission(
867 target_user_group=target_user_group, user_group=user_group)
875 target_user_group=target_user_group, user_group=user_group)
868 action_data = {
876 action_data = {
869 'added': changes['added'],
877 'added': changes['added'],
870 'updated': changes['updated'],
878 'updated': changes['updated'],
871 'deleted': changes['deleted'],
879 'deleted': changes['deleted'],
872 }
880 }
873 audit_logger.store_api(
881 audit_logger.store_api(
874 'user_group.edit.permissions', action_data=action_data,
882 'user_group.edit.permissions', action_data=action_data,
875 user=apiuser)
883 user=apiuser)
876
877 Session().commit()
884 Session().commit()
885 PermissionModel().flush_user_permission_caches(changes)
878
886
879 return {
887 return {
880 'msg': 'Revoked perm for user group: '
888 'msg': 'Revoked perm for user group: '
881 '`%s` in user group: `%s`' % (
889 '`%s` in user group: `%s`' % (
882 user_group.users_group_name,
890 user_group.users_group_name,
883 target_user_group.users_group_name
891 target_user_group.users_group_name
884 ),
892 ),
885 'success': True
893 'success': True
886 }
894 }
887 except Exception:
895 except Exception:
888 log.exception("Error occurred during editing permissions "
896 log.exception("Error occurred during editing permissions "
889 "for user group in user group")
897 "for user group in user group")
890 raise JSONRPCError(
898 raise JSONRPCError(
891 'failed to edit permission for user group: '
899 'failed to edit permission for user group: '
892 '`%s` in user group: `%s`' % (
900 '`%s` in user group: `%s`' % (
893 sourceusergroupid, target_user_group.users_group_name
901 sourceusergroupid, target_user_group.users_group_name
894 )
902 )
895 )
903 )
@@ -1,518 +1,518 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 re
21 import re
22 import logging
22 import logging
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25 import datetime
25 import datetime
26 from pyramid.interfaces import IRoutesMapper
26 from pyramid.interfaces import IRoutesMapper
27
27
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.httpexceptions import HTTPFound
29 from pyramid.httpexceptions import HTTPFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
35 from rhodecode import events
35 from rhodecode import events
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 from rhodecode.lib.utils2 import aslist, safe_unicode
40 from rhodecode.lib.utils2 import aslist, safe_unicode
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, coalesce, User, UserIpMap, UserSshKeys)
42 or_, coalesce, User, UserIpMap, UserSshKeys)
43 from rhodecode.model.forms import (
43 from rhodecode.model.forms import (
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
44 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
53 class AdminPermissionsView(BaseAppView, DataGridAppView):
54 def load_default_context(self):
54 def load_default_context(self):
55 c = self._get_local_tmpl_context()
55 c = self._get_local_tmpl_context()
56 PermissionModel().set_global_permission_choices(
56 PermissionModel().set_global_permission_choices(
57 c, gettext_translator=self.request.translate)
57 c, gettext_translator=self.request.translate)
58 return c
58 return c
59
59
60 @LoginRequired()
60 @LoginRequired()
61 @HasPermissionAllDecorator('hg.admin')
61 @HasPermissionAllDecorator('hg.admin')
62 @view_config(
62 @view_config(
63 route_name='admin_permissions_application', request_method='GET',
63 route_name='admin_permissions_application', request_method='GET',
64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
64 renderer='rhodecode:templates/admin/permissions/permissions.mako')
65 def permissions_application(self):
65 def permissions_application(self):
66 c = self.load_default_context()
66 c = self.load_default_context()
67 c.active = 'application'
67 c.active = 'application'
68
68
69 c.user = User.get_default_user(refresh=True)
69 c.user = User.get_default_user(refresh=True)
70
70
71 app_settings = SettingsModel().get_all_settings()
71 app_settings = SettingsModel().get_all_settings()
72 defaults = {
72 defaults = {
73 'anonymous': c.user.active,
73 'anonymous': c.user.active,
74 'default_register_message': app_settings.get(
74 'default_register_message': app_settings.get(
75 'rhodecode_register_message')
75 'rhodecode_register_message')
76 }
76 }
77 defaults.update(c.user.get_default_perms())
77 defaults.update(c.user.get_default_perms())
78
78
79 data = render('rhodecode:templates/admin/permissions/permissions.mako',
79 data = render('rhodecode:templates/admin/permissions/permissions.mako',
80 self._get_template_context(c), self.request)
80 self._get_template_context(c), self.request)
81 html = formencode.htmlfill.render(
81 html = formencode.htmlfill.render(
82 data,
82 data,
83 defaults=defaults,
83 defaults=defaults,
84 encoding="UTF-8",
84 encoding="UTF-8",
85 force_defaults=False
85 force_defaults=False
86 )
86 )
87 return Response(html)
87 return Response(html)
88
88
89 @LoginRequired()
89 @LoginRequired()
90 @HasPermissionAllDecorator('hg.admin')
90 @HasPermissionAllDecorator('hg.admin')
91 @CSRFRequired()
91 @CSRFRequired()
92 @view_config(
92 @view_config(
93 route_name='admin_permissions_application_update', request_method='POST',
93 route_name='admin_permissions_application_update', request_method='POST',
94 renderer='rhodecode:templates/admin/permissions/permissions.mako')
94 renderer='rhodecode:templates/admin/permissions/permissions.mako')
95 def permissions_application_update(self):
95 def permissions_application_update(self):
96 _ = self.request.translate
96 _ = self.request.translate
97 c = self.load_default_context()
97 c = self.load_default_context()
98 c.active = 'application'
98 c.active = 'application'
99
99
100 _form = ApplicationPermissionsForm(
100 _form = ApplicationPermissionsForm(
101 self.request.translate,
101 self.request.translate,
102 [x[0] for x in c.register_choices],
102 [x[0] for x in c.register_choices],
103 [x[0] for x in c.password_reset_choices],
103 [x[0] for x in c.password_reset_choices],
104 [x[0] for x in c.extern_activate_choices])()
104 [x[0] for x in c.extern_activate_choices])()
105
105
106 try:
106 try:
107 form_result = _form.to_python(dict(self.request.POST))
107 form_result = _form.to_python(dict(self.request.POST))
108 form_result.update({'perm_user_name': User.DEFAULT_USER})
108 form_result.update({'perm_user_name': User.DEFAULT_USER})
109 PermissionModel().update_application_permissions(form_result)
109 PermissionModel().update_application_permissions(form_result)
110
110
111 settings = [
111 settings = [
112 ('register_message', 'default_register_message'),
112 ('register_message', 'default_register_message'),
113 ]
113 ]
114 for setting, form_key in settings:
114 for setting, form_key in settings:
115 sett = SettingsModel().create_or_update_setting(
115 sett = SettingsModel().create_or_update_setting(
116 setting, form_result[form_key])
116 setting, form_result[form_key])
117 Session().add(sett)
117 Session().add(sett)
118
118
119 Session().commit()
119 Session().commit()
120 h.flash(_('Application permissions updated successfully'),
120 h.flash(_('Application permissions updated successfully'),
121 category='success')
121 category='success')
122
122
123 except formencode.Invalid as errors:
123 except formencode.Invalid as errors:
124 defaults = errors.value
124 defaults = errors.value
125
125
126 data = render(
126 data = render(
127 'rhodecode:templates/admin/permissions/permissions.mako',
127 'rhodecode:templates/admin/permissions/permissions.mako',
128 self._get_template_context(c), self.request)
128 self._get_template_context(c), self.request)
129 html = formencode.htmlfill.render(
129 html = formencode.htmlfill.render(
130 data,
130 data,
131 defaults=defaults,
131 defaults=defaults,
132 errors=errors.error_dict or {},
132 errors=errors.error_dict or {},
133 prefix_error=False,
133 prefix_error=False,
134 encoding="UTF-8",
134 encoding="UTF-8",
135 force_defaults=False
135 force_defaults=False
136 )
136 )
137 return Response(html)
137 return Response(html)
138
138
139 except Exception:
139 except Exception:
140 log.exception("Exception during update of permissions")
140 log.exception("Exception during update of permissions")
141 h.flash(_('Error occurred during update of permissions'),
141 h.flash(_('Error occurred during update of permissions'),
142 category='error')
142 category='error')
143
143
144 affected_user_ids = [User.get_default_user().user_id]
144 affected_user_ids = [User.get_default_user().user_id]
145 events.trigger(events.UserPermissionsChange(affected_user_ids))
145 PermissionModel().trigger_permission_flush(affected_user_ids)
146
146
147 raise HTTPFound(h.route_path('admin_permissions_application'))
147 raise HTTPFound(h.route_path('admin_permissions_application'))
148
148
149 @LoginRequired()
149 @LoginRequired()
150 @HasPermissionAllDecorator('hg.admin')
150 @HasPermissionAllDecorator('hg.admin')
151 @view_config(
151 @view_config(
152 route_name='admin_permissions_object', request_method='GET',
152 route_name='admin_permissions_object', request_method='GET',
153 renderer='rhodecode:templates/admin/permissions/permissions.mako')
153 renderer='rhodecode:templates/admin/permissions/permissions.mako')
154 def permissions_objects(self):
154 def permissions_objects(self):
155 c = self.load_default_context()
155 c = self.load_default_context()
156 c.active = 'objects'
156 c.active = 'objects'
157
157
158 c.user = User.get_default_user(refresh=True)
158 c.user = User.get_default_user(refresh=True)
159 defaults = {}
159 defaults = {}
160 defaults.update(c.user.get_default_perms())
160 defaults.update(c.user.get_default_perms())
161
161
162 data = render(
162 data = render(
163 'rhodecode:templates/admin/permissions/permissions.mako',
163 'rhodecode:templates/admin/permissions/permissions.mako',
164 self._get_template_context(c), self.request)
164 self._get_template_context(c), self.request)
165 html = formencode.htmlfill.render(
165 html = formencode.htmlfill.render(
166 data,
166 data,
167 defaults=defaults,
167 defaults=defaults,
168 encoding="UTF-8",
168 encoding="UTF-8",
169 force_defaults=False
169 force_defaults=False
170 )
170 )
171 return Response(html)
171 return Response(html)
172
172
173 @LoginRequired()
173 @LoginRequired()
174 @HasPermissionAllDecorator('hg.admin')
174 @HasPermissionAllDecorator('hg.admin')
175 @CSRFRequired()
175 @CSRFRequired()
176 @view_config(
176 @view_config(
177 route_name='admin_permissions_object_update', request_method='POST',
177 route_name='admin_permissions_object_update', request_method='POST',
178 renderer='rhodecode:templates/admin/permissions/permissions.mako')
178 renderer='rhodecode:templates/admin/permissions/permissions.mako')
179 def permissions_objects_update(self):
179 def permissions_objects_update(self):
180 _ = self.request.translate
180 _ = self.request.translate
181 c = self.load_default_context()
181 c = self.load_default_context()
182 c.active = 'objects'
182 c.active = 'objects'
183
183
184 _form = ObjectPermissionsForm(
184 _form = ObjectPermissionsForm(
185 self.request.translate,
185 self.request.translate,
186 [x[0] for x in c.repo_perms_choices],
186 [x[0] for x in c.repo_perms_choices],
187 [x[0] for x in c.group_perms_choices],
187 [x[0] for x in c.group_perms_choices],
188 [x[0] for x in c.user_group_perms_choices],
188 [x[0] for x in c.user_group_perms_choices],
189 )()
189 )()
190
190
191 try:
191 try:
192 form_result = _form.to_python(dict(self.request.POST))
192 form_result = _form.to_python(dict(self.request.POST))
193 form_result.update({'perm_user_name': User.DEFAULT_USER})
193 form_result.update({'perm_user_name': User.DEFAULT_USER})
194 PermissionModel().update_object_permissions(form_result)
194 PermissionModel().update_object_permissions(form_result)
195
195
196 Session().commit()
196 Session().commit()
197 h.flash(_('Object permissions updated successfully'),
197 h.flash(_('Object permissions updated successfully'),
198 category='success')
198 category='success')
199
199
200 except formencode.Invalid as errors:
200 except formencode.Invalid as errors:
201 defaults = errors.value
201 defaults = errors.value
202
202
203 data = render(
203 data = render(
204 'rhodecode:templates/admin/permissions/permissions.mako',
204 'rhodecode:templates/admin/permissions/permissions.mako',
205 self._get_template_context(c), self.request)
205 self._get_template_context(c), self.request)
206 html = formencode.htmlfill.render(
206 html = formencode.htmlfill.render(
207 data,
207 data,
208 defaults=defaults,
208 defaults=defaults,
209 errors=errors.error_dict or {},
209 errors=errors.error_dict or {},
210 prefix_error=False,
210 prefix_error=False,
211 encoding="UTF-8",
211 encoding="UTF-8",
212 force_defaults=False
212 force_defaults=False
213 )
213 )
214 return Response(html)
214 return Response(html)
215 except Exception:
215 except Exception:
216 log.exception("Exception during update of permissions")
216 log.exception("Exception during update of permissions")
217 h.flash(_('Error occurred during update of permissions'),
217 h.flash(_('Error occurred during update of permissions'),
218 category='error')
218 category='error')
219
219
220 affected_user_ids = [User.get_default_user().user_id]
220 affected_user_ids = [User.get_default_user().user_id]
221 events.trigger(events.UserPermissionsChange(affected_user_ids))
221 PermissionModel().trigger_permission_flush(affected_user_ids)
222
222
223 raise HTTPFound(h.route_path('admin_permissions_object'))
223 raise HTTPFound(h.route_path('admin_permissions_object'))
224
224
225 @LoginRequired()
225 @LoginRequired()
226 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
227 @view_config(
227 @view_config(
228 route_name='admin_permissions_branch', request_method='GET',
228 route_name='admin_permissions_branch', request_method='GET',
229 renderer='rhodecode:templates/admin/permissions/permissions.mako')
229 renderer='rhodecode:templates/admin/permissions/permissions.mako')
230 def permissions_branch(self):
230 def permissions_branch(self):
231 c = self.load_default_context()
231 c = self.load_default_context()
232 c.active = 'branch'
232 c.active = 'branch'
233
233
234 c.user = User.get_default_user(refresh=True)
234 c.user = User.get_default_user(refresh=True)
235 defaults = {}
235 defaults = {}
236 defaults.update(c.user.get_default_perms())
236 defaults.update(c.user.get_default_perms())
237
237
238 data = render(
238 data = render(
239 'rhodecode:templates/admin/permissions/permissions.mako',
239 'rhodecode:templates/admin/permissions/permissions.mako',
240 self._get_template_context(c), self.request)
240 self._get_template_context(c), self.request)
241 html = formencode.htmlfill.render(
241 html = formencode.htmlfill.render(
242 data,
242 data,
243 defaults=defaults,
243 defaults=defaults,
244 encoding="UTF-8",
244 encoding="UTF-8",
245 force_defaults=False
245 force_defaults=False
246 )
246 )
247 return Response(html)
247 return Response(html)
248
248
249 @LoginRequired()
249 @LoginRequired()
250 @HasPermissionAllDecorator('hg.admin')
250 @HasPermissionAllDecorator('hg.admin')
251 @view_config(
251 @view_config(
252 route_name='admin_permissions_global', request_method='GET',
252 route_name='admin_permissions_global', request_method='GET',
253 renderer='rhodecode:templates/admin/permissions/permissions.mako')
253 renderer='rhodecode:templates/admin/permissions/permissions.mako')
254 def permissions_global(self):
254 def permissions_global(self):
255 c = self.load_default_context()
255 c = self.load_default_context()
256 c.active = 'global'
256 c.active = 'global'
257
257
258 c.user = User.get_default_user(refresh=True)
258 c.user = User.get_default_user(refresh=True)
259 defaults = {}
259 defaults = {}
260 defaults.update(c.user.get_default_perms())
260 defaults.update(c.user.get_default_perms())
261
261
262 data = render(
262 data = render(
263 'rhodecode:templates/admin/permissions/permissions.mako',
263 'rhodecode:templates/admin/permissions/permissions.mako',
264 self._get_template_context(c), self.request)
264 self._get_template_context(c), self.request)
265 html = formencode.htmlfill.render(
265 html = formencode.htmlfill.render(
266 data,
266 data,
267 defaults=defaults,
267 defaults=defaults,
268 encoding="UTF-8",
268 encoding="UTF-8",
269 force_defaults=False
269 force_defaults=False
270 )
270 )
271 return Response(html)
271 return Response(html)
272
272
273 @LoginRequired()
273 @LoginRequired()
274 @HasPermissionAllDecorator('hg.admin')
274 @HasPermissionAllDecorator('hg.admin')
275 @CSRFRequired()
275 @CSRFRequired()
276 @view_config(
276 @view_config(
277 route_name='admin_permissions_global_update', request_method='POST',
277 route_name='admin_permissions_global_update', request_method='POST',
278 renderer='rhodecode:templates/admin/permissions/permissions.mako')
278 renderer='rhodecode:templates/admin/permissions/permissions.mako')
279 def permissions_global_update(self):
279 def permissions_global_update(self):
280 _ = self.request.translate
280 _ = self.request.translate
281 c = self.load_default_context()
281 c = self.load_default_context()
282 c.active = 'global'
282 c.active = 'global'
283
283
284 _form = UserPermissionsForm(
284 _form = UserPermissionsForm(
285 self.request.translate,
285 self.request.translate,
286 [x[0] for x in c.repo_create_choices],
286 [x[0] for x in c.repo_create_choices],
287 [x[0] for x in c.repo_create_on_write_choices],
287 [x[0] for x in c.repo_create_on_write_choices],
288 [x[0] for x in c.repo_group_create_choices],
288 [x[0] for x in c.repo_group_create_choices],
289 [x[0] for x in c.user_group_create_choices],
289 [x[0] for x in c.user_group_create_choices],
290 [x[0] for x in c.fork_choices],
290 [x[0] for x in c.fork_choices],
291 [x[0] for x in c.inherit_default_permission_choices])()
291 [x[0] for x in c.inherit_default_permission_choices])()
292
292
293 try:
293 try:
294 form_result = _form.to_python(dict(self.request.POST))
294 form_result = _form.to_python(dict(self.request.POST))
295 form_result.update({'perm_user_name': User.DEFAULT_USER})
295 form_result.update({'perm_user_name': User.DEFAULT_USER})
296 PermissionModel().update_user_permissions(form_result)
296 PermissionModel().update_user_permissions(form_result)
297
297
298 Session().commit()
298 Session().commit()
299 h.flash(_('Global permissions updated successfully'),
299 h.flash(_('Global permissions updated successfully'),
300 category='success')
300 category='success')
301
301
302 except formencode.Invalid as errors:
302 except formencode.Invalid as errors:
303 defaults = errors.value
303 defaults = errors.value
304
304
305 data = render(
305 data = render(
306 'rhodecode:templates/admin/permissions/permissions.mako',
306 'rhodecode:templates/admin/permissions/permissions.mako',
307 self._get_template_context(c), self.request)
307 self._get_template_context(c), self.request)
308 html = formencode.htmlfill.render(
308 html = formencode.htmlfill.render(
309 data,
309 data,
310 defaults=defaults,
310 defaults=defaults,
311 errors=errors.error_dict or {},
311 errors=errors.error_dict or {},
312 prefix_error=False,
312 prefix_error=False,
313 encoding="UTF-8",
313 encoding="UTF-8",
314 force_defaults=False
314 force_defaults=False
315 )
315 )
316 return Response(html)
316 return Response(html)
317 except Exception:
317 except Exception:
318 log.exception("Exception during update of permissions")
318 log.exception("Exception during update of permissions")
319 h.flash(_('Error occurred during update of permissions'),
319 h.flash(_('Error occurred during update of permissions'),
320 category='error')
320 category='error')
321
321
322 affected_user_ids = [User.get_default_user().user_id]
322 affected_user_ids = [User.get_default_user().user_id]
323 events.trigger(events.UserPermissionsChange(affected_user_ids))
323 PermissionModel().trigger_permission_flush(affected_user_ids)
324
324
325 raise HTTPFound(h.route_path('admin_permissions_global'))
325 raise HTTPFound(h.route_path('admin_permissions_global'))
326
326
327 @LoginRequired()
327 @LoginRequired()
328 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
329 @view_config(
329 @view_config(
330 route_name='admin_permissions_ips', request_method='GET',
330 route_name='admin_permissions_ips', request_method='GET',
331 renderer='rhodecode:templates/admin/permissions/permissions.mako')
331 renderer='rhodecode:templates/admin/permissions/permissions.mako')
332 def permissions_ips(self):
332 def permissions_ips(self):
333 c = self.load_default_context()
333 c = self.load_default_context()
334 c.active = 'ips'
334 c.active = 'ips'
335
335
336 c.user = User.get_default_user(refresh=True)
336 c.user = User.get_default_user(refresh=True)
337 c.user_ip_map = (
337 c.user_ip_map = (
338 UserIpMap.query().filter(UserIpMap.user == c.user).all())
338 UserIpMap.query().filter(UserIpMap.user == c.user).all())
339
339
340 return self._get_template_context(c)
340 return self._get_template_context(c)
341
341
342 @LoginRequired()
342 @LoginRequired()
343 @HasPermissionAllDecorator('hg.admin')
343 @HasPermissionAllDecorator('hg.admin')
344 @view_config(
344 @view_config(
345 route_name='admin_permissions_overview', request_method='GET',
345 route_name='admin_permissions_overview', request_method='GET',
346 renderer='rhodecode:templates/admin/permissions/permissions.mako')
346 renderer='rhodecode:templates/admin/permissions/permissions.mako')
347 def permissions_overview(self):
347 def permissions_overview(self):
348 c = self.load_default_context()
348 c = self.load_default_context()
349 c.active = 'perms'
349 c.active = 'perms'
350
350
351 c.user = User.get_default_user(refresh=True)
351 c.user = User.get_default_user(refresh=True)
352 c.perm_user = c.user.AuthUser()
352 c.perm_user = c.user.AuthUser()
353 return self._get_template_context(c)
353 return self._get_template_context(c)
354
354
355 @LoginRequired()
355 @LoginRequired()
356 @HasPermissionAllDecorator('hg.admin')
356 @HasPermissionAllDecorator('hg.admin')
357 @view_config(
357 @view_config(
358 route_name='admin_permissions_auth_token_access', request_method='GET',
358 route_name='admin_permissions_auth_token_access', request_method='GET',
359 renderer='rhodecode:templates/admin/permissions/permissions.mako')
359 renderer='rhodecode:templates/admin/permissions/permissions.mako')
360 def auth_token_access(self):
360 def auth_token_access(self):
361 from rhodecode import CONFIG
361 from rhodecode import CONFIG
362
362
363 c = self.load_default_context()
363 c = self.load_default_context()
364 c.active = 'auth_token_access'
364 c.active = 'auth_token_access'
365
365
366 c.user = User.get_default_user(refresh=True)
366 c.user = User.get_default_user(refresh=True)
367 c.perm_user = c.user.AuthUser()
367 c.perm_user = c.user.AuthUser()
368
368
369 mapper = self.request.registry.queryUtility(IRoutesMapper)
369 mapper = self.request.registry.queryUtility(IRoutesMapper)
370 c.view_data = []
370 c.view_data = []
371
371
372 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
372 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
373 introspector = self.request.registry.introspector
373 introspector = self.request.registry.introspector
374
374
375 view_intr = {}
375 view_intr = {}
376 for view_data in introspector.get_category('views'):
376 for view_data in introspector.get_category('views'):
377 intr = view_data['introspectable']
377 intr = view_data['introspectable']
378
378
379 if 'route_name' in intr and intr['attr']:
379 if 'route_name' in intr and intr['attr']:
380 view_intr[intr['route_name']] = '{}:{}'.format(
380 view_intr[intr['route_name']] = '{}:{}'.format(
381 str(intr['derived_callable'].func_name), intr['attr']
381 str(intr['derived_callable'].func_name), intr['attr']
382 )
382 )
383
383
384 c.whitelist_key = 'api_access_controllers_whitelist'
384 c.whitelist_key = 'api_access_controllers_whitelist'
385 c.whitelist_file = CONFIG.get('__file__')
385 c.whitelist_file = CONFIG.get('__file__')
386 whitelist_views = aslist(
386 whitelist_views = aslist(
387 CONFIG.get(c.whitelist_key), sep=',')
387 CONFIG.get(c.whitelist_key), sep=',')
388
388
389 for route_info in mapper.get_routes():
389 for route_info in mapper.get_routes():
390 if not route_info.name.startswith('__'):
390 if not route_info.name.startswith('__'):
391 routepath = route_info.pattern
391 routepath = route_info.pattern
392
392
393 def replace(matchobj):
393 def replace(matchobj):
394 if matchobj.group(1):
394 if matchobj.group(1):
395 return "{%s}" % matchobj.group(1).split(':')[0]
395 return "{%s}" % matchobj.group(1).split(':')[0]
396 else:
396 else:
397 return "{%s}" % matchobj.group(2)
397 return "{%s}" % matchobj.group(2)
398
398
399 routepath = _argument_prog.sub(replace, routepath)
399 routepath = _argument_prog.sub(replace, routepath)
400
400
401 if not routepath.startswith('/'):
401 if not routepath.startswith('/'):
402 routepath = '/' + routepath
402 routepath = '/' + routepath
403
403
404 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
404 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
405 active = view_fqn in whitelist_views
405 active = view_fqn in whitelist_views
406 c.view_data.append((route_info.name, view_fqn, routepath, active))
406 c.view_data.append((route_info.name, view_fqn, routepath, active))
407
407
408 c.whitelist_views = whitelist_views
408 c.whitelist_views = whitelist_views
409 return self._get_template_context(c)
409 return self._get_template_context(c)
410
410
411 def ssh_enabled(self):
411 def ssh_enabled(self):
412 return self.request.registry.settings.get(
412 return self.request.registry.settings.get(
413 'ssh.generate_authorized_keyfile')
413 'ssh.generate_authorized_keyfile')
414
414
415 @LoginRequired()
415 @LoginRequired()
416 @HasPermissionAllDecorator('hg.admin')
416 @HasPermissionAllDecorator('hg.admin')
417 @view_config(
417 @view_config(
418 route_name='admin_permissions_ssh_keys', request_method='GET',
418 route_name='admin_permissions_ssh_keys', request_method='GET',
419 renderer='rhodecode:templates/admin/permissions/permissions.mako')
419 renderer='rhodecode:templates/admin/permissions/permissions.mako')
420 def ssh_keys(self):
420 def ssh_keys(self):
421 c = self.load_default_context()
421 c = self.load_default_context()
422 c.active = 'ssh_keys'
422 c.active = 'ssh_keys'
423 c.ssh_enabled = self.ssh_enabled()
423 c.ssh_enabled = self.ssh_enabled()
424 return self._get_template_context(c)
424 return self._get_template_context(c)
425
425
426 @LoginRequired()
426 @LoginRequired()
427 @HasPermissionAllDecorator('hg.admin')
427 @HasPermissionAllDecorator('hg.admin')
428 @view_config(
428 @view_config(
429 route_name='admin_permissions_ssh_keys_data', request_method='GET',
429 route_name='admin_permissions_ssh_keys_data', request_method='GET',
430 renderer='json_ext', xhr=True)
430 renderer='json_ext', xhr=True)
431 def ssh_keys_data(self):
431 def ssh_keys_data(self):
432 _ = self.request.translate
432 _ = self.request.translate
433 self.load_default_context()
433 self.load_default_context()
434 column_map = {
434 column_map = {
435 'fingerprint': 'ssh_key_fingerprint',
435 'fingerprint': 'ssh_key_fingerprint',
436 'username': User.username
436 'username': User.username
437 }
437 }
438 draw, start, limit = self._extract_chunk(self.request)
438 draw, start, limit = self._extract_chunk(self.request)
439 search_q, order_by, order_dir = self._extract_ordering(
439 search_q, order_by, order_dir = self._extract_ordering(
440 self.request, column_map=column_map)
440 self.request, column_map=column_map)
441
441
442 ssh_keys_data_total_count = UserSshKeys.query()\
442 ssh_keys_data_total_count = UserSshKeys.query()\
443 .count()
443 .count()
444
444
445 # json generate
445 # json generate
446 base_q = UserSshKeys.query().join(UserSshKeys.user)
446 base_q = UserSshKeys.query().join(UserSshKeys.user)
447
447
448 if search_q:
448 if search_q:
449 like_expression = u'%{}%'.format(safe_unicode(search_q))
449 like_expression = u'%{}%'.format(safe_unicode(search_q))
450 base_q = base_q.filter(or_(
450 base_q = base_q.filter(or_(
451 User.username.ilike(like_expression),
451 User.username.ilike(like_expression),
452 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
452 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
453 ))
453 ))
454
454
455 users_data_total_filtered_count = base_q.count()
455 users_data_total_filtered_count = base_q.count()
456
456
457 sort_col = self._get_order_col(order_by, UserSshKeys)
457 sort_col = self._get_order_col(order_by, UserSshKeys)
458 if sort_col:
458 if sort_col:
459 if order_dir == 'asc':
459 if order_dir == 'asc':
460 # handle null values properly to order by NULL last
460 # handle null values properly to order by NULL last
461 if order_by in ['created_on']:
461 if order_by in ['created_on']:
462 sort_col = coalesce(sort_col, datetime.date.max)
462 sort_col = coalesce(sort_col, datetime.date.max)
463 sort_col = sort_col.asc()
463 sort_col = sort_col.asc()
464 else:
464 else:
465 # handle null values properly to order by NULL last
465 # handle null values properly to order by NULL last
466 if order_by in ['created_on']:
466 if order_by in ['created_on']:
467 sort_col = coalesce(sort_col, datetime.date.min)
467 sort_col = coalesce(sort_col, datetime.date.min)
468 sort_col = sort_col.desc()
468 sort_col = sort_col.desc()
469
469
470 base_q = base_q.order_by(sort_col)
470 base_q = base_q.order_by(sort_col)
471 base_q = base_q.offset(start).limit(limit)
471 base_q = base_q.offset(start).limit(limit)
472
472
473 ssh_keys = base_q.all()
473 ssh_keys = base_q.all()
474
474
475 ssh_keys_data = []
475 ssh_keys_data = []
476 for ssh_key in ssh_keys:
476 for ssh_key in ssh_keys:
477 ssh_keys_data.append({
477 ssh_keys_data.append({
478 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
478 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
479 "fingerprint": ssh_key.ssh_key_fingerprint,
479 "fingerprint": ssh_key.ssh_key_fingerprint,
480 "description": ssh_key.description,
480 "description": ssh_key.description,
481 "created_on": h.format_date(ssh_key.created_on),
481 "created_on": h.format_date(ssh_key.created_on),
482 "accessed_on": h.format_date(ssh_key.accessed_on),
482 "accessed_on": h.format_date(ssh_key.accessed_on),
483 "action": h.link_to(
483 "action": h.link_to(
484 _('Edit'), h.route_path('edit_user_ssh_keys',
484 _('Edit'), h.route_path('edit_user_ssh_keys',
485 user_id=ssh_key.user.user_id))
485 user_id=ssh_key.user.user_id))
486 })
486 })
487
487
488 data = ({
488 data = ({
489 'draw': draw,
489 'draw': draw,
490 'data': ssh_keys_data,
490 'data': ssh_keys_data,
491 'recordsTotal': ssh_keys_data_total_count,
491 'recordsTotal': ssh_keys_data_total_count,
492 'recordsFiltered': users_data_total_filtered_count,
492 'recordsFiltered': users_data_total_filtered_count,
493 })
493 })
494
494
495 return data
495 return data
496
496
497 @LoginRequired()
497 @LoginRequired()
498 @HasPermissionAllDecorator('hg.admin')
498 @HasPermissionAllDecorator('hg.admin')
499 @CSRFRequired()
499 @CSRFRequired()
500 @view_config(
500 @view_config(
501 route_name='admin_permissions_ssh_keys_update', request_method='POST',
501 route_name='admin_permissions_ssh_keys_update', request_method='POST',
502 renderer='rhodecode:templates/admin/permissions/permissions.mako')
502 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 def ssh_keys_update(self):
503 def ssh_keys_update(self):
504 _ = self.request.translate
504 _ = self.request.translate
505 self.load_default_context()
505 self.load_default_context()
506
506
507 ssh_enabled = self.ssh_enabled()
507 ssh_enabled = self.ssh_enabled()
508 key_file = self.request.registry.settings.get(
508 key_file = self.request.registry.settings.get(
509 'ssh.authorized_keys_file_path')
509 'ssh.authorized_keys_file_path')
510 if ssh_enabled:
510 if ssh_enabled:
511 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
511 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
512 h.flash(_('Updated SSH keys file: {}').format(key_file),
512 h.flash(_('Updated SSH keys file: {}').format(key_file),
513 category='success')
513 category='success')
514 else:
514 else:
515 h.flash(_('SSH key support is disabled in .ini file'),
515 h.flash(_('SSH key support is disabled in .ini file'),
516 category='warning')
516 category='warning')
517
517
518 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
518 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,361 +1,362 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 import datetime
20 import datetime
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
35 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
36 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib import helpers as h, audit_logger
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
38 from rhodecode.model.forms import RepoGroupForm
38 from rhodecode.model.forms import RepoGroupForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.repo_group import RepoGroupModel
40 from rhodecode.model.scm import RepoGroupList
41 from rhodecode.model.scm import RepoGroupList
41 from rhodecode.model.db import (
42 from rhodecode.model.db import (
42 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
48 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
48
49
49 def load_default_context(self):
50 def load_default_context(self):
50 c = self._get_local_tmpl_context()
51 c = self._get_local_tmpl_context()
51
52
52 return c
53 return c
53
54
54 def _load_form_data(self, c):
55 def _load_form_data(self, c):
55 allow_empty_group = False
56 allow_empty_group = False
56
57
57 if self._can_create_repo_group():
58 if self._can_create_repo_group():
58 # we're global admin, we're ok and we can create TOP level groups
59 # we're global admin, we're ok and we can create TOP level groups
59 allow_empty_group = True
60 allow_empty_group = True
60
61
61 # override the choices for this form, we need to filter choices
62 # override the choices for this form, we need to filter choices
62 # and display only those we have ADMIN right
63 # and display only those we have ADMIN right
63 groups_with_admin_rights = RepoGroupList(
64 groups_with_admin_rights = RepoGroupList(
64 RepoGroup.query().all(),
65 RepoGroup.query().all(),
65 perm_set=['group.admin'])
66 perm_set=['group.admin'])
66 c.repo_groups = RepoGroup.groups_choices(
67 c.repo_groups = RepoGroup.groups_choices(
67 groups=groups_with_admin_rights,
68 groups=groups_with_admin_rights,
68 show_empty_group=allow_empty_group)
69 show_empty_group=allow_empty_group)
69
70
70 def _can_create_repo_group(self, parent_group_id=None):
71 def _can_create_repo_group(self, parent_group_id=None):
71 is_admin = HasPermissionAny('hg.admin')('group create controller')
72 is_admin = HasPermissionAny('hg.admin')('group create controller')
72 create_repo_group = HasPermissionAny(
73 create_repo_group = HasPermissionAny(
73 'hg.repogroup.create.true')('group create controller')
74 'hg.repogroup.create.true')('group create controller')
74 if is_admin or (create_repo_group and not parent_group_id):
75 if is_admin or (create_repo_group and not parent_group_id):
75 # we're global admin, or we have global repo group create
76 # we're global admin, or we have global repo group create
76 # permission
77 # permission
77 # we're ok and we can create TOP level groups
78 # we're ok and we can create TOP level groups
78 return True
79 return True
79 elif parent_group_id:
80 elif parent_group_id:
80 # we check the permission if we can write to parent group
81 # we check the permission if we can write to parent group
81 group = RepoGroup.get(parent_group_id)
82 group = RepoGroup.get(parent_group_id)
82 group_name = group.group_name if group else None
83 group_name = group.group_name if group else None
83 if HasRepoGroupPermissionAny('group.admin')(
84 if HasRepoGroupPermissionAny('group.admin')(
84 group_name, 'check if user is an admin of group'):
85 group_name, 'check if user is an admin of group'):
85 # we're an admin of passed in group, we're ok.
86 # we're an admin of passed in group, we're ok.
86 return True
87 return True
87 else:
88 else:
88 return False
89 return False
89 return False
90 return False
90
91
91 # permission check in data loading of
92 # permission check in data loading of
92 # `repo_group_list_data` via RepoGroupList
93 # `repo_group_list_data` via RepoGroupList
93 @LoginRequired()
94 @LoginRequired()
94 @NotAnonymous()
95 @NotAnonymous()
95 @view_config(
96 @view_config(
96 route_name='repo_groups', request_method='GET',
97 route_name='repo_groups', request_method='GET',
97 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
98 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
98 def repo_group_list(self):
99 def repo_group_list(self):
99 c = self.load_default_context()
100 c = self.load_default_context()
100 return self._get_template_context(c)
101 return self._get_template_context(c)
101
102
102 # permission check inside
103 # permission check inside
103 @LoginRequired()
104 @LoginRequired()
104 @NotAnonymous()
105 @NotAnonymous()
105 @view_config(
106 @view_config(
106 route_name='repo_groups_data', request_method='GET',
107 route_name='repo_groups_data', request_method='GET',
107 renderer='json_ext', xhr=True)
108 renderer='json_ext', xhr=True)
108 def repo_group_list_data(self):
109 def repo_group_list_data(self):
109 self.load_default_context()
110 self.load_default_context()
110 column_map = {
111 column_map = {
111 'name_raw': 'group_name_hash',
112 'name_raw': 'group_name_hash',
112 'desc': 'group_description',
113 'desc': 'group_description',
113 'last_change_raw': 'updated_on',
114 'last_change_raw': 'updated_on',
114 'top_level_repos': 'repos_total',
115 'top_level_repos': 'repos_total',
115 'owner': 'user_username',
116 'owner': 'user_username',
116 }
117 }
117 draw, start, limit = self._extract_chunk(self.request)
118 draw, start, limit = self._extract_chunk(self.request)
118 search_q, order_by, order_dir = self._extract_ordering(
119 search_q, order_by, order_dir = self._extract_ordering(
119 self.request, column_map=column_map)
120 self.request, column_map=column_map)
120
121
121 _render = self.request.get_partial_renderer(
122 _render = self.request.get_partial_renderer(
122 'rhodecode:templates/data_table/_dt_elements.mako')
123 'rhodecode:templates/data_table/_dt_elements.mako')
123 c = _render.get_call_context()
124 c = _render.get_call_context()
124
125
125 def quick_menu(repo_group_name):
126 def quick_menu(repo_group_name):
126 return _render('quick_repo_group_menu', repo_group_name)
127 return _render('quick_repo_group_menu', repo_group_name)
127
128
128 def repo_group_lnk(repo_group_name):
129 def repo_group_lnk(repo_group_name):
129 return _render('repo_group_name', repo_group_name)
130 return _render('repo_group_name', repo_group_name)
130
131
131 def last_change(last_change):
132 def last_change(last_change):
132 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 delta = datetime.timedelta(
134 delta = datetime.timedelta(
134 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 last_change = last_change + delta
136 last_change = last_change + delta
136 return _render("last_change", last_change)
137 return _render("last_change", last_change)
137
138
138 def desc(desc, personal):
139 def desc(desc, personal):
139 return _render(
140 return _render(
140 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141
142
142 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 return _render(
144 return _render(
144 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145
146
146 def user_profile(username):
147 def user_profile(username):
147 return _render('user_profile', username)
148 return _render('user_profile', username)
148
149
149 auth_repo_group_list = RepoGroupList(
150 auth_repo_group_list = RepoGroupList(
150 RepoGroup.query().all(), perm_set=['group.admin'])
151 RepoGroup.query().all(), perm_set=['group.admin'])
151
152
152 allowed_ids = [-1]
153 allowed_ids = [-1]
153 for repo_group in auth_repo_group_list:
154 for repo_group in auth_repo_group_list:
154 allowed_ids.append(repo_group.group_id)
155 allowed_ids.append(repo_group.group_id)
155
156
156 repo_groups_data_total_count = RepoGroup.query()\
157 repo_groups_data_total_count = RepoGroup.query()\
157 .filter(or_(
158 .filter(or_(
158 # generate multiple IN to fix limitation problems
159 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 )) \
161 )) \
161 .count()
162 .count()
162
163
163 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .count()
166 .count()
166
167
167 repo_count = count(Repository.repo_id)
168 repo_count = count(Repository.repo_id)
168 base_q = Session.query(
169 base_q = Session.query(
169 RepoGroup.group_name,
170 RepoGroup.group_name,
170 RepoGroup.group_name_hash,
171 RepoGroup.group_name_hash,
171 RepoGroup.group_description,
172 RepoGroup.group_description,
172 RepoGroup.group_id,
173 RepoGroup.group_id,
173 RepoGroup.personal,
174 RepoGroup.personal,
174 RepoGroup.updated_on,
175 RepoGroup.updated_on,
175 User,
176 User,
176 repo_count.label('repos_count')
177 repo_count.label('repos_count')
177 ) \
178 ) \
178 .filter(or_(
179 .filter(or_(
179 # generate multiple IN to fix limitation problems
180 # generate multiple IN to fix limitation problems
180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 )) \
182 )) \
182 .outerjoin(Repository) \
183 .outerjoin(Repository) \
183 .join(User, User.user_id == RepoGroup.user_id) \
184 .join(User, User.user_id == RepoGroup.user_id) \
184 .group_by(RepoGroup, User)
185 .group_by(RepoGroup, User)
185
186
186 if search_q:
187 if search_q:
187 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 base_q = base_q.filter(or_(
189 base_q = base_q.filter(or_(
189 RepoGroup.group_name.ilike(like_expression),
190 RepoGroup.group_name.ilike(like_expression),
190 ))
191 ))
191
192
192 repo_groups_data_total_filtered_count = base_q.count()
193 repo_groups_data_total_filtered_count = base_q.count()
193 # the inactive isn't really used, but we still make it same as other data grids
194 # the inactive isn't really used, but we still make it same as other data grids
194 # which use inactive (users,user groups)
195 # which use inactive (users,user groups)
195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196
197
197 sort_defined = False
198 sort_defined = False
198 if order_by == 'group_name':
199 if order_by == 'group_name':
199 sort_col = func.lower(RepoGroup.group_name)
200 sort_col = func.lower(RepoGroup.group_name)
200 sort_defined = True
201 sort_defined = True
201 elif order_by == 'repos_total':
202 elif order_by == 'repos_total':
202 sort_col = repo_count
203 sort_col = repo_count
203 sort_defined = True
204 sort_defined = True
204 elif order_by == 'user_username':
205 elif order_by == 'user_username':
205 sort_col = User.username
206 sort_col = User.username
206 else:
207 else:
207 sort_col = getattr(RepoGroup, order_by, None)
208 sort_col = getattr(RepoGroup, order_by, None)
208
209
209 if sort_defined or sort_col:
210 if sort_defined or sort_col:
210 if order_dir == 'asc':
211 if order_dir == 'asc':
211 sort_col = sort_col.asc()
212 sort_col = sort_col.asc()
212 else:
213 else:
213 sort_col = sort_col.desc()
214 sort_col = sort_col.desc()
214
215
215 base_q = base_q.order_by(sort_col)
216 base_q = base_q.order_by(sort_col)
216 base_q = base_q.offset(start).limit(limit)
217 base_q = base_q.offset(start).limit(limit)
217
218
218 # authenticated access to user groups
219 # authenticated access to user groups
219 auth_repo_group_list = base_q.all()
220 auth_repo_group_list = base_q.all()
220
221
221 repo_groups_data = []
222 repo_groups_data = []
222 for repo_gr in auth_repo_group_list:
223 for repo_gr in auth_repo_group_list:
223 row = {
224 row = {
224 "menu": quick_menu(repo_gr.group_name),
225 "menu": quick_menu(repo_gr.group_name),
225 "name": repo_group_lnk(repo_gr.group_name),
226 "name": repo_group_lnk(repo_gr.group_name),
226 "name_raw": repo_gr.group_name,
227 "name_raw": repo_gr.group_name,
227 "last_change": last_change(repo_gr.updated_on),
228 "last_change": last_change(repo_gr.updated_on),
228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229
230
230 "last_changeset": "",
231 "last_changeset": "",
231 "last_changeset_raw": "",
232 "last_changeset_raw": "",
232
233
233 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 "owner": user_profile(repo_gr.User.username),
235 "owner": user_profile(repo_gr.User.username),
235 "top_level_repos": repo_gr.repos_count,
236 "top_level_repos": repo_gr.repos_count,
236 "action": repo_group_actions(
237 "action": repo_group_actions(
237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238
239
239 }
240 }
240
241
241 repo_groups_data.append(row)
242 repo_groups_data.append(row)
242
243
243 data = ({
244 data = ({
244 'draw': draw,
245 'draw': draw,
245 'data': repo_groups_data,
246 'data': repo_groups_data,
246 'recordsTotal': repo_groups_data_total_count,
247 'recordsTotal': repo_groups_data_total_count,
247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 'recordsFiltered': repo_groups_data_total_filtered_count,
249 'recordsFiltered': repo_groups_data_total_filtered_count,
249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 })
251 })
251
252
252 return data
253 return data
253
254
254 @LoginRequired()
255 @LoginRequired()
255 @NotAnonymous()
256 @NotAnonymous()
256 # perm checks inside
257 # perm checks inside
257 @view_config(
258 @view_config(
258 route_name='repo_group_new', request_method='GET',
259 route_name='repo_group_new', request_method='GET',
259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
260 def repo_group_new(self):
261 def repo_group_new(self):
261 c = self.load_default_context()
262 c = self.load_default_context()
262
263
263 # perm check for admin, create_group perm or admin of parent_group
264 # perm check for admin, create_group perm or admin of parent_group
264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 parent_group_id = safe_int(self.request.GET.get('parent_group'))
265 if not self._can_create_repo_group(parent_group_id):
266 if not self._can_create_repo_group(parent_group_id):
266 raise HTTPForbidden()
267 raise HTTPForbidden()
267
268
268 self._load_form_data(c)
269 self._load_form_data(c)
269
270
270 defaults = {} # Future proof for default of repo group
271 defaults = {} # Future proof for default of repo group
271 data = render(
272 data = render(
272 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
273 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
273 self._get_template_context(c), self.request)
274 self._get_template_context(c), self.request)
274 html = formencode.htmlfill.render(
275 html = formencode.htmlfill.render(
275 data,
276 data,
276 defaults=defaults,
277 defaults=defaults,
277 encoding="UTF-8",
278 encoding="UTF-8",
278 force_defaults=False
279 force_defaults=False
279 )
280 )
280 return Response(html)
281 return Response(html)
281
282
282 @LoginRequired()
283 @LoginRequired()
283 @NotAnonymous()
284 @NotAnonymous()
284 @CSRFRequired()
285 @CSRFRequired()
285 # perm checks inside
286 # perm checks inside
286 @view_config(
287 @view_config(
287 route_name='repo_group_create', request_method='POST',
288 route_name='repo_group_create', request_method='POST',
288 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
289 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
289 def repo_group_create(self):
290 def repo_group_create(self):
290 c = self.load_default_context()
291 c = self.load_default_context()
291 _ = self.request.translate
292 _ = self.request.translate
292
293
293 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 can_create = self._can_create_repo_group(parent_group_id)
295 can_create = self._can_create_repo_group(parent_group_id)
295
296
296 self._load_form_data(c)
297 self._load_form_data(c)
297 # permissions for can create group based on parent_id are checked
298 # permissions for can create group based on parent_id are checked
298 # here in the Form
299 # here in the Form
299 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 repo_group_form = RepoGroupForm(
301 repo_group_form = RepoGroupForm(
301 self.request.translate, available_groups=available_groups,
302 self.request.translate, available_groups=available_groups,
302 can_create_in_root=can_create)()
303 can_create_in_root=can_create)()
303
304
304 repo_group_name = self.request.POST.get('group_name')
305 repo_group_name = self.request.POST.get('group_name')
305 try:
306 try:
306 owner = self._rhodecode_user
307 owner = self._rhodecode_user
307 form_result = repo_group_form.to_python(dict(self.request.POST))
308 form_result = repo_group_form.to_python(dict(self.request.POST))
308 copy_permissions = form_result.get('group_copy_permissions')
309 copy_permissions = form_result.get('group_copy_permissions')
309 repo_group = RepoGroupModel().create(
310 repo_group = RepoGroupModel().create(
310 group_name=form_result['group_name_full'],
311 group_name=form_result['group_name_full'],
311 group_description=form_result['group_description'],
312 group_description=form_result['group_description'],
312 owner=owner.user_id,
313 owner=owner.user_id,
313 copy_permissions=form_result['group_copy_permissions']
314 copy_permissions=form_result['group_copy_permissions']
314 )
315 )
315 Session().flush()
316 Session().flush()
316
317
317 repo_group_data = repo_group.get_api_data()
318 repo_group_data = repo_group.get_api_data()
318 audit_logger.store_web(
319 audit_logger.store_web(
319 'repo_group.create', action_data={'data': repo_group_data},
320 'repo_group.create', action_data={'data': repo_group_data},
320 user=self._rhodecode_user)
321 user=self._rhodecode_user)
321
322
322 Session().commit()
323 Session().commit()
323
324
324 _new_group_name = form_result['group_name_full']
325 _new_group_name = form_result['group_name_full']
325
326
326 repo_group_url = h.link_to(
327 repo_group_url = h.link_to(
327 _new_group_name,
328 _new_group_name,
328 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 h.flash(h.literal(_('Created repository group %s')
330 h.flash(h.literal(_('Created repository group %s')
330 % repo_group_url), category='success')
331 % repo_group_url), category='success')
331
332
332 except formencode.Invalid as errors:
333 except formencode.Invalid as errors:
333 data = render(
334 data = render(
334 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 self._get_template_context(c), self.request)
336 self._get_template_context(c), self.request)
336 html = formencode.htmlfill.render(
337 html = formencode.htmlfill.render(
337 data,
338 data,
338 defaults=errors.value,
339 defaults=errors.value,
339 errors=errors.error_dict or {},
340 errors=errors.error_dict or {},
340 prefix_error=False,
341 prefix_error=False,
341 encoding="UTF-8",
342 encoding="UTF-8",
342 force_defaults=False
343 force_defaults=False
343 )
344 )
344 return Response(html)
345 return Response(html)
345 except Exception:
346 except Exception:
346 log.exception("Exception during creation of repository group")
347 log.exception("Exception during creation of repository group")
347 h.flash(_('Error occurred during creation of repository group %s')
348 h.flash(_('Error occurred during creation of repository group %s')
348 % repo_group_name, category='error')
349 % repo_group_name, category='error')
349 raise HTTPFound(h.route_path('home'))
350 raise HTTPFound(h.route_path('home'))
350
351
351 affected_user_ids = [self._rhodecode_user.user_id]
352 affected_user_ids = [self._rhodecode_user.user_id]
352 if copy_permissions:
353 if copy_permissions:
353 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
354 copy_perms = [perm['user_id'] for perm in user_group_perms]
355 copy_perms = [perm['user_id'] for perm in user_group_perms]
355 # also include those newly created by copy
356 # also include those newly created by copy
356 affected_user_ids.extend(copy_perms)
357 affected_user_ids.extend(copy_perms)
357 events.trigger(events.UserPermissionsChange(affected_user_ids))
358 PermissionModel().trigger_permission_flush(affected_user_ids)
358
359
359 raise HTTPFound(
360 raise HTTPFound(
360 h.route_path('repo_group_home',
361 h.route_path('repo_group_home',
361 repo_group_name=form_result['group_name_full']))
362 repo_group_name=form_result['group_name_full']))
@@ -1,189 +1,190 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.celerylib.utils import get_task_id
32 from rhodecode.lib.celerylib.utils import get_task_id
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
36 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
37 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 from rhodecode.model.forms import RepoForm
41 from rhodecode.model.forms import RepoForm
42 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.db import Repository, RepoGroup
46 from rhodecode.model.db import Repository, RepoGroup
46
47
47 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
48
49
49
50
50 class AdminReposView(BaseAppView, DataGridAppView):
51 class AdminReposView(BaseAppView, DataGridAppView):
51
52
52 def load_default_context(self):
53 def load_default_context(self):
53 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
54
55
55 return c
56 return c
56
57
57 def _load_form_data(self, c):
58 def _load_form_data(self, c):
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 perm_set=['group.write', 'group.admin'])
60 perm_set=['group.write', 'group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.landing_revs_choices, c.landing_revs = \
63 c.landing_revs_choices, c.landing_revs = \
63 ScmModel().get_repo_landing_revs(self.request.translate)
64 ScmModel().get_repo_landing_revs(self.request.translate)
64 c.personal_repo_group = self._rhodecode_user.personal_repo_group
65 c.personal_repo_group = self._rhodecode_user.personal_repo_group
65
66
66 @LoginRequired()
67 @LoginRequired()
67 @NotAnonymous()
68 @NotAnonymous()
68 # perms check inside
69 # perms check inside
69 @view_config(
70 @view_config(
70 route_name='repos', request_method='GET',
71 route_name='repos', request_method='GET',
71 renderer='rhodecode:templates/admin/repos/repos.mako')
72 renderer='rhodecode:templates/admin/repos/repos.mako')
72 def repository_list(self):
73 def repository_list(self):
73 c = self.load_default_context()
74 c = self.load_default_context()
74
75
75 repo_list = Repository.get_all_repos()
76 repo_list = Repository.get_all_repos()
76 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
77 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
77 repos_data = RepoModel().get_repos_as_dict(
78 repos_data = RepoModel().get_repos_as_dict(
78 repo_list=c.repo_list, admin=True, super_user_actions=True)
79 repo_list=c.repo_list, admin=True, super_user_actions=True)
79 # json used to render the grid
80 # json used to render the grid
80 c.data = json.dumps(repos_data)
81 c.data = json.dumps(repos_data)
81
82
82 return self._get_template_context(c)
83 return self._get_template_context(c)
83
84
84 @LoginRequired()
85 @LoginRequired()
85 @NotAnonymous()
86 @NotAnonymous()
86 # perms check inside
87 # perms check inside
87 @view_config(
88 @view_config(
88 route_name='repo_new', request_method='GET',
89 route_name='repo_new', request_method='GET',
89 renderer='rhodecode:templates/admin/repos/repo_add.mako')
90 renderer='rhodecode:templates/admin/repos/repo_add.mako')
90 def repository_new(self):
91 def repository_new(self):
91 c = self.load_default_context()
92 c = self.load_default_context()
92
93
93 new_repo = self.request.GET.get('repo', '')
94 new_repo = self.request.GET.get('repo', '')
94 parent_group = safe_int(self.request.GET.get('parent_group'))
95 parent_group = safe_int(self.request.GET.get('parent_group'))
95 _gr = RepoGroup.get(parent_group)
96 _gr = RepoGroup.get(parent_group)
96
97
97 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
98 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
98 # you're not super admin nor have global create permissions,
99 # you're not super admin nor have global create permissions,
99 # but maybe you have at least write permission to a parent group ?
100 # but maybe you have at least write permission to a parent group ?
100
101
101 gr_name = _gr.group_name if _gr else None
102 gr_name = _gr.group_name if _gr else None
102 # create repositories with write permission on group is set to true
103 # create repositories with write permission on group is set to true
103 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
104 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
104 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
105 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
105 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
106 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
106 if not (group_admin or (group_write and create_on_write)):
107 if not (group_admin or (group_write and create_on_write)):
107 raise HTTPForbidden()
108 raise HTTPForbidden()
108
109
109 self._load_form_data(c)
110 self._load_form_data(c)
110 c.new_repo = repo_name_slug(new_repo)
111 c.new_repo = repo_name_slug(new_repo)
111
112
112 # apply the defaults from defaults page
113 # apply the defaults from defaults page
113 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
114 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
114 # set checkbox to autochecked
115 # set checkbox to autochecked
115 defaults['repo_copy_permissions'] = True
116 defaults['repo_copy_permissions'] = True
116
117
117 parent_group_choice = '-1'
118 parent_group_choice = '-1'
118 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
119 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
119 parent_group_choice = self._rhodecode_user.personal_repo_group
120 parent_group_choice = self._rhodecode_user.personal_repo_group
120
121
121 if parent_group and _gr:
122 if parent_group and _gr:
122 if parent_group in [x[0] for x in c.repo_groups]:
123 if parent_group in [x[0] for x in c.repo_groups]:
123 parent_group_choice = safe_unicode(parent_group)
124 parent_group_choice = safe_unicode(parent_group)
124
125
125 defaults.update({'repo_group': parent_group_choice})
126 defaults.update({'repo_group': parent_group_choice})
126
127
127 data = render('rhodecode:templates/admin/repos/repo_add.mako',
128 data = render('rhodecode:templates/admin/repos/repo_add.mako',
128 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
129 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
130 data,
131 data,
131 defaults=defaults,
132 defaults=defaults,
132 encoding="UTF-8",
133 encoding="UTF-8",
133 force_defaults=False
134 force_defaults=False
134 )
135 )
135 return Response(html)
136 return Response(html)
136
137
137 @LoginRequired()
138 @LoginRequired()
138 @NotAnonymous()
139 @NotAnonymous()
139 @CSRFRequired()
140 @CSRFRequired()
140 # perms check inside
141 # perms check inside
141 @view_config(
142 @view_config(
142 route_name='repo_create', request_method='POST',
143 route_name='repo_create', request_method='POST',
143 renderer='rhodecode:templates/admin/repos/repos.mako')
144 renderer='rhodecode:templates/admin/repos/repos.mako')
144 def repository_create(self):
145 def repository_create(self):
145 c = self.load_default_context()
146 c = self.load_default_context()
146
147
147 form_result = {}
148 form_result = {}
148 self._load_form_data(c)
149 self._load_form_data(c)
149
150
150 try:
151 try:
151 # CanWriteToGroup validators checks permissions of this POST
152 # CanWriteToGroup validators checks permissions of this POST
152 form = RepoForm(
153 form = RepoForm(
153 self.request.translate, repo_groups=c.repo_groups_choices,
154 self.request.translate, repo_groups=c.repo_groups_choices,
154 landing_revs=c.landing_revs_choices)()
155 landing_revs=c.landing_revs_choices)()
155 form_result = form.to_python(dict(self.request.POST))
156 form_result = form.to_python(dict(self.request.POST))
156 copy_permissions = form_result.get('repo_copy_permissions')
157 copy_permissions = form_result.get('repo_copy_permissions')
157 # create is done sometimes async on celery, db transaction
158 # create is done sometimes async on celery, db transaction
158 # management is handled there.
159 # management is handled there.
159 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
160 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
160 task_id = get_task_id(task)
161 task_id = get_task_id(task)
161 except formencode.Invalid as errors:
162 except formencode.Invalid as errors:
162 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 self._get_template_context(c), self.request)
164 self._get_template_context(c), self.request)
164 html = formencode.htmlfill.render(
165 html = formencode.htmlfill.render(
165 data,
166 data,
166 defaults=errors.value,
167 defaults=errors.value,
167 errors=errors.error_dict or {},
168 errors=errors.error_dict or {},
168 prefix_error=False,
169 prefix_error=False,
169 encoding="UTF-8",
170 encoding="UTF-8",
170 force_defaults=False
171 force_defaults=False
171 )
172 )
172 return Response(html)
173 return Response(html)
173
174
174 except Exception as e:
175 except Exception as e:
175 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 h.flash(msg, category='error')
177 h.flash(msg, category='error')
177 raise HTTPFound(h.route_path('home'))
178 raise HTTPFound(h.route_path('home'))
178
179
179 repo_name = form_result.get('repo_name_full')
180 repo_name = form_result.get('repo_name_full')
180
181
181 affected_user_ids = [self._rhodecode_user.user_id]
182 affected_user_ids = [self._rhodecode_user.user_id]
182 if copy_permissions:
183 if copy_permissions:
183 # permission flush is done in repo creating
184 # permission flush is done in repo creating
184 pass
185 pass
185 events.trigger(events.UserPermissionsChange(affected_user_ids))
186 PermissionModel().trigger_permission_flush(affected_user_ids)
186
187
187 raise HTTPFound(
188 raise HTTPFound(
188 h.route_path('repo_creating', repo_name=repo_name,
189 h.route_path('repo_creating', repo_name=repo_name,
189 _query=dict(task_id=task_id)))
190 _query=dict(task_id=task_id)))
@@ -1,271 +1,273 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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
22
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 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 from rhodecode.lib import helpers as h, audit_logger
35 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib.utils2 import safe_unicode
36 from rhodecode.lib.utils2 import safe_unicode
37
37
38 from rhodecode.model.forms import UserGroupForm
38 from rhodecode.model.forms import UserGroupForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.scm import UserGroupList
40 from rhodecode.model.scm import UserGroupList
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.user_group import UserGroupModel
44 from rhodecode.model.user_group import UserGroupModel
45 from rhodecode.model.db import true
45 from rhodecode.model.db import true
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 PermissionModel().set_global_permission_choices(
55 PermissionModel().set_global_permission_choices(
56 c, gettext_translator=self.request.translate)
56 c, gettext_translator=self.request.translate)
57
57
58 return c
58 return c
59
59
60 # permission check in data loading of
60 # permission check in data loading of
61 # `user_groups_list_data` via UserGroupList
61 # `user_groups_list_data` via UserGroupList
62 @LoginRequired()
62 @LoginRequired()
63 @NotAnonymous()
63 @NotAnonymous()
64 @view_config(
64 @view_config(
65 route_name='user_groups', request_method='GET',
65 route_name='user_groups', request_method='GET',
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 def user_groups_list(self):
67 def user_groups_list(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 # permission check inside
71 # permission check inside
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='user_groups_data', request_method='GET',
75 route_name='user_groups_data', request_method='GET',
76 renderer='json_ext', xhr=True)
76 renderer='json_ext', xhr=True)
77 def user_groups_list_data(self):
77 def user_groups_list_data(self):
78 self.load_default_context()
78 self.load_default_context()
79 column_map = {
79 column_map = {
80 'active': 'users_group_active',
80 'active': 'users_group_active',
81 'description': 'user_group_description',
81 'description': 'user_group_description',
82 'members': 'members_total',
82 'members': 'members_total',
83 'owner': 'user_username',
83 'owner': 'user_username',
84 'sync': 'group_data'
84 'sync': 'group_data'
85 }
85 }
86 draw, start, limit = self._extract_chunk(self.request)
86 draw, start, limit = self._extract_chunk(self.request)
87 search_q, order_by, order_dir = self._extract_ordering(
87 search_q, order_by, order_dir = self._extract_ordering(
88 self.request, column_map=column_map)
88 self.request, column_map=column_map)
89
89
90 _render = self.request.get_partial_renderer(
90 _render = self.request.get_partial_renderer(
91 'rhodecode:templates/data_table/_dt_elements.mako')
91 'rhodecode:templates/data_table/_dt_elements.mako')
92
92
93 def user_group_name(user_group_name):
93 def user_group_name(user_group_name):
94 return _render("user_group_name", user_group_name)
94 return _render("user_group_name", user_group_name)
95
95
96 def user_group_actions(user_group_id, user_group_name):
96 def user_group_actions(user_group_id, user_group_name):
97 return _render("user_group_actions", user_group_id, user_group_name)
97 return _render("user_group_actions", user_group_id, user_group_name)
98
98
99 def user_profile(username):
99 def user_profile(username):
100 return _render('user_profile', username)
100 return _render('user_profile', username)
101
101
102 auth_user_group_list = UserGroupList(
102 auth_user_group_list = UserGroupList(
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
104
104
105 allowed_ids = [-1]
105 allowed_ids = [-1]
106 for user_group in auth_user_group_list:
106 for user_group in auth_user_group_list:
107 allowed_ids.append(user_group.users_group_id)
107 allowed_ids.append(user_group.users_group_id)
108
108
109 user_groups_data_total_count = UserGroup.query()\
109 user_groups_data_total_count = UserGroup.query()\
110 .filter(or_(
110 .filter(or_(
111 # generate multiple IN to fix limitation problems
111 # generate multiple IN to fix limitation problems
112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
113 ))\
113 ))\
114 .count()
114 .count()
115
115
116 user_groups_data_total_inactive_count = UserGroup.query()\
116 user_groups_data_total_inactive_count = UserGroup.query()\
117 .filter(or_(
117 .filter(or_(
118 # generate multiple IN to fix limitation problems
118 # generate multiple IN to fix limitation problems
119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
120 ))\
120 ))\
121 .filter(UserGroup.users_group_active != true()).count()
121 .filter(UserGroup.users_group_active != true()).count()
122
122
123 member_count = count(UserGroupMember.user_id)
123 member_count = count(UserGroupMember.user_id)
124 base_q = Session.query(
124 base_q = Session.query(
125 UserGroup.users_group_name,
125 UserGroup.users_group_name,
126 UserGroup.user_group_description,
126 UserGroup.user_group_description,
127 UserGroup.users_group_active,
127 UserGroup.users_group_active,
128 UserGroup.users_group_id,
128 UserGroup.users_group_id,
129 UserGroup.group_data,
129 UserGroup.group_data,
130 User,
130 User,
131 member_count.label('member_count')
131 member_count.label('member_count')
132 ) \
132 ) \
133 .filter(or_(
133 .filter(or_(
134 # generate multiple IN to fix limitation problems
134 # generate multiple IN to fix limitation problems
135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
136 )) \
136 )) \
137 .outerjoin(UserGroupMember) \
137 .outerjoin(UserGroupMember) \
138 .join(User, User.user_id == UserGroup.user_id) \
138 .join(User, User.user_id == UserGroup.user_id) \
139 .group_by(UserGroup, User)
139 .group_by(UserGroup, User)
140
140
141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
142
142
143 if search_q:
143 if search_q:
144 like_expression = u'%{}%'.format(safe_unicode(search_q))
144 like_expression = u'%{}%'.format(safe_unicode(search_q))
145 base_q = base_q.filter(or_(
145 base_q = base_q.filter(or_(
146 UserGroup.users_group_name.ilike(like_expression),
146 UserGroup.users_group_name.ilike(like_expression),
147 ))
147 ))
148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
149
149
150 user_groups_data_total_filtered_count = base_q.count()
150 user_groups_data_total_filtered_count = base_q.count()
151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
152
152
153 sort_defined = False
153 sort_defined = False
154 if order_by == 'members_total':
154 if order_by == 'members_total':
155 sort_col = member_count
155 sort_col = member_count
156 sort_defined = True
156 sort_defined = True
157 elif order_by == 'user_username':
157 elif order_by == 'user_username':
158 sort_col = User.username
158 sort_col = User.username
159 else:
159 else:
160 sort_col = getattr(UserGroup, order_by, None)
160 sort_col = getattr(UserGroup, order_by, None)
161
161
162 if sort_defined or sort_col:
162 if sort_defined or sort_col:
163 if order_dir == 'asc':
163 if order_dir == 'asc':
164 sort_col = sort_col.asc()
164 sort_col = sort_col.asc()
165 else:
165 else:
166 sort_col = sort_col.desc()
166 sort_col = sort_col.desc()
167
167
168 base_q = base_q.order_by(sort_col)
168 base_q = base_q.order_by(sort_col)
169 base_q = base_q.offset(start).limit(limit)
169 base_q = base_q.offset(start).limit(limit)
170
170
171 # authenticated access to user groups
171 # authenticated access to user groups
172 auth_user_group_list = base_q.all()
172 auth_user_group_list = base_q.all()
173
173
174 user_groups_data = []
174 user_groups_data = []
175 for user_gr in auth_user_group_list:
175 for user_gr in auth_user_group_list:
176 row = {
176 row = {
177 "users_group_name": user_group_name(user_gr.users_group_name),
177 "users_group_name": user_group_name(user_gr.users_group_name),
178 "name_raw": h.escape(user_gr.users_group_name),
178 "name_raw": h.escape(user_gr.users_group_name),
179 "description": h.escape(user_gr.user_group_description),
179 "description": h.escape(user_gr.user_group_description),
180 "members": user_gr.member_count,
180 "members": user_gr.member_count,
181 # NOTE(marcink): because of advanced query we
181 # NOTE(marcink): because of advanced query we
182 # need to load it like that
182 # need to load it like that
183 "sync": UserGroup._load_sync(
183 "sync": UserGroup._load_sync(
184 UserGroup._load_group_data(user_gr.group_data)),
184 UserGroup._load_group_data(user_gr.group_data)),
185 "active": h.bool2icon(user_gr.users_group_active),
185 "active": h.bool2icon(user_gr.users_group_active),
186 "owner": user_profile(user_gr.User.username),
186 "owner": user_profile(user_gr.User.username),
187 "action": user_group_actions(
187 "action": user_group_actions(
188 user_gr.users_group_id, user_gr.users_group_name)
188 user_gr.users_group_id, user_gr.users_group_name)
189 }
189 }
190 user_groups_data.append(row)
190 user_groups_data.append(row)
191
191
192 data = ({
192 data = ({
193 'draw': draw,
193 'draw': draw,
194 'data': user_groups_data,
194 'data': user_groups_data,
195 'recordsTotal': user_groups_data_total_count,
195 'recordsTotal': user_groups_data_total_count,
196 'recordsTotalInactive': user_groups_data_total_inactive_count,
196 'recordsTotalInactive': user_groups_data_total_inactive_count,
197 'recordsFiltered': user_groups_data_total_filtered_count,
197 'recordsFiltered': user_groups_data_total_filtered_count,
198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
199 })
199 })
200
200
201 return data
201 return data
202
202
203 @LoginRequired()
203 @LoginRequired()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
205 @view_config(
205 @view_config(
206 route_name='user_groups_new', request_method='GET',
206 route_name='user_groups_new', request_method='GET',
207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
208 def user_groups_new(self):
208 def user_groups_new(self):
209 c = self.load_default_context()
209 c = self.load_default_context()
210 return self._get_template_context(c)
210 return self._get_template_context(c)
211
211
212 @LoginRequired()
212 @LoginRequired()
213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
214 @CSRFRequired()
214 @CSRFRequired()
215 @view_config(
215 @view_config(
216 route_name='user_groups_create', request_method='POST',
216 route_name='user_groups_create', request_method='POST',
217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
218 def user_groups_create(self):
218 def user_groups_create(self):
219 _ = self.request.translate
219 _ = self.request.translate
220 c = self.load_default_context()
220 c = self.load_default_context()
221 users_group_form = UserGroupForm(self.request.translate)()
221 users_group_form = UserGroupForm(self.request.translate)()
222
222
223 user_group_name = self.request.POST.get('users_group_name')
223 user_group_name = self.request.POST.get('users_group_name')
224 try:
224 try:
225 form_result = users_group_form.to_python(dict(self.request.POST))
225 form_result = users_group_form.to_python(dict(self.request.POST))
226 user_group = UserGroupModel().create(
226 user_group = UserGroupModel().create(
227 name=form_result['users_group_name'],
227 name=form_result['users_group_name'],
228 description=form_result['user_group_description'],
228 description=form_result['user_group_description'],
229 owner=self._rhodecode_user.user_id,
229 owner=self._rhodecode_user.user_id,
230 active=form_result['users_group_active'])
230 active=form_result['users_group_active'])
231 Session().flush()
231 Session().flush()
232 creation_data = user_group.get_api_data()
232 creation_data = user_group.get_api_data()
233 user_group_name = form_result['users_group_name']
233 user_group_name = form_result['users_group_name']
234
234
235 audit_logger.store_web(
235 audit_logger.store_web(
236 'user_group.create', action_data={'data': creation_data},
236 'user_group.create', action_data={'data': creation_data},
237 user=self._rhodecode_user)
237 user=self._rhodecode_user)
238
238
239 user_group_link = h.link_to(
239 user_group_link = h.link_to(
240 h.escape(user_group_name),
240 h.escape(user_group_name),
241 h.route_path(
241 h.route_path(
242 'edit_user_group', user_group_id=user_group.users_group_id))
242 'edit_user_group', user_group_id=user_group.users_group_id))
243 h.flash(h.literal(_('Created user group %(user_group_link)s')
243 h.flash(h.literal(_('Created user group %(user_group_link)s')
244 % {'user_group_link': user_group_link}),
244 % {'user_group_link': user_group_link}),
245 category='success')
245 category='success')
246 Session().commit()
246 Session().commit()
247 user_group_id = user_group.users_group_id
247 user_group_id = user_group.users_group_id
248 except formencode.Invalid as errors:
248 except formencode.Invalid as errors:
249
249
250 data = render(
250 data = render(
251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
252 self._get_template_context(c), self.request)
252 self._get_template_context(c), self.request)
253 html = formencode.htmlfill.render(
253 html = formencode.htmlfill.render(
254 data,
254 data,
255 defaults=errors.value,
255 defaults=errors.value,
256 errors=errors.error_dict or {},
256 errors=errors.error_dict or {},
257 prefix_error=False,
257 prefix_error=False,
258 encoding="UTF-8",
258 encoding="UTF-8",
259 force_defaults=False
259 force_defaults=False
260 )
260 )
261 return Response(html)
261 return Response(html)
262
262
263 except Exception:
263 except Exception:
264 log.exception("Exception creating user group")
264 log.exception("Exception creating user group")
265 h.flash(_('Error occurred during creation of user group %s') \
265 h.flash(_('Error occurred during creation of user group %s') \
266 % user_group_name, category='error')
266 % user_group_name, category='error')
267 raise HTTPFound(h.route_path('user_groups_new'))
267 raise HTTPFound(h.route_path('user_groups_new'))
268
268
269 events.trigger(events.UserPermissionsChange([self._rhodecode_user.user_id]))
269 affected_user_ids = [self._rhodecode_user.user_id]
270 PermissionModel().trigger_permission_flush(affected_user_ids)
271
270 raise HTTPFound(
272 raise HTTPFound(
271 h.route_path('edit_user_group', user_group_id=user_group_id))
273 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1265 +1,1265 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 from pyramid.view import view_config
27 from pyramid.view import view_config
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 BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.events import trigger
35 from rhodecode.events import trigger
36 from rhodecode.model.db import true
36 from rhodecode.model.db import true
37
37
38 from rhodecode.lib import audit_logger, rc_cache
38 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib.exceptions import (
39 from rhodecode.lib.exceptions import (
40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserOwnsUserGroupsException, DefaultUserException)
41 UserOwnsUserGroupsException, DefaultUserException)
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.auth import (
43 from rhodecode.lib.auth import (
44 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 from rhodecode.lib import helpers as h
45 from rhodecode.lib import helpers as h
46 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
47 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.forms import (
48 from rhodecode.model.forms import (
49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
50 UserExtraEmailForm, UserExtraIpForm)
50 UserExtraEmailForm, UserExtraIpForm)
51 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.permission import PermissionModel
52 from rhodecode.model.repo_group import RepoGroupModel
52 from rhodecode.model.repo_group import RepoGroupModel
53 from rhodecode.model.ssh_key import SshKeyModel
53 from rhodecode.model.ssh_key import SshKeyModel
54 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
55 from rhodecode.model.user_group import UserGroupModel
55 from rhodecode.model.user_group import UserGroupModel
56 from rhodecode.model.db import (
56 from rhodecode.model.db import (
57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
58 UserApiKeys, UserSshKeys, RepoGroup)
58 UserApiKeys, UserSshKeys, RepoGroup)
59 from rhodecode.model.meta import Session
59 from rhodecode.model.meta import Session
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class AdminUsersView(BaseAppView, DataGridAppView):
64 class AdminUsersView(BaseAppView, DataGridAppView):
65
65
66 def load_default_context(self):
66 def load_default_context(self):
67 c = self._get_local_tmpl_context()
67 c = self._get_local_tmpl_context()
68 return c
68 return c
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @HasPermissionAllDecorator('hg.admin')
71 @HasPermissionAllDecorator('hg.admin')
72 @view_config(
72 @view_config(
73 route_name='users', request_method='GET',
73 route_name='users', request_method='GET',
74 renderer='rhodecode:templates/admin/users/users.mako')
74 renderer='rhodecode:templates/admin/users/users.mako')
75 def users_list(self):
75 def users_list(self):
76 c = self.load_default_context()
76 c = self.load_default_context()
77 return self._get_template_context(c)
77 return self._get_template_context(c)
78
78
79 @LoginRequired()
79 @LoginRequired()
80 @HasPermissionAllDecorator('hg.admin')
80 @HasPermissionAllDecorator('hg.admin')
81 @view_config(
81 @view_config(
82 # renderer defined below
82 # renderer defined below
83 route_name='users_data', request_method='GET',
83 route_name='users_data', request_method='GET',
84 renderer='json_ext', xhr=True)
84 renderer='json_ext', xhr=True)
85 def users_list_data(self):
85 def users_list_data(self):
86 self.load_default_context()
86 self.load_default_context()
87 column_map = {
87 column_map = {
88 'first_name': 'name',
88 'first_name': 'name',
89 'last_name': 'lastname',
89 'last_name': 'lastname',
90 }
90 }
91 draw, start, limit = self._extract_chunk(self.request)
91 draw, start, limit = self._extract_chunk(self.request)
92 search_q, order_by, order_dir = self._extract_ordering(
92 search_q, order_by, order_dir = self._extract_ordering(
93 self.request, column_map=column_map)
93 self.request, column_map=column_map)
94 _render = self.request.get_partial_renderer(
94 _render = self.request.get_partial_renderer(
95 'rhodecode:templates/data_table/_dt_elements.mako')
95 'rhodecode:templates/data_table/_dt_elements.mako')
96
96
97 def user_actions(user_id, username):
97 def user_actions(user_id, username):
98 return _render("user_actions", user_id, username)
98 return _render("user_actions", user_id, username)
99
99
100 users_data_total_count = User.query()\
100 users_data_total_count = User.query()\
101 .filter(User.username != User.DEFAULT_USER) \
101 .filter(User.username != User.DEFAULT_USER) \
102 .count()
102 .count()
103
103
104 users_data_total_inactive_count = User.query()\
104 users_data_total_inactive_count = User.query()\
105 .filter(User.username != User.DEFAULT_USER) \
105 .filter(User.username != User.DEFAULT_USER) \
106 .filter(User.active != true())\
106 .filter(User.active != true())\
107 .count()
107 .count()
108
108
109 # json generate
109 # json generate
110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111 base_inactive_q = base_q.filter(User.active != true())
111 base_inactive_q = base_q.filter(User.active != true())
112
112
113 if search_q:
113 if search_q:
114 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 like_expression = u'%{}%'.format(safe_unicode(search_q))
115 base_q = base_q.filter(or_(
115 base_q = base_q.filter(or_(
116 User.username.ilike(like_expression),
116 User.username.ilike(like_expression),
117 User._email.ilike(like_expression),
117 User._email.ilike(like_expression),
118 User.name.ilike(like_expression),
118 User.name.ilike(like_expression),
119 User.lastname.ilike(like_expression),
119 User.lastname.ilike(like_expression),
120 ))
120 ))
121 base_inactive_q = base_q.filter(User.active != true())
121 base_inactive_q = base_q.filter(User.active != true())
122
122
123 users_data_total_filtered_count = base_q.count()
123 users_data_total_filtered_count = base_q.count()
124 users_data_total_filtered_inactive_count = base_inactive_q.count()
124 users_data_total_filtered_inactive_count = base_inactive_q.count()
125
125
126 sort_col = getattr(User, order_by, None)
126 sort_col = getattr(User, order_by, None)
127 if sort_col:
127 if sort_col:
128 if order_dir == 'asc':
128 if order_dir == 'asc':
129 # handle null values properly to order by NULL last
129 # handle null values properly to order by NULL last
130 if order_by in ['last_activity']:
130 if order_by in ['last_activity']:
131 sort_col = coalesce(sort_col, datetime.date.max)
131 sort_col = coalesce(sort_col, datetime.date.max)
132 sort_col = sort_col.asc()
132 sort_col = sort_col.asc()
133 else:
133 else:
134 # handle null values properly to order by NULL last
134 # handle null values properly to order by NULL last
135 if order_by in ['last_activity']:
135 if order_by in ['last_activity']:
136 sort_col = coalesce(sort_col, datetime.date.min)
136 sort_col = coalesce(sort_col, datetime.date.min)
137 sort_col = sort_col.desc()
137 sort_col = sort_col.desc()
138
138
139 base_q = base_q.order_by(sort_col)
139 base_q = base_q.order_by(sort_col)
140 base_q = base_q.offset(start).limit(limit)
140 base_q = base_q.offset(start).limit(limit)
141
141
142 users_list = base_q.all()
142 users_list = base_q.all()
143
143
144 users_data = []
144 users_data = []
145 for user in users_list:
145 for user in users_list:
146 users_data.append({
146 users_data.append({
147 "username": h.gravatar_with_user(self.request, user.username),
147 "username": h.gravatar_with_user(self.request, user.username),
148 "email": user.email,
148 "email": user.email,
149 "first_name": user.first_name,
149 "first_name": user.first_name,
150 "last_name": user.last_name,
150 "last_name": user.last_name,
151 "last_login": h.format_date(user.last_login),
151 "last_login": h.format_date(user.last_login),
152 "last_activity": h.format_date(user.last_activity),
152 "last_activity": h.format_date(user.last_activity),
153 "active": h.bool2icon(user.active),
153 "active": h.bool2icon(user.active),
154 "active_raw": user.active,
154 "active_raw": user.active,
155 "admin": h.bool2icon(user.admin),
155 "admin": h.bool2icon(user.admin),
156 "extern_type": user.extern_type,
156 "extern_type": user.extern_type,
157 "extern_name": user.extern_name,
157 "extern_name": user.extern_name,
158 "action": user_actions(user.user_id, user.username),
158 "action": user_actions(user.user_id, user.username),
159 })
159 })
160 data = ({
160 data = ({
161 'draw': draw,
161 'draw': draw,
162 'data': users_data,
162 'data': users_data,
163 'recordsTotal': users_data_total_count,
163 'recordsTotal': users_data_total_count,
164 'recordsFiltered': users_data_total_filtered_count,
164 'recordsFiltered': users_data_total_filtered_count,
165 'recordsTotalInactive': users_data_total_inactive_count,
165 'recordsTotalInactive': users_data_total_inactive_count,
166 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 'recordsFilteredInactive': users_data_total_filtered_inactive_count
167 })
167 })
168
168
169 return data
169 return data
170
170
171 def _set_personal_repo_group_template_vars(self, c_obj):
171 def _set_personal_repo_group_template_vars(self, c_obj):
172 DummyUser = AttributeDict({
172 DummyUser = AttributeDict({
173 'username': '${username}',
173 'username': '${username}',
174 'user_id': '${user_id}',
174 'user_id': '${user_id}',
175 })
175 })
176 c_obj.default_create_repo_group = RepoGroupModel() \
176 c_obj.default_create_repo_group = RepoGroupModel() \
177 .get_default_create_personal_repo_group()
177 .get_default_create_personal_repo_group()
178 c_obj.personal_repo_group_name = RepoGroupModel() \
178 c_obj.personal_repo_group_name = RepoGroupModel() \
179 .get_personal_group_name(DummyUser)
179 .get_personal_group_name(DummyUser)
180
180
181 @LoginRequired()
181 @LoginRequired()
182 @HasPermissionAllDecorator('hg.admin')
182 @HasPermissionAllDecorator('hg.admin')
183 @view_config(
183 @view_config(
184 route_name='users_new', request_method='GET',
184 route_name='users_new', request_method='GET',
185 renderer='rhodecode:templates/admin/users/user_add.mako')
185 renderer='rhodecode:templates/admin/users/user_add.mako')
186 def users_new(self):
186 def users_new(self):
187 _ = self.request.translate
187 _ = self.request.translate
188 c = self.load_default_context()
188 c = self.load_default_context()
189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
190 self._set_personal_repo_group_template_vars(c)
190 self._set_personal_repo_group_template_vars(c)
191 return self._get_template_context(c)
191 return self._get_template_context(c)
192
192
193 @LoginRequired()
193 @LoginRequired()
194 @HasPermissionAllDecorator('hg.admin')
194 @HasPermissionAllDecorator('hg.admin')
195 @CSRFRequired()
195 @CSRFRequired()
196 @view_config(
196 @view_config(
197 route_name='users_create', request_method='POST',
197 route_name='users_create', request_method='POST',
198 renderer='rhodecode:templates/admin/users/user_add.mako')
198 renderer='rhodecode:templates/admin/users/user_add.mako')
199 def users_create(self):
199 def users_create(self):
200 _ = self.request.translate
200 _ = self.request.translate
201 c = self.load_default_context()
201 c = self.load_default_context()
202 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
202 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
203 user_model = UserModel()
203 user_model = UserModel()
204 user_form = UserForm(self.request.translate)()
204 user_form = UserForm(self.request.translate)()
205 try:
205 try:
206 form_result = user_form.to_python(dict(self.request.POST))
206 form_result = user_form.to_python(dict(self.request.POST))
207 user = user_model.create(form_result)
207 user = user_model.create(form_result)
208 Session().flush()
208 Session().flush()
209 creation_data = user.get_api_data()
209 creation_data = user.get_api_data()
210 username = form_result['username']
210 username = form_result['username']
211
211
212 audit_logger.store_web(
212 audit_logger.store_web(
213 'user.create', action_data={'data': creation_data},
213 'user.create', action_data={'data': creation_data},
214 user=c.rhodecode_user)
214 user=c.rhodecode_user)
215
215
216 user_link = h.link_to(
216 user_link = h.link_to(
217 h.escape(username),
217 h.escape(username),
218 h.route_path('user_edit', user_id=user.user_id))
218 h.route_path('user_edit', user_id=user.user_id))
219 h.flash(h.literal(_('Created user %(user_link)s')
219 h.flash(h.literal(_('Created user %(user_link)s')
220 % {'user_link': user_link}), category='success')
220 % {'user_link': user_link}), category='success')
221 Session().commit()
221 Session().commit()
222 except formencode.Invalid as errors:
222 except formencode.Invalid as errors:
223 self._set_personal_repo_group_template_vars(c)
223 self._set_personal_repo_group_template_vars(c)
224 data = render(
224 data = render(
225 'rhodecode:templates/admin/users/user_add.mako',
225 'rhodecode:templates/admin/users/user_add.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 UserCreationError as e:
236 except UserCreationError as e:
237 h.flash(e, 'error')
237 h.flash(e, 'error')
238 except Exception:
238 except Exception:
239 log.exception("Exception creation of user")
239 log.exception("Exception creation of user")
240 h.flash(_('Error occurred during creation of user %s')
240 h.flash(_('Error occurred during creation of user %s')
241 % self.request.POST.get('username'), category='error')
241 % self.request.POST.get('username'), category='error')
242 raise HTTPFound(h.route_path('users'))
242 raise HTTPFound(h.route_path('users'))
243
243
244
244
245 class UsersView(UserAppView):
245 class UsersView(UserAppView):
246 ALLOW_SCOPED_TOKENS = False
246 ALLOW_SCOPED_TOKENS = False
247 """
247 """
248 This view has alternative version inside EE, if modified please take a look
248 This view has alternative version inside EE, if modified please take a look
249 in there as well.
249 in there as well.
250 """
250 """
251
251
252 def load_default_context(self):
252 def load_default_context(self):
253 c = self._get_local_tmpl_context()
253 c = self._get_local_tmpl_context()
254 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
255 c.allowed_languages = [
255 c.allowed_languages = [
256 ('en', 'English (en)'),
256 ('en', 'English (en)'),
257 ('de', 'German (de)'),
257 ('de', 'German (de)'),
258 ('fr', 'French (fr)'),
258 ('fr', 'French (fr)'),
259 ('it', 'Italian (it)'),
259 ('it', 'Italian (it)'),
260 ('ja', 'Japanese (ja)'),
260 ('ja', 'Japanese (ja)'),
261 ('pl', 'Polish (pl)'),
261 ('pl', 'Polish (pl)'),
262 ('pt', 'Portuguese (pt)'),
262 ('pt', 'Portuguese (pt)'),
263 ('ru', 'Russian (ru)'),
263 ('ru', 'Russian (ru)'),
264 ('zh', 'Chinese (zh)'),
264 ('zh', 'Chinese (zh)'),
265 ]
265 ]
266 req = self.request
266 req = self.request
267
267
268 c.available_permissions = req.registry.settings['available_permissions']
268 c.available_permissions = req.registry.settings['available_permissions']
269 PermissionModel().set_global_permission_choices(
269 PermissionModel().set_global_permission_choices(
270 c, gettext_translator=req.translate)
270 c, gettext_translator=req.translate)
271
271
272 return c
272 return c
273
273
274 @LoginRequired()
274 @LoginRequired()
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 @CSRFRequired()
276 @CSRFRequired()
277 @view_config(
277 @view_config(
278 route_name='user_update', request_method='POST',
278 route_name='user_update', request_method='POST',
279 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 renderer='rhodecode:templates/admin/users/user_edit.mako')
280 def user_update(self):
280 def user_update(self):
281 _ = self.request.translate
281 _ = self.request.translate
282 c = self.load_default_context()
282 c = self.load_default_context()
283
283
284 user_id = self.db_user_id
284 user_id = self.db_user_id
285 c.user = self.db_user
285 c.user = self.db_user
286
286
287 c.active = 'profile'
287 c.active = 'profile'
288 c.extern_type = c.user.extern_type
288 c.extern_type = c.user.extern_type
289 c.extern_name = c.user.extern_name
289 c.extern_name = c.user.extern_name
290 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
291 available_languages = [x[0] for x in c.allowed_languages]
291 available_languages = [x[0] for x in c.allowed_languages]
292 _form = UserForm(self.request.translate, edit=True,
292 _form = UserForm(self.request.translate, edit=True,
293 available_languages=available_languages,
293 available_languages=available_languages,
294 old_data={'user_id': user_id,
294 old_data={'user_id': user_id,
295 'email': c.user.email})()
295 'email': c.user.email})()
296 form_result = {}
296 form_result = {}
297 old_values = c.user.get_api_data()
297 old_values = c.user.get_api_data()
298 try:
298 try:
299 form_result = _form.to_python(dict(self.request.POST))
299 form_result = _form.to_python(dict(self.request.POST))
300 skip_attrs = ['extern_type', 'extern_name']
300 skip_attrs = ['extern_type', 'extern_name']
301 # TODO: plugin should define if username can be updated
301 # TODO: plugin should define if username can be updated
302 if c.extern_type != "rhodecode":
302 if c.extern_type != "rhodecode":
303 # forbid updating username for external accounts
303 # forbid updating username for external accounts
304 skip_attrs.append('username')
304 skip_attrs.append('username')
305
305
306 UserModel().update_user(
306 UserModel().update_user(
307 user_id, skip_attrs=skip_attrs, **form_result)
307 user_id, skip_attrs=skip_attrs, **form_result)
308
308
309 audit_logger.store_web(
309 audit_logger.store_web(
310 'user.edit', action_data={'old_data': old_values},
310 'user.edit', action_data={'old_data': old_values},
311 user=c.rhodecode_user)
311 user=c.rhodecode_user)
312
312
313 Session().commit()
313 Session().commit()
314 h.flash(_('User updated successfully'), category='success')
314 h.flash(_('User updated successfully'), category='success')
315 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
316 data = render(
316 data = render(
317 'rhodecode:templates/admin/users/user_edit.mako',
317 'rhodecode:templates/admin/users/user_edit.mako',
318 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
319 html = formencode.htmlfill.render(
319 html = formencode.htmlfill.render(
320 data,
320 data,
321 defaults=errors.value,
321 defaults=errors.value,
322 errors=errors.error_dict or {},
322 errors=errors.error_dict or {},
323 prefix_error=False,
323 prefix_error=False,
324 encoding="UTF-8",
324 encoding="UTF-8",
325 force_defaults=False
325 force_defaults=False
326 )
326 )
327 return Response(html)
327 return Response(html)
328 except UserCreationError as e:
328 except UserCreationError as e:
329 h.flash(e, 'error')
329 h.flash(e, 'error')
330 except Exception:
330 except Exception:
331 log.exception("Exception updating user")
331 log.exception("Exception updating user")
332 h.flash(_('Error occurred during update of user %s')
332 h.flash(_('Error occurred during update of user %s')
333 % form_result.get('username'), category='error')
333 % form_result.get('username'), category='error')
334 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
335
335
336 @LoginRequired()
336 @LoginRequired()
337 @HasPermissionAllDecorator('hg.admin')
337 @HasPermissionAllDecorator('hg.admin')
338 @CSRFRequired()
338 @CSRFRequired()
339 @view_config(
339 @view_config(
340 route_name='user_delete', request_method='POST',
340 route_name='user_delete', request_method='POST',
341 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 renderer='rhodecode:templates/admin/users/user_edit.mako')
342 def user_delete(self):
342 def user_delete(self):
343 _ = self.request.translate
343 _ = self.request.translate
344 c = self.load_default_context()
344 c = self.load_default_context()
345 c.user = self.db_user
345 c.user = self.db_user
346
346
347 _repos = c.user.repositories
347 _repos = c.user.repositories
348 _repo_groups = c.user.repository_groups
348 _repo_groups = c.user.repository_groups
349 _user_groups = c.user.user_groups
349 _user_groups = c.user.user_groups
350
350
351 handle_repos = None
351 handle_repos = None
352 handle_repo_groups = None
352 handle_repo_groups = None
353 handle_user_groups = None
353 handle_user_groups = None
354 # dummy call for flash of handle
354 # dummy call for flash of handle
355 set_handle_flash_repos = lambda: None
355 set_handle_flash_repos = lambda: None
356 set_handle_flash_repo_groups = lambda: None
356 set_handle_flash_repo_groups = lambda: None
357 set_handle_flash_user_groups = lambda: None
357 set_handle_flash_user_groups = lambda: None
358
358
359 if _repos and self.request.POST.get('user_repos'):
359 if _repos and self.request.POST.get('user_repos'):
360 do = self.request.POST['user_repos']
360 do = self.request.POST['user_repos']
361 if do == 'detach':
361 if do == 'detach':
362 handle_repos = 'detach'
362 handle_repos = 'detach'
363 set_handle_flash_repos = lambda: h.flash(
363 set_handle_flash_repos = lambda: h.flash(
364 _('Detached %s repositories') % len(_repos),
364 _('Detached %s repositories') % len(_repos),
365 category='success')
365 category='success')
366 elif do == 'delete':
366 elif do == 'delete':
367 handle_repos = 'delete'
367 handle_repos = 'delete'
368 set_handle_flash_repos = lambda: h.flash(
368 set_handle_flash_repos = lambda: h.flash(
369 _('Deleted %s repositories') % len(_repos),
369 _('Deleted %s repositories') % len(_repos),
370 category='success')
370 category='success')
371
371
372 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 if _repo_groups and self.request.POST.get('user_repo_groups'):
373 do = self.request.POST['user_repo_groups']
373 do = self.request.POST['user_repo_groups']
374 if do == 'detach':
374 if do == 'detach':
375 handle_repo_groups = 'detach'
375 handle_repo_groups = 'detach'
376 set_handle_flash_repo_groups = lambda: h.flash(
376 set_handle_flash_repo_groups = lambda: h.flash(
377 _('Detached %s repository groups') % len(_repo_groups),
377 _('Detached %s repository groups') % len(_repo_groups),
378 category='success')
378 category='success')
379 elif do == 'delete':
379 elif do == 'delete':
380 handle_repo_groups = 'delete'
380 handle_repo_groups = 'delete'
381 set_handle_flash_repo_groups = lambda: h.flash(
381 set_handle_flash_repo_groups = lambda: h.flash(
382 _('Deleted %s repository groups') % len(_repo_groups),
382 _('Deleted %s repository groups') % len(_repo_groups),
383 category='success')
383 category='success')
384
384
385 if _user_groups and self.request.POST.get('user_user_groups'):
385 if _user_groups and self.request.POST.get('user_user_groups'):
386 do = self.request.POST['user_user_groups']
386 do = self.request.POST['user_user_groups']
387 if do == 'detach':
387 if do == 'detach':
388 handle_user_groups = 'detach'
388 handle_user_groups = 'detach'
389 set_handle_flash_user_groups = lambda: h.flash(
389 set_handle_flash_user_groups = lambda: h.flash(
390 _('Detached %s user groups') % len(_user_groups),
390 _('Detached %s user groups') % len(_user_groups),
391 category='success')
391 category='success')
392 elif do == 'delete':
392 elif do == 'delete':
393 handle_user_groups = 'delete'
393 handle_user_groups = 'delete'
394 set_handle_flash_user_groups = lambda: h.flash(
394 set_handle_flash_user_groups = lambda: h.flash(
395 _('Deleted %s user groups') % len(_user_groups),
395 _('Deleted %s user groups') % len(_user_groups),
396 category='success')
396 category='success')
397
397
398 old_values = c.user.get_api_data()
398 old_values = c.user.get_api_data()
399 try:
399 try:
400 UserModel().delete(c.user, handle_repos=handle_repos,
400 UserModel().delete(c.user, handle_repos=handle_repos,
401 handle_repo_groups=handle_repo_groups,
401 handle_repo_groups=handle_repo_groups,
402 handle_user_groups=handle_user_groups)
402 handle_user_groups=handle_user_groups)
403
403
404 audit_logger.store_web(
404 audit_logger.store_web(
405 'user.delete', action_data={'old_data': old_values},
405 'user.delete', action_data={'old_data': old_values},
406 user=c.rhodecode_user)
406 user=c.rhodecode_user)
407
407
408 Session().commit()
408 Session().commit()
409 set_handle_flash_repos()
409 set_handle_flash_repos()
410 set_handle_flash_repo_groups()
410 set_handle_flash_repo_groups()
411 set_handle_flash_user_groups()
411 set_handle_flash_user_groups()
412 h.flash(_('Successfully deleted user'), category='success')
412 h.flash(_('Successfully deleted user'), category='success')
413 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 except (UserOwnsReposException, UserOwnsRepoGroupsException,
414 UserOwnsUserGroupsException, DefaultUserException) as e:
414 UserOwnsUserGroupsException, DefaultUserException) as e:
415 h.flash(e, category='warning')
415 h.flash(e, category='warning')
416 except Exception:
416 except Exception:
417 log.exception("Exception during deletion of user")
417 log.exception("Exception during deletion of user")
418 h.flash(_('An error occurred during deletion of user'),
418 h.flash(_('An error occurred during deletion of user'),
419 category='error')
419 category='error')
420 raise HTTPFound(h.route_path('users'))
420 raise HTTPFound(h.route_path('users'))
421
421
422 @LoginRequired()
422 @LoginRequired()
423 @HasPermissionAllDecorator('hg.admin')
423 @HasPermissionAllDecorator('hg.admin')
424 @view_config(
424 @view_config(
425 route_name='user_edit', request_method='GET',
425 route_name='user_edit', request_method='GET',
426 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 renderer='rhodecode:templates/admin/users/user_edit.mako')
427 def user_edit(self):
427 def user_edit(self):
428 _ = self.request.translate
428 _ = self.request.translate
429 c = self.load_default_context()
429 c = self.load_default_context()
430 c.user = self.db_user
430 c.user = self.db_user
431
431
432 c.active = 'profile'
432 c.active = 'profile'
433 c.extern_type = c.user.extern_type
433 c.extern_type = c.user.extern_type
434 c.extern_name = c.user.extern_name
434 c.extern_name = c.user.extern_name
435 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
436
436
437 defaults = c.user.get_dict()
437 defaults = c.user.get_dict()
438 defaults.update({'language': c.user.user_data.get('language')})
438 defaults.update({'language': c.user.user_data.get('language')})
439
439
440 data = render(
440 data = render(
441 'rhodecode:templates/admin/users/user_edit.mako',
441 'rhodecode:templates/admin/users/user_edit.mako',
442 self._get_template_context(c), self.request)
442 self._get_template_context(c), self.request)
443 html = formencode.htmlfill.render(
443 html = formencode.htmlfill.render(
444 data,
444 data,
445 defaults=defaults,
445 defaults=defaults,
446 encoding="UTF-8",
446 encoding="UTF-8",
447 force_defaults=False
447 force_defaults=False
448 )
448 )
449 return Response(html)
449 return Response(html)
450
450
451 @LoginRequired()
451 @LoginRequired()
452 @HasPermissionAllDecorator('hg.admin')
452 @HasPermissionAllDecorator('hg.admin')
453 @view_config(
453 @view_config(
454 route_name='user_edit_advanced', request_method='GET',
454 route_name='user_edit_advanced', request_method='GET',
455 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 renderer='rhodecode:templates/admin/users/user_edit.mako')
456 def user_edit_advanced(self):
456 def user_edit_advanced(self):
457 _ = self.request.translate
457 _ = self.request.translate
458 c = self.load_default_context()
458 c = self.load_default_context()
459
459
460 user_id = self.db_user_id
460 user_id = self.db_user_id
461 c.user = self.db_user
461 c.user = self.db_user
462
462
463 c.active = 'advanced'
463 c.active = 'advanced'
464 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
465 c.personal_repo_group_name = RepoGroupModel()\
465 c.personal_repo_group_name = RepoGroupModel()\
466 .get_personal_group_name(c.user)
466 .get_personal_group_name(c.user)
467
467
468 c.user_to_review_rules = sorted(
468 c.user_to_review_rules = sorted(
469 (x.user for x in c.user.user_review_rules),
469 (x.user for x in c.user.user_review_rules),
470 key=lambda u: u.username.lower())
470 key=lambda u: u.username.lower())
471
471
472 c.first_admin = User.get_first_super_admin()
472 c.first_admin = User.get_first_super_admin()
473 defaults = c.user.get_dict()
473 defaults = c.user.get_dict()
474
474
475 # Interim workaround if the user participated on any pull requests as a
475 # Interim workaround if the user participated on any pull requests as a
476 # reviewer.
476 # reviewer.
477 has_review = len(c.user.reviewer_pull_requests)
477 has_review = len(c.user.reviewer_pull_requests)
478 c.can_delete_user = not has_review
478 c.can_delete_user = not has_review
479 c.can_delete_user_message = ''
479 c.can_delete_user_message = ''
480 inactive_link = h.link_to(
480 inactive_link = h.link_to(
481 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
482 if has_review == 1:
482 if has_review == 1:
483 c.can_delete_user_message = h.literal(_(
483 c.can_delete_user_message = h.literal(_(
484 'The user participates as reviewer in {} pull request and '
484 'The user participates as reviewer in {} pull request and '
485 'cannot be deleted. \nYou can set the user to '
485 'cannot be deleted. \nYou can set the user to '
486 '"{}" instead of deleting it.').format(
486 '"{}" instead of deleting it.').format(
487 has_review, inactive_link))
487 has_review, inactive_link))
488 elif has_review:
488 elif has_review:
489 c.can_delete_user_message = h.literal(_(
489 c.can_delete_user_message = h.literal(_(
490 'The user participates as reviewer in {} pull requests and '
490 'The user participates as reviewer in {} pull requests and '
491 'cannot be deleted. \nYou can set the user to '
491 'cannot be deleted. \nYou can set the user to '
492 '"{}" instead of deleting it.').format(
492 '"{}" instead of deleting it.').format(
493 has_review, inactive_link))
493 has_review, inactive_link))
494
494
495 data = render(
495 data = render(
496 'rhodecode:templates/admin/users/user_edit.mako',
496 'rhodecode:templates/admin/users/user_edit.mako',
497 self._get_template_context(c), self.request)
497 self._get_template_context(c), self.request)
498 html = formencode.htmlfill.render(
498 html = formencode.htmlfill.render(
499 data,
499 data,
500 defaults=defaults,
500 defaults=defaults,
501 encoding="UTF-8",
501 encoding="UTF-8",
502 force_defaults=False
502 force_defaults=False
503 )
503 )
504 return Response(html)
504 return Response(html)
505
505
506 @LoginRequired()
506 @LoginRequired()
507 @HasPermissionAllDecorator('hg.admin')
507 @HasPermissionAllDecorator('hg.admin')
508 @view_config(
508 @view_config(
509 route_name='user_edit_global_perms', request_method='GET',
509 route_name='user_edit_global_perms', request_method='GET',
510 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 renderer='rhodecode:templates/admin/users/user_edit.mako')
511 def user_edit_global_perms(self):
511 def user_edit_global_perms(self):
512 _ = self.request.translate
512 _ = self.request.translate
513 c = self.load_default_context()
513 c = self.load_default_context()
514 c.user = self.db_user
514 c.user = self.db_user
515
515
516 c.active = 'global_perms'
516 c.active = 'global_perms'
517
517
518 c.default_user = User.get_default_user()
518 c.default_user = User.get_default_user()
519 defaults = c.user.get_dict()
519 defaults = c.user.get_dict()
520 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
521 defaults.update(c.default_user.get_default_perms())
521 defaults.update(c.default_user.get_default_perms())
522 defaults.update(c.user.get_default_perms())
522 defaults.update(c.user.get_default_perms())
523
523
524 data = render(
524 data = render(
525 'rhodecode:templates/admin/users/user_edit.mako',
525 'rhodecode:templates/admin/users/user_edit.mako',
526 self._get_template_context(c), self.request)
526 self._get_template_context(c), self.request)
527 html = formencode.htmlfill.render(
527 html = formencode.htmlfill.render(
528 data,
528 data,
529 defaults=defaults,
529 defaults=defaults,
530 encoding="UTF-8",
530 encoding="UTF-8",
531 force_defaults=False
531 force_defaults=False
532 )
532 )
533 return Response(html)
533 return Response(html)
534
534
535 @LoginRequired()
535 @LoginRequired()
536 @HasPermissionAllDecorator('hg.admin')
536 @HasPermissionAllDecorator('hg.admin')
537 @CSRFRequired()
537 @CSRFRequired()
538 @view_config(
538 @view_config(
539 route_name='user_edit_global_perms_update', request_method='POST',
539 route_name='user_edit_global_perms_update', request_method='POST',
540 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 renderer='rhodecode:templates/admin/users/user_edit.mako')
541 def user_edit_global_perms_update(self):
541 def user_edit_global_perms_update(self):
542 _ = self.request.translate
542 _ = self.request.translate
543 c = self.load_default_context()
543 c = self.load_default_context()
544
544
545 user_id = self.db_user_id
545 user_id = self.db_user_id
546 c.user = self.db_user
546 c.user = self.db_user
547
547
548 c.active = 'global_perms'
548 c.active = 'global_perms'
549 try:
549 try:
550 # first stage that verifies the checkbox
550 # first stage that verifies the checkbox
551 _form = UserIndividualPermissionsForm(self.request.translate)
551 _form = UserIndividualPermissionsForm(self.request.translate)
552 form_result = _form.to_python(dict(self.request.POST))
552 form_result = _form.to_python(dict(self.request.POST))
553 inherit_perms = form_result['inherit_default_permissions']
553 inherit_perms = form_result['inherit_default_permissions']
554 c.user.inherit_default_permissions = inherit_perms
554 c.user.inherit_default_permissions = inherit_perms
555 Session().add(c.user)
555 Session().add(c.user)
556
556
557 if not inherit_perms:
557 if not inherit_perms:
558 # only update the individual ones if we un check the flag
558 # only update the individual ones if we un check the flag
559 _form = UserPermissionsForm(
559 _form = UserPermissionsForm(
560 self.request.translate,
560 self.request.translate,
561 [x[0] for x in c.repo_create_choices],
561 [x[0] for x in c.repo_create_choices],
562 [x[0] for x in c.repo_create_on_write_choices],
562 [x[0] for x in c.repo_create_on_write_choices],
563 [x[0] for x in c.repo_group_create_choices],
563 [x[0] for x in c.repo_group_create_choices],
564 [x[0] for x in c.user_group_create_choices],
564 [x[0] for x in c.user_group_create_choices],
565 [x[0] for x in c.fork_choices],
565 [x[0] for x in c.fork_choices],
566 [x[0] for x in c.inherit_default_permission_choices])()
566 [x[0] for x in c.inherit_default_permission_choices])()
567
567
568 form_result = _form.to_python(dict(self.request.POST))
568 form_result = _form.to_python(dict(self.request.POST))
569 form_result.update({'perm_user_id': c.user.user_id})
569 form_result.update({'perm_user_id': c.user.user_id})
570
570
571 PermissionModel().update_user_permissions(form_result)
571 PermissionModel().update_user_permissions(form_result)
572
572
573 # TODO(marcink): implement global permissions
573 # TODO(marcink): implement global permissions
574 # audit_log.store_web('user.edit.permissions')
574 # audit_log.store_web('user.edit.permissions')
575
575
576 Session().commit()
576 Session().commit()
577
577
578 h.flash(_('User global permissions updated successfully'),
578 h.flash(_('User global permissions updated successfully'),
579 category='success')
579 category='success')
580
580
581 except formencode.Invalid as errors:
581 except formencode.Invalid as errors:
582 data = render(
582 data = render(
583 'rhodecode:templates/admin/users/user_edit.mako',
583 'rhodecode:templates/admin/users/user_edit.mako',
584 self._get_template_context(c), self.request)
584 self._get_template_context(c), self.request)
585 html = formencode.htmlfill.render(
585 html = formencode.htmlfill.render(
586 data,
586 data,
587 defaults=errors.value,
587 defaults=errors.value,
588 errors=errors.error_dict or {},
588 errors=errors.error_dict or {},
589 prefix_error=False,
589 prefix_error=False,
590 encoding="UTF-8",
590 encoding="UTF-8",
591 force_defaults=False
591 force_defaults=False
592 )
592 )
593 return Response(html)
593 return Response(html)
594 except Exception:
594 except Exception:
595 log.exception("Exception during permissions saving")
595 log.exception("Exception during permissions saving")
596 h.flash(_('An error occurred during permissions saving'),
596 h.flash(_('An error occurred during permissions saving'),
597 category='error')
597 category='error')
598
598
599 affected_user_ids = [user_id]
599 affected_user_ids = [user_id]
600 events.trigger(events.UserPermissionsChange(affected_user_ids))
600 PermissionModel().trigger_permission_flush(affected_user_ids)
601 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
601 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
602
602
603 @LoginRequired()
603 @LoginRequired()
604 @HasPermissionAllDecorator('hg.admin')
604 @HasPermissionAllDecorator('hg.admin')
605 @CSRFRequired()
605 @CSRFRequired()
606 @view_config(
606 @view_config(
607 route_name='user_enable_force_password_reset', request_method='POST',
607 route_name='user_enable_force_password_reset', request_method='POST',
608 renderer='rhodecode:templates/admin/users/user_edit.mako')
608 renderer='rhodecode:templates/admin/users/user_edit.mako')
609 def user_enable_force_password_reset(self):
609 def user_enable_force_password_reset(self):
610 _ = self.request.translate
610 _ = self.request.translate
611 c = self.load_default_context()
611 c = self.load_default_context()
612
612
613 user_id = self.db_user_id
613 user_id = self.db_user_id
614 c.user = self.db_user
614 c.user = self.db_user
615
615
616 try:
616 try:
617 c.user.update_userdata(force_password_change=True)
617 c.user.update_userdata(force_password_change=True)
618
618
619 msg = _('Force password change enabled for user')
619 msg = _('Force password change enabled for user')
620 audit_logger.store_web('user.edit.password_reset.enabled',
620 audit_logger.store_web('user.edit.password_reset.enabled',
621 user=c.rhodecode_user)
621 user=c.rhodecode_user)
622
622
623 Session().commit()
623 Session().commit()
624 h.flash(msg, category='success')
624 h.flash(msg, category='success')
625 except Exception:
625 except Exception:
626 log.exception("Exception during password reset for user")
626 log.exception("Exception during password reset for user")
627 h.flash(_('An error occurred during password reset for user'),
627 h.flash(_('An error occurred during password reset for user'),
628 category='error')
628 category='error')
629
629
630 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
630 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
631
631
632 @LoginRequired()
632 @LoginRequired()
633 @HasPermissionAllDecorator('hg.admin')
633 @HasPermissionAllDecorator('hg.admin')
634 @CSRFRequired()
634 @CSRFRequired()
635 @view_config(
635 @view_config(
636 route_name='user_disable_force_password_reset', request_method='POST',
636 route_name='user_disable_force_password_reset', request_method='POST',
637 renderer='rhodecode:templates/admin/users/user_edit.mako')
637 renderer='rhodecode:templates/admin/users/user_edit.mako')
638 def user_disable_force_password_reset(self):
638 def user_disable_force_password_reset(self):
639 _ = self.request.translate
639 _ = self.request.translate
640 c = self.load_default_context()
640 c = self.load_default_context()
641
641
642 user_id = self.db_user_id
642 user_id = self.db_user_id
643 c.user = self.db_user
643 c.user = self.db_user
644
644
645 try:
645 try:
646 c.user.update_userdata(force_password_change=False)
646 c.user.update_userdata(force_password_change=False)
647
647
648 msg = _('Force password change disabled for user')
648 msg = _('Force password change disabled for user')
649 audit_logger.store_web(
649 audit_logger.store_web(
650 'user.edit.password_reset.disabled',
650 'user.edit.password_reset.disabled',
651 user=c.rhodecode_user)
651 user=c.rhodecode_user)
652
652
653 Session().commit()
653 Session().commit()
654 h.flash(msg, category='success')
654 h.flash(msg, category='success')
655 except Exception:
655 except Exception:
656 log.exception("Exception during password reset for user")
656 log.exception("Exception during password reset for user")
657 h.flash(_('An error occurred during password reset for user'),
657 h.flash(_('An error occurred during password reset for user'),
658 category='error')
658 category='error')
659
659
660 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
661
661
662 @LoginRequired()
662 @LoginRequired()
663 @HasPermissionAllDecorator('hg.admin')
663 @HasPermissionAllDecorator('hg.admin')
664 @CSRFRequired()
664 @CSRFRequired()
665 @view_config(
665 @view_config(
666 route_name='user_create_personal_repo_group', request_method='POST',
666 route_name='user_create_personal_repo_group', request_method='POST',
667 renderer='rhodecode:templates/admin/users/user_edit.mako')
667 renderer='rhodecode:templates/admin/users/user_edit.mako')
668 def user_create_personal_repo_group(self):
668 def user_create_personal_repo_group(self):
669 """
669 """
670 Create personal repository group for this user
670 Create personal repository group for this user
671 """
671 """
672 from rhodecode.model.repo_group import RepoGroupModel
672 from rhodecode.model.repo_group import RepoGroupModel
673
673
674 _ = self.request.translate
674 _ = self.request.translate
675 c = self.load_default_context()
675 c = self.load_default_context()
676
676
677 user_id = self.db_user_id
677 user_id = self.db_user_id
678 c.user = self.db_user
678 c.user = self.db_user
679
679
680 personal_repo_group = RepoGroup.get_user_personal_repo_group(
680 personal_repo_group = RepoGroup.get_user_personal_repo_group(
681 c.user.user_id)
681 c.user.user_id)
682 if personal_repo_group:
682 if personal_repo_group:
683 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
683 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
684
684
685 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
685 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
686 named_personal_group = RepoGroup.get_by_group_name(
686 named_personal_group = RepoGroup.get_by_group_name(
687 personal_repo_group_name)
687 personal_repo_group_name)
688 try:
688 try:
689
689
690 if named_personal_group and named_personal_group.user_id == c.user.user_id:
690 if named_personal_group and named_personal_group.user_id == c.user.user_id:
691 # migrate the same named group, and mark it as personal
691 # migrate the same named group, and mark it as personal
692 named_personal_group.personal = True
692 named_personal_group.personal = True
693 Session().add(named_personal_group)
693 Session().add(named_personal_group)
694 Session().commit()
694 Session().commit()
695 msg = _('Linked repository group `%s` as personal' % (
695 msg = _('Linked repository group `%s` as personal' % (
696 personal_repo_group_name,))
696 personal_repo_group_name,))
697 h.flash(msg, category='success')
697 h.flash(msg, category='success')
698 elif not named_personal_group:
698 elif not named_personal_group:
699 RepoGroupModel().create_personal_repo_group(c.user)
699 RepoGroupModel().create_personal_repo_group(c.user)
700
700
701 msg = _('Created repository group `%s`' % (
701 msg = _('Created repository group `%s`' % (
702 personal_repo_group_name,))
702 personal_repo_group_name,))
703 h.flash(msg, category='success')
703 h.flash(msg, category='success')
704 else:
704 else:
705 msg = _('Repository group `%s` is already taken' % (
705 msg = _('Repository group `%s` is already taken' % (
706 personal_repo_group_name,))
706 personal_repo_group_name,))
707 h.flash(msg, category='warning')
707 h.flash(msg, category='warning')
708 except Exception:
708 except Exception:
709 log.exception("Exception during repository group creation")
709 log.exception("Exception during repository group creation")
710 msg = _(
710 msg = _(
711 'An error occurred during repository group creation for user')
711 'An error occurred during repository group creation for user')
712 h.flash(msg, category='error')
712 h.flash(msg, category='error')
713 Session().rollback()
713 Session().rollback()
714
714
715 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
715 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
716
716
717 @LoginRequired()
717 @LoginRequired()
718 @HasPermissionAllDecorator('hg.admin')
718 @HasPermissionAllDecorator('hg.admin')
719 @view_config(
719 @view_config(
720 route_name='edit_user_auth_tokens', request_method='GET',
720 route_name='edit_user_auth_tokens', request_method='GET',
721 renderer='rhodecode:templates/admin/users/user_edit.mako')
721 renderer='rhodecode:templates/admin/users/user_edit.mako')
722 def auth_tokens(self):
722 def auth_tokens(self):
723 _ = self.request.translate
723 _ = self.request.translate
724 c = self.load_default_context()
724 c = self.load_default_context()
725 c.user = self.db_user
725 c.user = self.db_user
726
726
727 c.active = 'auth_tokens'
727 c.active = 'auth_tokens'
728
728
729 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
729 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
730 c.role_values = [
730 c.role_values = [
731 (x, AuthTokenModel.cls._get_role_name(x))
731 (x, AuthTokenModel.cls._get_role_name(x))
732 for x in AuthTokenModel.cls.ROLES]
732 for x in AuthTokenModel.cls.ROLES]
733 c.role_options = [(c.role_values, _("Role"))]
733 c.role_options = [(c.role_values, _("Role"))]
734 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
734 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
735 c.user.user_id, show_expired=True)
735 c.user.user_id, show_expired=True)
736 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
736 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
737 return self._get_template_context(c)
737 return self._get_template_context(c)
738
738
739 def maybe_attach_token_scope(self, token):
739 def maybe_attach_token_scope(self, token):
740 # implemented in EE edition
740 # implemented in EE edition
741 pass
741 pass
742
742
743 @LoginRequired()
743 @LoginRequired()
744 @HasPermissionAllDecorator('hg.admin')
744 @HasPermissionAllDecorator('hg.admin')
745 @CSRFRequired()
745 @CSRFRequired()
746 @view_config(
746 @view_config(
747 route_name='edit_user_auth_tokens_add', request_method='POST')
747 route_name='edit_user_auth_tokens_add', request_method='POST')
748 def auth_tokens_add(self):
748 def auth_tokens_add(self):
749 _ = self.request.translate
749 _ = self.request.translate
750 c = self.load_default_context()
750 c = self.load_default_context()
751
751
752 user_id = self.db_user_id
752 user_id = self.db_user_id
753 c.user = self.db_user
753 c.user = self.db_user
754
754
755 user_data = c.user.get_api_data()
755 user_data = c.user.get_api_data()
756 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
756 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
757 description = self.request.POST.get('description')
757 description = self.request.POST.get('description')
758 role = self.request.POST.get('role')
758 role = self.request.POST.get('role')
759
759
760 token = UserModel().add_auth_token(
760 token = UserModel().add_auth_token(
761 user=c.user.user_id,
761 user=c.user.user_id,
762 lifetime_minutes=lifetime, role=role, description=description,
762 lifetime_minutes=lifetime, role=role, description=description,
763 scope_callback=self.maybe_attach_token_scope)
763 scope_callback=self.maybe_attach_token_scope)
764 token_data = token.get_api_data()
764 token_data = token.get_api_data()
765
765
766 audit_logger.store_web(
766 audit_logger.store_web(
767 'user.edit.token.add', action_data={
767 'user.edit.token.add', action_data={
768 'data': {'token': token_data, 'user': user_data}},
768 'data': {'token': token_data, 'user': user_data}},
769 user=self._rhodecode_user, )
769 user=self._rhodecode_user, )
770 Session().commit()
770 Session().commit()
771
771
772 h.flash(_("Auth token successfully created"), category='success')
772 h.flash(_("Auth token successfully created"), category='success')
773 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
773 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
774
774
775 @LoginRequired()
775 @LoginRequired()
776 @HasPermissionAllDecorator('hg.admin')
776 @HasPermissionAllDecorator('hg.admin')
777 @CSRFRequired()
777 @CSRFRequired()
778 @view_config(
778 @view_config(
779 route_name='edit_user_auth_tokens_delete', request_method='POST')
779 route_name='edit_user_auth_tokens_delete', request_method='POST')
780 def auth_tokens_delete(self):
780 def auth_tokens_delete(self):
781 _ = self.request.translate
781 _ = self.request.translate
782 c = self.load_default_context()
782 c = self.load_default_context()
783
783
784 user_id = self.db_user_id
784 user_id = self.db_user_id
785 c.user = self.db_user
785 c.user = self.db_user
786
786
787 user_data = c.user.get_api_data()
787 user_data = c.user.get_api_data()
788
788
789 del_auth_token = self.request.POST.get('del_auth_token')
789 del_auth_token = self.request.POST.get('del_auth_token')
790
790
791 if del_auth_token:
791 if del_auth_token:
792 token = UserApiKeys.get_or_404(del_auth_token)
792 token = UserApiKeys.get_or_404(del_auth_token)
793 token_data = token.get_api_data()
793 token_data = token.get_api_data()
794
794
795 AuthTokenModel().delete(del_auth_token, c.user.user_id)
795 AuthTokenModel().delete(del_auth_token, c.user.user_id)
796 audit_logger.store_web(
796 audit_logger.store_web(
797 'user.edit.token.delete', action_data={
797 'user.edit.token.delete', action_data={
798 'data': {'token': token_data, 'user': user_data}},
798 'data': {'token': token_data, 'user': user_data}},
799 user=self._rhodecode_user,)
799 user=self._rhodecode_user,)
800 Session().commit()
800 Session().commit()
801 h.flash(_("Auth token successfully deleted"), category='success')
801 h.flash(_("Auth token successfully deleted"), category='success')
802
802
803 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
803 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
804
804
805 @LoginRequired()
805 @LoginRequired()
806 @HasPermissionAllDecorator('hg.admin')
806 @HasPermissionAllDecorator('hg.admin')
807 @view_config(
807 @view_config(
808 route_name='edit_user_ssh_keys', request_method='GET',
808 route_name='edit_user_ssh_keys', request_method='GET',
809 renderer='rhodecode:templates/admin/users/user_edit.mako')
809 renderer='rhodecode:templates/admin/users/user_edit.mako')
810 def ssh_keys(self):
810 def ssh_keys(self):
811 _ = self.request.translate
811 _ = self.request.translate
812 c = self.load_default_context()
812 c = self.load_default_context()
813 c.user = self.db_user
813 c.user = self.db_user
814
814
815 c.active = 'ssh_keys'
815 c.active = 'ssh_keys'
816 c.default_key = self.request.GET.get('default_key')
816 c.default_key = self.request.GET.get('default_key')
817 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
817 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
818 return self._get_template_context(c)
818 return self._get_template_context(c)
819
819
820 @LoginRequired()
820 @LoginRequired()
821 @HasPermissionAllDecorator('hg.admin')
821 @HasPermissionAllDecorator('hg.admin')
822 @view_config(
822 @view_config(
823 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
823 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
824 renderer='rhodecode:templates/admin/users/user_edit.mako')
824 renderer='rhodecode:templates/admin/users/user_edit.mako')
825 def ssh_keys_generate_keypair(self):
825 def ssh_keys_generate_keypair(self):
826 _ = self.request.translate
826 _ = self.request.translate
827 c = self.load_default_context()
827 c = self.load_default_context()
828
828
829 c.user = self.db_user
829 c.user = self.db_user
830
830
831 c.active = 'ssh_keys_generate'
831 c.active = 'ssh_keys_generate'
832 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
832 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
833 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
833 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
834
834
835 return self._get_template_context(c)
835 return self._get_template_context(c)
836
836
837 @LoginRequired()
837 @LoginRequired()
838 @HasPermissionAllDecorator('hg.admin')
838 @HasPermissionAllDecorator('hg.admin')
839 @CSRFRequired()
839 @CSRFRequired()
840 @view_config(
840 @view_config(
841 route_name='edit_user_ssh_keys_add', request_method='POST')
841 route_name='edit_user_ssh_keys_add', request_method='POST')
842 def ssh_keys_add(self):
842 def ssh_keys_add(self):
843 _ = self.request.translate
843 _ = self.request.translate
844 c = self.load_default_context()
844 c = self.load_default_context()
845
845
846 user_id = self.db_user_id
846 user_id = self.db_user_id
847 c.user = self.db_user
847 c.user = self.db_user
848
848
849 user_data = c.user.get_api_data()
849 user_data = c.user.get_api_data()
850 key_data = self.request.POST.get('key_data')
850 key_data = self.request.POST.get('key_data')
851 description = self.request.POST.get('description')
851 description = self.request.POST.get('description')
852
852
853 fingerprint = 'unknown'
853 fingerprint = 'unknown'
854 try:
854 try:
855 if not key_data:
855 if not key_data:
856 raise ValueError('Please add a valid public key')
856 raise ValueError('Please add a valid public key')
857
857
858 key = SshKeyModel().parse_key(key_data.strip())
858 key = SshKeyModel().parse_key(key_data.strip())
859 fingerprint = key.hash_md5()
859 fingerprint = key.hash_md5()
860
860
861 ssh_key = SshKeyModel().create(
861 ssh_key = SshKeyModel().create(
862 c.user.user_id, fingerprint, key.keydata, description)
862 c.user.user_id, fingerprint, key.keydata, description)
863 ssh_key_data = ssh_key.get_api_data()
863 ssh_key_data = ssh_key.get_api_data()
864
864
865 audit_logger.store_web(
865 audit_logger.store_web(
866 'user.edit.ssh_key.add', action_data={
866 'user.edit.ssh_key.add', action_data={
867 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
867 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
868 user=self._rhodecode_user, )
868 user=self._rhodecode_user, )
869 Session().commit()
869 Session().commit()
870
870
871 # Trigger an event on change of keys.
871 # Trigger an event on change of keys.
872 trigger(SshKeyFileChangeEvent(), self.request.registry)
872 trigger(SshKeyFileChangeEvent(), self.request.registry)
873
873
874 h.flash(_("Ssh Key successfully created"), category='success')
874 h.flash(_("Ssh Key successfully created"), category='success')
875
875
876 except IntegrityError:
876 except IntegrityError:
877 log.exception("Exception during ssh key saving")
877 log.exception("Exception during ssh key saving")
878 err = 'Such key with fingerprint `{}` already exists, ' \
878 err = 'Such key with fingerprint `{}` already exists, ' \
879 'please use a different one'.format(fingerprint)
879 'please use a different one'.format(fingerprint)
880 h.flash(_('An error occurred during ssh key saving: {}').format(err),
880 h.flash(_('An error occurred during ssh key saving: {}').format(err),
881 category='error')
881 category='error')
882 except Exception as e:
882 except Exception as e:
883 log.exception("Exception during ssh key saving")
883 log.exception("Exception during ssh key saving")
884 h.flash(_('An error occurred during ssh key saving: {}').format(e),
884 h.flash(_('An error occurred during ssh key saving: {}').format(e),
885 category='error')
885 category='error')
886
886
887 return HTTPFound(
887 return HTTPFound(
888 h.route_path('edit_user_ssh_keys', user_id=user_id))
888 h.route_path('edit_user_ssh_keys', user_id=user_id))
889
889
890 @LoginRequired()
890 @LoginRequired()
891 @HasPermissionAllDecorator('hg.admin')
891 @HasPermissionAllDecorator('hg.admin')
892 @CSRFRequired()
892 @CSRFRequired()
893 @view_config(
893 @view_config(
894 route_name='edit_user_ssh_keys_delete', request_method='POST')
894 route_name='edit_user_ssh_keys_delete', request_method='POST')
895 def ssh_keys_delete(self):
895 def ssh_keys_delete(self):
896 _ = self.request.translate
896 _ = self.request.translate
897 c = self.load_default_context()
897 c = self.load_default_context()
898
898
899 user_id = self.db_user_id
899 user_id = self.db_user_id
900 c.user = self.db_user
900 c.user = self.db_user
901
901
902 user_data = c.user.get_api_data()
902 user_data = c.user.get_api_data()
903
903
904 del_ssh_key = self.request.POST.get('del_ssh_key')
904 del_ssh_key = self.request.POST.get('del_ssh_key')
905
905
906 if del_ssh_key:
906 if del_ssh_key:
907 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
907 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
908 ssh_key_data = ssh_key.get_api_data()
908 ssh_key_data = ssh_key.get_api_data()
909
909
910 SshKeyModel().delete(del_ssh_key, c.user.user_id)
910 SshKeyModel().delete(del_ssh_key, c.user.user_id)
911 audit_logger.store_web(
911 audit_logger.store_web(
912 'user.edit.ssh_key.delete', action_data={
912 'user.edit.ssh_key.delete', action_data={
913 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
913 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
914 user=self._rhodecode_user,)
914 user=self._rhodecode_user,)
915 Session().commit()
915 Session().commit()
916 # Trigger an event on change of keys.
916 # Trigger an event on change of keys.
917 trigger(SshKeyFileChangeEvent(), self.request.registry)
917 trigger(SshKeyFileChangeEvent(), self.request.registry)
918 h.flash(_("Ssh key successfully deleted"), category='success')
918 h.flash(_("Ssh key successfully deleted"), category='success')
919
919
920 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
920 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
921
921
922 @LoginRequired()
922 @LoginRequired()
923 @HasPermissionAllDecorator('hg.admin')
923 @HasPermissionAllDecorator('hg.admin')
924 @view_config(
924 @view_config(
925 route_name='edit_user_emails', request_method='GET',
925 route_name='edit_user_emails', request_method='GET',
926 renderer='rhodecode:templates/admin/users/user_edit.mako')
926 renderer='rhodecode:templates/admin/users/user_edit.mako')
927 def emails(self):
927 def emails(self):
928 _ = self.request.translate
928 _ = self.request.translate
929 c = self.load_default_context()
929 c = self.load_default_context()
930 c.user = self.db_user
930 c.user = self.db_user
931
931
932 c.active = 'emails'
932 c.active = 'emails'
933 c.user_email_map = UserEmailMap.query() \
933 c.user_email_map = UserEmailMap.query() \
934 .filter(UserEmailMap.user == c.user).all()
934 .filter(UserEmailMap.user == c.user).all()
935
935
936 return self._get_template_context(c)
936 return self._get_template_context(c)
937
937
938 @LoginRequired()
938 @LoginRequired()
939 @HasPermissionAllDecorator('hg.admin')
939 @HasPermissionAllDecorator('hg.admin')
940 @CSRFRequired()
940 @CSRFRequired()
941 @view_config(
941 @view_config(
942 route_name='edit_user_emails_add', request_method='POST')
942 route_name='edit_user_emails_add', request_method='POST')
943 def emails_add(self):
943 def emails_add(self):
944 _ = self.request.translate
944 _ = self.request.translate
945 c = self.load_default_context()
945 c = self.load_default_context()
946
946
947 user_id = self.db_user_id
947 user_id = self.db_user_id
948 c.user = self.db_user
948 c.user = self.db_user
949
949
950 email = self.request.POST.get('new_email')
950 email = self.request.POST.get('new_email')
951 user_data = c.user.get_api_data()
951 user_data = c.user.get_api_data()
952 try:
952 try:
953
953
954 form = UserExtraEmailForm(self.request.translate)()
954 form = UserExtraEmailForm(self.request.translate)()
955 data = form.to_python({'email': email})
955 data = form.to_python({'email': email})
956 email = data['email']
956 email = data['email']
957
957
958 UserModel().add_extra_email(c.user.user_id, email)
958 UserModel().add_extra_email(c.user.user_id, email)
959 audit_logger.store_web(
959 audit_logger.store_web(
960 'user.edit.email.add',
960 'user.edit.email.add',
961 action_data={'email': email, 'user': user_data},
961 action_data={'email': email, 'user': user_data},
962 user=self._rhodecode_user)
962 user=self._rhodecode_user)
963 Session().commit()
963 Session().commit()
964 h.flash(_("Added new email address `%s` for user account") % email,
964 h.flash(_("Added new email address `%s` for user account") % email,
965 category='success')
965 category='success')
966 except formencode.Invalid as error:
966 except formencode.Invalid as error:
967 h.flash(h.escape(error.error_dict['email']), category='error')
967 h.flash(h.escape(error.error_dict['email']), category='error')
968 except IntegrityError:
968 except IntegrityError:
969 log.warning("Email %s already exists", email)
969 log.warning("Email %s already exists", email)
970 h.flash(_('Email `{}` is already registered for another user.').format(email),
970 h.flash(_('Email `{}` is already registered for another user.').format(email),
971 category='error')
971 category='error')
972 except Exception:
972 except Exception:
973 log.exception("Exception during email saving")
973 log.exception("Exception during email saving")
974 h.flash(_('An error occurred during email saving'),
974 h.flash(_('An error occurred during email saving'),
975 category='error')
975 category='error')
976 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
976 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
977
977
978 @LoginRequired()
978 @LoginRequired()
979 @HasPermissionAllDecorator('hg.admin')
979 @HasPermissionAllDecorator('hg.admin')
980 @CSRFRequired()
980 @CSRFRequired()
981 @view_config(
981 @view_config(
982 route_name='edit_user_emails_delete', request_method='POST')
982 route_name='edit_user_emails_delete', request_method='POST')
983 def emails_delete(self):
983 def emails_delete(self):
984 _ = self.request.translate
984 _ = self.request.translate
985 c = self.load_default_context()
985 c = self.load_default_context()
986
986
987 user_id = self.db_user_id
987 user_id = self.db_user_id
988 c.user = self.db_user
988 c.user = self.db_user
989
989
990 email_id = self.request.POST.get('del_email_id')
990 email_id = self.request.POST.get('del_email_id')
991 user_model = UserModel()
991 user_model = UserModel()
992
992
993 email = UserEmailMap.query().get(email_id).email
993 email = UserEmailMap.query().get(email_id).email
994 user_data = c.user.get_api_data()
994 user_data = c.user.get_api_data()
995 user_model.delete_extra_email(c.user.user_id, email_id)
995 user_model.delete_extra_email(c.user.user_id, email_id)
996 audit_logger.store_web(
996 audit_logger.store_web(
997 'user.edit.email.delete',
997 'user.edit.email.delete',
998 action_data={'email': email, 'user': user_data},
998 action_data={'email': email, 'user': user_data},
999 user=self._rhodecode_user)
999 user=self._rhodecode_user)
1000 Session().commit()
1000 Session().commit()
1001 h.flash(_("Removed email address from user account"),
1001 h.flash(_("Removed email address from user account"),
1002 category='success')
1002 category='success')
1003 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1003 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1004
1004
1005 @LoginRequired()
1005 @LoginRequired()
1006 @HasPermissionAllDecorator('hg.admin')
1006 @HasPermissionAllDecorator('hg.admin')
1007 @view_config(
1007 @view_config(
1008 route_name='edit_user_ips', request_method='GET',
1008 route_name='edit_user_ips', request_method='GET',
1009 renderer='rhodecode:templates/admin/users/user_edit.mako')
1009 renderer='rhodecode:templates/admin/users/user_edit.mako')
1010 def ips(self):
1010 def ips(self):
1011 _ = self.request.translate
1011 _ = self.request.translate
1012 c = self.load_default_context()
1012 c = self.load_default_context()
1013 c.user = self.db_user
1013 c.user = self.db_user
1014
1014
1015 c.active = 'ips'
1015 c.active = 'ips'
1016 c.user_ip_map = UserIpMap.query() \
1016 c.user_ip_map = UserIpMap.query() \
1017 .filter(UserIpMap.user == c.user).all()
1017 .filter(UserIpMap.user == c.user).all()
1018
1018
1019 c.inherit_default_ips = c.user.inherit_default_permissions
1019 c.inherit_default_ips = c.user.inherit_default_permissions
1020 c.default_user_ip_map = UserIpMap.query() \
1020 c.default_user_ip_map = UserIpMap.query() \
1021 .filter(UserIpMap.user == User.get_default_user()).all()
1021 .filter(UserIpMap.user == User.get_default_user()).all()
1022
1022
1023 return self._get_template_context(c)
1023 return self._get_template_context(c)
1024
1024
1025 @LoginRequired()
1025 @LoginRequired()
1026 @HasPermissionAllDecorator('hg.admin')
1026 @HasPermissionAllDecorator('hg.admin')
1027 @CSRFRequired()
1027 @CSRFRequired()
1028 @view_config(
1028 @view_config(
1029 route_name='edit_user_ips_add', request_method='POST')
1029 route_name='edit_user_ips_add', request_method='POST')
1030 # NOTE(marcink): this view is allowed for default users, as we can
1030 # NOTE(marcink): this view is allowed for default users, as we can
1031 # edit their IP white list
1031 # edit their IP white list
1032 def ips_add(self):
1032 def ips_add(self):
1033 _ = self.request.translate
1033 _ = self.request.translate
1034 c = self.load_default_context()
1034 c = self.load_default_context()
1035
1035
1036 user_id = self.db_user_id
1036 user_id = self.db_user_id
1037 c.user = self.db_user
1037 c.user = self.db_user
1038
1038
1039 user_model = UserModel()
1039 user_model = UserModel()
1040 desc = self.request.POST.get('description')
1040 desc = self.request.POST.get('description')
1041 try:
1041 try:
1042 ip_list = user_model.parse_ip_range(
1042 ip_list = user_model.parse_ip_range(
1043 self.request.POST.get('new_ip'))
1043 self.request.POST.get('new_ip'))
1044 except Exception as e:
1044 except Exception as e:
1045 ip_list = []
1045 ip_list = []
1046 log.exception("Exception during ip saving")
1046 log.exception("Exception during ip saving")
1047 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1047 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1048 category='error')
1048 category='error')
1049 added = []
1049 added = []
1050 user_data = c.user.get_api_data()
1050 user_data = c.user.get_api_data()
1051 for ip in ip_list:
1051 for ip in ip_list:
1052 try:
1052 try:
1053 form = UserExtraIpForm(self.request.translate)()
1053 form = UserExtraIpForm(self.request.translate)()
1054 data = form.to_python({'ip': ip})
1054 data = form.to_python({'ip': ip})
1055 ip = data['ip']
1055 ip = data['ip']
1056
1056
1057 user_model.add_extra_ip(c.user.user_id, ip, desc)
1057 user_model.add_extra_ip(c.user.user_id, ip, desc)
1058 audit_logger.store_web(
1058 audit_logger.store_web(
1059 'user.edit.ip.add',
1059 'user.edit.ip.add',
1060 action_data={'ip': ip, 'user': user_data},
1060 action_data={'ip': ip, 'user': user_data},
1061 user=self._rhodecode_user)
1061 user=self._rhodecode_user)
1062 Session().commit()
1062 Session().commit()
1063 added.append(ip)
1063 added.append(ip)
1064 except formencode.Invalid as error:
1064 except formencode.Invalid as error:
1065 msg = error.error_dict['ip']
1065 msg = error.error_dict['ip']
1066 h.flash(msg, category='error')
1066 h.flash(msg, category='error')
1067 except Exception:
1067 except Exception:
1068 log.exception("Exception during ip saving")
1068 log.exception("Exception during ip saving")
1069 h.flash(_('An error occurred during ip saving'),
1069 h.flash(_('An error occurred during ip saving'),
1070 category='error')
1070 category='error')
1071 if added:
1071 if added:
1072 h.flash(
1072 h.flash(
1073 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1073 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1074 category='success')
1074 category='success')
1075 if 'default_user' in self.request.POST:
1075 if 'default_user' in self.request.POST:
1076 # case for editing global IP list we do it for 'DEFAULT' user
1076 # case for editing global IP list we do it for 'DEFAULT' user
1077 raise HTTPFound(h.route_path('admin_permissions_ips'))
1077 raise HTTPFound(h.route_path('admin_permissions_ips'))
1078 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1078 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1079
1079
1080 @LoginRequired()
1080 @LoginRequired()
1081 @HasPermissionAllDecorator('hg.admin')
1081 @HasPermissionAllDecorator('hg.admin')
1082 @CSRFRequired()
1082 @CSRFRequired()
1083 @view_config(
1083 @view_config(
1084 route_name='edit_user_ips_delete', request_method='POST')
1084 route_name='edit_user_ips_delete', request_method='POST')
1085 # NOTE(marcink): this view is allowed for default users, as we can
1085 # NOTE(marcink): this view is allowed for default users, as we can
1086 # edit their IP white list
1086 # edit their IP white list
1087 def ips_delete(self):
1087 def ips_delete(self):
1088 _ = self.request.translate
1088 _ = self.request.translate
1089 c = self.load_default_context()
1089 c = self.load_default_context()
1090
1090
1091 user_id = self.db_user_id
1091 user_id = self.db_user_id
1092 c.user = self.db_user
1092 c.user = self.db_user
1093
1093
1094 ip_id = self.request.POST.get('del_ip_id')
1094 ip_id = self.request.POST.get('del_ip_id')
1095 user_model = UserModel()
1095 user_model = UserModel()
1096 user_data = c.user.get_api_data()
1096 user_data = c.user.get_api_data()
1097 ip = UserIpMap.query().get(ip_id).ip_addr
1097 ip = UserIpMap.query().get(ip_id).ip_addr
1098 user_model.delete_extra_ip(c.user.user_id, ip_id)
1098 user_model.delete_extra_ip(c.user.user_id, ip_id)
1099 audit_logger.store_web(
1099 audit_logger.store_web(
1100 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1100 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1101 user=self._rhodecode_user)
1101 user=self._rhodecode_user)
1102 Session().commit()
1102 Session().commit()
1103 h.flash(_("Removed ip address from user whitelist"), category='success')
1103 h.flash(_("Removed ip address from user whitelist"), category='success')
1104
1104
1105 if 'default_user' in self.request.POST:
1105 if 'default_user' in self.request.POST:
1106 # case for editing global IP list we do it for 'DEFAULT' user
1106 # case for editing global IP list we do it for 'DEFAULT' user
1107 raise HTTPFound(h.route_path('admin_permissions_ips'))
1107 raise HTTPFound(h.route_path('admin_permissions_ips'))
1108 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1108 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1109
1109
1110 @LoginRequired()
1110 @LoginRequired()
1111 @HasPermissionAllDecorator('hg.admin')
1111 @HasPermissionAllDecorator('hg.admin')
1112 @view_config(
1112 @view_config(
1113 route_name='edit_user_groups_management', request_method='GET',
1113 route_name='edit_user_groups_management', request_method='GET',
1114 renderer='rhodecode:templates/admin/users/user_edit.mako')
1114 renderer='rhodecode:templates/admin/users/user_edit.mako')
1115 def groups_management(self):
1115 def groups_management(self):
1116 c = self.load_default_context()
1116 c = self.load_default_context()
1117 c.user = self.db_user
1117 c.user = self.db_user
1118 c.data = c.user.group_member
1118 c.data = c.user.group_member
1119
1119
1120 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1120 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1121 for group in c.user.group_member]
1121 for group in c.user.group_member]
1122 c.groups = json.dumps(groups)
1122 c.groups = json.dumps(groups)
1123 c.active = 'groups'
1123 c.active = 'groups'
1124
1124
1125 return self._get_template_context(c)
1125 return self._get_template_context(c)
1126
1126
1127 @LoginRequired()
1127 @LoginRequired()
1128 @HasPermissionAllDecorator('hg.admin')
1128 @HasPermissionAllDecorator('hg.admin')
1129 @CSRFRequired()
1129 @CSRFRequired()
1130 @view_config(
1130 @view_config(
1131 route_name='edit_user_groups_management_updates', request_method='POST')
1131 route_name='edit_user_groups_management_updates', request_method='POST')
1132 def groups_management_updates(self):
1132 def groups_management_updates(self):
1133 _ = self.request.translate
1133 _ = self.request.translate
1134 c = self.load_default_context()
1134 c = self.load_default_context()
1135
1135
1136 user_id = self.db_user_id
1136 user_id = self.db_user_id
1137 c.user = self.db_user
1137 c.user = self.db_user
1138
1138
1139 user_groups = set(self.request.POST.getall('users_group_id'))
1139 user_groups = set(self.request.POST.getall('users_group_id'))
1140 user_groups_objects = []
1140 user_groups_objects = []
1141
1141
1142 for ugid in user_groups:
1142 for ugid in user_groups:
1143 user_groups_objects.append(
1143 user_groups_objects.append(
1144 UserGroupModel().get_group(safe_int(ugid)))
1144 UserGroupModel().get_group(safe_int(ugid)))
1145 user_group_model = UserGroupModel()
1145 user_group_model = UserGroupModel()
1146 added_to_groups, removed_from_groups = \
1146 added_to_groups, removed_from_groups = \
1147 user_group_model.change_groups(c.user, user_groups_objects)
1147 user_group_model.change_groups(c.user, user_groups_objects)
1148
1148
1149 user_data = c.user.get_api_data()
1149 user_data = c.user.get_api_data()
1150 for user_group_id in added_to_groups:
1150 for user_group_id in added_to_groups:
1151 user_group = UserGroup.get(user_group_id)
1151 user_group = UserGroup.get(user_group_id)
1152 old_values = user_group.get_api_data()
1152 old_values = user_group.get_api_data()
1153 audit_logger.store_web(
1153 audit_logger.store_web(
1154 'user_group.edit.member.add',
1154 'user_group.edit.member.add',
1155 action_data={'user': user_data, 'old_data': old_values},
1155 action_data={'user': user_data, 'old_data': old_values},
1156 user=self._rhodecode_user)
1156 user=self._rhodecode_user)
1157
1157
1158 for user_group_id in removed_from_groups:
1158 for user_group_id in removed_from_groups:
1159 user_group = UserGroup.get(user_group_id)
1159 user_group = UserGroup.get(user_group_id)
1160 old_values = user_group.get_api_data()
1160 old_values = user_group.get_api_data()
1161 audit_logger.store_web(
1161 audit_logger.store_web(
1162 'user_group.edit.member.delete',
1162 'user_group.edit.member.delete',
1163 action_data={'user': user_data, 'old_data': old_values},
1163 action_data={'user': user_data, 'old_data': old_values},
1164 user=self._rhodecode_user)
1164 user=self._rhodecode_user)
1165
1165
1166 Session().commit()
1166 Session().commit()
1167 c.active = 'user_groups_management'
1167 c.active = 'user_groups_management'
1168 h.flash(_("Groups successfully changed"), category='success')
1168 h.flash(_("Groups successfully changed"), category='success')
1169
1169
1170 return HTTPFound(h.route_path(
1170 return HTTPFound(h.route_path(
1171 'edit_user_groups_management', user_id=user_id))
1171 'edit_user_groups_management', user_id=user_id))
1172
1172
1173 @LoginRequired()
1173 @LoginRequired()
1174 @HasPermissionAllDecorator('hg.admin')
1174 @HasPermissionAllDecorator('hg.admin')
1175 @view_config(
1175 @view_config(
1176 route_name='edit_user_audit_logs', request_method='GET',
1176 route_name='edit_user_audit_logs', request_method='GET',
1177 renderer='rhodecode:templates/admin/users/user_edit.mako')
1177 renderer='rhodecode:templates/admin/users/user_edit.mako')
1178 def user_audit_logs(self):
1178 def user_audit_logs(self):
1179 _ = self.request.translate
1179 _ = self.request.translate
1180 c = self.load_default_context()
1180 c = self.load_default_context()
1181 c.user = self.db_user
1181 c.user = self.db_user
1182
1182
1183 c.active = 'audit'
1183 c.active = 'audit'
1184
1184
1185 p = safe_int(self.request.GET.get('page', 1), 1)
1185 p = safe_int(self.request.GET.get('page', 1), 1)
1186
1186
1187 filter_term = self.request.GET.get('filter')
1187 filter_term = self.request.GET.get('filter')
1188 user_log = UserModel().get_user_log(c.user, filter_term)
1188 user_log = UserModel().get_user_log(c.user, filter_term)
1189
1189
1190 def url_generator(**kw):
1190 def url_generator(**kw):
1191 if filter_term:
1191 if filter_term:
1192 kw['filter'] = filter_term
1192 kw['filter'] = filter_term
1193 return self.request.current_route_path(_query=kw)
1193 return self.request.current_route_path(_query=kw)
1194
1194
1195 c.audit_logs = h.Page(
1195 c.audit_logs = h.Page(
1196 user_log, page=p, items_per_page=10, url=url_generator)
1196 user_log, page=p, items_per_page=10, url=url_generator)
1197 c.filter_term = filter_term
1197 c.filter_term = filter_term
1198 return self._get_template_context(c)
1198 return self._get_template_context(c)
1199
1199
1200 @LoginRequired()
1200 @LoginRequired()
1201 @HasPermissionAllDecorator('hg.admin')
1201 @HasPermissionAllDecorator('hg.admin')
1202 @view_config(
1202 @view_config(
1203 route_name='edit_user_perms_summary', request_method='GET',
1203 route_name='edit_user_perms_summary', request_method='GET',
1204 renderer='rhodecode:templates/admin/users/user_edit.mako')
1204 renderer='rhodecode:templates/admin/users/user_edit.mako')
1205 def user_perms_summary(self):
1205 def user_perms_summary(self):
1206 _ = self.request.translate
1206 _ = self.request.translate
1207 c = self.load_default_context()
1207 c = self.load_default_context()
1208 c.user = self.db_user
1208 c.user = self.db_user
1209
1209
1210 c.active = 'perms_summary'
1210 c.active = 'perms_summary'
1211 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1211 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1212
1212
1213 return self._get_template_context(c)
1213 return self._get_template_context(c)
1214
1214
1215 @LoginRequired()
1215 @LoginRequired()
1216 @HasPermissionAllDecorator('hg.admin')
1216 @HasPermissionAllDecorator('hg.admin')
1217 @view_config(
1217 @view_config(
1218 route_name='edit_user_perms_summary_json', request_method='GET',
1218 route_name='edit_user_perms_summary_json', request_method='GET',
1219 renderer='json_ext')
1219 renderer='json_ext')
1220 def user_perms_summary_json(self):
1220 def user_perms_summary_json(self):
1221 self.load_default_context()
1221 self.load_default_context()
1222 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1222 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1223
1223
1224 return perm_user.permissions
1224 return perm_user.permissions
1225
1225
1226 @LoginRequired()
1226 @LoginRequired()
1227 @HasPermissionAllDecorator('hg.admin')
1227 @HasPermissionAllDecorator('hg.admin')
1228 @view_config(
1228 @view_config(
1229 route_name='edit_user_caches', request_method='GET',
1229 route_name='edit_user_caches', request_method='GET',
1230 renderer='rhodecode:templates/admin/users/user_edit.mako')
1230 renderer='rhodecode:templates/admin/users/user_edit.mako')
1231 def user_caches(self):
1231 def user_caches(self):
1232 _ = self.request.translate
1232 _ = self.request.translate
1233 c = self.load_default_context()
1233 c = self.load_default_context()
1234 c.user = self.db_user
1234 c.user = self.db_user
1235
1235
1236 c.active = 'caches'
1236 c.active = 'caches'
1237 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1237 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1238
1238
1239 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1239 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1240 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1240 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1241 c.backend = c.region.backend
1241 c.backend = c.region.backend
1242 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1242 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1243
1243
1244 return self._get_template_context(c)
1244 return self._get_template_context(c)
1245
1245
1246 @LoginRequired()
1246 @LoginRequired()
1247 @HasPermissionAllDecorator('hg.admin')
1247 @HasPermissionAllDecorator('hg.admin')
1248 @CSRFRequired()
1248 @CSRFRequired()
1249 @view_config(
1249 @view_config(
1250 route_name='edit_user_caches_update', request_method='POST')
1250 route_name='edit_user_caches_update', request_method='POST')
1251 def user_caches_update(self):
1251 def user_caches_update(self):
1252 _ = self.request.translate
1252 _ = self.request.translate
1253 c = self.load_default_context()
1253 c = self.load_default_context()
1254 c.user = self.db_user
1254 c.user = self.db_user
1255
1255
1256 c.active = 'caches'
1256 c.active = 'caches'
1257 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1257 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1258
1258
1259 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1259 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1260 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1260 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1261
1261
1262 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1262 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1263
1263
1264 return HTTPFound(h.route_path(
1264 return HTTPFound(h.route_path(
1265 'edit_user_caches', user_id=c.user.user_id))
1265 'edit_user_caches', user_id=c.user.user_id))
@@ -1,116 +1,103 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode import events
27 from rhodecode.apps._base import RepoGroupAppView
26 from rhodecode.apps._base import RepoGroupAppView
28 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
29 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
31 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.model.permission import PermissionModel
33 from rhodecode.model.db import UserGroup
34 from rhodecode.model.repo_group import RepoGroupModel
32 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.forms import RepoGroupPermsForm
33 from rhodecode.model.forms import RepoGroupPermsForm
36 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
37
35
38 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
39
37
40
38
41 class RepoGroupPermissionsView(RepoGroupAppView):
39 class RepoGroupPermissionsView(RepoGroupAppView):
42 def load_default_context(self):
40 def load_default_context(self):
43 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
44
42
45 return c
43 return c
46
44
47 @LoginRequired()
45 @LoginRequired()
48 @HasRepoGroupPermissionAnyDecorator('group.admin')
46 @HasRepoGroupPermissionAnyDecorator('group.admin')
49 @view_config(
47 @view_config(
50 route_name='edit_repo_group_perms', request_method='GET',
48 route_name='edit_repo_group_perms', request_method='GET',
51 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
49 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
52 def edit_repo_group_permissions(self):
50 def edit_repo_group_permissions(self):
53 c = self.load_default_context()
51 c = self.load_default_context()
54 c.active = 'permissions'
52 c.active = 'permissions'
55 c.repo_group = self.db_repo_group
53 c.repo_group = self.db_repo_group
56 return self._get_template_context(c)
54 return self._get_template_context(c)
57
55
58 @LoginRequired()
56 @LoginRequired()
59 @HasRepoGroupPermissionAnyDecorator('group.admin')
57 @HasRepoGroupPermissionAnyDecorator('group.admin')
60 @CSRFRequired()
58 @CSRFRequired()
61 @view_config(
59 @view_config(
62 route_name='edit_repo_group_perms_update', request_method='POST',
60 route_name='edit_repo_group_perms_update', request_method='POST',
63 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
61 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
64 def edit_repo_groups_permissions_update(self):
62 def edit_repo_groups_permissions_update(self):
65 _ = self.request.translate
63 _ = self.request.translate
66 c = self.load_default_context()
64 c = self.load_default_context()
67 c.active = 'perms'
65 c.active = 'perms'
68 c.repo_group = self.db_repo_group
66 c.repo_group = self.db_repo_group
69
67
70 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
68 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
71 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
69 form = RepoGroupPermsForm(self.request.translate, valid_recursive_choices)()\
72 .to_python(self.request.POST)
70 .to_python(self.request.POST)
73
71
74 if not c.rhodecode_user.is_admin:
72 if not c.rhodecode_user.is_admin:
75 if self._revoke_perms_on_yourself(form):
73 if self._revoke_perms_on_yourself(form):
76 msg = _('Cannot change permission for yourself as admin')
74 msg = _('Cannot change permission for yourself as admin')
77 h.flash(msg, category='warning')
75 h.flash(msg, category='warning')
78 raise HTTPFound(
76 raise HTTPFound(
79 h.route_path('edit_repo_group_perms',
77 h.route_path('edit_repo_group_perms',
80 repo_group_name=self.db_repo_group_name))
78 repo_group_name=self.db_repo_group_name))
81
79
82 # iterate over all members(if in recursive mode) of this groups and
80 # iterate over all members(if in recursive mode) of this groups and
83 # set the permissions !
81 # set the permissions !
84 # this can be potentially heavy operation
82 # this can be potentially heavy operation
85 changes = RepoGroupModel().update_permissions(
83 changes = RepoGroupModel().update_permissions(
86 c.repo_group,
84 c.repo_group,
87 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
85 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
88 form['recursive'])
86 form['recursive'])
89
87
90 action_data = {
88 action_data = {
91 'added': changes['added'],
89 'added': changes['added'],
92 'updated': changes['updated'],
90 'updated': changes['updated'],
93 'deleted': changes['deleted'],
91 'deleted': changes['deleted'],
94 }
92 }
95 audit_logger.store_web(
93 audit_logger.store_web(
96 'repo_group.edit.permissions', action_data=action_data,
94 'repo_group.edit.permissions', action_data=action_data,
97 user=c.rhodecode_user)
95 user=c.rhodecode_user)
98
96
99 Session().commit()
97 Session().commit()
100 h.flash(_('Repository Group permissions updated'), category='success')
98 h.flash(_('Repository Group permissions updated'), category='success')
101
99 PermissionModel().flush_user_permission_caches(changes)
102 affected_user_ids = []
103 for change in changes['added'] + changes['updated'] + changes['deleted']:
104 if change['type'] == 'user':
105 affected_user_ids.append(change['id'])
106 if change['type'] == 'user_group':
107 user_group = UserGroup.get(safe_int(change['id']))
108 if user_group:
109 group_members_ids = [x.user_id for x in user_group.members]
110 affected_user_ids.extend(group_members_ids)
111
112 events.trigger(events.UserPermissionsChange(affected_user_ids))
113
100
114 raise HTTPFound(
101 raise HTTPFound(
115 h.route_path('edit_repo_group_perms',
102 h.route_path('edit_repo_group_perms',
116 repo_group_name=self.db_repo_group_name))
103 repo_group_name=self.db_repo_group_name))
@@ -1,193 +1,194 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 deform
22 import deform
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode import events
27 from rhodecode import events
28 from rhodecode.apps._base import RepoGroupAppView
28 from rhodecode.apps._base import RepoGroupAppView
29 from rhodecode.forms import RcForm
29 from rhodecode.forms import RcForm
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasPermissionAll,
33 LoginRequired, HasPermissionAll,
34 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
34 HasRepoGroupPermissionAny, HasRepoGroupPermissionAnyDecorator, CSRFRequired)
35 from rhodecode.model.db import Session, RepoGroup, User
35 from rhodecode.model.db import Session, RepoGroup, User
36 from rhodecode.model.permission import PermissionModel
36 from rhodecode.model.scm import RepoGroupList
37 from rhodecode.model.scm import RepoGroupList
37 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.validation_schema.schemas import repo_group_schema
39 from rhodecode.model.validation_schema.schemas import repo_group_schema
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 class RepoGroupSettingsView(RepoGroupAppView):
44 class RepoGroupSettingsView(RepoGroupAppView):
44 def load_default_context(self):
45 def load_default_context(self):
45 c = self._get_local_tmpl_context()
46 c = self._get_local_tmpl_context()
46 c.repo_group = self.db_repo_group
47 c.repo_group = self.db_repo_group
47 no_parrent = not c.repo_group.parent_group
48 no_parrent = not c.repo_group.parent_group
48 can_create_in_root = self._can_create_repo_group()
49 can_create_in_root = self._can_create_repo_group()
49
50
50 show_root_location = False
51 show_root_location = False
51 if no_parrent or can_create_in_root:
52 if no_parrent or can_create_in_root:
52 # we're global admin, we're ok and we can create TOP level groups
53 # we're global admin, we're ok and we can create TOP level groups
53 # or in case this group is already at top-level we also allow
54 # or in case this group is already at top-level we also allow
54 # creation in root
55 # creation in root
55 show_root_location = True
56 show_root_location = True
56
57
57 acl_groups = RepoGroupList(
58 acl_groups = RepoGroupList(
58 RepoGroup.query().all(),
59 RepoGroup.query().all(),
59 perm_set=['group.admin'])
60 perm_set=['group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(
61 c.repo_groups = RepoGroup.groups_choices(
61 groups=acl_groups,
62 groups=acl_groups,
62 show_empty_group=show_root_location)
63 show_empty_group=show_root_location)
63 # filter out current repo group
64 # filter out current repo group
64 exclude_group_ids = [c.repo_group.group_id]
65 exclude_group_ids = [c.repo_group.group_id]
65 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
66 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
66 c.repo_groups)
67 c.repo_groups)
67 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
68 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
68
69
69 parent_group = c.repo_group.parent_group
70 parent_group = c.repo_group.parent_group
70
71
71 add_parent_group = (parent_group and (
72 add_parent_group = (parent_group and (
72 parent_group.group_id not in c.repo_groups_choices))
73 parent_group.group_id not in c.repo_groups_choices))
73 if add_parent_group:
74 if add_parent_group:
74 c.repo_groups_choices.append(parent_group.group_id)
75 c.repo_groups_choices.append(parent_group.group_id)
75 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
76 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
76 return c
77 return c
77
78
78 def _can_create_repo_group(self, parent_group_id=None):
79 def _can_create_repo_group(self, parent_group_id=None):
79 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 is_admin = HasPermissionAll('hg.admin')('group create controller')
80 create_repo_group = HasPermissionAll(
81 create_repo_group = HasPermissionAll(
81 'hg.repogroup.create.true')('group create controller')
82 'hg.repogroup.create.true')('group create controller')
82 if is_admin or (create_repo_group and not parent_group_id):
83 if is_admin or (create_repo_group and not parent_group_id):
83 # we're global admin, or we have global repo group create
84 # we're global admin, or we have global repo group create
84 # permission
85 # permission
85 # we're ok and we can create TOP level groups
86 # we're ok and we can create TOP level groups
86 return True
87 return True
87 elif parent_group_id:
88 elif parent_group_id:
88 # we check the permission if we can write to parent group
89 # we check the permission if we can write to parent group
89 group = RepoGroup.get(parent_group_id)
90 group = RepoGroup.get(parent_group_id)
90 group_name = group.group_name if group else None
91 group_name = group.group_name if group else None
91 if HasRepoGroupPermissionAny('group.admin')(
92 if HasRepoGroupPermissionAny('group.admin')(
92 group_name, 'check if user is an admin of group'):
93 group_name, 'check if user is an admin of group'):
93 # we're an admin of passed in group, we're ok.
94 # we're an admin of passed in group, we're ok.
94 return True
95 return True
95 else:
96 else:
96 return False
97 return False
97 return False
98 return False
98
99
99 def _get_schema(self, c, old_values=None):
100 def _get_schema(self, c, old_values=None):
100 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 return repo_group_schema.RepoGroupSettingsSchema().bind(
101 repo_group_repo_group_options=c.repo_groups_choices,
102 repo_group_repo_group_options=c.repo_groups_choices,
102 repo_group_repo_group_items=c.repo_groups,
103 repo_group_repo_group_items=c.repo_groups,
103
104
104 # user caller
105 # user caller
105 user=self._rhodecode_user,
106 user=self._rhodecode_user,
106 old_values=old_values
107 old_values=old_values
107 )
108 )
108
109
109 @LoginRequired()
110 @LoginRequired()
110 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 @HasRepoGroupPermissionAnyDecorator('group.admin')
111 @view_config(
112 @view_config(
112 route_name='edit_repo_group', request_method='GET',
113 route_name='edit_repo_group', request_method='GET',
113 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
114 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
114 def edit_settings(self):
115 def edit_settings(self):
115 c = self.load_default_context()
116 c = self.load_default_context()
116 c.active = 'settings'
117 c.active = 'settings'
117
118
118 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
119 defaults = RepoGroupModel()._get_defaults(self.db_repo_group_name)
119 defaults['repo_group_owner'] = defaults['user']
120 defaults['repo_group_owner'] = defaults['user']
120
121
121 schema = self._get_schema(c)
122 schema = self._get_schema(c)
122 c.form = RcForm(schema, appstruct=defaults)
123 c.form = RcForm(schema, appstruct=defaults)
123 return self._get_template_context(c)
124 return self._get_template_context(c)
124
125
125 @LoginRequired()
126 @LoginRequired()
126 @HasRepoGroupPermissionAnyDecorator('group.admin')
127 @HasRepoGroupPermissionAnyDecorator('group.admin')
127 @CSRFRequired()
128 @CSRFRequired()
128 @view_config(
129 @view_config(
129 route_name='edit_repo_group', request_method='POST',
130 route_name='edit_repo_group', request_method='POST',
130 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
131 renderer='rhodecode:templates/admin/repo_groups/repo_group_edit.mako')
131 def edit_settings_update(self):
132 def edit_settings_update(self):
132 _ = self.request.translate
133 _ = self.request.translate
133 c = self.load_default_context()
134 c = self.load_default_context()
134 c.active = 'settings'
135 c.active = 'settings'
135
136
136 old_repo_group_name = self.db_repo_group_name
137 old_repo_group_name = self.db_repo_group_name
137 new_repo_group_name = old_repo_group_name
138 new_repo_group_name = old_repo_group_name
138
139
139 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
140 old_values = RepoGroupModel()._get_defaults(self.db_repo_group_name)
140 schema = self._get_schema(c, old_values=old_values)
141 schema = self._get_schema(c, old_values=old_values)
141
142
142 c.form = RcForm(schema)
143 c.form = RcForm(schema)
143 pstruct = self.request.POST.items()
144 pstruct = self.request.POST.items()
144
145
145 try:
146 try:
146 schema_data = c.form.validate(pstruct)
147 schema_data = c.form.validate(pstruct)
147 except deform.ValidationFailure as err_form:
148 except deform.ValidationFailure as err_form:
148 return self._get_template_context(c)
149 return self._get_template_context(c)
149
150
150 # data is now VALID, proceed with updates
151 # data is now VALID, proceed with updates
151 # save validated data back into the updates dict
152 # save validated data back into the updates dict
152 validated_updates = dict(
153 validated_updates = dict(
153 group_name=schema_data['repo_group']['repo_group_name_without_group'],
154 group_name=schema_data['repo_group']['repo_group_name_without_group'],
154 group_parent_id=schema_data['repo_group']['repo_group_id'],
155 group_parent_id=schema_data['repo_group']['repo_group_id'],
155 user=schema_data['repo_group_owner'],
156 user=schema_data['repo_group_owner'],
156 group_description=schema_data['repo_group_description'],
157 group_description=schema_data['repo_group_description'],
157 enable_locking=schema_data['repo_group_enable_locking'],
158 enable_locking=schema_data['repo_group_enable_locking'],
158 )
159 )
159
160
160 try:
161 try:
161 RepoGroupModel().update(self.db_repo_group, validated_updates)
162 RepoGroupModel().update(self.db_repo_group, validated_updates)
162
163
163 audit_logger.store_web(
164 audit_logger.store_web(
164 'repo_group.edit', action_data={'old_data': old_values},
165 'repo_group.edit', action_data={'old_data': old_values},
165 user=c.rhodecode_user)
166 user=c.rhodecode_user)
166
167
167 Session().commit()
168 Session().commit()
168
169
169 # use the new full name for redirect once we know we updated
170 # use the new full name for redirect once we know we updated
170 # the name on filesystem and in DB
171 # the name on filesystem and in DB
171 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
172 new_repo_group_name = schema_data['repo_group']['repo_group_name_with_group']
172
173
173 h.flash(_('Repository Group `{}` updated successfully').format(
174 h.flash(_('Repository Group `{}` updated successfully').format(
174 old_repo_group_name), category='success')
175 old_repo_group_name), category='success')
175
176
176 except Exception:
177 except Exception:
177 log.exception("Exception during update or repository group")
178 log.exception("Exception during update or repository group")
178 h.flash(_('Error occurred during update of repository group %s')
179 h.flash(_('Error occurred during update of repository group %s')
179 % old_repo_group_name, category='error')
180 % old_repo_group_name, category='error')
180
181
181 name_changed = old_repo_group_name != new_repo_group_name
182 name_changed = old_repo_group_name != new_repo_group_name
182 if name_changed:
183 if name_changed:
183 current_perms = self.db_repo_group.permissions(expand_from_user_groups=True)
184 current_perms = self.db_repo_group.permissions(expand_from_user_groups=True)
184 affected_user_ids = [perm['user_id'] for perm in current_perms]
185 affected_user_ids = [perm['user_id'] for perm in current_perms]
185
186
186 # NOTE(marcink): also add owner maybe it has changed
187 # NOTE(marcink): also add owner maybe it has changed
187 owner = User.get_by_username(schema_data['repo_group_owner'])
188 owner = User.get_by_username(schema_data['repo_group_owner'])
188 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
189 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
189 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
190 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
190 events.trigger(events.UserPermissionsChange(affected_user_ids))
191 PermissionModel().trigger_permission_flush(affected_user_ids)
191
192
192 raise HTTPFound(
193 raise HTTPFound(
193 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
194 h.route_path('edit_repo_group', repo_group_name=new_repo_group_name))
@@ -1,125 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
25
25
26 from rhodecode import events
26 from rhodecode import events
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
29 from rhodecode.lib.auth import (NotAnonymous, HasRepoPermissionAny)
30 from rhodecode.model.db import Repository
30 from rhodecode.model.db import Repository
31 from rhodecode.model.permission import PermissionModel
31 from rhodecode.model.validation_schema.types import RepoNameType
32 from rhodecode.model.validation_schema.types import RepoNameType
32
33
33 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
34
35
35
36
36 class RepoChecksView(BaseAppView):
37 class RepoChecksView(BaseAppView):
37 def load_default_context(self):
38 def load_default_context(self):
38 c = self._get_local_tmpl_context()
39 c = self._get_local_tmpl_context()
39
40
40 return c
41 return c
41
42
42 @NotAnonymous()
43 @NotAnonymous()
43 @view_config(
44 @view_config(
44 route_name='repo_creating', request_method='GET',
45 route_name='repo_creating', request_method='GET',
45 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
46 renderer='rhodecode:templates/admin/repos/repo_creating.mako')
46 def repo_creating(self):
47 def repo_creating(self):
47 c = self.load_default_context()
48 c = self.load_default_context()
48 repo_name = self.request.matchdict['repo_name']
49 repo_name = self.request.matchdict['repo_name']
49 repo_name = RepoNameType().deserialize(None, repo_name)
50 repo_name = RepoNameType().deserialize(None, repo_name)
50 db_repo = Repository.get_by_repo_name(repo_name)
51 db_repo = Repository.get_by_repo_name(repo_name)
51
52
52 # check if maybe repo is already created
53 # check if maybe repo is already created
53 if db_repo and db_repo.repo_state in [Repository.STATE_CREATED]:
54 if db_repo and db_repo.repo_state in [Repository.STATE_CREATED]:
54 self.flush_permissions_on_creation(db_repo)
55 self.flush_permissions_on_creation(db_repo)
55
56
56 # re-check permissions before redirecting to prevent resource
57 # re-check permissions before redirecting to prevent resource
57 # discovery by checking the 302 code
58 # discovery by checking the 302 code
58 perm_set = ['repository.read', 'repository.write', 'repository.admin']
59 perm_set = ['repository.read', 'repository.write', 'repository.admin']
59 has_perm = HasRepoPermissionAny(*perm_set)(
60 has_perm = HasRepoPermissionAny(*perm_set)(
60 db_repo.repo_name, 'Repo Creating check')
61 db_repo.repo_name, 'Repo Creating check')
61 if not has_perm:
62 if not has_perm:
62 raise HTTPNotFound()
63 raise HTTPNotFound()
63
64
64 raise HTTPFound(h.route_path(
65 raise HTTPFound(h.route_path(
65 'repo_summary', repo_name=db_repo.repo_name))
66 'repo_summary', repo_name=db_repo.repo_name))
66
67
67 c.task_id = self.request.GET.get('task_id')
68 c.task_id = self.request.GET.get('task_id')
68 c.repo_name = repo_name
69 c.repo_name = repo_name
69
70
70 return self._get_template_context(c)
71 return self._get_template_context(c)
71
72
72 @NotAnonymous()
73 @NotAnonymous()
73 @view_config(
74 @view_config(
74 route_name='repo_creating_check', request_method='GET',
75 route_name='repo_creating_check', request_method='GET',
75 renderer='json_ext')
76 renderer='json_ext')
76 def repo_creating_check(self):
77 def repo_creating_check(self):
77 _ = self.request.translate
78 _ = self.request.translate
78 task_id = self.request.GET.get('task_id')
79 task_id = self.request.GET.get('task_id')
79 self.load_default_context()
80 self.load_default_context()
80
81
81 repo_name = self.request.matchdict['repo_name']
82 repo_name = self.request.matchdict['repo_name']
82
83
83 if task_id and task_id not in ['None']:
84 if task_id and task_id not in ['None']:
84 import rhodecode
85 import rhodecode
85 from rhodecode.lib.celerylib.loader import celery_app, exceptions
86 from rhodecode.lib.celerylib.loader import celery_app, exceptions
86 if rhodecode.CELERY_ENABLED:
87 if rhodecode.CELERY_ENABLED:
87 log.debug('celery: checking result for task:%s', task_id)
88 log.debug('celery: checking result for task:%s', task_id)
88 task = celery_app.AsyncResult(task_id)
89 task = celery_app.AsyncResult(task_id)
89 try:
90 try:
90 task.get(timeout=10)
91 task.get(timeout=10)
91 except exceptions.TimeoutError:
92 except exceptions.TimeoutError:
92 task = None
93 task = None
93 if task and task.failed():
94 if task and task.failed():
94 msg = self._log_creation_exception(task.result, repo_name)
95 msg = self._log_creation_exception(task.result, repo_name)
95 h.flash(msg, category='error')
96 h.flash(msg, category='error')
96 raise HTTPFound(h.route_path('home'), code=501)
97 raise HTTPFound(h.route_path('home'), code=501)
97
98
98 db_repo = Repository.get_by_repo_name(repo_name)
99 db_repo = Repository.get_by_repo_name(repo_name)
99 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
100 if db_repo and db_repo.repo_state == Repository.STATE_CREATED:
100 if db_repo.clone_uri:
101 if db_repo.clone_uri:
101 clone_uri = db_repo.clone_uri_hidden
102 clone_uri = db_repo.clone_uri_hidden
102 h.flash(_('Created repository %s from %s')
103 h.flash(_('Created repository %s from %s')
103 % (db_repo.repo_name, clone_uri), category='success')
104 % (db_repo.repo_name, clone_uri), category='success')
104 else:
105 else:
105 repo_url = h.link_to(
106 repo_url = h.link_to(
106 db_repo.repo_name,
107 db_repo.repo_name,
107 h.route_path('repo_summary', repo_name=db_repo.repo_name))
108 h.route_path('repo_summary', repo_name=db_repo.repo_name))
108 fork = db_repo.fork
109 fork = db_repo.fork
109 if fork:
110 if fork:
110 fork_name = fork.repo_name
111 fork_name = fork.repo_name
111 h.flash(h.literal(_('Forked repository %s as %s')
112 h.flash(h.literal(_('Forked repository %s as %s')
112 % (fork_name, repo_url)), category='success')
113 % (fork_name, repo_url)), category='success')
113 else:
114 else:
114 h.flash(h.literal(_('Created repository %s') % repo_url),
115 h.flash(h.literal(_('Created repository %s') % repo_url),
115 category='success')
116 category='success')
116 self.flush_permissions_on_creation(db_repo)
117 self.flush_permissions_on_creation(db_repo)
117
118
118 return {'result': True}
119 return {'result': True}
119 return {'result': False}
120 return {'result': False}
120
121
121 def flush_permissions_on_creation(self, db_repo):
122 def flush_permissions_on_creation(self, db_repo):
122 # repo is finished and created, we flush the permissions now
123 # repo is finished and created, we flush the permissions now
123 user_group_perms = db_repo.permissions(expand_from_user_groups=True)
124 user_group_perms = db_repo.permissions(expand_from_user_groups=True)
124 affected_user_ids = [perm['user_id'] for perm in user_group_perms]
125 affected_user_ids = [perm['user_id'] for perm in user_group_perms]
125 events.trigger(events.UserPermissionsChange(affected_user_ids))
126 PermissionModel().trigger_permission_flush(affected_user_ids)
@@ -1,266 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 from pyramid.view import view_config
27 from pyramid.view import view_config
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.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.forms import RepoForkForm
41 from rhodecode.model.forms import RepoForkForm
41 from rhodecode.model.scm import ScmModel, RepoGroupList
42 from rhodecode.model.scm import ScmModel, RepoGroupList
42 from rhodecode.lib.utils2 import safe_int, safe_unicode
43 from rhodecode.lib.utils2 import safe_int, safe_unicode
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class RepoForksView(RepoAppView, DataGridAppView):
48 class RepoForksView(RepoAppView, DataGridAppView):
48
49
49 def load_default_context(self):
50 def load_default_context(self):
50 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52
53
53 acl_groups = RepoGroupList(
54 acl_groups = RepoGroupList(
54 RepoGroup.query().all(),
55 RepoGroup.query().all(),
55 perm_set=['group.write', 'group.admin'])
56 perm_set=['group.write', 'group.admin'])
56 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 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)
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
59 self.request.translate)
60 self.request.translate)
60 c.landing_revs_choices = choices
61 c.landing_revs_choices = choices
61 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62
63
63 return c
64 return c
64
65
65 @LoginRequired()
66 @LoginRequired()
66 @HasRepoPermissionAnyDecorator(
67 @HasRepoPermissionAnyDecorator(
67 'repository.read', 'repository.write', 'repository.admin')
68 'repository.read', 'repository.write', 'repository.admin')
68 @view_config(
69 @view_config(
69 route_name='repo_forks_show_all', request_method='GET',
70 route_name='repo_forks_show_all', request_method='GET',
70 renderer='rhodecode:templates/forks/forks.mako')
71 renderer='rhodecode:templates/forks/forks.mako')
71 def repo_forks_show_all(self):
72 def repo_forks_show_all(self):
72 c = self.load_default_context()
73 c = self.load_default_context()
73 return self._get_template_context(c)
74 return self._get_template_context(c)
74
75
75 @LoginRequired()
76 @LoginRequired()
76 @HasRepoPermissionAnyDecorator(
77 @HasRepoPermissionAnyDecorator(
77 'repository.read', 'repository.write', 'repository.admin')
78 'repository.read', 'repository.write', 'repository.admin')
78 @view_config(
79 @view_config(
79 route_name='repo_forks_data', request_method='GET',
80 route_name='repo_forks_data', request_method='GET',
80 renderer='json_ext', xhr=True)
81 renderer='json_ext', xhr=True)
81 def repo_forks_data(self):
82 def repo_forks_data(self):
82 _ = self.request.translate
83 _ = self.request.translate
83 self.load_default_context()
84 self.load_default_context()
84 column_map = {
85 column_map = {
85 'fork_name': 'repo_name',
86 'fork_name': 'repo_name',
86 'fork_date': 'created_on',
87 'fork_date': 'created_on',
87 'last_activity': 'updated_on'
88 'last_activity': 'updated_on'
88 }
89 }
89 draw, start, limit = self._extract_chunk(self.request)
90 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
91 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
92 self.request, column_map=column_map)
92
93
93 acl_check = HasRepoPermissionAny(
94 acl_check = HasRepoPermissionAny(
94 'repository.read', 'repository.write', 'repository.admin')
95 'repository.read', 'repository.write', 'repository.admin')
95 repo_id = self.db_repo.repo_id
96 repo_id = self.db_repo.repo_id
96 allowed_ids = [-1]
97 allowed_ids = [-1]
97 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 if acl_check(f.repo_name, 'get forks check'):
99 if acl_check(f.repo_name, 'get forks check'):
99 allowed_ids.append(f.repo_id)
100 allowed_ids.append(f.repo_id)
100
101
101 forks_data_total_count = Repository.query()\
102 forks_data_total_count = Repository.query()\
102 .filter(Repository.fork_id == repo_id)\
103 .filter(Repository.fork_id == repo_id)\
103 .filter(Repository.repo_id.in_(allowed_ids))\
104 .filter(Repository.repo_id.in_(allowed_ids))\
104 .count()
105 .count()
105
106
106 # json generate
107 # json generate
107 base_q = Repository.query()\
108 base_q = Repository.query()\
108 .filter(Repository.fork_id == repo_id)\
109 .filter(Repository.fork_id == repo_id)\
109 .filter(Repository.repo_id.in_(allowed_ids))\
110 .filter(Repository.repo_id.in_(allowed_ids))\
110
111
111 if search_q:
112 if search_q:
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 base_q = base_q.filter(or_(
114 base_q = base_q.filter(or_(
114 Repository.repo_name.ilike(like_expression),
115 Repository.repo_name.ilike(like_expression),
115 Repository.description.ilike(like_expression),
116 Repository.description.ilike(like_expression),
116 ))
117 ))
117
118
118 forks_data_total_filtered_count = base_q.count()
119 forks_data_total_filtered_count = base_q.count()
119
120
120 sort_col = getattr(Repository, order_by, None)
121 sort_col = getattr(Repository, order_by, None)
121 if sort_col:
122 if sort_col:
122 if order_dir == 'asc':
123 if order_dir == 'asc':
123 # handle null values properly to order by NULL last
124 # handle null values properly to order by NULL last
124 if order_by in ['last_activity']:
125 if order_by in ['last_activity']:
125 sort_col = coalesce(sort_col, datetime.date.max)
126 sort_col = coalesce(sort_col, datetime.date.max)
126 sort_col = sort_col.asc()
127 sort_col = sort_col.asc()
127 else:
128 else:
128 # handle null values properly to order by NULL last
129 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
130 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.min)
131 sort_col = coalesce(sort_col, datetime.date.min)
131 sort_col = sort_col.desc()
132 sort_col = sort_col.desc()
132
133
133 base_q = base_q.order_by(sort_col)
134 base_q = base_q.order_by(sort_col)
134 base_q = base_q.offset(start).limit(limit)
135 base_q = base_q.offset(start).limit(limit)
135
136
136 fork_list = base_q.all()
137 fork_list = base_q.all()
137
138
138 def fork_actions(fork):
139 def fork_actions(fork):
139 url_link = h.route_path(
140 url_link = h.route_path(
140 'repo_compare',
141 'repo_compare',
141 repo_name=fork.repo_name,
142 repo_name=fork.repo_name,
142 source_ref_type=self.db_repo.landing_rev[0],
143 source_ref_type=self.db_repo.landing_rev[0],
143 source_ref=self.db_repo.landing_rev[1],
144 source_ref=self.db_repo.landing_rev[1],
144 target_ref_type=self.db_repo.landing_rev[0],
145 target_ref_type=self.db_repo.landing_rev[0],
145 target_ref=self.db_repo.landing_rev[1],
146 target_ref=self.db_repo.landing_rev[1],
146 _query=dict(merge=1, target_repo=f.repo_name))
147 _query=dict(merge=1, target_repo=f.repo_name))
147 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148
149
149 def fork_name(fork):
150 def fork_name(fork):
150 return h.link_to(fork.repo_name,
151 return h.link_to(fork.repo_name,
151 h.route_path('repo_summary', repo_name=fork.repo_name))
152 h.route_path('repo_summary', repo_name=fork.repo_name))
152
153
153 forks_data = []
154 forks_data = []
154 for fork in fork_list:
155 for fork in fork_list:
155 forks_data.append({
156 forks_data.append({
156 "username": h.gravatar_with_user(self.request, fork.user.username),
157 "username": h.gravatar_with_user(self.request, fork.user.username),
157 "fork_name": fork_name(fork),
158 "fork_name": fork_name(fork),
158 "description": fork.description_safe,
159 "description": fork.description_safe,
159 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 "last_activity": h.format_date(fork.updated_on),
161 "last_activity": h.format_date(fork.updated_on),
161 "action": fork_actions(fork),
162 "action": fork_actions(fork),
162 })
163 })
163
164
164 data = ({
165 data = ({
165 'draw': draw,
166 'draw': draw,
166 'data': forks_data,
167 'data': forks_data,
167 'recordsTotal': forks_data_total_count,
168 'recordsTotal': forks_data_total_count,
168 'recordsFiltered': forks_data_total_filtered_count,
169 'recordsFiltered': forks_data_total_filtered_count,
169 })
170 })
170
171
171 return data
172 return data
172
173
173 @LoginRequired()
174 @LoginRequired()
174 @NotAnonymous()
175 @NotAnonymous()
175 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
179 @view_config(
179 route_name='repo_fork_new', request_method='GET',
180 route_name='repo_fork_new', request_method='GET',
180 renderer='rhodecode:templates/forks/forks.mako')
181 renderer='rhodecode:templates/forks/forks.mako')
181 def repo_fork_new(self):
182 def repo_fork_new(self):
182 c = self.load_default_context()
183 c = self.load_default_context()
183
184
184 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 # alter the description to indicate a fork
186 # alter the description to indicate a fork
186 defaults['description'] = (
187 defaults['description'] = (
187 'fork of repository: %s \n%s' % (
188 'fork of repository: %s \n%s' % (
188 defaults['repo_name'], defaults['description']))
189 defaults['repo_name'], defaults['description']))
189 # add suffix to fork
190 # add suffix to fork
190 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191
192
192 data = render('rhodecode:templates/forks/fork.mako',
193 data = render('rhodecode:templates/forks/fork.mako',
193 self._get_template_context(c), self.request)
194 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
195 html = formencode.htmlfill.render(
195 data,
196 data,
196 defaults=defaults,
197 defaults=defaults,
197 encoding="UTF-8",
198 encoding="UTF-8",
198 force_defaults=False
199 force_defaults=False
199 )
200 )
200 return Response(html)
201 return Response(html)
201
202
202 @LoginRequired()
203 @LoginRequired()
203 @NotAnonymous()
204 @NotAnonymous()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 @HasRepoPermissionAnyDecorator(
206 @HasRepoPermissionAnyDecorator(
206 'repository.read', 'repository.write', 'repository.admin')
207 'repository.read', 'repository.write', 'repository.admin')
207 @CSRFRequired()
208 @CSRFRequired()
208 @view_config(
209 @view_config(
209 route_name='repo_fork_create', request_method='POST',
210 route_name='repo_fork_create', request_method='POST',
210 renderer='rhodecode:templates/forks/fork.mako')
211 renderer='rhodecode:templates/forks/fork.mako')
211 def repo_fork_create(self):
212 def repo_fork_create(self):
212 _ = self.request.translate
213 _ = self.request.translate
213 c = self.load_default_context()
214 c = self.load_default_context()
214
215
215 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
216 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
216 repo_groups=c.repo_groups_choices,
217 repo_groups=c.repo_groups_choices,
217 landing_revs=c.landing_revs_choices)()
218 landing_revs=c.landing_revs_choices)()
218 post_data = dict(self.request.POST)
219 post_data = dict(self.request.POST)
219
220
220 # forbid injecting other repo by forging a request
221 # forbid injecting other repo by forging a request
221 post_data['fork_parent_id'] = self.db_repo.repo_id
222 post_data['fork_parent_id'] = self.db_repo.repo_id
222
223
223 form_result = {}
224 form_result = {}
224 task_id = None
225 task_id = None
225 try:
226 try:
226 form_result = _form.to_python(post_data)
227 form_result = _form.to_python(post_data)
227 copy_permissions = form_result.get('copy_permissions')
228 copy_permissions = form_result.get('copy_permissions')
228 # create fork is done sometimes async on celery, db transaction
229 # create fork is done sometimes async on celery, db transaction
229 # management is handled there.
230 # management is handled there.
230 task = RepoModel().create_fork(
231 task = RepoModel().create_fork(
231 form_result, c.rhodecode_user.user_id)
232 form_result, c.rhodecode_user.user_id)
232
233
233 task_id = get_task_id(task)
234 task_id = get_task_id(task)
234 except formencode.Invalid as errors:
235 except formencode.Invalid as errors:
235 c.rhodecode_db_repo = self.db_repo
236 c.rhodecode_db_repo = self.db_repo
236
237
237 data = render('rhodecode:templates/forks/fork.mako',
238 data = render('rhodecode:templates/forks/fork.mako',
238 self._get_template_context(c), self.request)
239 self._get_template_context(c), self.request)
239 html = formencode.htmlfill.render(
240 html = formencode.htmlfill.render(
240 data,
241 data,
241 defaults=errors.value,
242 defaults=errors.value,
242 errors=errors.error_dict or {},
243 errors=errors.error_dict or {},
243 prefix_error=False,
244 prefix_error=False,
244 encoding="UTF-8",
245 encoding="UTF-8",
245 force_defaults=False
246 force_defaults=False
246 )
247 )
247 return Response(html)
248 return Response(html)
248 except Exception:
249 except Exception:
249 log.exception(
250 log.exception(
250 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 h.flash(msg, category='error')
253 h.flash(msg, category='error')
253 raise HTTPFound(h.route_path('home'))
254 raise HTTPFound(h.route_path('home'))
254
255
255 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256
257
257 affected_user_ids = [self._rhodecode_user.user_id]
258 affected_user_ids = [self._rhodecode_user.user_id]
258 if copy_permissions:
259 if copy_permissions:
259 # permission flush is done in repo creating
260 # permission flush is done in repo creating
260 pass
261 pass
261
262
262 events.trigger(events.UserPermissionsChange(affected_user_ids))
263 PermissionModel().trigger_permission_flush(affected_user_ids)
263
264
264 raise HTTPFound(
265 raise HTTPFound(
265 h.route_path('repo_creating', repo_name=repo_name,
266 h.route_path('repo_creating', repo_name=repo_name,
266 _query=dict(task_id=task_id)))
267 _query=dict(task_id=task_id)))
@@ -1,134 +1,122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode import events
27 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
29 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import UserGroup
34 from rhodecode.model.forms import RepoPermsForm
31 from rhodecode.model.forms import RepoPermsForm
35 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.permission import PermissionModel
36 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.repo import RepoModel
37
35
38 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
39
37
40
38
41 class RepoSettingsPermissionsView(RepoAppView):
39 class RepoSettingsPermissionsView(RepoAppView):
42
40
43 def load_default_context(self):
41 def load_default_context(self):
44 c = self._get_local_tmpl_context()
42 c = self._get_local_tmpl_context()
45 return c
43 return c
46
44
47 @LoginRequired()
45 @LoginRequired()
48 @HasRepoPermissionAnyDecorator('repository.admin')
46 @HasRepoPermissionAnyDecorator('repository.admin')
49 @view_config(
47 @view_config(
50 route_name='edit_repo_perms', request_method='GET',
48 route_name='edit_repo_perms', request_method='GET',
51 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
52 def edit_permissions(self):
50 def edit_permissions(self):
53 _ = self.request.translate
51 _ = self.request.translate
54 c = self.load_default_context()
52 c = self.load_default_context()
55 c.active = 'permissions'
53 c.active = 'permissions'
56 if self.request.GET.get('branch_permissions'):
54 if self.request.GET.get('branch_permissions'):
57 h.flash(_('Explicitly add user or user group with write+ '
55 h.flash(_('Explicitly add user or user group with write+ '
58 'permission to modify their branch permissions.'),
56 'permission to modify their branch permissions.'),
59 category='notice')
57 category='notice')
60 return self._get_template_context(c)
58 return self._get_template_context(c)
61
59
62 @LoginRequired()
60 @LoginRequired()
63 @HasRepoPermissionAnyDecorator('repository.admin')
61 @HasRepoPermissionAnyDecorator('repository.admin')
64 @CSRFRequired()
62 @CSRFRequired()
65 @view_config(
63 @view_config(
66 route_name='edit_repo_perms', request_method='POST',
64 route_name='edit_repo_perms', request_method='POST',
67 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
65 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
68 def edit_permissions_update(self):
66 def edit_permissions_update(self):
69 _ = self.request.translate
67 _ = self.request.translate
70 c = self.load_default_context()
68 c = self.load_default_context()
71 c.active = 'permissions'
69 c.active = 'permissions'
72 data = self.request.POST
70 data = self.request.POST
73 # store private flag outside of HTML to verify if we can modify
71 # store private flag outside of HTML to verify if we can modify
74 # default user permissions, prevents submission of FAKE post data
72 # default user permissions, prevents submission of FAKE post data
75 # into the form for private repos
73 # into the form for private repos
76 data['repo_private'] = self.db_repo.private
74 data['repo_private'] = self.db_repo.private
77 form = RepoPermsForm(self.request.translate)().to_python(data)
75 form = RepoPermsForm(self.request.translate)().to_python(data)
78 changes = RepoModel().update_permissions(
76 changes = RepoModel().update_permissions(
79 self.db_repo_name, form['perm_additions'], form['perm_updates'],
77 self.db_repo_name, form['perm_additions'], form['perm_updates'],
80 form['perm_deletions'])
78 form['perm_deletions'])
81
79
82 action_data = {
80 action_data = {
83 'added': changes['added'],
81 'added': changes['added'],
84 'updated': changes['updated'],
82 'updated': changes['updated'],
85 'deleted': changes['deleted'],
83 'deleted': changes['deleted'],
86 }
84 }
87 audit_logger.store_web(
85 audit_logger.store_web(
88 'repo.edit.permissions', action_data=action_data,
86 'repo.edit.permissions', action_data=action_data,
89 user=self._rhodecode_user, repo=self.db_repo)
87 user=self._rhodecode_user, repo=self.db_repo)
90
88
91 Session().commit()
89 Session().commit()
92 h.flash(_('Repository permissions updated'), category='success')
90 h.flash(_('Repository permissions updated'), category='success')
93
91
94 affected_user_ids = []
92 PermissionModel().flush_user_permission_caches(changes)
95 for change in changes['added'] + changes['updated'] + changes['deleted']:
96 if change['type'] == 'user':
97 affected_user_ids.append(change['id'])
98 if change['type'] == 'user_group':
99 user_group = UserGroup.get(safe_int(change['id']))
100 if user_group:
101 group_members_ids = [x.user_id for x in user_group.members]
102 affected_user_ids.extend(group_members_ids)
103
104 events.trigger(events.UserPermissionsChange(affected_user_ids))
105
93
106 raise HTTPFound(
94 raise HTTPFound(
107 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
95 h.route_path('edit_repo_perms', repo_name=self.db_repo_name))
108
96
109 @LoginRequired()
97 @LoginRequired()
110 @HasRepoPermissionAnyDecorator('repository.admin')
98 @HasRepoPermissionAnyDecorator('repository.admin')
111 @CSRFRequired()
99 @CSRFRequired()
112 @view_config(
100 @view_config(
113 route_name='edit_repo_perms_set_private', request_method='POST',
101 route_name='edit_repo_perms_set_private', request_method='POST',
114 renderer='json_ext')
102 renderer='json_ext')
115 def edit_permissions_set_private_repo(self):
103 def edit_permissions_set_private_repo(self):
116 _ = self.request.translate
104 _ = self.request.translate
117 self.load_default_context()
105 self.load_default_context()
118
106
119 try:
107 try:
120 RepoModel().update(
108 RepoModel().update(
121 self.db_repo, **{'repo_private': True, 'repo_name': self.db_repo_name})
109 self.db_repo, **{'repo_private': True, 'repo_name': self.db_repo_name})
122 Session().commit()
110 Session().commit()
123
111
124 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
112 h.flash(_('Repository `{}` private mode set successfully').format(self.db_repo_name),
125 category='success')
113 category='success')
126 except Exception:
114 except Exception:
127 log.exception("Exception during update of repository")
115 log.exception("Exception during update of repository")
128 h.flash(_('Error occurred during update of repository {}').format(
116 h.flash(_('Error occurred during update of repository {}').format(
129 self.db_repo_name), category='error')
117 self.db_repo_name), category='error')
130
118
131 return {
119 return {
132 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
120 'redirect_url': h.route_path('edit_repo_perms', repo_name=self.db_repo_name),
133 'private': True
121 'private': True
134 }
122 }
@@ -1,266 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 import deform
23 import deform
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode import events
27 from rhodecode import events
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.forms import RcForm
29 from rhodecode.forms import RcForm
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
33 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup, Repository, User
34 from rhodecode.model.db import RepositoryField, RepoGroup, Repository, User
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.model.permission import PermissionModel
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.validation_schema.schemas import repo_schema
39 from rhodecode.model.validation_schema.schemas import repo_schema
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 class RepoSettingsView(RepoAppView):
44 class RepoSettingsView(RepoAppView):
44
45
45 def load_default_context(self):
46 def load_default_context(self):
46 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
47
48
48 acl_groups = RepoGroupList(
49 acl_groups = RepoGroupList(
49 RepoGroup.query().all(),
50 RepoGroup.query().all(),
50 perm_set=['group.write', 'group.admin'])
51 perm_set=['group.write', 'group.admin'])
51 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
52 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
52 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
53 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
53
54
54 # in case someone no longer have a group.write access to a repository
55 # in case someone no longer have a group.write access to a repository
55 # pre fill the list with this entry, we don't care if this is the same
56 # pre fill the list with this entry, we don't care if this is the same
56 # but it will allow saving repo data properly.
57 # but it will allow saving repo data properly.
57 repo_group = self.db_repo.group
58 repo_group = self.db_repo.group
58 if repo_group and repo_group.group_id not in c.repo_groups_choices:
59 if repo_group and repo_group.group_id not in c.repo_groups_choices:
59 c.repo_groups_choices.append(repo_group.group_id)
60 c.repo_groups_choices.append(repo_group.group_id)
60 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
61 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
61
62
62 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
63 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
63 # we might be in missing requirement state, so we load things
64 # we might be in missing requirement state, so we load things
64 # without touching scm_instance()
65 # without touching scm_instance()
65 c.landing_revs_choices, c.landing_revs = \
66 c.landing_revs_choices, c.landing_revs = \
66 ScmModel().get_repo_landing_revs(self.request.translate)
67 ScmModel().get_repo_landing_revs(self.request.translate)
67 else:
68 else:
68 c.landing_revs_choices, c.landing_revs = \
69 c.landing_revs_choices, c.landing_revs = \
69 ScmModel().get_repo_landing_revs(
70 ScmModel().get_repo_landing_revs(
70 self.request.translate, self.db_repo)
71 self.request.translate, self.db_repo)
71
72
72 c.personal_repo_group = c.auth_user.personal_repo_group
73 c.personal_repo_group = c.auth_user.personal_repo_group
73 c.repo_fields = RepositoryField.query()\
74 c.repo_fields = RepositoryField.query()\
74 .filter(RepositoryField.repository == self.db_repo).all()
75 .filter(RepositoryField.repository == self.db_repo).all()
75 return c
76 return c
76
77
77 def _get_schema(self, c, old_values=None):
78 def _get_schema(self, c, old_values=None):
78 return repo_schema.RepoSettingsSchema().bind(
79 return repo_schema.RepoSettingsSchema().bind(
79 repo_type=self.db_repo.repo_type,
80 repo_type=self.db_repo.repo_type,
80 repo_type_options=[self.db_repo.repo_type],
81 repo_type_options=[self.db_repo.repo_type],
81 repo_ref_options=c.landing_revs_choices,
82 repo_ref_options=c.landing_revs_choices,
82 repo_ref_items=c.landing_revs,
83 repo_ref_items=c.landing_revs,
83 repo_repo_group_options=c.repo_groups_choices,
84 repo_repo_group_options=c.repo_groups_choices,
84 repo_repo_group_items=c.repo_groups,
85 repo_repo_group_items=c.repo_groups,
85 # user caller
86 # user caller
86 user=self._rhodecode_user,
87 user=self._rhodecode_user,
87 old_values=old_values
88 old_values=old_values
88 )
89 )
89
90
90 @LoginRequired()
91 @LoginRequired()
91 @HasRepoPermissionAnyDecorator('repository.admin')
92 @HasRepoPermissionAnyDecorator('repository.admin')
92 @view_config(
93 @view_config(
93 route_name='edit_repo', request_method='GET',
94 route_name='edit_repo', request_method='GET',
94 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
95 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
95 def edit_settings(self):
96 def edit_settings(self):
96 c = self.load_default_context()
97 c = self.load_default_context()
97 c.active = 'settings'
98 c.active = 'settings'
98
99
99 defaults = RepoModel()._get_defaults(self.db_repo_name)
100 defaults = RepoModel()._get_defaults(self.db_repo_name)
100 defaults['repo_owner'] = defaults['user']
101 defaults['repo_owner'] = defaults['user']
101 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
102 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
102
103
103 schema = self._get_schema(c)
104 schema = self._get_schema(c)
104 c.form = RcForm(schema, appstruct=defaults)
105 c.form = RcForm(schema, appstruct=defaults)
105 return self._get_template_context(c)
106 return self._get_template_context(c)
106
107
107 @LoginRequired()
108 @LoginRequired()
108 @HasRepoPermissionAnyDecorator('repository.admin')
109 @HasRepoPermissionAnyDecorator('repository.admin')
109 @CSRFRequired()
110 @CSRFRequired()
110 @view_config(
111 @view_config(
111 route_name='edit_repo', request_method='POST',
112 route_name='edit_repo', request_method='POST',
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 def edit_settings_update(self):
114 def edit_settings_update(self):
114 _ = self.request.translate
115 _ = self.request.translate
115 c = self.load_default_context()
116 c = self.load_default_context()
116 c.active = 'settings'
117 c.active = 'settings'
117 old_repo_name = self.db_repo_name
118 old_repo_name = self.db_repo_name
118
119
119 old_values = self.db_repo.get_api_data()
120 old_values = self.db_repo.get_api_data()
120 schema = self._get_schema(c, old_values=old_values)
121 schema = self._get_schema(c, old_values=old_values)
121
122
122 c.form = RcForm(schema)
123 c.form = RcForm(schema)
123 pstruct = self.request.POST.items()
124 pstruct = self.request.POST.items()
124 pstruct.append(('repo_type', self.db_repo.repo_type))
125 pstruct.append(('repo_type', self.db_repo.repo_type))
125 try:
126 try:
126 schema_data = c.form.validate(pstruct)
127 schema_data = c.form.validate(pstruct)
127 except deform.ValidationFailure as err_form:
128 except deform.ValidationFailure as err_form:
128 return self._get_template_context(c)
129 return self._get_template_context(c)
129
130
130 # data is now VALID, proceed with updates
131 # data is now VALID, proceed with updates
131 # save validated data back into the updates dict
132 # save validated data back into the updates dict
132 validated_updates = dict(
133 validated_updates = dict(
133 repo_name=schema_data['repo_group']['repo_name_without_group'],
134 repo_name=schema_data['repo_group']['repo_name_without_group'],
134 repo_group=schema_data['repo_group']['repo_group_id'],
135 repo_group=schema_data['repo_group']['repo_group_id'],
135
136
136 user=schema_data['repo_owner'],
137 user=schema_data['repo_owner'],
137 repo_description=schema_data['repo_description'],
138 repo_description=schema_data['repo_description'],
138 repo_private=schema_data['repo_private'],
139 repo_private=schema_data['repo_private'],
139 clone_uri=schema_data['repo_clone_uri'],
140 clone_uri=schema_data['repo_clone_uri'],
140 push_uri=schema_data['repo_push_uri'],
141 push_uri=schema_data['repo_push_uri'],
141 repo_landing_rev=schema_data['repo_landing_commit_ref'],
142 repo_landing_rev=schema_data['repo_landing_commit_ref'],
142 repo_enable_statistics=schema_data['repo_enable_statistics'],
143 repo_enable_statistics=schema_data['repo_enable_statistics'],
143 repo_enable_locking=schema_data['repo_enable_locking'],
144 repo_enable_locking=schema_data['repo_enable_locking'],
144 repo_enable_downloads=schema_data['repo_enable_downloads'],
145 repo_enable_downloads=schema_data['repo_enable_downloads'],
145 )
146 )
146 # detect if SYNC URI changed, if we get OLD means we keep old values
147 # detect if SYNC URI changed, if we get OLD means we keep old values
147 if schema_data['repo_clone_uri_change'] == 'OLD':
148 if schema_data['repo_clone_uri_change'] == 'OLD':
148 validated_updates['clone_uri'] = self.db_repo.clone_uri
149 validated_updates['clone_uri'] = self.db_repo.clone_uri
149
150
150 if schema_data['repo_push_uri_change'] == 'OLD':
151 if schema_data['repo_push_uri_change'] == 'OLD':
151 validated_updates['push_uri'] = self.db_repo.push_uri
152 validated_updates['push_uri'] = self.db_repo.push_uri
152
153
153 # use the new full name for redirect
154 # use the new full name for redirect
154 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155 new_repo_name = schema_data['repo_group']['repo_name_with_group']
155
156
156 # save extra fields into our validated data
157 # save extra fields into our validated data
157 for key, value in pstruct:
158 for key, value in pstruct:
158 if key.startswith(RepositoryField.PREFIX):
159 if key.startswith(RepositoryField.PREFIX):
159 validated_updates[key] = value
160 validated_updates[key] = value
160
161
161 try:
162 try:
162 RepoModel().update(self.db_repo, **validated_updates)
163 RepoModel().update(self.db_repo, **validated_updates)
163 ScmModel().mark_for_invalidation(new_repo_name)
164 ScmModel().mark_for_invalidation(new_repo_name)
164
165
165 audit_logger.store_web(
166 audit_logger.store_web(
166 'repo.edit', action_data={'old_data': old_values},
167 'repo.edit', action_data={'old_data': old_values},
167 user=self._rhodecode_user, repo=self.db_repo)
168 user=self._rhodecode_user, repo=self.db_repo)
168
169
169 Session().commit()
170 Session().commit()
170
171
171 h.flash(_('Repository `{}` updated successfully').format(old_repo_name),
172 h.flash(_('Repository `{}` updated successfully').format(old_repo_name),
172 category='success')
173 category='success')
173 except Exception:
174 except Exception:
174 log.exception("Exception during update of repository")
175 log.exception("Exception during update of repository")
175 h.flash(_('Error occurred during update of repository {}').format(
176 h.flash(_('Error occurred during update of repository {}').format(
176 old_repo_name), category='error')
177 old_repo_name), category='error')
177
178
178 name_changed = old_repo_name != new_repo_name
179 name_changed = old_repo_name != new_repo_name
179 if name_changed:
180 if name_changed:
180 current_perms = self.db_repo.permissions(expand_from_user_groups=True)
181 current_perms = self.db_repo.permissions(expand_from_user_groups=True)
181 affected_user_ids = [perm['user_id'] for perm in current_perms]
182 affected_user_ids = [perm['user_id'] for perm in current_perms]
182
183
183 # NOTE(marcink): also add owner maybe it has changed
184 # NOTE(marcink): also add owner maybe it has changed
184 owner = User.get_by_username(schema_data['repo_owner'])
185 owner = User.get_by_username(schema_data['repo_owner'])
185 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
186 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
186 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
187 affected_user_ids.extend([self._rhodecode_user.user_id, owner_id])
187 events.trigger(events.UserPermissionsChange(affected_user_ids))
188 PermissionModel().trigger_permission_flush(affected_user_ids)
188
189
189 raise HTTPFound(
190 raise HTTPFound(
190 h.route_path('edit_repo', repo_name=new_repo_name))
191 h.route_path('edit_repo', repo_name=new_repo_name))
191
192
192 @LoginRequired()
193 @LoginRequired()
193 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
194 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
194 @view_config(
195 @view_config(
195 route_name='repo_edit_toggle_locking', request_method='GET',
196 route_name='repo_edit_toggle_locking', request_method='GET',
196 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
197 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
197 def toggle_locking(self):
198 def toggle_locking(self):
198 """
199 """
199 Toggle locking of repository by simple GET call to url
200 Toggle locking of repository by simple GET call to url
200 """
201 """
201 _ = self.request.translate
202 _ = self.request.translate
202 repo = self.db_repo
203 repo = self.db_repo
203
204
204 try:
205 try:
205 if repo.enable_locking:
206 if repo.enable_locking:
206 if repo.locked[0]:
207 if repo.locked[0]:
207 Repository.unlock(repo)
208 Repository.unlock(repo)
208 action = _('Unlocked')
209 action = _('Unlocked')
209 else:
210 else:
210 Repository.lock(
211 Repository.lock(
211 repo, self._rhodecode_user.user_id,
212 repo, self._rhodecode_user.user_id,
212 lock_reason=Repository.LOCK_WEB)
213 lock_reason=Repository.LOCK_WEB)
213 action = _('Locked')
214 action = _('Locked')
214
215
215 h.flash(_('Repository has been %s') % action,
216 h.flash(_('Repository has been %s') % action,
216 category='success')
217 category='success')
217 except Exception:
218 except Exception:
218 log.exception("Exception during unlocking")
219 log.exception("Exception during unlocking")
219 h.flash(_('An error occurred during unlocking'),
220 h.flash(_('An error occurred during unlocking'),
220 category='error')
221 category='error')
221 raise HTTPFound(
222 raise HTTPFound(
222 h.route_path('repo_summary', repo_name=self.db_repo_name))
223 h.route_path('repo_summary', repo_name=self.db_repo_name))
223
224
224 @LoginRequired()
225 @LoginRequired()
225 @HasRepoPermissionAnyDecorator('repository.admin')
226 @HasRepoPermissionAnyDecorator('repository.admin')
226 @view_config(
227 @view_config(
227 route_name='edit_repo_statistics', request_method='GET',
228 route_name='edit_repo_statistics', request_method='GET',
228 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
229 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
229 def edit_statistics_form(self):
230 def edit_statistics_form(self):
230 c = self.load_default_context()
231 c = self.load_default_context()
231
232
232 if self.db_repo.stats:
233 if self.db_repo.stats:
233 # this is on what revision we ended up so we add +1 for count
234 # this is on what revision we ended up so we add +1 for count
234 last_rev = self.db_repo.stats.stat_on_revision + 1
235 last_rev = self.db_repo.stats.stat_on_revision + 1
235 else:
236 else:
236 last_rev = 0
237 last_rev = 0
237
238
238 c.active = 'statistics'
239 c.active = 'statistics'
239 c.stats_revision = last_rev
240 c.stats_revision = last_rev
240 c.repo_last_rev = self.rhodecode_vcs_repo.count()
241 c.repo_last_rev = self.rhodecode_vcs_repo.count()
241
242
242 if last_rev == 0 or c.repo_last_rev == 0:
243 if last_rev == 0 or c.repo_last_rev == 0:
243 c.stats_percentage = 0
244 c.stats_percentage = 0
244 else:
245 else:
245 c.stats_percentage = '%.2f' % (
246 c.stats_percentage = '%.2f' % (
246 (float((last_rev)) / c.repo_last_rev) * 100)
247 (float((last_rev)) / c.repo_last_rev) * 100)
247 return self._get_template_context(c)
248 return self._get_template_context(c)
248
249
249 @LoginRequired()
250 @LoginRequired()
250 @HasRepoPermissionAnyDecorator('repository.admin')
251 @HasRepoPermissionAnyDecorator('repository.admin')
251 @CSRFRequired()
252 @CSRFRequired()
252 @view_config(
253 @view_config(
253 route_name='edit_repo_statistics_reset', request_method='POST',
254 route_name='edit_repo_statistics_reset', request_method='POST',
254 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
255 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
255 def repo_statistics_reset(self):
256 def repo_statistics_reset(self):
256 _ = self.request.translate
257 _ = self.request.translate
257
258
258 try:
259 try:
259 RepoModel().delete_stats(self.db_repo_name)
260 RepoModel().delete_stats(self.db_repo_name)
260 Session().commit()
261 Session().commit()
261 except Exception:
262 except Exception:
262 log.exception('Edit statistics failure')
263 log.exception('Edit statistics failure')
263 h.flash(_('An error occurred during deletion of repository stats'),
264 h.flash(_('An error occurred during deletion of repository stats'),
264 category='error')
265 category='error')
265 raise HTTPFound(
266 raise HTTPFound(
266 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
267 h.route_path('edit_repo_statistics', repo_name=self.db_repo_name))
@@ -1,310 +1,311 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25
25
26 from rhodecode import events
26 from rhodecode import events
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib import audit_logger
29 from rhodecode.lib import audit_logger
30 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired,
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired,
32 HasRepoPermissionAny)
32 HasRepoPermissionAny)
33 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
33 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.vcs import RepositoryError
35 from rhodecode.lib.vcs import RepositoryError
36 from rhodecode.model.db import Session, UserFollowing, User, Repository
36 from rhodecode.model.db import Session, UserFollowing, User, Repository
37 from rhodecode.model.permission import PermissionModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 class RepoSettingsView(RepoAppView):
44 class RepoSettingsView(RepoAppView):
44
45
45 def load_default_context(self):
46 def load_default_context(self):
46 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
47 return c
48 return c
48
49
49 def _get_users_with_permissions(self):
50 def _get_users_with_permissions(self):
50 user_permissions = {}
51 user_permissions = {}
51 for perm in self.db_repo.permissions():
52 for perm in self.db_repo.permissions():
52 user_permissions[perm.user_id] = perm
53 user_permissions[perm.user_id] = perm
53
54
54 return user_permissions
55 return user_permissions
55
56
56 @LoginRequired()
57 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @HasRepoPermissionAnyDecorator('repository.admin')
58 @view_config(
59 @view_config(
59 route_name='edit_repo_advanced', request_method='GET',
60 route_name='edit_repo_advanced', request_method='GET',
60 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
61 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
61 def edit_advanced(self):
62 def edit_advanced(self):
62 c = self.load_default_context()
63 c = self.load_default_context()
63 c.active = 'advanced'
64 c.active = 'advanced'
64
65
65 c.default_user_id = User.get_default_user().user_id
66 c.default_user_id = User.get_default_user().user_id
66 c.in_public_journal = UserFollowing.query() \
67 c.in_public_journal = UserFollowing.query() \
67 .filter(UserFollowing.user_id == c.default_user_id) \
68 .filter(UserFollowing.user_id == c.default_user_id) \
68 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
69 .filter(UserFollowing.follows_repository == self.db_repo).scalar()
69
70
70 c.ver_info_dict = self.rhodecode_vcs_repo.get_hooks_info()
71 c.ver_info_dict = self.rhodecode_vcs_repo.get_hooks_info()
71
72
72 return self._get_template_context(c)
73 return self._get_template_context(c)
73
74
74 @LoginRequired()
75 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @HasRepoPermissionAnyDecorator('repository.admin')
76 @CSRFRequired()
77 @CSRFRequired()
77 @view_config(
78 @view_config(
78 route_name='edit_repo_advanced_archive', request_method='POST',
79 route_name='edit_repo_advanced_archive', request_method='POST',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def edit_advanced_archive(self):
81 def edit_advanced_archive(self):
81 """
82 """
82 Archives the repository. It will become read-only, and not visible in search
83 Archives the repository. It will become read-only, and not visible in search
83 or other queries. But still visible for super-admins.
84 or other queries. But still visible for super-admins.
84 """
85 """
85
86
86 _ = self.request.translate
87 _ = self.request.translate
87
88
88 try:
89 try:
89 old_data = self.db_repo.get_api_data()
90 old_data = self.db_repo.get_api_data()
90 RepoModel().archive(self.db_repo)
91 RepoModel().archive(self.db_repo)
91
92
92 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
93 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
93 audit_logger.store_web(
94 audit_logger.store_web(
94 'repo.archive', action_data={'old_data': old_data},
95 'repo.archive', action_data={'old_data': old_data},
95 user=self._rhodecode_user, repo=repo)
96 user=self._rhodecode_user, repo=repo)
96
97
97 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
98 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
98 h.flash(
99 h.flash(
99 _('Archived repository `%s`') % self.db_repo_name,
100 _('Archived repository `%s`') % self.db_repo_name,
100 category='success')
101 category='success')
101 Session().commit()
102 Session().commit()
102 except Exception:
103 except Exception:
103 log.exception("Exception during archiving of repository")
104 log.exception("Exception during archiving of repository")
104 h.flash(_('An error occurred during archiving of `%s`')
105 h.flash(_('An error occurred during archiving of `%s`')
105 % self.db_repo_name, category='error')
106 % self.db_repo_name, category='error')
106 # redirect to advanced for more deletion options
107 # redirect to advanced for more deletion options
107 raise HTTPFound(
108 raise HTTPFound(
108 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
109 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
109 _anchor='advanced-archive'))
110 _anchor='advanced-archive'))
110
111
111 # flush permissions for all users defined in permissions
112 # flush permissions for all users defined in permissions
112 affected_user_ids = self._get_users_with_permissions().keys()
113 affected_user_ids = self._get_users_with_permissions().keys()
113 events.trigger(events.UserPermissionsChange(affected_user_ids))
114 PermissionModel().trigger_permission_flush(affected_user_ids)
114
115
115 raise HTTPFound(h.route_path('home'))
116 raise HTTPFound(h.route_path('home'))
116
117
117 @LoginRequired()
118 @LoginRequired()
118 @HasRepoPermissionAnyDecorator('repository.admin')
119 @HasRepoPermissionAnyDecorator('repository.admin')
119 @CSRFRequired()
120 @CSRFRequired()
120 @view_config(
121 @view_config(
121 route_name='edit_repo_advanced_delete', request_method='POST',
122 route_name='edit_repo_advanced_delete', request_method='POST',
122 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
123 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
123 def edit_advanced_delete(self):
124 def edit_advanced_delete(self):
124 """
125 """
125 Deletes the repository, or shows warnings if deletion is not possible
126 Deletes the repository, or shows warnings if deletion is not possible
126 because of attached forks or other errors.
127 because of attached forks or other errors.
127 """
128 """
128 _ = self.request.translate
129 _ = self.request.translate
129 handle_forks = self.request.POST.get('forks', None)
130 handle_forks = self.request.POST.get('forks', None)
130 if handle_forks == 'detach_forks':
131 if handle_forks == 'detach_forks':
131 handle_forks = 'detach'
132 handle_forks = 'detach'
132 elif handle_forks == 'delete_forks':
133 elif handle_forks == 'delete_forks':
133 handle_forks = 'delete'
134 handle_forks = 'delete'
134
135
135 try:
136 try:
136 old_data = self.db_repo.get_api_data()
137 old_data = self.db_repo.get_api_data()
137 RepoModel().delete(self.db_repo, forks=handle_forks)
138 RepoModel().delete(self.db_repo, forks=handle_forks)
138
139
139 _forks = self.db_repo.forks.count()
140 _forks = self.db_repo.forks.count()
140 if _forks and handle_forks:
141 if _forks and handle_forks:
141 if handle_forks == 'detach_forks':
142 if handle_forks == 'detach_forks':
142 h.flash(_('Detached %s forks') % _forks, category='success')
143 h.flash(_('Detached %s forks') % _forks, category='success')
143 elif handle_forks == 'delete_forks':
144 elif handle_forks == 'delete_forks':
144 h.flash(_('Deleted %s forks') % _forks, category='success')
145 h.flash(_('Deleted %s forks') % _forks, category='success')
145
146
146 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
147 repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
147 audit_logger.store_web(
148 audit_logger.store_web(
148 'repo.delete', action_data={'old_data': old_data},
149 'repo.delete', action_data={'old_data': old_data},
149 user=self._rhodecode_user, repo=repo)
150 user=self._rhodecode_user, repo=repo)
150
151
151 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
152 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
152 h.flash(
153 h.flash(
153 _('Deleted repository `%s`') % self.db_repo_name,
154 _('Deleted repository `%s`') % self.db_repo_name,
154 category='success')
155 category='success')
155 Session().commit()
156 Session().commit()
156 except AttachedForksError:
157 except AttachedForksError:
157 repo_advanced_url = h.route_path(
158 repo_advanced_url = h.route_path(
158 'edit_repo_advanced', repo_name=self.db_repo_name,
159 'edit_repo_advanced', repo_name=self.db_repo_name,
159 _anchor='advanced-delete')
160 _anchor='advanced-delete')
160 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
161 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
161 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
162 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
162 'Try using {delete_or_detach} option.')
163 'Try using {delete_or_detach} option.')
163 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
164 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
164 category='warning')
165 category='warning')
165
166
166 # redirect to advanced for forks handle action ?
167 # redirect to advanced for forks handle action ?
167 raise HTTPFound(repo_advanced_url)
168 raise HTTPFound(repo_advanced_url)
168
169
169 except AttachedPullRequestsError:
170 except AttachedPullRequestsError:
170 repo_advanced_url = h.route_path(
171 repo_advanced_url = h.route_path(
171 'edit_repo_advanced', repo_name=self.db_repo_name,
172 'edit_repo_advanced', repo_name=self.db_repo_name,
172 _anchor='advanced-delete')
173 _anchor='advanced-delete')
173 attached_prs = len(self.db_repo.pull_requests_source +
174 attached_prs = len(self.db_repo.pull_requests_source +
174 self.db_repo.pull_requests_target)
175 self.db_repo.pull_requests_target)
175 h.flash(
176 h.flash(
176 _('Cannot delete `{repo}` it still contains {num} attached pull requests. '
177 _('Cannot delete `{repo}` it still contains {num} attached pull requests. '
177 'Consider archiving the repository instead.').format(
178 'Consider archiving the repository instead.').format(
178 repo=self.db_repo_name, num=attached_prs), category='warning')
179 repo=self.db_repo_name, num=attached_prs), category='warning')
179
180
180 # redirect to advanced for forks handle action ?
181 # redirect to advanced for forks handle action ?
181 raise HTTPFound(repo_advanced_url)
182 raise HTTPFound(repo_advanced_url)
182
183
183 except Exception:
184 except Exception:
184 log.exception("Exception during deletion of repository")
185 log.exception("Exception during deletion of repository")
185 h.flash(_('An error occurred during deletion of `%s`')
186 h.flash(_('An error occurred during deletion of `%s`')
186 % self.db_repo_name, category='error')
187 % self.db_repo_name, category='error')
187 # redirect to advanced for more deletion options
188 # redirect to advanced for more deletion options
188 raise HTTPFound(
189 raise HTTPFound(
189 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
190 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name,
190 _anchor='advanced-delete'))
191 _anchor='advanced-delete'))
191
192
192 raise HTTPFound(h.route_path('home'))
193 raise HTTPFound(h.route_path('home'))
193
194
194 @LoginRequired()
195 @LoginRequired()
195 @HasRepoPermissionAnyDecorator('repository.admin')
196 @HasRepoPermissionAnyDecorator('repository.admin')
196 @CSRFRequired()
197 @CSRFRequired()
197 @view_config(
198 @view_config(
198 route_name='edit_repo_advanced_journal', request_method='POST',
199 route_name='edit_repo_advanced_journal', request_method='POST',
199 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
200 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
200 def edit_advanced_journal(self):
201 def edit_advanced_journal(self):
201 """
202 """
202 Set's this repository to be visible in public journal,
203 Set's this repository to be visible in public journal,
203 in other words making default user to follow this repo
204 in other words making default user to follow this repo
204 """
205 """
205 _ = self.request.translate
206 _ = self.request.translate
206
207
207 try:
208 try:
208 user_id = User.get_default_user().user_id
209 user_id = User.get_default_user().user_id
209 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
210 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
210 h.flash(_('Updated repository visibility in public journal'),
211 h.flash(_('Updated repository visibility in public journal'),
211 category='success')
212 category='success')
212 Session().commit()
213 Session().commit()
213 except Exception:
214 except Exception:
214 h.flash(_('An error occurred during setting this '
215 h.flash(_('An error occurred during setting this '
215 'repository in public journal'),
216 'repository in public journal'),
216 category='error')
217 category='error')
217
218
218 raise HTTPFound(
219 raise HTTPFound(
219 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
220 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
220
221
221 @LoginRequired()
222 @LoginRequired()
222 @HasRepoPermissionAnyDecorator('repository.admin')
223 @HasRepoPermissionAnyDecorator('repository.admin')
223 @CSRFRequired()
224 @CSRFRequired()
224 @view_config(
225 @view_config(
225 route_name='edit_repo_advanced_fork', request_method='POST',
226 route_name='edit_repo_advanced_fork', request_method='POST',
226 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
227 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
227 def edit_advanced_fork(self):
228 def edit_advanced_fork(self):
228 """
229 """
229 Mark given repository as a fork of another
230 Mark given repository as a fork of another
230 """
231 """
231 _ = self.request.translate
232 _ = self.request.translate
232
233
233 new_fork_id = safe_int(self.request.POST.get('id_fork_of'))
234 new_fork_id = safe_int(self.request.POST.get('id_fork_of'))
234
235
235 # valid repo, re-check permissions
236 # valid repo, re-check permissions
236 if new_fork_id:
237 if new_fork_id:
237 repo = Repository.get(new_fork_id)
238 repo = Repository.get(new_fork_id)
238 # ensure we have at least read access to the repo we mark
239 # ensure we have at least read access to the repo we mark
239 perm_check = HasRepoPermissionAny(
240 perm_check = HasRepoPermissionAny(
240 'repository.read', 'repository.write', 'repository.admin')
241 'repository.read', 'repository.write', 'repository.admin')
241
242
242 if repo and perm_check(repo_name=repo.repo_name):
243 if repo and perm_check(repo_name=repo.repo_name):
243 new_fork_id = repo.repo_id
244 new_fork_id = repo.repo_id
244 else:
245 else:
245 new_fork_id = None
246 new_fork_id = None
246
247
247 try:
248 try:
248 repo = ScmModel().mark_as_fork(
249 repo = ScmModel().mark_as_fork(
249 self.db_repo_name, new_fork_id, self._rhodecode_user.user_id)
250 self.db_repo_name, new_fork_id, self._rhodecode_user.user_id)
250 fork = repo.fork.repo_name if repo.fork else _('Nothing')
251 fork = repo.fork.repo_name if repo.fork else _('Nothing')
251 Session().commit()
252 Session().commit()
252 h.flash(
253 h.flash(
253 _('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
254 _('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
254 category='success')
255 category='success')
255 except RepositoryError as e:
256 except RepositoryError as e:
256 log.exception("Repository Error occurred")
257 log.exception("Repository Error occurred")
257 h.flash(str(e), category='error')
258 h.flash(str(e), category='error')
258 except Exception:
259 except Exception:
259 log.exception("Exception while editing fork")
260 log.exception("Exception while editing fork")
260 h.flash(_('An error occurred during this operation'),
261 h.flash(_('An error occurred during this operation'),
261 category='error')
262 category='error')
262
263
263 raise HTTPFound(
264 raise HTTPFound(
264 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
265 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
265
266
266 @LoginRequired()
267 @LoginRequired()
267 @HasRepoPermissionAnyDecorator('repository.admin')
268 @HasRepoPermissionAnyDecorator('repository.admin')
268 @CSRFRequired()
269 @CSRFRequired()
269 @view_config(
270 @view_config(
270 route_name='edit_repo_advanced_locking', request_method='POST',
271 route_name='edit_repo_advanced_locking', request_method='POST',
271 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
272 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
272 def edit_advanced_locking(self):
273 def edit_advanced_locking(self):
273 """
274 """
274 Toggle locking of repository
275 Toggle locking of repository
275 """
276 """
276 _ = self.request.translate
277 _ = self.request.translate
277 set_lock = self.request.POST.get('set_lock')
278 set_lock = self.request.POST.get('set_lock')
278 set_unlock = self.request.POST.get('set_unlock')
279 set_unlock = self.request.POST.get('set_unlock')
279
280
280 try:
281 try:
281 if set_lock:
282 if set_lock:
282 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
283 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
283 lock_reason=Repository.LOCK_WEB)
284 lock_reason=Repository.LOCK_WEB)
284 h.flash(_('Locked repository'), category='success')
285 h.flash(_('Locked repository'), category='success')
285 elif set_unlock:
286 elif set_unlock:
286 Repository.unlock(self.db_repo)
287 Repository.unlock(self.db_repo)
287 h.flash(_('Unlocked repository'), category='success')
288 h.flash(_('Unlocked repository'), category='success')
288 except Exception as e:
289 except Exception as e:
289 log.exception("Exception during unlocking")
290 log.exception("Exception during unlocking")
290 h.flash(_('An error occurred during unlocking'), category='error')
291 h.flash(_('An error occurred during unlocking'), category='error')
291
292
292 raise HTTPFound(
293 raise HTTPFound(
293 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
294 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
294
295
295 @LoginRequired()
296 @LoginRequired()
296 @HasRepoPermissionAnyDecorator('repository.admin')
297 @HasRepoPermissionAnyDecorator('repository.admin')
297 @view_config(
298 @view_config(
298 route_name='edit_repo_advanced_hooks', request_method='GET',
299 route_name='edit_repo_advanced_hooks', request_method='GET',
299 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
300 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
300 def edit_advanced_install_hooks(self):
301 def edit_advanced_install_hooks(self):
301 """
302 """
302 Install Hooks for repository
303 Install Hooks for repository
303 """
304 """
304 _ = self.request.translate
305 _ = self.request.translate
305 self.load_default_context()
306 self.load_default_context()
306 self.rhodecode_vcs_repo.install_hooks(force=True)
307 self.rhodecode_vcs_repo.install_hooks(force=True)
307 h.flash(_('installed updated hooks into this repository'),
308 h.flash(_('installed updated hooks into this repository'),
308 category='success')
309 category='success')
309 raise HTTPFound(
310 raise HTTPFound(
310 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
311 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,550 +1,550 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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
22
23 import peppercorn
23 import peppercorn
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib.exceptions import (
32 from rhodecode.lib.exceptions import (
33 RepoGroupAssignmentError, UserGroupAssignedException)
33 RepoGroupAssignmentError, UserGroupAssignedException)
34 from rhodecode.model.forms import (
34 from rhodecode.model.forms import (
35 UserGroupPermsForm, UserGroupForm, UserIndividualPermissionsForm,
35 UserGroupPermsForm, UserGroupForm, UserIndividualPermissionsForm,
36 UserPermissionsForm)
36 UserPermissionsForm)
37 from rhodecode.model.permission import PermissionModel
37 from rhodecode.model.permission import PermissionModel
38
38
39 from rhodecode.apps._base import UserGroupAppView
39 from rhodecode.apps._base import UserGroupAppView
40 from rhodecode.lib.auth import (
40 from rhodecode.lib.auth import (
41 LoginRequired, HasUserGroupPermissionAnyDecorator, CSRFRequired)
41 LoginRequired, HasUserGroupPermissionAnyDecorator, CSRFRequired)
42 from rhodecode.lib import helpers as h, audit_logger
42 from rhodecode.lib import helpers as h, audit_logger
43 from rhodecode.lib.utils2 import str2bool, safe_int
43 from rhodecode.lib.utils2 import str2bool, safe_int
44 from rhodecode.model.db import User, UserGroup
44 from rhodecode.model.db import User, UserGroup
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.user_group import UserGroupModel
46 from rhodecode.model.user_group import UserGroupModel
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class UserGroupsView(UserGroupAppView):
51 class UserGroupsView(UserGroupAppView):
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55
55
56 PermissionModel().set_global_permission_choices(
56 PermissionModel().set_global_permission_choices(
57 c, gettext_translator=self.request.translate)
57 c, gettext_translator=self.request.translate)
58
58
59 return c
59 return c
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
62 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
63 @view_config(
63 @view_config(
64 route_name='user_group_members_data', request_method='GET',
64 route_name='user_group_members_data', request_method='GET',
65 renderer='json_ext', xhr=True)
65 renderer='json_ext', xhr=True)
66 def user_group_members(self):
66 def user_group_members(self):
67 """
67 """
68 Return members of given user group
68 Return members of given user group
69 """
69 """
70 self.load_default_context()
70 self.load_default_context()
71 user_group = self.db_user_group
71 user_group = self.db_user_group
72 group_members_obj = sorted((x.user for x in user_group.members),
72 group_members_obj = sorted((x.user for x in user_group.members),
73 key=lambda u: u.username.lower())
73 key=lambda u: u.username.lower())
74
74
75 group_members = [
75 group_members = [
76 {
76 {
77 'id': user.user_id,
77 'id': user.user_id,
78 'first_name': user.first_name,
78 'first_name': user.first_name,
79 'last_name': user.last_name,
79 'last_name': user.last_name,
80 'username': user.username,
80 'username': user.username,
81 'icon_link': h.gravatar_url(user.email, 30),
81 'icon_link': h.gravatar_url(user.email, 30),
82 'value_display': h.person(user.email),
82 'value_display': h.person(user.email),
83 'value': user.username,
83 'value': user.username,
84 'value_type': 'user',
84 'value_type': 'user',
85 'active': user.active,
85 'active': user.active,
86 }
86 }
87 for user in group_members_obj
87 for user in group_members_obj
88 ]
88 ]
89
89
90 return {
90 return {
91 'members': group_members
91 'members': group_members
92 }
92 }
93
93
94 @LoginRequired()
94 @LoginRequired()
95 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
95 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
96 @view_config(
96 @view_config(
97 route_name='edit_user_group_perms_summary', request_method='GET',
97 route_name='edit_user_group_perms_summary', request_method='GET',
98 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
98 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
99 def user_group_perms_summary(self):
99 def user_group_perms_summary(self):
100 c = self.load_default_context()
100 c = self.load_default_context()
101 c.user_group = self.db_user_group
101 c.user_group = self.db_user_group
102 c.active = 'perms_summary'
102 c.active = 'perms_summary'
103 c.permissions = UserGroupModel().get_perms_summary(
103 c.permissions = UserGroupModel().get_perms_summary(
104 c.user_group.users_group_id)
104 c.user_group.users_group_id)
105 return self._get_template_context(c)
105 return self._get_template_context(c)
106
106
107 @LoginRequired()
107 @LoginRequired()
108 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
108 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
109 @view_config(
109 @view_config(
110 route_name='edit_user_group_perms_summary_json', request_method='GET',
110 route_name='edit_user_group_perms_summary_json', request_method='GET',
111 renderer='json_ext')
111 renderer='json_ext')
112 def user_group_perms_summary_json(self):
112 def user_group_perms_summary_json(self):
113 self.load_default_context()
113 self.load_default_context()
114 user_group = self.db_user_group
114 user_group = self.db_user_group
115 return UserGroupModel().get_perms_summary(user_group.users_group_id)
115 return UserGroupModel().get_perms_summary(user_group.users_group_id)
116
116
117 def _revoke_perms_on_yourself(self, form_result):
117 def _revoke_perms_on_yourself(self, form_result):
118 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
118 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
119 form_result['perm_updates'])
119 form_result['perm_updates'])
120 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
120 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
121 form_result['perm_additions'])
121 form_result['perm_additions'])
122 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
122 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
123 form_result['perm_deletions'])
123 form_result['perm_deletions'])
124 admin_perm = 'usergroup.admin'
124 admin_perm = 'usergroup.admin'
125 if _updates and _updates[0][1] != admin_perm or \
125 if _updates and _updates[0][1] != admin_perm or \
126 _additions and _additions[0][1] != admin_perm or \
126 _additions and _additions[0][1] != admin_perm or \
127 _deletions and _deletions[0][1] != admin_perm:
127 _deletions and _deletions[0][1] != admin_perm:
128 return True
128 return True
129 return False
129 return False
130
130
131 @LoginRequired()
131 @LoginRequired()
132 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
132 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
133 @CSRFRequired()
133 @CSRFRequired()
134 @view_config(
134 @view_config(
135 route_name='user_groups_update', request_method='POST',
135 route_name='user_groups_update', request_method='POST',
136 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
136 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
137 def user_group_update(self):
137 def user_group_update(self):
138 _ = self.request.translate
138 _ = self.request.translate
139
139
140 user_group = self.db_user_group
140 user_group = self.db_user_group
141 user_group_id = user_group.users_group_id
141 user_group_id = user_group.users_group_id
142
142
143 old_user_group_name = self.db_user_group_name
143 old_user_group_name = self.db_user_group_name
144 new_user_group_name = old_user_group_name
144 new_user_group_name = old_user_group_name
145
145
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.user_group = user_group
147 c.user_group = user_group
148 c.group_members_obj = [x.user for x in c.user_group.members]
148 c.group_members_obj = [x.user for x in c.user_group.members]
149 c.group_members_obj.sort(key=lambda u: u.username.lower())
149 c.group_members_obj.sort(key=lambda u: u.username.lower())
150 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
150 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
151 c.active = 'settings'
151 c.active = 'settings'
152
152
153 users_group_form = UserGroupForm(
153 users_group_form = UserGroupForm(
154 self.request.translate, edit=True,
154 self.request.translate, edit=True,
155 old_data=c.user_group.get_dict(), allow_disabled=True)()
155 old_data=c.user_group.get_dict(), allow_disabled=True)()
156
156
157 old_values = c.user_group.get_api_data()
157 old_values = c.user_group.get_api_data()
158
158
159 try:
159 try:
160 form_result = users_group_form.to_python(self.request.POST)
160 form_result = users_group_form.to_python(self.request.POST)
161 pstruct = peppercorn.parse(self.request.POST.items())
161 pstruct = peppercorn.parse(self.request.POST.items())
162 form_result['users_group_members'] = pstruct['user_group_members']
162 form_result['users_group_members'] = pstruct['user_group_members']
163
163
164 user_group, added_members, removed_members = \
164 user_group, added_members, removed_members = \
165 UserGroupModel().update(c.user_group, form_result)
165 UserGroupModel().update(c.user_group, form_result)
166 new_user_group_name = form_result['users_group_name']
166 new_user_group_name = form_result['users_group_name']
167
167
168 for user_id in added_members:
168 for user_id in added_members:
169 user = User.get(user_id)
169 user = User.get(user_id)
170 user_data = user.get_api_data()
170 user_data = user.get_api_data()
171 audit_logger.store_web(
171 audit_logger.store_web(
172 'user_group.edit.member.add',
172 'user_group.edit.member.add',
173 action_data={'user': user_data, 'old_data': old_values},
173 action_data={'user': user_data, 'old_data': old_values},
174 user=self._rhodecode_user)
174 user=self._rhodecode_user)
175
175
176 for user_id in removed_members:
176 for user_id in removed_members:
177 user = User.get(user_id)
177 user = User.get(user_id)
178 user_data = user.get_api_data()
178 user_data = user.get_api_data()
179 audit_logger.store_web(
179 audit_logger.store_web(
180 'user_group.edit.member.delete',
180 'user_group.edit.member.delete',
181 action_data={'user': user_data, 'old_data': old_values},
181 action_data={'user': user_data, 'old_data': old_values},
182 user=self._rhodecode_user)
182 user=self._rhodecode_user)
183
183
184 audit_logger.store_web(
184 audit_logger.store_web(
185 'user_group.edit', action_data={'old_data': old_values},
185 'user_group.edit', action_data={'old_data': old_values},
186 user=self._rhodecode_user)
186 user=self._rhodecode_user)
187
187
188 h.flash(_('Updated user group %s') % new_user_group_name,
188 h.flash(_('Updated user group %s') % new_user_group_name,
189 category='success')
189 category='success')
190
190
191 affected_user_ids = []
191 affected_user_ids = []
192 for user_id in added_members + removed_members:
192 for user_id in added_members + removed_members:
193 affected_user_ids.append(user_id)
193 affected_user_ids.append(user_id)
194
194
195 name_changed = old_user_group_name != new_user_group_name
195 name_changed = old_user_group_name != new_user_group_name
196 if name_changed:
196 if name_changed:
197 owner = User.get_by_username(form_result['user'])
197 owner = User.get_by_username(form_result['user'])
198 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
198 owner_id = owner.user_id if owner else self._rhodecode_user.user_id
199 affected_user_ids.append(self._rhodecode_user.user_id)
199 affected_user_ids.append(self._rhodecode_user.user_id)
200 affected_user_ids.append(owner_id)
200 affected_user_ids.append(owner_id)
201
201
202 events.trigger(events.UserPermissionsChange(affected_user_ids))
202 PermissionModel().trigger_permission_flush(affected_user_ids)
203
203
204 Session().commit()
204 Session().commit()
205 except formencode.Invalid as errors:
205 except formencode.Invalid as errors:
206 defaults = errors.value
206 defaults = errors.value
207 e = errors.error_dict or {}
207 e = errors.error_dict or {}
208
208
209 data = render(
209 data = render(
210 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
210 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
211 self._get_template_context(c), self.request)
211 self._get_template_context(c), self.request)
212 html = formencode.htmlfill.render(
212 html = formencode.htmlfill.render(
213 data,
213 data,
214 defaults=defaults,
214 defaults=defaults,
215 errors=e,
215 errors=e,
216 prefix_error=False,
216 prefix_error=False,
217 encoding="UTF-8",
217 encoding="UTF-8",
218 force_defaults=False
218 force_defaults=False
219 )
219 )
220 return Response(html)
220 return Response(html)
221
221
222 except Exception:
222 except Exception:
223 log.exception("Exception during update of user group")
223 log.exception("Exception during update of user group")
224 h.flash(_('Error occurred during update of user group %s')
224 h.flash(_('Error occurred during update of user group %s')
225 % new_user_group_name, category='error')
225 % new_user_group_name, category='error')
226
226
227 raise HTTPFound(
227 raise HTTPFound(
228 h.route_path('edit_user_group', user_group_id=user_group_id))
228 h.route_path('edit_user_group', user_group_id=user_group_id))
229
229
230 @LoginRequired()
230 @LoginRequired()
231 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
231 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
232 @CSRFRequired()
232 @CSRFRequired()
233 @view_config(
233 @view_config(
234 route_name='user_groups_delete', request_method='POST',
234 route_name='user_groups_delete', request_method='POST',
235 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
235 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
236 def user_group_delete(self):
236 def user_group_delete(self):
237 _ = self.request.translate
237 _ = self.request.translate
238 user_group = self.db_user_group
238 user_group = self.db_user_group
239
239
240 self.load_default_context()
240 self.load_default_context()
241 force = str2bool(self.request.POST.get('force'))
241 force = str2bool(self.request.POST.get('force'))
242
242
243 old_values = user_group.get_api_data()
243 old_values = user_group.get_api_data()
244 try:
244 try:
245 UserGroupModel().delete(user_group, force=force)
245 UserGroupModel().delete(user_group, force=force)
246 audit_logger.store_web(
246 audit_logger.store_web(
247 'user.delete', action_data={'old_data': old_values},
247 'user.delete', action_data={'old_data': old_values},
248 user=self._rhodecode_user)
248 user=self._rhodecode_user)
249 Session().commit()
249 Session().commit()
250 h.flash(_('Successfully deleted user group'), category='success')
250 h.flash(_('Successfully deleted user group'), category='success')
251 except UserGroupAssignedException as e:
251 except UserGroupAssignedException as e:
252 h.flash(str(e), category='error')
252 h.flash(str(e), category='error')
253 except Exception:
253 except Exception:
254 log.exception("Exception during deletion of user group")
254 log.exception("Exception during deletion of user group")
255 h.flash(_('An error occurred during deletion of user group'),
255 h.flash(_('An error occurred during deletion of user group'),
256 category='error')
256 category='error')
257 raise HTTPFound(h.route_path('user_groups'))
257 raise HTTPFound(h.route_path('user_groups'))
258
258
259 @LoginRequired()
259 @LoginRequired()
260 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
260 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
261 @view_config(
261 @view_config(
262 route_name='edit_user_group', request_method='GET',
262 route_name='edit_user_group', request_method='GET',
263 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
263 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
264 def user_group_edit(self):
264 def user_group_edit(self):
265 user_group = self.db_user_group
265 user_group = self.db_user_group
266
266
267 c = self.load_default_context()
267 c = self.load_default_context()
268 c.user_group = user_group
268 c.user_group = user_group
269 c.group_members_obj = [x.user for x in c.user_group.members]
269 c.group_members_obj = [x.user for x in c.user_group.members]
270 c.group_members_obj.sort(key=lambda u: u.username.lower())
270 c.group_members_obj.sort(key=lambda u: u.username.lower())
271 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
271 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
272
272
273 c.active = 'settings'
273 c.active = 'settings'
274
274
275 defaults = user_group.get_dict()
275 defaults = user_group.get_dict()
276 # fill owner
276 # fill owner
277 if user_group.user:
277 if user_group.user:
278 defaults.update({'user': user_group.user.username})
278 defaults.update({'user': user_group.user.username})
279 else:
279 else:
280 replacement_user = User.get_first_super_admin().username
280 replacement_user = User.get_first_super_admin().username
281 defaults.update({'user': replacement_user})
281 defaults.update({'user': replacement_user})
282
282
283 data = render(
283 data = render(
284 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
284 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
285 self._get_template_context(c), self.request)
285 self._get_template_context(c), self.request)
286 html = formencode.htmlfill.render(
286 html = formencode.htmlfill.render(
287 data,
287 data,
288 defaults=defaults,
288 defaults=defaults,
289 encoding="UTF-8",
289 encoding="UTF-8",
290 force_defaults=False
290 force_defaults=False
291 )
291 )
292 return Response(html)
292 return Response(html)
293
293
294 @LoginRequired()
294 @LoginRequired()
295 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
295 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
296 @view_config(
296 @view_config(
297 route_name='edit_user_group_perms', request_method='GET',
297 route_name='edit_user_group_perms', request_method='GET',
298 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
298 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
299 def user_group_edit_perms(self):
299 def user_group_edit_perms(self):
300 user_group = self.db_user_group
300 user_group = self.db_user_group
301 c = self.load_default_context()
301 c = self.load_default_context()
302 c.user_group = user_group
302 c.user_group = user_group
303 c.active = 'perms'
303 c.active = 'perms'
304
304
305 defaults = {}
305 defaults = {}
306 # fill user group users
306 # fill user group users
307 for p in c.user_group.user_user_group_to_perm:
307 for p in c.user_group.user_user_group_to_perm:
308 defaults.update({'u_perm_%s' % p.user.user_id:
308 defaults.update({'u_perm_%s' % p.user.user_id:
309 p.permission.permission_name})
309 p.permission.permission_name})
310
310
311 for p in c.user_group.user_group_user_group_to_perm:
311 for p in c.user_group.user_group_user_group_to_perm:
312 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
312 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
313 p.permission.permission_name})
313 p.permission.permission_name})
314
314
315 data = render(
315 data = render(
316 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
316 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
317 self._get_template_context(c), self.request)
317 self._get_template_context(c), self.request)
318 html = formencode.htmlfill.render(
318 html = formencode.htmlfill.render(
319 data,
319 data,
320 defaults=defaults,
320 defaults=defaults,
321 encoding="UTF-8",
321 encoding="UTF-8",
322 force_defaults=False
322 force_defaults=False
323 )
323 )
324 return Response(html)
324 return Response(html)
325
325
326 @LoginRequired()
326 @LoginRequired()
327 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
327 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
328 @CSRFRequired()
328 @CSRFRequired()
329 @view_config(
329 @view_config(
330 route_name='edit_user_group_perms_update', request_method='POST',
330 route_name='edit_user_group_perms_update', request_method='POST',
331 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
331 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
332 def user_group_update_perms(self):
332 def user_group_update_perms(self):
333 """
333 """
334 grant permission for given user group
334 grant permission for given user group
335 """
335 """
336 _ = self.request.translate
336 _ = self.request.translate
337
337
338 user_group = self.db_user_group
338 user_group = self.db_user_group
339 user_group_id = user_group.users_group_id
339 user_group_id = user_group.users_group_id
340 c = self.load_default_context()
340 c = self.load_default_context()
341 c.user_group = user_group
341 c.user_group = user_group
342 form = UserGroupPermsForm(self.request.translate)().to_python(self.request.POST)
342 form = UserGroupPermsForm(self.request.translate)().to_python(self.request.POST)
343
343
344 if not self._rhodecode_user.is_admin:
344 if not self._rhodecode_user.is_admin:
345 if self._revoke_perms_on_yourself(form):
345 if self._revoke_perms_on_yourself(form):
346 msg = _('Cannot change permission for yourself as admin')
346 msg = _('Cannot change permission for yourself as admin')
347 h.flash(msg, category='warning')
347 h.flash(msg, category='warning')
348 raise HTTPFound(
348 raise HTTPFound(
349 h.route_path('edit_user_group_perms',
349 h.route_path('edit_user_group_perms',
350 user_group_id=user_group_id))
350 user_group_id=user_group_id))
351
351
352 try:
352 try:
353 changes = UserGroupModel().update_permissions(
353 changes = UserGroupModel().update_permissions(
354 user_group,
354 user_group,
355 form['perm_additions'], form['perm_updates'],
355 form['perm_additions'], form['perm_updates'],
356 form['perm_deletions'])
356 form['perm_deletions'])
357
357
358 except RepoGroupAssignmentError:
358 except RepoGroupAssignmentError:
359 h.flash(_('Target group cannot be the same'), category='error')
359 h.flash(_('Target group cannot be the same'), category='error')
360 raise HTTPFound(
360 raise HTTPFound(
361 h.route_path('edit_user_group_perms',
361 h.route_path('edit_user_group_perms',
362 user_group_id=user_group_id))
362 user_group_id=user_group_id))
363
363
364 action_data = {
364 action_data = {
365 'added': changes['added'],
365 'added': changes['added'],
366 'updated': changes['updated'],
366 'updated': changes['updated'],
367 'deleted': changes['deleted'],
367 'deleted': changes['deleted'],
368 }
368 }
369 audit_logger.store_web(
369 audit_logger.store_web(
370 'user_group.edit.permissions', action_data=action_data,
370 'user_group.edit.permissions', action_data=action_data,
371 user=self._rhodecode_user)
371 user=self._rhodecode_user)
372
372
373 Session().commit()
373 Session().commit()
374 h.flash(_('User Group permissions updated'), category='success')
374 h.flash(_('User Group permissions updated'), category='success')
375
375
376 affected_user_ids = []
376 affected_user_ids = []
377 for change in changes['added'] + changes['updated'] + changes['deleted']:
377 for change in changes['added'] + changes['updated'] + changes['deleted']:
378 if change['type'] == 'user':
378 if change['type'] == 'user':
379 affected_user_ids.append(change['id'])
379 affected_user_ids.append(change['id'])
380 if change['type'] == 'user_group':
380 if change['type'] == 'user_group':
381 user_group = UserGroup.get(safe_int(change['id']))
381 user_group = UserGroup.get(safe_int(change['id']))
382 if user_group:
382 if user_group:
383 group_members_ids = [x.user_id for x in user_group.members]
383 group_members_ids = [x.user_id for x in user_group.members]
384 affected_user_ids.extend(group_members_ids)
384 affected_user_ids.extend(group_members_ids)
385
385
386 events.trigger(events.UserPermissionsChange(affected_user_ids))
386 PermissionModel().trigger_permission_flush(affected_user_ids)
387
387
388 raise HTTPFound(
388 raise HTTPFound(
389 h.route_path('edit_user_group_perms', user_group_id=user_group_id))
389 h.route_path('edit_user_group_perms', user_group_id=user_group_id))
390
390
391 @LoginRequired()
391 @LoginRequired()
392 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
392 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
393 @view_config(
393 @view_config(
394 route_name='edit_user_group_global_perms', request_method='GET',
394 route_name='edit_user_group_global_perms', request_method='GET',
395 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
395 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
396 def user_group_global_perms_edit(self):
396 def user_group_global_perms_edit(self):
397 user_group = self.db_user_group
397 user_group = self.db_user_group
398 c = self.load_default_context()
398 c = self.load_default_context()
399 c.user_group = user_group
399 c.user_group = user_group
400 c.active = 'global_perms'
400 c.active = 'global_perms'
401
401
402 c.default_user = User.get_default_user()
402 c.default_user = User.get_default_user()
403 defaults = c.user_group.get_dict()
403 defaults = c.user_group.get_dict()
404 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
404 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
405 defaults.update(c.user_group.get_default_perms())
405 defaults.update(c.user_group.get_default_perms())
406
406
407 data = render(
407 data = render(
408 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
408 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
409 self._get_template_context(c), self.request)
409 self._get_template_context(c), self.request)
410 html = formencode.htmlfill.render(
410 html = formencode.htmlfill.render(
411 data,
411 data,
412 defaults=defaults,
412 defaults=defaults,
413 encoding="UTF-8",
413 encoding="UTF-8",
414 force_defaults=False
414 force_defaults=False
415 )
415 )
416 return Response(html)
416 return Response(html)
417
417
418 @LoginRequired()
418 @LoginRequired()
419 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
419 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
420 @CSRFRequired()
420 @CSRFRequired()
421 @view_config(
421 @view_config(
422 route_name='edit_user_group_global_perms_update', request_method='POST',
422 route_name='edit_user_group_global_perms_update', request_method='POST',
423 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
423 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
424 def user_group_global_perms_update(self):
424 def user_group_global_perms_update(self):
425 _ = self.request.translate
425 _ = self.request.translate
426 user_group = self.db_user_group
426 user_group = self.db_user_group
427 user_group_id = self.db_user_group.users_group_id
427 user_group_id = self.db_user_group.users_group_id
428
428
429 c = self.load_default_context()
429 c = self.load_default_context()
430 c.user_group = user_group
430 c.user_group = user_group
431 c.active = 'global_perms'
431 c.active = 'global_perms'
432
432
433 try:
433 try:
434 # first stage that verifies the checkbox
434 # first stage that verifies the checkbox
435 _form = UserIndividualPermissionsForm(self.request.translate)
435 _form = UserIndividualPermissionsForm(self.request.translate)
436 form_result = _form.to_python(dict(self.request.POST))
436 form_result = _form.to_python(dict(self.request.POST))
437 inherit_perms = form_result['inherit_default_permissions']
437 inherit_perms = form_result['inherit_default_permissions']
438 user_group.inherit_default_permissions = inherit_perms
438 user_group.inherit_default_permissions = inherit_perms
439 Session().add(user_group)
439 Session().add(user_group)
440
440
441 if not inherit_perms:
441 if not inherit_perms:
442 # only update the individual ones if we un check the flag
442 # only update the individual ones if we un check the flag
443 _form = UserPermissionsForm(
443 _form = UserPermissionsForm(
444 self.request.translate,
444 self.request.translate,
445 [x[0] for x in c.repo_create_choices],
445 [x[0] for x in c.repo_create_choices],
446 [x[0] for x in c.repo_create_on_write_choices],
446 [x[0] for x in c.repo_create_on_write_choices],
447 [x[0] for x in c.repo_group_create_choices],
447 [x[0] for x in c.repo_group_create_choices],
448 [x[0] for x in c.user_group_create_choices],
448 [x[0] for x in c.user_group_create_choices],
449 [x[0] for x in c.fork_choices],
449 [x[0] for x in c.fork_choices],
450 [x[0] for x in c.inherit_default_permission_choices])()
450 [x[0] for x in c.inherit_default_permission_choices])()
451
451
452 form_result = _form.to_python(dict(self.request.POST))
452 form_result = _form.to_python(dict(self.request.POST))
453 form_result.update(
453 form_result.update(
454 {'perm_user_group_id': user_group.users_group_id})
454 {'perm_user_group_id': user_group.users_group_id})
455
455
456 PermissionModel().update_user_group_permissions(form_result)
456 PermissionModel().update_user_group_permissions(form_result)
457
457
458 Session().commit()
458 Session().commit()
459 h.flash(_('User Group global permissions updated successfully'),
459 h.flash(_('User Group global permissions updated successfully'),
460 category='success')
460 category='success')
461
461
462 except formencode.Invalid as errors:
462 except formencode.Invalid as errors:
463 defaults = errors.value
463 defaults = errors.value
464
464
465 data = render(
465 data = render(
466 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
466 'rhodecode:templates/admin/user_groups/user_group_edit.mako',
467 self._get_template_context(c), self.request)
467 self._get_template_context(c), self.request)
468 html = formencode.htmlfill.render(
468 html = formencode.htmlfill.render(
469 data,
469 data,
470 defaults=defaults,
470 defaults=defaults,
471 errors=errors.error_dict or {},
471 errors=errors.error_dict or {},
472 prefix_error=False,
472 prefix_error=False,
473 encoding="UTF-8",
473 encoding="UTF-8",
474 force_defaults=False
474 force_defaults=False
475 )
475 )
476 return Response(html)
476 return Response(html)
477 except Exception:
477 except Exception:
478 log.exception("Exception during permissions saving")
478 log.exception("Exception during permissions saving")
479 h.flash(_('An error occurred during permissions saving'),
479 h.flash(_('An error occurred during permissions saving'),
480 category='error')
480 category='error')
481
481
482 raise HTTPFound(
482 raise HTTPFound(
483 h.route_path('edit_user_group_global_perms',
483 h.route_path('edit_user_group_global_perms',
484 user_group_id=user_group_id))
484 user_group_id=user_group_id))
485
485
486 @LoginRequired()
486 @LoginRequired()
487 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
487 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
488 @view_config(
488 @view_config(
489 route_name='edit_user_group_advanced', request_method='GET',
489 route_name='edit_user_group_advanced', request_method='GET',
490 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
490 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
491 def user_group_edit_advanced(self):
491 def user_group_edit_advanced(self):
492 user_group = self.db_user_group
492 user_group = self.db_user_group
493
493
494 c = self.load_default_context()
494 c = self.load_default_context()
495 c.user_group = user_group
495 c.user_group = user_group
496 c.active = 'advanced'
496 c.active = 'advanced'
497 c.group_members_obj = sorted(
497 c.group_members_obj = sorted(
498 (x.user for x in c.user_group.members),
498 (x.user for x in c.user_group.members),
499 key=lambda u: u.username.lower())
499 key=lambda u: u.username.lower())
500
500
501 c.group_to_repos = sorted(
501 c.group_to_repos = sorted(
502 (x.repository for x in c.user_group.users_group_repo_to_perm),
502 (x.repository for x in c.user_group.users_group_repo_to_perm),
503 key=lambda u: u.repo_name.lower())
503 key=lambda u: u.repo_name.lower())
504
504
505 c.group_to_repo_groups = sorted(
505 c.group_to_repo_groups = sorted(
506 (x.group for x in c.user_group.users_group_repo_group_to_perm),
506 (x.group for x in c.user_group.users_group_repo_group_to_perm),
507 key=lambda u: u.group_name.lower())
507 key=lambda u: u.group_name.lower())
508
508
509 c.group_to_review_rules = sorted(
509 c.group_to_review_rules = sorted(
510 (x.users_group for x in c.user_group.user_group_review_rules),
510 (x.users_group for x in c.user_group.user_group_review_rules),
511 key=lambda u: u.users_group_name.lower())
511 key=lambda u: u.users_group_name.lower())
512
512
513 return self._get_template_context(c)
513 return self._get_template_context(c)
514
514
515 @LoginRequired()
515 @LoginRequired()
516 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
516 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
517 @CSRFRequired()
517 @CSRFRequired()
518 @view_config(
518 @view_config(
519 route_name='edit_user_group_advanced_sync', request_method='POST',
519 route_name='edit_user_group_advanced_sync', request_method='POST',
520 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
520 renderer='rhodecode:templates/admin/user_groups/user_group_edit.mako')
521 def user_group_edit_advanced_set_synchronization(self):
521 def user_group_edit_advanced_set_synchronization(self):
522 _ = self.request.translate
522 _ = self.request.translate
523 user_group = self.db_user_group
523 user_group = self.db_user_group
524 user_group_id = user_group.users_group_id
524 user_group_id = user_group.users_group_id
525
525
526 existing = user_group.group_data.get('extern_type')
526 existing = user_group.group_data.get('extern_type')
527
527
528 if existing:
528 if existing:
529 new_state = user_group.group_data
529 new_state = user_group.group_data
530 new_state['extern_type'] = None
530 new_state['extern_type'] = None
531 else:
531 else:
532 new_state = user_group.group_data
532 new_state = user_group.group_data
533 new_state['extern_type'] = 'manual'
533 new_state['extern_type'] = 'manual'
534 new_state['extern_type_set_by'] = self._rhodecode_user.username
534 new_state['extern_type_set_by'] = self._rhodecode_user.username
535
535
536 try:
536 try:
537 user_group.group_data = new_state
537 user_group.group_data = new_state
538 Session().add(user_group)
538 Session().add(user_group)
539 Session().commit()
539 Session().commit()
540
540
541 h.flash(_('User Group synchronization updated successfully'),
541 h.flash(_('User Group synchronization updated successfully'),
542 category='success')
542 category='success')
543 except Exception:
543 except Exception:
544 log.exception("Exception during sync settings saving")
544 log.exception("Exception during sync settings saving")
545 h.flash(_('An error occurred during synchronization update'),
545 h.flash(_('An error occurred during synchronization update'),
546 category='error')
546 category='error')
547
547
548 raise HTTPFound(
548 raise HTTPFound(
549 h.route_path('edit_user_group_advanced',
549 h.route_path('edit_user_group_advanced',
550 user_group_id=user_group_id))
550 user_group_id=user_group_id))
@@ -1,558 +1,577 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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
24
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode import events
31 from rhodecode.model import BaseModel
32 from rhodecode.model import BaseModel
32 from rhodecode.model.db import (
33 from rhodecode.model.db import (
33 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 User, Permission, UserToPerm, UserRepoToPerm, UserRepoGroupToPerm,
34 UserUserGroupToPerm, UserGroup, UserGroupToPerm, UserToRepoBranchPermission)
35 UserUserGroupToPerm, UserGroup, UserGroupToPerm, UserToRepoBranchPermission)
35 from rhodecode.lib.utils2 import str2bool, safe_int
36 from rhodecode.lib.utils2 import str2bool, safe_int
36
37
37 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
38
39
39
40
40 class PermissionModel(BaseModel):
41 class PermissionModel(BaseModel):
41 """
42 """
42 Permissions model for RhodeCode
43 Permissions model for RhodeCode
43 """
44 """
44
45
45 cls = Permission
46 cls = Permission
46 global_perms = {
47 global_perms = {
47 'default_repo_create': None,
48 'default_repo_create': None,
48 # special case for create repos on write access to group
49 # special case for create repos on write access to group
49 'default_repo_create_on_write': None,
50 'default_repo_create_on_write': None,
50 'default_repo_group_create': None,
51 'default_repo_group_create': None,
51 'default_user_group_create': None,
52 'default_user_group_create': None,
52 'default_fork_create': None,
53 'default_fork_create': None,
53 'default_inherit_default_permissions': None,
54 'default_inherit_default_permissions': None,
54 'default_register': None,
55 'default_register': None,
55 'default_password_reset': None,
56 'default_password_reset': None,
56 'default_extern_activate': None,
57 'default_extern_activate': None,
57
58
58 # object permissions below
59 # object permissions below
59 'default_repo_perm': None,
60 'default_repo_perm': None,
60 'default_group_perm': None,
61 'default_group_perm': None,
61 'default_user_group_perm': None,
62 'default_user_group_perm': None,
62
63
63 # branch
64 # branch
64 'default_branch_perm': None,
65 'default_branch_perm': None,
65 }
66 }
66
67
67 def set_global_permission_choices(self, c_obj, gettext_translator):
68 def set_global_permission_choices(self, c_obj, gettext_translator):
68 _ = gettext_translator
69 _ = gettext_translator
69
70
70 c_obj.repo_perms_choices = [
71 c_obj.repo_perms_choices = [
71 ('repository.none', _('None'),),
72 ('repository.none', _('None'),),
72 ('repository.read', _('Read'),),
73 ('repository.read', _('Read'),),
73 ('repository.write', _('Write'),),
74 ('repository.write', _('Write'),),
74 ('repository.admin', _('Admin'),)]
75 ('repository.admin', _('Admin'),)]
75
76
76 c_obj.group_perms_choices = [
77 c_obj.group_perms_choices = [
77 ('group.none', _('None'),),
78 ('group.none', _('None'),),
78 ('group.read', _('Read'),),
79 ('group.read', _('Read'),),
79 ('group.write', _('Write'),),
80 ('group.write', _('Write'),),
80 ('group.admin', _('Admin'),)]
81 ('group.admin', _('Admin'),)]
81
82
82 c_obj.user_group_perms_choices = [
83 c_obj.user_group_perms_choices = [
83 ('usergroup.none', _('None'),),
84 ('usergroup.none', _('None'),),
84 ('usergroup.read', _('Read'),),
85 ('usergroup.read', _('Read'),),
85 ('usergroup.write', _('Write'),),
86 ('usergroup.write', _('Write'),),
86 ('usergroup.admin', _('Admin'),)]
87 ('usergroup.admin', _('Admin'),)]
87
88
88 c_obj.branch_perms_choices = [
89 c_obj.branch_perms_choices = [
89 ('branch.none', _('Protected/No Access'),),
90 ('branch.none', _('Protected/No Access'),),
90 ('branch.merge', _('Web merge'),),
91 ('branch.merge', _('Web merge'),),
91 ('branch.push', _('Push'),),
92 ('branch.push', _('Push'),),
92 ('branch.push_force', _('Force Push'),)]
93 ('branch.push_force', _('Force Push'),)]
93
94
94 c_obj.register_choices = [
95 c_obj.register_choices = [
95 ('hg.register.none', _('Disabled')),
96 ('hg.register.none', _('Disabled')),
96 ('hg.register.manual_activate', _('Allowed with manual account activation')),
97 ('hg.register.manual_activate', _('Allowed with manual account activation')),
97 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
98 ('hg.register.auto_activate', _('Allowed with automatic account activation')),]
98
99
99 c_obj.password_reset_choices = [
100 c_obj.password_reset_choices = [
100 ('hg.password_reset.enabled', _('Allow password recovery')),
101 ('hg.password_reset.enabled', _('Allow password recovery')),
101 ('hg.password_reset.hidden', _('Hide password recovery link')),
102 ('hg.password_reset.hidden', _('Hide password recovery link')),
102 ('hg.password_reset.disabled', _('Disable password recovery')),]
103 ('hg.password_reset.disabled', _('Disable password recovery')),]
103
104
104 c_obj.extern_activate_choices = [
105 c_obj.extern_activate_choices = [
105 ('hg.extern_activate.manual', _('Manual activation of external account')),
106 ('hg.extern_activate.manual', _('Manual activation of external account')),
106 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
107 ('hg.extern_activate.auto', _('Automatic activation of external account')),]
107
108
108 c_obj.repo_create_choices = [
109 c_obj.repo_create_choices = [
109 ('hg.create.none', _('Disabled')),
110 ('hg.create.none', _('Disabled')),
110 ('hg.create.repository', _('Enabled'))]
111 ('hg.create.repository', _('Enabled'))]
111
112
112 c_obj.repo_create_on_write_choices = [
113 c_obj.repo_create_on_write_choices = [
113 ('hg.create.write_on_repogroup.false', _('Disabled')),
114 ('hg.create.write_on_repogroup.false', _('Disabled')),
114 ('hg.create.write_on_repogroup.true', _('Enabled'))]
115 ('hg.create.write_on_repogroup.true', _('Enabled'))]
115
116
116 c_obj.user_group_create_choices = [
117 c_obj.user_group_create_choices = [
117 ('hg.usergroup.create.false', _('Disabled')),
118 ('hg.usergroup.create.false', _('Disabled')),
118 ('hg.usergroup.create.true', _('Enabled'))]
119 ('hg.usergroup.create.true', _('Enabled'))]
119
120
120 c_obj.repo_group_create_choices = [
121 c_obj.repo_group_create_choices = [
121 ('hg.repogroup.create.false', _('Disabled')),
122 ('hg.repogroup.create.false', _('Disabled')),
122 ('hg.repogroup.create.true', _('Enabled'))]
123 ('hg.repogroup.create.true', _('Enabled'))]
123
124
124 c_obj.fork_choices = [
125 c_obj.fork_choices = [
125 ('hg.fork.none', _('Disabled')),
126 ('hg.fork.none', _('Disabled')),
126 ('hg.fork.repository', _('Enabled'))]
127 ('hg.fork.repository', _('Enabled'))]
127
128
128 c_obj.inherit_default_permission_choices = [
129 c_obj.inherit_default_permission_choices = [
129 ('hg.inherit_default_perms.false', _('Disabled')),
130 ('hg.inherit_default_perms.false', _('Disabled')),
130 ('hg.inherit_default_perms.true', _('Enabled'))]
131 ('hg.inherit_default_perms.true', _('Enabled'))]
131
132
132 def get_default_perms(self, object_perms, suffix):
133 def get_default_perms(self, object_perms, suffix):
133 defaults = {}
134 defaults = {}
134 for perm in object_perms:
135 for perm in object_perms:
135 # perms
136 # perms
136 if perm.permission.permission_name.startswith('repository.'):
137 if perm.permission.permission_name.startswith('repository.'):
137 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
138 defaults['default_repo_perm' + suffix] = perm.permission.permission_name
138
139
139 if perm.permission.permission_name.startswith('group.'):
140 if perm.permission.permission_name.startswith('group.'):
140 defaults['default_group_perm' + suffix] = perm.permission.permission_name
141 defaults['default_group_perm' + suffix] = perm.permission.permission_name
141
142
142 if perm.permission.permission_name.startswith('usergroup.'):
143 if perm.permission.permission_name.startswith('usergroup.'):
143 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
144 defaults['default_user_group_perm' + suffix] = perm.permission.permission_name
144
145
145 # branch
146 # branch
146 if perm.permission.permission_name.startswith('branch.'):
147 if perm.permission.permission_name.startswith('branch.'):
147 defaults['default_branch_perm' + suffix] = perm.permission.permission_name
148 defaults['default_branch_perm' + suffix] = perm.permission.permission_name
148
149
149 # creation of objects
150 # creation of objects
150 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
151 if perm.permission.permission_name.startswith('hg.create.write_on_repogroup'):
151 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
152 defaults['default_repo_create_on_write' + suffix] = perm.permission.permission_name
152
153
153 elif perm.permission.permission_name.startswith('hg.create.'):
154 elif perm.permission.permission_name.startswith('hg.create.'):
154 defaults['default_repo_create' + suffix] = perm.permission.permission_name
155 defaults['default_repo_create' + suffix] = perm.permission.permission_name
155
156
156 if perm.permission.permission_name.startswith('hg.fork.'):
157 if perm.permission.permission_name.startswith('hg.fork.'):
157 defaults['default_fork_create' + suffix] = perm.permission.permission_name
158 defaults['default_fork_create' + suffix] = perm.permission.permission_name
158
159
159 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
160 if perm.permission.permission_name.startswith('hg.inherit_default_perms.'):
160 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
161 defaults['default_inherit_default_permissions' + suffix] = perm.permission.permission_name
161
162
162 if perm.permission.permission_name.startswith('hg.repogroup.'):
163 if perm.permission.permission_name.startswith('hg.repogroup.'):
163 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
164 defaults['default_repo_group_create' + suffix] = perm.permission.permission_name
164
165
165 if perm.permission.permission_name.startswith('hg.usergroup.'):
166 if perm.permission.permission_name.startswith('hg.usergroup.'):
166 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
167 defaults['default_user_group_create' + suffix] = perm.permission.permission_name
167
168
168 # registration and external account activation
169 # registration and external account activation
169 if perm.permission.permission_name.startswith('hg.register.'):
170 if perm.permission.permission_name.startswith('hg.register.'):
170 defaults['default_register' + suffix] = perm.permission.permission_name
171 defaults['default_register' + suffix] = perm.permission.permission_name
171
172
172 if perm.permission.permission_name.startswith('hg.password_reset.'):
173 if perm.permission.permission_name.startswith('hg.password_reset.'):
173 defaults['default_password_reset' + suffix] = perm.permission.permission_name
174 defaults['default_password_reset' + suffix] = perm.permission.permission_name
174
175
175 if perm.permission.permission_name.startswith('hg.extern_activate.'):
176 if perm.permission.permission_name.startswith('hg.extern_activate.'):
176 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
177 defaults['default_extern_activate' + suffix] = perm.permission.permission_name
177
178
178 return defaults
179 return defaults
179
180
180 def _make_new_user_perm(self, user, perm_name):
181 def _make_new_user_perm(self, user, perm_name):
181 log.debug('Creating new user permission:%s', perm_name)
182 log.debug('Creating new user permission:%s', perm_name)
182 new = UserToPerm()
183 new = UserToPerm()
183 new.user = user
184 new.user = user
184 new.permission = Permission.get_by_key(perm_name)
185 new.permission = Permission.get_by_key(perm_name)
185 return new
186 return new
186
187
187 def _make_new_user_group_perm(self, user_group, perm_name):
188 def _make_new_user_group_perm(self, user_group, perm_name):
188 log.debug('Creating new user group permission:%s', perm_name)
189 log.debug('Creating new user group permission:%s', perm_name)
189 new = UserGroupToPerm()
190 new = UserGroupToPerm()
190 new.users_group = user_group
191 new.users_group = user_group
191 new.permission = Permission.get_by_key(perm_name)
192 new.permission = Permission.get_by_key(perm_name)
192 return new
193 return new
193
194
194 def _keep_perm(self, perm_name, keep_fields):
195 def _keep_perm(self, perm_name, keep_fields):
195 def get_pat(field_name):
196 def get_pat(field_name):
196 return {
197 return {
197 # global perms
198 # global perms
198 'default_repo_create': 'hg.create.',
199 'default_repo_create': 'hg.create.',
199 # special case for create repos on write access to group
200 # special case for create repos on write access to group
200 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
201 'default_repo_create_on_write': 'hg.create.write_on_repogroup.',
201 'default_repo_group_create': 'hg.repogroup.create.',
202 'default_repo_group_create': 'hg.repogroup.create.',
202 'default_user_group_create': 'hg.usergroup.create.',
203 'default_user_group_create': 'hg.usergroup.create.',
203 'default_fork_create': 'hg.fork.',
204 'default_fork_create': 'hg.fork.',
204 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
205 'default_inherit_default_permissions': 'hg.inherit_default_perms.',
205
206
206 # application perms
207 # application perms
207 'default_register': 'hg.register.',
208 'default_register': 'hg.register.',
208 'default_password_reset': 'hg.password_reset.',
209 'default_password_reset': 'hg.password_reset.',
209 'default_extern_activate': 'hg.extern_activate.',
210 'default_extern_activate': 'hg.extern_activate.',
210
211
211 # object permissions below
212 # object permissions below
212 'default_repo_perm': 'repository.',
213 'default_repo_perm': 'repository.',
213 'default_group_perm': 'group.',
214 'default_group_perm': 'group.',
214 'default_user_group_perm': 'usergroup.',
215 'default_user_group_perm': 'usergroup.',
215 # branch
216 # branch
216 'default_branch_perm': 'branch.',
217 'default_branch_perm': 'branch.',
217
218
218 }[field_name]
219 }[field_name]
219 for field in keep_fields:
220 for field in keep_fields:
220 pat = get_pat(field)
221 pat = get_pat(field)
221 if perm_name.startswith(pat):
222 if perm_name.startswith(pat):
222 return True
223 return True
223 return False
224 return False
224
225
225 def _clear_object_perm(self, object_perms, preserve=None):
226 def _clear_object_perm(self, object_perms, preserve=None):
226 preserve = preserve or []
227 preserve = preserve or []
227 _deleted = []
228 _deleted = []
228 for perm in object_perms:
229 for perm in object_perms:
229 perm_name = perm.permission.permission_name
230 perm_name = perm.permission.permission_name
230 if not self._keep_perm(perm_name, keep_fields=preserve):
231 if not self._keep_perm(perm_name, keep_fields=preserve):
231 _deleted.append(perm_name)
232 _deleted.append(perm_name)
232 self.sa.delete(perm)
233 self.sa.delete(perm)
233 return _deleted
234 return _deleted
234
235
235 def _clear_user_perms(self, user_id, preserve=None):
236 def _clear_user_perms(self, user_id, preserve=None):
236 perms = self.sa.query(UserToPerm)\
237 perms = self.sa.query(UserToPerm)\
237 .filter(UserToPerm.user_id == user_id)\
238 .filter(UserToPerm.user_id == user_id)\
238 .all()
239 .all()
239 return self._clear_object_perm(perms, preserve=preserve)
240 return self._clear_object_perm(perms, preserve=preserve)
240
241
241 def _clear_user_group_perms(self, user_group_id, preserve=None):
242 def _clear_user_group_perms(self, user_group_id, preserve=None):
242 perms = self.sa.query(UserGroupToPerm)\
243 perms = self.sa.query(UserGroupToPerm)\
243 .filter(UserGroupToPerm.users_group_id == user_group_id)\
244 .filter(UserGroupToPerm.users_group_id == user_group_id)\
244 .all()
245 .all()
245 return self._clear_object_perm(perms, preserve=preserve)
246 return self._clear_object_perm(perms, preserve=preserve)
246
247
247 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
248 def _set_new_object_perms(self, obj_type, object, form_result, preserve=None):
248 # clear current entries, to make this function idempotent
249 # clear current entries, to make this function idempotent
249 # it will fix even if we define more permissions or permissions
250 # it will fix even if we define more permissions or permissions
250 # are somehow missing
251 # are somehow missing
251 preserve = preserve or []
252 preserve = preserve or []
252 _global_perms = self.global_perms.copy()
253 _global_perms = self.global_perms.copy()
253 if obj_type not in ['user', 'user_group']:
254 if obj_type not in ['user', 'user_group']:
254 raise ValueError("obj_type must be on of 'user' or 'user_group'")
255 raise ValueError("obj_type must be on of 'user' or 'user_group'")
255 global_perms = len(_global_perms)
256 global_perms = len(_global_perms)
256 default_user_perms = len(Permission.DEFAULT_USER_PERMISSIONS)
257 default_user_perms = len(Permission.DEFAULT_USER_PERMISSIONS)
257 if global_perms != default_user_perms:
258 if global_perms != default_user_perms:
258 raise Exception(
259 raise Exception(
259 'Inconsistent permissions definition. Got {} vs {}'.format(
260 'Inconsistent permissions definition. Got {} vs {}'.format(
260 global_perms, default_user_perms))
261 global_perms, default_user_perms))
261
262
262 if obj_type == 'user':
263 if obj_type == 'user':
263 self._clear_user_perms(object.user_id, preserve)
264 self._clear_user_perms(object.user_id, preserve)
264 if obj_type == 'user_group':
265 if obj_type == 'user_group':
265 self._clear_user_group_perms(object.users_group_id, preserve)
266 self._clear_user_group_perms(object.users_group_id, preserve)
266
267
267 # now kill the keys that we want to preserve from the form.
268 # now kill the keys that we want to preserve from the form.
268 for key in preserve:
269 for key in preserve:
269 del _global_perms[key]
270 del _global_perms[key]
270
271
271 for k in _global_perms.copy():
272 for k in _global_perms.copy():
272 _global_perms[k] = form_result[k]
273 _global_perms[k] = form_result[k]
273
274
274 # at that stage we validate all are passed inside form_result
275 # at that stage we validate all are passed inside form_result
275 for _perm_key, perm_value in _global_perms.items():
276 for _perm_key, perm_value in _global_perms.items():
276 if perm_value is None:
277 if perm_value is None:
277 raise ValueError('Missing permission for %s' % (_perm_key,))
278 raise ValueError('Missing permission for %s' % (_perm_key,))
278
279
279 if obj_type == 'user':
280 if obj_type == 'user':
280 p = self._make_new_user_perm(object, perm_value)
281 p = self._make_new_user_perm(object, perm_value)
281 self.sa.add(p)
282 self.sa.add(p)
282 if obj_type == 'user_group':
283 if obj_type == 'user_group':
283 p = self._make_new_user_group_perm(object, perm_value)
284 p = self._make_new_user_group_perm(object, perm_value)
284 self.sa.add(p)
285 self.sa.add(p)
285
286
286 def _set_new_user_perms(self, user, form_result, preserve=None):
287 def _set_new_user_perms(self, user, form_result, preserve=None):
287 return self._set_new_object_perms(
288 return self._set_new_object_perms(
288 'user', user, form_result, preserve)
289 'user', user, form_result, preserve)
289
290
290 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
291 def _set_new_user_group_perms(self, user_group, form_result, preserve=None):
291 return self._set_new_object_perms(
292 return self._set_new_object_perms(
292 'user_group', user_group, form_result, preserve)
293 'user_group', user_group, form_result, preserve)
293
294
294 def set_new_user_perms(self, user, form_result):
295 def set_new_user_perms(self, user, form_result):
295 # calculate what to preserve from what is given in form_result
296 # calculate what to preserve from what is given in form_result
296 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
297 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
297 return self._set_new_user_perms(user, form_result, preserve)
298 return self._set_new_user_perms(user, form_result, preserve)
298
299
299 def set_new_user_group_perms(self, user_group, form_result):
300 def set_new_user_group_perms(self, user_group, form_result):
300 # calculate what to preserve from what is given in form_result
301 # calculate what to preserve from what is given in form_result
301 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
302 preserve = set(self.global_perms.keys()).difference(set(form_result.keys()))
302 return self._set_new_user_group_perms(user_group, form_result, preserve)
303 return self._set_new_user_group_perms(user_group, form_result, preserve)
303
304
304 def create_permissions(self):
305 def create_permissions(self):
305 """
306 """
306 Create permissions for whole system
307 Create permissions for whole system
307 """
308 """
308 for p in Permission.PERMS:
309 for p in Permission.PERMS:
309 if not Permission.get_by_key(p[0]):
310 if not Permission.get_by_key(p[0]):
310 new_perm = Permission()
311 new_perm = Permission()
311 new_perm.permission_name = p[0]
312 new_perm.permission_name = p[0]
312 new_perm.permission_longname = p[0] # translation err with p[1]
313 new_perm.permission_longname = p[0] # translation err with p[1]
313 self.sa.add(new_perm)
314 self.sa.add(new_perm)
314
315
315 def _create_default_object_permission(self, obj_type, obj, obj_perms,
316 def _create_default_object_permission(self, obj_type, obj, obj_perms,
316 force=False):
317 force=False):
317 if obj_type not in ['user', 'user_group']:
318 if obj_type not in ['user', 'user_group']:
318 raise ValueError("obj_type must be on of 'user' or 'user_group'")
319 raise ValueError("obj_type must be on of 'user' or 'user_group'")
319
320
320 def _get_group(perm_name):
321 def _get_group(perm_name):
321 return '.'.join(perm_name.split('.')[:1])
322 return '.'.join(perm_name.split('.')[:1])
322
323
323 defined_perms_groups = map(
324 defined_perms_groups = map(
324 _get_group, (x.permission.permission_name for x in obj_perms))
325 _get_group, (x.permission.permission_name for x in obj_perms))
325 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
326 log.debug('GOT ALREADY DEFINED:%s', obj_perms)
326
327
327 if force:
328 if force:
328 self._clear_object_perm(obj_perms)
329 self._clear_object_perm(obj_perms)
329 self.sa.commit()
330 self.sa.commit()
330 defined_perms_groups = []
331 defined_perms_groups = []
331 # for every default permission that needs to be created, we check if
332 # 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
333 # it's group is already defined, if it's not we create default perm
333 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
334 for perm_name in Permission.DEFAULT_USER_PERMISSIONS:
334 gr = _get_group(perm_name)
335 gr = _get_group(perm_name)
335 if gr not in defined_perms_groups:
336 if gr not in defined_perms_groups:
336 log.debug('GR:%s not found, creating permission %s',
337 log.debug('GR:%s not found, creating permission %s',
337 gr, perm_name)
338 gr, perm_name)
338 if obj_type == 'user':
339 if obj_type == 'user':
339 new_perm = self._make_new_user_perm(obj, perm_name)
340 new_perm = self._make_new_user_perm(obj, perm_name)
340 self.sa.add(new_perm)
341 self.sa.add(new_perm)
341 if obj_type == 'user_group':
342 if obj_type == 'user_group':
342 new_perm = self._make_new_user_group_perm(obj, perm_name)
343 new_perm = self._make_new_user_group_perm(obj, perm_name)
343 self.sa.add(new_perm)
344 self.sa.add(new_perm)
344
345
345 def create_default_user_permissions(self, user, force=False):
346 def create_default_user_permissions(self, user, force=False):
346 """
347 """
347 Creates only missing default permissions for user, if force is set it
348 Creates only missing default permissions for user, if force is set it
348 resets the default permissions for that user
349 resets the default permissions for that user
349
350
350 :param user:
351 :param user:
351 :param force:
352 :param force:
352 """
353 """
353 user = self._get_user(user)
354 user = self._get_user(user)
354 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
355 obj_perms = UserToPerm.query().filter(UserToPerm.user == user).all()
355 return self._create_default_object_permission(
356 return self._create_default_object_permission(
356 'user', user, obj_perms, force)
357 'user', user, obj_perms, force)
357
358
358 def create_default_user_group_permissions(self, user_group, force=False):
359 def create_default_user_group_permissions(self, user_group, force=False):
359 """
360 """
360 Creates only missing default permissions for user group, if force is
361 Creates only missing default permissions for user group, if force is
361 set it resets the default permissions for that user group
362 set it resets the default permissions for that user group
362
363
363 :param user_group:
364 :param user_group:
364 :param force:
365 :param force:
365 """
366 """
366 user_group = self._get_user_group(user_group)
367 user_group = self._get_user_group(user_group)
367 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
368 obj_perms = UserToPerm.query().filter(UserGroupToPerm.users_group == user_group).all()
368 return self._create_default_object_permission(
369 return self._create_default_object_permission(
369 'user_group', user_group, obj_perms, force)
370 'user_group', user_group, obj_perms, force)
370
371
371 def update_application_permissions(self, form_result):
372 def update_application_permissions(self, form_result):
372 if 'perm_user_id' in form_result:
373 if 'perm_user_id' in form_result:
373 perm_user = User.get(safe_int(form_result['perm_user_id']))
374 perm_user = User.get(safe_int(form_result['perm_user_id']))
374 else:
375 else:
375 # used mostly to do lookup for default user
376 # used mostly to do lookup for default user
376 perm_user = User.get_by_username(form_result['perm_user_name'])
377 perm_user = User.get_by_username(form_result['perm_user_name'])
377
378
378 try:
379 try:
379 # stage 1 set anonymous access
380 # stage 1 set anonymous access
380 if perm_user.username == User.DEFAULT_USER:
381 if perm_user.username == User.DEFAULT_USER:
381 perm_user.active = str2bool(form_result['anonymous'])
382 perm_user.active = str2bool(form_result['anonymous'])
382 self.sa.add(perm_user)
383 self.sa.add(perm_user)
383
384
384 # stage 2 reset defaults and set them from form data
385 # stage 2 reset defaults and set them from form data
385 self._set_new_user_perms(perm_user, form_result, preserve=[
386 self._set_new_user_perms(perm_user, form_result, preserve=[
386 'default_repo_perm',
387 'default_repo_perm',
387 'default_group_perm',
388 'default_group_perm',
388 'default_user_group_perm',
389 'default_user_group_perm',
389 'default_branch_perm',
390 'default_branch_perm',
390
391
391 'default_repo_group_create',
392 'default_repo_group_create',
392 'default_user_group_create',
393 'default_user_group_create',
393 'default_repo_create_on_write',
394 'default_repo_create_on_write',
394 'default_repo_create',
395 'default_repo_create',
395 'default_fork_create',
396 'default_fork_create',
396 'default_inherit_default_permissions',])
397 'default_inherit_default_permissions',])
397
398
398 self.sa.commit()
399 self.sa.commit()
399 except (DatabaseError,):
400 except (DatabaseError,):
400 log.error(traceback.format_exc())
401 log.error(traceback.format_exc())
401 self.sa.rollback()
402 self.sa.rollback()
402 raise
403 raise
403
404
404 def update_user_permissions(self, form_result):
405 def update_user_permissions(self, form_result):
405 if 'perm_user_id' in form_result:
406 if 'perm_user_id' in form_result:
406 perm_user = User.get(safe_int(form_result['perm_user_id']))
407 perm_user = User.get(safe_int(form_result['perm_user_id']))
407 else:
408 else:
408 # used mostly to do lookup for default user
409 # used mostly to do lookup for default user
409 perm_user = User.get_by_username(form_result['perm_user_name'])
410 perm_user = User.get_by_username(form_result['perm_user_name'])
410 try:
411 try:
411 # stage 2 reset defaults and set them from form data
412 # stage 2 reset defaults and set them from form data
412 self._set_new_user_perms(perm_user, form_result, preserve=[
413 self._set_new_user_perms(perm_user, form_result, preserve=[
413 'default_repo_perm',
414 'default_repo_perm',
414 'default_group_perm',
415 'default_group_perm',
415 'default_user_group_perm',
416 'default_user_group_perm',
416 'default_branch_perm',
417 'default_branch_perm',
417
418
418 'default_register',
419 'default_register',
419 'default_password_reset',
420 'default_password_reset',
420 'default_extern_activate'])
421 'default_extern_activate'])
421 self.sa.commit()
422 self.sa.commit()
422 except (DatabaseError,):
423 except (DatabaseError,):
423 log.error(traceback.format_exc())
424 log.error(traceback.format_exc())
424 self.sa.rollback()
425 self.sa.rollback()
425 raise
426 raise
426
427
427 def update_user_group_permissions(self, form_result):
428 def update_user_group_permissions(self, form_result):
428 if 'perm_user_group_id' in form_result:
429 if 'perm_user_group_id' in form_result:
429 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
430 perm_user_group = UserGroup.get(safe_int(form_result['perm_user_group_id']))
430 else:
431 else:
431 # used mostly to do lookup for default user
432 # used mostly to do lookup for default user
432 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
433 perm_user_group = UserGroup.get_by_group_name(form_result['perm_user_group_name'])
433 try:
434 try:
434 # stage 2 reset defaults and set them from form data
435 # stage 2 reset defaults and set them from form data
435 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
436 self._set_new_user_group_perms(perm_user_group, form_result, preserve=[
436 'default_repo_perm',
437 'default_repo_perm',
437 'default_group_perm',
438 'default_group_perm',
438 'default_user_group_perm',
439 'default_user_group_perm',
439 'default_branch_perm',
440 'default_branch_perm',
440
441
441 'default_register',
442 'default_register',
442 'default_password_reset',
443 'default_password_reset',
443 'default_extern_activate'])
444 'default_extern_activate'])
444 self.sa.commit()
445 self.sa.commit()
445 except (DatabaseError,):
446 except (DatabaseError,):
446 log.error(traceback.format_exc())
447 log.error(traceback.format_exc())
447 self.sa.rollback()
448 self.sa.rollback()
448 raise
449 raise
449
450
450 def update_object_permissions(self, form_result):
451 def update_object_permissions(self, form_result):
451 if 'perm_user_id' in form_result:
452 if 'perm_user_id' in form_result:
452 perm_user = User.get(safe_int(form_result['perm_user_id']))
453 perm_user = User.get(safe_int(form_result['perm_user_id']))
453 else:
454 else:
454 # used mostly to do lookup for default user
455 # used mostly to do lookup for default user
455 perm_user = User.get_by_username(form_result['perm_user_name'])
456 perm_user = User.get_by_username(form_result['perm_user_name'])
456 try:
457 try:
457
458
458 # stage 2 reset defaults and set them from form data
459 # stage 2 reset defaults and set them from form data
459 self._set_new_user_perms(perm_user, form_result, preserve=[
460 self._set_new_user_perms(perm_user, form_result, preserve=[
460 'default_repo_group_create',
461 'default_repo_group_create',
461 'default_user_group_create',
462 'default_user_group_create',
462 'default_repo_create_on_write',
463 'default_repo_create_on_write',
463 'default_repo_create',
464 'default_repo_create',
464 'default_fork_create',
465 'default_fork_create',
465 'default_inherit_default_permissions',
466 'default_inherit_default_permissions',
466 'default_branch_perm',
467 'default_branch_perm',
467
468
468 'default_register',
469 'default_register',
469 'default_password_reset',
470 'default_password_reset',
470 'default_extern_activate'])
471 'default_extern_activate'])
471
472
472 # overwrite default repo permissions
473 # overwrite default repo permissions
473 if form_result['overwrite_default_repo']:
474 if form_result['overwrite_default_repo']:
474 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
475 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
475 _def = Permission.get_by_key('repository.' + _def_name)
476 _def = Permission.get_by_key('repository.' + _def_name)
476 for r2p in self.sa.query(UserRepoToPerm)\
477 for r2p in self.sa.query(UserRepoToPerm)\
477 .filter(UserRepoToPerm.user == perm_user)\
478 .filter(UserRepoToPerm.user == perm_user)\
478 .all():
479 .all():
479 # don't reset PRIVATE repositories
480 # don't reset PRIVATE repositories
480 if not r2p.repository.private:
481 if not r2p.repository.private:
481 r2p.permission = _def
482 r2p.permission = _def
482 self.sa.add(r2p)
483 self.sa.add(r2p)
483
484
484 # overwrite default repo group permissions
485 # overwrite default repo group permissions
485 if form_result['overwrite_default_group']:
486 if form_result['overwrite_default_group']:
486 _def_name = form_result['default_group_perm'].split('group.')[-1]
487 _def_name = form_result['default_group_perm'].split('group.')[-1]
487 _def = Permission.get_by_key('group.' + _def_name)
488 _def = Permission.get_by_key('group.' + _def_name)
488 for g2p in self.sa.query(UserRepoGroupToPerm)\
489 for g2p in self.sa.query(UserRepoGroupToPerm)\
489 .filter(UserRepoGroupToPerm.user == perm_user)\
490 .filter(UserRepoGroupToPerm.user == perm_user)\
490 .all():
491 .all():
491 g2p.permission = _def
492 g2p.permission = _def
492 self.sa.add(g2p)
493 self.sa.add(g2p)
493
494
494 # overwrite default user group permissions
495 # overwrite default user group permissions
495 if form_result['overwrite_default_user_group']:
496 if form_result['overwrite_default_user_group']:
496 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
497 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
497 # user groups
498 # user groups
498 _def = Permission.get_by_key('usergroup.' + _def_name)
499 _def = Permission.get_by_key('usergroup.' + _def_name)
499 for g2p in self.sa.query(UserUserGroupToPerm)\
500 for g2p in self.sa.query(UserUserGroupToPerm)\
500 .filter(UserUserGroupToPerm.user == perm_user)\
501 .filter(UserUserGroupToPerm.user == perm_user)\
501 .all():
502 .all():
502 g2p.permission = _def
503 g2p.permission = _def
503 self.sa.add(g2p)
504 self.sa.add(g2p)
504
505
505 # COMMIT
506 # COMMIT
506 self.sa.commit()
507 self.sa.commit()
507 except (DatabaseError,):
508 except (DatabaseError,):
508 log.exception('Failed to set default object permissions')
509 log.exception('Failed to set default object permissions')
509 self.sa.rollback()
510 self.sa.rollback()
510 raise
511 raise
511
512
512 def update_branch_permissions(self, form_result):
513 def update_branch_permissions(self, form_result):
513 if 'perm_user_id' in form_result:
514 if 'perm_user_id' in form_result:
514 perm_user = User.get(safe_int(form_result['perm_user_id']))
515 perm_user = User.get(safe_int(form_result['perm_user_id']))
515 else:
516 else:
516 # used mostly to do lookup for default user
517 # used mostly to do lookup for default user
517 perm_user = User.get_by_username(form_result['perm_user_name'])
518 perm_user = User.get_by_username(form_result['perm_user_name'])
518 try:
519 try:
519
520
520 # stage 2 reset defaults and set them from form data
521 # stage 2 reset defaults and set them from form data
521 self._set_new_user_perms(perm_user, form_result, preserve=[
522 self._set_new_user_perms(perm_user, form_result, preserve=[
522 'default_repo_perm',
523 'default_repo_perm',
523 'default_group_perm',
524 'default_group_perm',
524 'default_user_group_perm',
525 'default_user_group_perm',
525
526
526 'default_repo_group_create',
527 'default_repo_group_create',
527 'default_user_group_create',
528 'default_user_group_create',
528 'default_repo_create_on_write',
529 'default_repo_create_on_write',
529 'default_repo_create',
530 'default_repo_create',
530 'default_fork_create',
531 'default_fork_create',
531 'default_inherit_default_permissions',
532 'default_inherit_default_permissions',
532
533
533 'default_register',
534 'default_register',
534 'default_password_reset',
535 'default_password_reset',
535 'default_extern_activate'])
536 'default_extern_activate'])
536
537
537 # overwrite default branch permissions
538 # overwrite default branch permissions
538 if form_result['overwrite_default_branch']:
539 if form_result['overwrite_default_branch']:
539 _def_name = \
540 _def_name = \
540 form_result['default_branch_perm'].split('branch.')[-1]
541 form_result['default_branch_perm'].split('branch.')[-1]
541
542
542 _def = Permission.get_by_key('branch.' + _def_name)
543 _def = Permission.get_by_key('branch.' + _def_name)
543
544
544 user_perms = UserToRepoBranchPermission.query()\
545 user_perms = UserToRepoBranchPermission.query()\
545 .join(UserToRepoBranchPermission.user_repo_to_perm)\
546 .join(UserToRepoBranchPermission.user_repo_to_perm)\
546 .filter(UserRepoToPerm.user == perm_user).all()
547 .filter(UserRepoToPerm.user == perm_user).all()
547
548
548 for g2p in user_perms:
549 for g2p in user_perms:
549 g2p.permission = _def
550 g2p.permission = _def
550 self.sa.add(g2p)
551 self.sa.add(g2p)
551
552
552 # COMMIT
553 # COMMIT
553 self.sa.commit()
554 self.sa.commit()
554 except (DatabaseError,):
555 except (DatabaseError,):
555 log.exception('Failed to set default branch permissions')
556 log.exception('Failed to set default branch permissions')
556 self.sa.rollback()
557 self.sa.rollback()
557 raise
558 raise
558
559
560 def trigger_permission_flush(self, affected_user_ids):
561 events.trigger(events.UserPermissionsChange(affected_user_ids))
562
563 def flush_user_permission_caches(self, changes, affected_user_ids=None):
564 affected_user_ids = affected_user_ids or []
565
566 for change in changes['added'] + changes['updated'] + changes['deleted']:
567 if change['type'] == 'user':
568 affected_user_ids.append(change['id'])
569 if change['type'] == 'user_group':
570 user_group = UserGroup.get(safe_int(change['id']))
571 if user_group:
572 group_members_ids = [x.user_id for x in user_group.members]
573 affected_user_ids.extend(group_members_ids)
574
575 self.trigger_permission_flush(affected_user_ids)
576
577 return affected_user_ids
General Comments 0
You need to be logged in to leave comments. Login now