##// END OF EJS Templates
permissions: properly flush user cache permissions in more cases of permission changes....
marcink -
r3887:85d5bce0 default
parent child Browse files
Show More
@@ -1,2327 +1,2332 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 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 def compute_fts_tree(repo_id, commit_id, root_path, cache_ver):
620 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621
622
622 try:
623 try:
623 # check if repo is not empty by any chance, skip quicker if it is.
624 # check if repo is not empty by any chance, skip quicker if it is.
624 _scm = repo.scm_instance()
625 _scm = repo.scm_instance()
625 if _scm.is_empty():
626 if _scm.is_empty():
626 return []
627 return []
627 except RepositoryError:
628 except RepositoryError:
628 log.exception("Exception occurred while trying to get repo nodes")
629 log.exception("Exception occurred while trying to get repo nodes")
629 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630
631
631 try:
632 try:
632 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 # sending 'master' is a pointer that needs to be translated to current commit.
634 # sending 'master' is a pointer that needs to be translated to current commit.
634 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 log.debug(
636 log.debug(
636 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'with caching: %s[TTL: %ss]' % (
638 'with caching: %s[TTL: %ss]' % (
638 repo_id, commit_id, cache_on, cache_seconds or 0))
639 repo_id, commit_id, cache_on, cache_seconds or 0))
639
640
640 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
641 tree_files = compute_fts_tree(repo_id, commit_id, root_path, 'v1')
641 return tree_files
642 return tree_files
642
643
643 except Exception:
644 except Exception:
644 log.exception("Exception occurred while trying to get repo nodes")
645 log.exception("Exception occurred while trying to get repo nodes")
645 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646
647
647
648
648 @jsonrpc_method()
649 @jsonrpc_method()
649 def get_repo_refs(request, apiuser, repoid):
650 def get_repo_refs(request, apiuser, repoid):
650 """
651 """
651 Returns a dictionary of current references. It returns
652 Returns a dictionary of current references. It returns
652 bookmarks, branches, closed_branches, and tags for given repository
653 bookmarks, branches, closed_branches, and tags for given repository
653
654
654 It's possible to specify ret_type to show only `files` or `dirs`.
655 It's possible to specify ret_type to show only `files` or `dirs`.
655
656
656 This command can only be run using an |authtoken| with admin rights,
657 This command can only be run using an |authtoken| with admin rights,
657 or users with at least read rights to |repos|.
658 or users with at least read rights to |repos|.
658
659
659 :param apiuser: This is filled automatically from the |authtoken|.
660 :param apiuser: This is filled automatically from the |authtoken|.
660 :type apiuser: AuthUser
661 :type apiuser: AuthUser
661 :param repoid: The repository name or repository ID.
662 :param repoid: The repository name or repository ID.
662 :type repoid: str or int
663 :type repoid: str or int
663
664
664 Example output:
665 Example output:
665
666
666 .. code-block:: bash
667 .. code-block:: bash
667
668
668 id : <id_given_in_input>
669 id : <id_given_in_input>
669 "result": {
670 "result": {
670 "bookmarks": {
671 "bookmarks": {
671 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 },
674 },
674 "branches": {
675 "branches": {
675 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 },
678 },
678 "branches_closed": {},
679 "branches_closed": {},
679 "tags": {
680 "tags": {
680 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 }
685 }
685 }
686 }
686 error: null
687 error: null
687 """
688 """
688
689
689 repo = get_repo_or_error(repoid)
690 repo = get_repo_or_error(repoid)
690 if not has_superadmin_permission(apiuser):
691 if not has_superadmin_permission(apiuser):
691 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 validate_repo_permissions(apiuser, repoid, repo, _perms)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
693
694
694 try:
695 try:
695 # check if repo is not empty by any chance, skip quicker if it is.
696 # check if repo is not empty by any chance, skip quicker if it is.
696 vcs_instance = repo.scm_instance()
697 vcs_instance = repo.scm_instance()
697 refs = vcs_instance.refs()
698 refs = vcs_instance.refs()
698 return refs
699 return refs
699 except Exception:
700 except Exception:
700 log.exception("Exception occurred while trying to get repo refs")
701 log.exception("Exception occurred while trying to get repo refs")
701 raise JSONRPCError(
702 raise JSONRPCError(
702 'failed to get repo: `%s` references' % repo.repo_name
703 'failed to get repo: `%s` references' % repo.repo_name
703 )
704 )
704
705
705
706
706 @jsonrpc_method()
707 @jsonrpc_method()
707 def create_repo(
708 def create_repo(
708 request, apiuser, repo_name, repo_type,
709 request, apiuser, repo_name, repo_type,
709 owner=Optional(OAttr('apiuser')),
710 owner=Optional(OAttr('apiuser')),
710 description=Optional(''),
711 description=Optional(''),
711 private=Optional(False),
712 private=Optional(False),
712 clone_uri=Optional(None),
713 clone_uri=Optional(None),
713 push_uri=Optional(None),
714 push_uri=Optional(None),
714 landing_rev=Optional(None),
715 landing_rev=Optional(None),
715 enable_statistics=Optional(False),
716 enable_statistics=Optional(False),
716 enable_locking=Optional(False),
717 enable_locking=Optional(False),
717 enable_downloads=Optional(False),
718 enable_downloads=Optional(False),
718 copy_permissions=Optional(False)):
719 copy_permissions=Optional(False)):
719 """
720 """
720 Creates a repository.
721 Creates a repository.
721
722
722 * If the repository name contains "/", repository will be created inside
723 * If the repository name contains "/", repository will be created inside
723 a repository group or nested repository groups
724 a repository group or nested repository groups
724
725
725 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 group "foo/bar". You have to have permissions to access and write to
727 group "foo/bar". You have to have permissions to access and write to
727 the last repository group ("bar" in this example)
728 the last repository group ("bar" in this example)
728
729
729 This command can only be run using an |authtoken| with at least
730 This command can only be run using an |authtoken| with at least
730 permissions to create repositories, or write permissions to
731 permissions to create repositories, or write permissions to
731 parent repository groups.
732 parent repository groups.
732
733
733 :param apiuser: This is filled automatically from the |authtoken|.
734 :param apiuser: This is filled automatically from the |authtoken|.
734 :type apiuser: AuthUser
735 :type apiuser: AuthUser
735 :param repo_name: Set the repository name.
736 :param repo_name: Set the repository name.
736 :type repo_name: str
737 :type repo_name: str
737 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :type repo_type: str
739 :type repo_type: str
739 :param owner: user_id or username
740 :param owner: user_id or username
740 :type owner: Optional(str)
741 :type owner: Optional(str)
741 :param description: Set the repository description.
742 :param description: Set the repository description.
742 :type description: Optional(str)
743 :type description: Optional(str)
743 :param private: set repository as private
744 :param private: set repository as private
744 :type private: bool
745 :type private: bool
745 :param clone_uri: set clone_uri
746 :param clone_uri: set clone_uri
746 :type clone_uri: str
747 :type clone_uri: str
747 :param push_uri: set push_uri
748 :param push_uri: set push_uri
748 :type push_uri: str
749 :type push_uri: str
749 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :type landing_rev: str
751 :type landing_rev: str
751 :param enable_locking:
752 :param enable_locking:
752 :type enable_locking: bool
753 :type enable_locking: bool
753 :param enable_downloads:
754 :param enable_downloads:
754 :type enable_downloads: bool
755 :type enable_downloads: bool
755 :param enable_statistics:
756 :param enable_statistics:
756 :type enable_statistics: bool
757 :type enable_statistics: bool
757 :param copy_permissions: Copy permission from group in which the
758 :param copy_permissions: Copy permission from group in which the
758 repository is being created.
759 repository is being created.
759 :type copy_permissions: bool
760 :type copy_permissions: bool
760
761
761
762
762 Example output:
763 Example output:
763
764
764 .. code-block:: bash
765 .. code-block:: bash
765
766
766 id : <id_given_in_input>
767 id : <id_given_in_input>
767 result: {
768 result: {
768 "msg": "Created new repository `<reponame>`",
769 "msg": "Created new repository `<reponame>`",
769 "success": true,
770 "success": true,
770 "task": "<celery task id or None if done sync>"
771 "task": "<celery task id or None if done sync>"
771 }
772 }
772 error: null
773 error: null
773
774
774
775
775 Example error output:
776 Example error output:
776
777
777 .. code-block:: bash
778 .. code-block:: bash
778
779
779 id : <id_given_in_input>
780 id : <id_given_in_input>
780 result : null
781 result : null
781 error : {
782 error : {
782 'failed to create repository `<repo_name>`'
783 'failed to create repository `<repo_name>`'
783 }
784 }
784
785
785 """
786 """
786
787
787 owner = validate_set_owner_permissions(apiuser, owner)
788 owner = validate_set_owner_permissions(apiuser, owner)
788
789
789 description = Optional.extract(description)
790 description = Optional.extract(description)
790 copy_permissions = Optional.extract(copy_permissions)
791 copy_permissions = Optional.extract(copy_permissions)
791 clone_uri = Optional.extract(clone_uri)
792 clone_uri = Optional.extract(clone_uri)
792 push_uri = Optional.extract(push_uri)
793 push_uri = Optional.extract(push_uri)
793
794
794 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 if isinstance(private, Optional):
796 if isinstance(private, Optional):
796 private = defs.get('repo_private') or Optional.extract(private)
797 private = defs.get('repo_private') or Optional.extract(private)
797 if isinstance(repo_type, Optional):
798 if isinstance(repo_type, Optional):
798 repo_type = defs.get('repo_type')
799 repo_type = defs.get('repo_type')
799 if isinstance(enable_statistics, Optional):
800 if isinstance(enable_statistics, Optional):
800 enable_statistics = defs.get('repo_enable_statistics')
801 enable_statistics = defs.get('repo_enable_statistics')
801 if isinstance(enable_locking, Optional):
802 if isinstance(enable_locking, Optional):
802 enable_locking = defs.get('repo_enable_locking')
803 enable_locking = defs.get('repo_enable_locking')
803 if isinstance(enable_downloads, Optional):
804 if isinstance(enable_downloads, Optional):
804 enable_downloads = defs.get('repo_enable_downloads')
805 enable_downloads = defs.get('repo_enable_downloads')
805
806
806 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices = list(set(ref_choices + [landing_ref]))
809 ref_choices = list(set(ref_choices + [landing_ref]))
809
810
810 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811
812
812 schema = repo_schema.RepoSchema().bind(
813 schema = repo_schema.RepoSchema().bind(
813 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_ref_options=ref_choices,
815 repo_ref_options=ref_choices,
815 repo_type=repo_type,
816 repo_type=repo_type,
816 # user caller
817 # user caller
817 user=apiuser)
818 user=apiuser)
818
819
819 try:
820 try:
820 schema_data = schema.deserialize(dict(
821 schema_data = schema.deserialize(dict(
821 repo_name=repo_name,
822 repo_name=repo_name,
822 repo_type=repo_type,
823 repo_type=repo_type,
823 repo_owner=owner.username,
824 repo_owner=owner.username,
824 repo_description=description,
825 repo_description=description,
825 repo_landing_commit_ref=landing_commit_ref,
826 repo_landing_commit_ref=landing_commit_ref,
826 repo_clone_uri=clone_uri,
827 repo_clone_uri=clone_uri,
827 repo_push_uri=push_uri,
828 repo_push_uri=push_uri,
828 repo_private=private,
829 repo_private=private,
829 repo_copy_permissions=copy_permissions,
830 repo_copy_permissions=copy_permissions,
830 repo_enable_statistics=enable_statistics,
831 repo_enable_statistics=enable_statistics,
831 repo_enable_downloads=enable_downloads,
832 repo_enable_downloads=enable_downloads,
832 repo_enable_locking=enable_locking))
833 repo_enable_locking=enable_locking))
833 except validation_schema.Invalid as err:
834 except validation_schema.Invalid as err:
834 raise JSONRPCValidationError(colander_exc=err)
835 raise JSONRPCValidationError(colander_exc=err)
835
836
836 try:
837 try:
837 data = {
838 data = {
838 'owner': owner,
839 'owner': owner,
839 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 'repo_name_full': schema_data['repo_name'],
841 'repo_name_full': schema_data['repo_name'],
841 'repo_group': schema_data['repo_group']['repo_group_id'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
842 'repo_type': schema_data['repo_type'],
843 'repo_type': schema_data['repo_type'],
843 'repo_description': schema_data['repo_description'],
844 'repo_description': schema_data['repo_description'],
844 'repo_private': schema_data['repo_private'],
845 'repo_private': schema_data['repo_private'],
845 'clone_uri': schema_data['repo_clone_uri'],
846 'clone_uri': schema_data['repo_clone_uri'],
846 'push_uri': schema_data['repo_push_uri'],
847 'push_uri': schema_data['repo_push_uri'],
847 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 'enable_statistics': schema_data['repo_enable_statistics'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
849 'enable_locking': schema_data['repo_enable_locking'],
850 'enable_locking': schema_data['repo_enable_locking'],
850 'enable_downloads': schema_data['repo_enable_downloads'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
851 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 }
853 }
853
854
854 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 task_id = get_task_id(task)
856 task_id = get_task_id(task)
856 # no commit, it's done in RepoModel, or async via celery
857 # no commit, it's done in RepoModel, or async via celery
857 return {
858 return {
858 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 'success': True, # cannot return the repo data here since fork
860 'success': True, # cannot return the repo data here since fork
860 # can be done async
861 # can be done async
861 'task': task_id
862 'task': task_id
862 }
863 }
863 except Exception:
864 except Exception:
864 log.exception(
865 log.exception(
865 u"Exception while trying to create the repository %s",
866 u"Exception while trying to create the repository %s",
866 schema_data['repo_name'])
867 schema_data['repo_name'])
867 raise JSONRPCError(
868 raise JSONRPCError(
868 'failed to create repository `%s`' % (schema_data['repo_name'],))
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
869
870
870
871
871 @jsonrpc_method()
872 @jsonrpc_method()
872 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 description=Optional('')):
874 description=Optional('')):
874 """
875 """
875 Adds an extra field to a repository.
876 Adds an extra field to a repository.
876
877
877 This command can only be run using an |authtoken| with at least
878 This command can only be run using an |authtoken| with at least
878 write permissions to the |repo|.
879 write permissions to the |repo|.
879
880
880 :param apiuser: This is filled automatically from the |authtoken|.
881 :param apiuser: This is filled automatically from the |authtoken|.
881 :type apiuser: AuthUser
882 :type apiuser: AuthUser
882 :param repoid: Set the repository name or repository id.
883 :param repoid: Set the repository name or repository id.
883 :type repoid: str or int
884 :type repoid: str or int
884 :param key: Create a unique field key for this repository.
885 :param key: Create a unique field key for this repository.
885 :type key: str
886 :type key: str
886 :param label:
887 :param label:
887 :type label: Optional(str)
888 :type label: Optional(str)
888 :param description:
889 :param description:
889 :type description: Optional(str)
890 :type description: Optional(str)
890 """
891 """
891 repo = get_repo_or_error(repoid)
892 repo = get_repo_or_error(repoid)
892 if not has_superadmin_permission(apiuser):
893 if not has_superadmin_permission(apiuser):
893 _perms = ('repository.admin',)
894 _perms = ('repository.admin',)
894 validate_repo_permissions(apiuser, repoid, repo, _perms)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
895
896
896 label = Optional.extract(label) or key
897 label = Optional.extract(label) or key
897 description = Optional.extract(description)
898 description = Optional.extract(description)
898
899
899 field = RepositoryField.get_by_key_name(key, repo)
900 field = RepositoryField.get_by_key_name(key, repo)
900 if field:
901 if field:
901 raise JSONRPCError('Field with key '
902 raise JSONRPCError('Field with key '
902 '`%s` exists for repo `%s`' % (key, repoid))
903 '`%s` exists for repo `%s`' % (key, repoid))
903
904
904 try:
905 try:
905 RepoModel().add_repo_field(repo, key, field_label=label,
906 RepoModel().add_repo_field(repo, key, field_label=label,
906 field_desc=description)
907 field_desc=description)
907 Session().commit()
908 Session().commit()
908 return {
909 return {
909 'msg': "Added new repository field `%s`" % (key,),
910 'msg': "Added new repository field `%s`" % (key,),
910 'success': True,
911 'success': True,
911 }
912 }
912 except Exception:
913 except Exception:
913 log.exception("Exception occurred while trying to add field to repo")
914 log.exception("Exception occurred while trying to add field to repo")
914 raise JSONRPCError(
915 raise JSONRPCError(
915 'failed to create new field for repository `%s`' % (repoid,))
916 'failed to create new field for repository `%s`' % (repoid,))
916
917
917
918
918 @jsonrpc_method()
919 @jsonrpc_method()
919 def remove_field_from_repo(request, apiuser, repoid, key):
920 def remove_field_from_repo(request, apiuser, repoid, key):
920 """
921 """
921 Removes an extra field from a repository.
922 Removes an extra field from a repository.
922
923
923 This command can only be run using an |authtoken| with at least
924 This command can only be run using an |authtoken| with at least
924 write permissions to the |repo|.
925 write permissions to the |repo|.
925
926
926 :param apiuser: This is filled automatically from the |authtoken|.
927 :param apiuser: This is filled automatically from the |authtoken|.
927 :type apiuser: AuthUser
928 :type apiuser: AuthUser
928 :param repoid: Set the repository name or repository ID.
929 :param repoid: Set the repository name or repository ID.
929 :type repoid: str or int
930 :type repoid: str or int
930 :param key: Set the unique field key for this repository.
931 :param key: Set the unique field key for this repository.
931 :type key: str
932 :type key: str
932 """
933 """
933
934
934 repo = get_repo_or_error(repoid)
935 repo = get_repo_or_error(repoid)
935 if not has_superadmin_permission(apiuser):
936 if not has_superadmin_permission(apiuser):
936 _perms = ('repository.admin',)
937 _perms = ('repository.admin',)
937 validate_repo_permissions(apiuser, repoid, repo, _perms)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
938
939
939 field = RepositoryField.get_by_key_name(key, repo)
940 field = RepositoryField.get_by_key_name(key, repo)
940 if not field:
941 if not field:
941 raise JSONRPCError('Field with key `%s` does not '
942 raise JSONRPCError('Field with key `%s` does not '
942 'exists for repo `%s`' % (key, repoid))
943 'exists for repo `%s`' % (key, repoid))
943
944
944 try:
945 try:
945 RepoModel().delete_repo_field(repo, field_key=key)
946 RepoModel().delete_repo_field(repo, field_key=key)
946 Session().commit()
947 Session().commit()
947 return {
948 return {
948 'msg': "Deleted repository field `%s`" % (key,),
949 'msg': "Deleted repository field `%s`" % (key,),
949 'success': True,
950 'success': True,
950 }
951 }
951 except Exception:
952 except Exception:
952 log.exception(
953 log.exception(
953 "Exception occurred while trying to delete field from repo")
954 "Exception occurred while trying to delete field from repo")
954 raise JSONRPCError(
955 raise JSONRPCError(
955 'failed to delete field for repository `%s`' % (repoid,))
956 'failed to delete field for repository `%s`' % (repoid,))
956
957
957
958
958 @jsonrpc_method()
959 @jsonrpc_method()
959 def update_repo(
960 def update_repo(
960 request, apiuser, repoid, repo_name=Optional(None),
961 request, apiuser, repoid, repo_name=Optional(None),
961 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 private=Optional(False),
963 private=Optional(False),
963 clone_uri=Optional(None), push_uri=Optional(None),
964 clone_uri=Optional(None), push_uri=Optional(None),
964 landing_rev=Optional(None), fork_of=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
965 enable_statistics=Optional(False),
966 enable_statistics=Optional(False),
966 enable_locking=Optional(False),
967 enable_locking=Optional(False),
967 enable_downloads=Optional(False), fields=Optional('')):
968 enable_downloads=Optional(False), fields=Optional('')):
968 """
969 """
969 Updates a repository with the given information.
970 Updates a repository with the given information.
970
971
971 This command can only be run using an |authtoken| with at least
972 This command can only be run using an |authtoken| with at least
972 admin permissions to the |repo|.
973 admin permissions to the |repo|.
973
974
974 * If the repository name contains "/", repository will be updated
975 * If the repository name contains "/", repository will be updated
975 accordingly with a repository group or nested repository groups
976 accordingly with a repository group or nested repository groups
976
977
977 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 called "repo-test" and place it inside group "foo/bar".
979 called "repo-test" and place it inside group "foo/bar".
979 You have to have permissions to access and write to the last repository
980 You have to have permissions to access and write to the last repository
980 group ("bar" in this example)
981 group ("bar" in this example)
981
982
982 :param apiuser: This is filled automatically from the |authtoken|.
983 :param apiuser: This is filled automatically from the |authtoken|.
983 :type apiuser: AuthUser
984 :type apiuser: AuthUser
984 :param repoid: repository name or repository ID.
985 :param repoid: repository name or repository ID.
985 :type repoid: str or int
986 :type repoid: str or int
986 :param repo_name: Update the |repo| name, including the
987 :param repo_name: Update the |repo| name, including the
987 repository group it's in.
988 repository group it's in.
988 :type repo_name: str
989 :type repo_name: str
989 :param owner: Set the |repo| owner.
990 :param owner: Set the |repo| owner.
990 :type owner: str
991 :type owner: str
991 :param fork_of: Set the |repo| as fork of another |repo|.
992 :param fork_of: Set the |repo| as fork of another |repo|.
992 :type fork_of: str
993 :type fork_of: str
993 :param description: Update the |repo| description.
994 :param description: Update the |repo| description.
994 :type description: str
995 :type description: str
995 :param private: Set the |repo| as private. (True | False)
996 :param private: Set the |repo| as private. (True | False)
996 :type private: bool
997 :type private: bool
997 :param clone_uri: Update the |repo| clone URI.
998 :param clone_uri: Update the |repo| clone URI.
998 :type clone_uri: str
999 :type clone_uri: str
999 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 :type landing_rev: str
1001 :type landing_rev: str
1001 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 :type enable_statistics: bool
1003 :type enable_statistics: bool
1003 :param enable_locking: Enable |repo| locking.
1004 :param enable_locking: Enable |repo| locking.
1004 :type enable_locking: bool
1005 :type enable_locking: bool
1005 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 :type enable_downloads: bool
1007 :type enable_downloads: bool
1007 :param fields: Add extra fields to the |repo|. Use the following
1008 :param fields: Add extra fields to the |repo|. Use the following
1008 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 Escape ', ' with \,
1010 Escape ', ' with \,
1010 :type fields: str
1011 :type fields: str
1011 """
1012 """
1012
1013
1013 repo = get_repo_or_error(repoid)
1014 repo = get_repo_or_error(repoid)
1014
1015
1015 include_secrets = False
1016 include_secrets = False
1016 if not has_superadmin_permission(apiuser):
1017 if not has_superadmin_permission(apiuser):
1017 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 else:
1019 else:
1019 include_secrets = True
1020 include_secrets = True
1020
1021
1021 updates = dict(
1022 updates = dict(
1022 repo_name=repo_name
1023 repo_name=repo_name
1023 if not isinstance(repo_name, Optional) else repo.repo_name,
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1024
1025
1025 fork_id=fork_of
1026 fork_id=fork_of
1026 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027
1028
1028 user=owner
1029 user=owner
1029 if not isinstance(owner, Optional) else repo.user.username,
1030 if not isinstance(owner, Optional) else repo.user.username,
1030
1031
1031 repo_description=description
1032 repo_description=description
1032 if not isinstance(description, Optional) else repo.description,
1033 if not isinstance(description, Optional) else repo.description,
1033
1034
1034 repo_private=private
1035 repo_private=private
1035 if not isinstance(private, Optional) else repo.private,
1036 if not isinstance(private, Optional) else repo.private,
1036
1037
1037 clone_uri=clone_uri
1038 clone_uri=clone_uri
1038 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039
1040
1040 push_uri=push_uri
1041 push_uri=push_uri
1041 if not isinstance(push_uri, Optional) else repo.push_uri,
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1042
1043
1043 repo_landing_rev=landing_rev
1044 repo_landing_rev=landing_rev
1044 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045
1046
1046 repo_enable_statistics=enable_statistics
1047 repo_enable_statistics=enable_statistics
1047 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048
1049
1049 repo_enable_locking=enable_locking
1050 repo_enable_locking=enable_locking
1050 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051
1052
1052 repo_enable_downloads=enable_downloads
1053 repo_enable_downloads=enable_downloads
1053 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054
1055
1055 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 request.translate, repo=repo)
1058 request.translate, repo=repo)
1058 ref_choices = list(set(ref_choices + [landing_ref]))
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1059
1060
1060 old_values = repo.get_api_data()
1061 old_values = repo.get_api_data()
1061 repo_type = repo.repo_type
1062 repo_type = repo.repo_type
1062 schema = repo_schema.RepoSchema().bind(
1063 schema = repo_schema.RepoSchema().bind(
1063 repo_type_options=rhodecode.BACKENDS.keys(),
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1064 repo_ref_options=ref_choices,
1065 repo_ref_options=ref_choices,
1065 repo_type=repo_type,
1066 repo_type=repo_type,
1066 # user caller
1067 # user caller
1067 user=apiuser,
1068 user=apiuser,
1068 old_values=old_values)
1069 old_values=old_values)
1069 try:
1070 try:
1070 schema_data = schema.deserialize(dict(
1071 schema_data = schema.deserialize(dict(
1071 # we save old value, users cannot change type
1072 # we save old value, users cannot change type
1072 repo_type=repo_type,
1073 repo_type=repo_type,
1073
1074
1074 repo_name=updates['repo_name'],
1075 repo_name=updates['repo_name'],
1075 repo_owner=updates['user'],
1076 repo_owner=updates['user'],
1076 repo_description=updates['repo_description'],
1077 repo_description=updates['repo_description'],
1077 repo_clone_uri=updates['clone_uri'],
1078 repo_clone_uri=updates['clone_uri'],
1078 repo_push_uri=updates['push_uri'],
1079 repo_push_uri=updates['push_uri'],
1079 repo_fork_of=updates['fork_id'],
1080 repo_fork_of=updates['fork_id'],
1080 repo_private=updates['repo_private'],
1081 repo_private=updates['repo_private'],
1081 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 repo_enable_statistics=updates['repo_enable_statistics'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1083 repo_enable_downloads=updates['repo_enable_downloads'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1084 repo_enable_locking=updates['repo_enable_locking']))
1085 repo_enable_locking=updates['repo_enable_locking']))
1085 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1086 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1087
1088
1088 # save validated data back into the updates dict
1089 # save validated data back into the updates dict
1089 validated_updates = dict(
1090 validated_updates = dict(
1090 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 repo_group=schema_data['repo_group']['repo_group_id'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1092
1093
1093 user=schema_data['repo_owner'],
1094 user=schema_data['repo_owner'],
1094 repo_description=schema_data['repo_description'],
1095 repo_description=schema_data['repo_description'],
1095 repo_private=schema_data['repo_private'],
1096 repo_private=schema_data['repo_private'],
1096 clone_uri=schema_data['repo_clone_uri'],
1097 clone_uri=schema_data['repo_clone_uri'],
1097 push_uri=schema_data['repo_push_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1098 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 repo_enable_locking=schema_data['repo_enable_locking'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1101 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 )
1103 )
1103
1104
1104 if schema_data['repo_fork_of']:
1105 if schema_data['repo_fork_of']:
1105 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 validated_updates['fork_id'] = fork_repo.repo_id
1107 validated_updates['fork_id'] = fork_repo.repo_id
1107
1108
1108 # extra fields
1109 # extra fields
1109 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 if fields:
1111 if fields:
1111 validated_updates.update(fields)
1112 validated_updates.update(fields)
1112
1113
1113 try:
1114 try:
1114 RepoModel().update(repo, **validated_updates)
1115 RepoModel().update(repo, **validated_updates)
1115 audit_logger.store_api(
1116 audit_logger.store_api(
1116 'repo.edit', action_data={'old_data': old_values},
1117 'repo.edit', action_data={'old_data': old_values},
1117 user=apiuser, repo=repo)
1118 user=apiuser, repo=repo)
1118 Session().commit()
1119 Session().commit()
1119 return {
1120 return {
1120 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 }
1123 }
1123 except Exception:
1124 except Exception:
1124 log.exception(
1125 log.exception(
1125 u"Exception while trying to update the repository %s",
1126 u"Exception while trying to update the repository %s",
1126 repoid)
1127 repoid)
1127 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128
1129
1129
1130
1130 @jsonrpc_method()
1131 @jsonrpc_method()
1131 def fork_repo(request, apiuser, repoid, fork_name,
1132 def fork_repo(request, apiuser, repoid, fork_name,
1132 owner=Optional(OAttr('apiuser')),
1133 owner=Optional(OAttr('apiuser')),
1133 description=Optional(''),
1134 description=Optional(''),
1134 private=Optional(False),
1135 private=Optional(False),
1135 clone_uri=Optional(None),
1136 clone_uri=Optional(None),
1136 landing_rev=Optional(None),
1137 landing_rev=Optional(None),
1137 copy_permissions=Optional(False)):
1138 copy_permissions=Optional(False)):
1138 """
1139 """
1139 Creates a fork of the specified |repo|.
1140 Creates a fork of the specified |repo|.
1140
1141
1141 * If the fork_name contains "/", fork will be created inside
1142 * If the fork_name contains "/", fork will be created inside
1142 a repository group or nested repository groups
1143 a repository group or nested repository groups
1143
1144
1144 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 inside group "foo/bar". You have to have permissions to access and
1146 inside group "foo/bar". You have to have permissions to access and
1146 write to the last repository group ("bar" in this example)
1147 write to the last repository group ("bar" in this example)
1147
1148
1148 This command can only be run using an |authtoken| with minimum
1149 This command can only be run using an |authtoken| with minimum
1149 read permissions of the forked repo, create fork permissions for an user.
1150 read permissions of the forked repo, create fork permissions for an user.
1150
1151
1151 :param apiuser: This is filled automatically from the |authtoken|.
1152 :param apiuser: This is filled automatically from the |authtoken|.
1152 :type apiuser: AuthUser
1153 :type apiuser: AuthUser
1153 :param repoid: Set repository name or repository ID.
1154 :param repoid: Set repository name or repository ID.
1154 :type repoid: str or int
1155 :type repoid: str or int
1155 :param fork_name: Set the fork name, including it's repository group membership.
1156 :param fork_name: Set the fork name, including it's repository group membership.
1156 :type fork_name: str
1157 :type fork_name: str
1157 :param owner: Set the fork owner.
1158 :param owner: Set the fork owner.
1158 :type owner: str
1159 :type owner: str
1159 :param description: Set the fork description.
1160 :param description: Set the fork description.
1160 :type description: str
1161 :type description: str
1161 :param copy_permissions: Copy permissions from parent |repo|. The
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1162 default is False.
1163 default is False.
1163 :type copy_permissions: bool
1164 :type copy_permissions: bool
1164 :param private: Make the fork private. The default is False.
1165 :param private: Make the fork private. The default is False.
1165 :type private: bool
1166 :type private: bool
1166 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167
1168
1168 Example output:
1169 Example output:
1169
1170
1170 .. code-block:: bash
1171 .. code-block:: bash
1171
1172
1172 id : <id_for_response>
1173 id : <id_for_response>
1173 api_key : "<api_key>"
1174 api_key : "<api_key>"
1174 args: {
1175 args: {
1175 "repoid" : "<reponame or repo_id>",
1176 "repoid" : "<reponame or repo_id>",
1176 "fork_name": "<forkname>",
1177 "fork_name": "<forkname>",
1177 "owner": "<username or user_id = Optional(=apiuser)>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1178 "description": "<description>",
1179 "description": "<description>",
1179 "copy_permissions": "<bool>",
1180 "copy_permissions": "<bool>",
1180 "private": "<bool>",
1181 "private": "<bool>",
1181 "landing_rev": "<landing_rev>"
1182 "landing_rev": "<landing_rev>"
1182 }
1183 }
1183
1184
1184 Example error output:
1185 Example error output:
1185
1186
1186 .. code-block:: bash
1187 .. code-block:: bash
1187
1188
1188 id : <id_given_in_input>
1189 id : <id_given_in_input>
1189 result: {
1190 result: {
1190 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 "success": true,
1192 "success": true,
1192 "task": "<celery task id or None if done sync>"
1193 "task": "<celery task id or None if done sync>"
1193 }
1194 }
1194 error: null
1195 error: null
1195
1196
1196 """
1197 """
1197
1198
1198 repo = get_repo_or_error(repoid)
1199 repo = get_repo_or_error(repoid)
1199 repo_name = repo.repo_name
1200 repo_name = repo.repo_name
1200
1201
1201 if not has_superadmin_permission(apiuser):
1202 if not has_superadmin_permission(apiuser):
1202 # check if we have at least read permission for
1203 # check if we have at least read permission for
1203 # this repo that we fork !
1204 # this repo that we fork !
1204 _perms = (
1205 _perms = (
1205 'repository.admin', 'repository.write', 'repository.read')
1206 'repository.admin', 'repository.write', 'repository.read')
1206 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207
1208
1208 # check if the regular user has at least fork permissions as well
1209 # check if the regular user has at least fork permissions as well
1209 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 raise JSONRPCForbidden()
1211 raise JSONRPCForbidden()
1211
1212
1212 # check if user can set owner parameter
1213 # check if user can set owner parameter
1213 owner = validate_set_owner_permissions(apiuser, owner)
1214 owner = validate_set_owner_permissions(apiuser, owner)
1214
1215
1215 description = Optional.extract(description)
1216 description = Optional.extract(description)
1216 copy_permissions = Optional.extract(copy_permissions)
1217 copy_permissions = Optional.extract(copy_permissions)
1217 clone_uri = Optional.extract(clone_uri)
1218 clone_uri = Optional.extract(clone_uri)
1218
1219
1219 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices = list(set(ref_choices + [landing_ref]))
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1222 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223
1224
1224 private = Optional.extract(private)
1225 private = Optional.extract(private)
1225
1226
1226 schema = repo_schema.RepoSchema().bind(
1227 schema = repo_schema.RepoSchema().bind(
1227 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_ref_options=ref_choices,
1229 repo_ref_options=ref_choices,
1229 repo_type=repo.repo_type,
1230 repo_type=repo.repo_type,
1230 # user caller
1231 # user caller
1231 user=apiuser)
1232 user=apiuser)
1232
1233
1233 try:
1234 try:
1234 schema_data = schema.deserialize(dict(
1235 schema_data = schema.deserialize(dict(
1235 repo_name=fork_name,
1236 repo_name=fork_name,
1236 repo_type=repo.repo_type,
1237 repo_type=repo.repo_type,
1237 repo_owner=owner.username,
1238 repo_owner=owner.username,
1238 repo_description=description,
1239 repo_description=description,
1239 repo_landing_commit_ref=landing_commit_ref,
1240 repo_landing_commit_ref=landing_commit_ref,
1240 repo_clone_uri=clone_uri,
1241 repo_clone_uri=clone_uri,
1241 repo_private=private,
1242 repo_private=private,
1242 repo_copy_permissions=copy_permissions))
1243 repo_copy_permissions=copy_permissions))
1243 except validation_schema.Invalid as err:
1244 except validation_schema.Invalid as err:
1244 raise JSONRPCValidationError(colander_exc=err)
1245 raise JSONRPCValidationError(colander_exc=err)
1245
1246
1246 try:
1247 try:
1247 data = {
1248 data = {
1248 'fork_parent_id': repo.repo_id,
1249 'fork_parent_id': repo.repo_id,
1249
1250
1250 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 'repo_name_full': schema_data['repo_name'],
1252 'repo_name_full': schema_data['repo_name'],
1252 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 'repo_type': schema_data['repo_type'],
1254 'repo_type': schema_data['repo_type'],
1254 'description': schema_data['repo_description'],
1255 'description': schema_data['repo_description'],
1255 'private': schema_data['repo_private'],
1256 'private': schema_data['repo_private'],
1256 'copy_permissions': schema_data['repo_copy_permissions'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1257 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 }
1259 }
1259
1260
1260 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 # no commit, it's done in RepoModel, or async via celery
1262 # no commit, it's done in RepoModel, or async via celery
1262 task_id = get_task_id(task)
1263 task_id = get_task_id(task)
1263
1264
1264 return {
1265 return {
1265 'msg': 'Created fork of `%s` as `%s`' % (
1266 'msg': 'Created fork of `%s` as `%s`' % (
1266 repo.repo_name, schema_data['repo_name']),
1267 repo.repo_name, schema_data['repo_name']),
1267 'success': True, # cannot return the repo data here since fork
1268 'success': True, # cannot return the repo data here since fork
1268 # can be done async
1269 # can be done async
1269 'task': task_id
1270 'task': task_id
1270 }
1271 }
1271 except Exception:
1272 except Exception:
1272 log.exception(
1273 log.exception(
1273 u"Exception while trying to create fork %s",
1274 u"Exception while trying to create fork %s",
1274 schema_data['repo_name'])
1275 schema_data['repo_name'])
1275 raise JSONRPCError(
1276 raise JSONRPCError(
1276 'failed to fork repository `%s` as `%s`' % (
1277 'failed to fork repository `%s` as `%s`' % (
1277 repo_name, schema_data['repo_name']))
1278 repo_name, schema_data['repo_name']))
1278
1279
1279
1280
1280 @jsonrpc_method()
1281 @jsonrpc_method()
1281 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 """
1283 """
1283 Deletes a repository.
1284 Deletes a repository.
1284
1285
1285 * When the `forks` parameter is set it's possible to detach or delete
1286 * When the `forks` parameter is set it's possible to detach or delete
1286 forks of deleted repository.
1287 forks of deleted repository.
1287
1288
1288 This command can only be run using an |authtoken| with admin
1289 This command can only be run using an |authtoken| with admin
1289 permissions on the |repo|.
1290 permissions on the |repo|.
1290
1291
1291 :param apiuser: This is filled automatically from the |authtoken|.
1292 :param apiuser: This is filled automatically from the |authtoken|.
1292 :type apiuser: AuthUser
1293 :type apiuser: AuthUser
1293 :param repoid: Set the repository name or repository ID.
1294 :param repoid: Set the repository name or repository ID.
1294 :type repoid: str or int
1295 :type repoid: str or int
1295 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 :type forks: Optional(str)
1297 :type forks: Optional(str)
1297
1298
1298 Example error output:
1299 Example error output:
1299
1300
1300 .. code-block:: bash
1301 .. code-block:: bash
1301
1302
1302 id : <id_given_in_input>
1303 id : <id_given_in_input>
1303 result: {
1304 result: {
1304 "msg": "Deleted repository `<reponame>`",
1305 "msg": "Deleted repository `<reponame>`",
1305 "success": true
1306 "success": true
1306 }
1307 }
1307 error: null
1308 error: null
1308 """
1309 """
1309
1310
1310 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1311 repo_name = repo.repo_name
1312 repo_name = repo.repo_name
1312 if not has_superadmin_permission(apiuser):
1313 if not has_superadmin_permission(apiuser):
1313 _perms = ('repository.admin',)
1314 _perms = ('repository.admin',)
1314 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315
1316
1316 try:
1317 try:
1317 handle_forks = Optional.extract(forks)
1318 handle_forks = Optional.extract(forks)
1318 _forks_msg = ''
1319 _forks_msg = ''
1319 _forks = [f for f in repo.forks]
1320 _forks = [f for f in repo.forks]
1320 if handle_forks == 'detach':
1321 if handle_forks == 'detach':
1321 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 elif handle_forks == 'delete':
1323 elif handle_forks == 'delete':
1323 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 elif _forks:
1325 elif _forks:
1325 raise JSONRPCError(
1326 raise JSONRPCError(
1326 'Cannot delete `%s` it still contains attached forks' %
1327 'Cannot delete `%s` it still contains attached forks' %
1327 (repo.repo_name,)
1328 (repo.repo_name,)
1328 )
1329 )
1329 old_data = repo.get_api_data()
1330 old_data = repo.get_api_data()
1330 RepoModel().delete(repo, forks=forks)
1331 RepoModel().delete(repo, forks=forks)
1331
1332
1332 repo = audit_logger.RepoWrap(repo_id=None,
1333 repo = audit_logger.RepoWrap(repo_id=None,
1333 repo_name=repo.repo_name)
1334 repo_name=repo.repo_name)
1334
1335
1335 audit_logger.store_api(
1336 audit_logger.store_api(
1336 'repo.delete', action_data={'old_data': old_data},
1337 'repo.delete', action_data={'old_data': old_data},
1337 user=apiuser, repo=repo)
1338 user=apiuser, repo=repo)
1338
1339
1339 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 Session().commit()
1341 Session().commit()
1341 return {
1342 return {
1342 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 'success': True
1344 'success': True
1344 }
1345 }
1345 except Exception:
1346 except Exception:
1346 log.exception("Exception occurred while trying to delete repo")
1347 log.exception("Exception occurred while trying to delete repo")
1347 raise JSONRPCError(
1348 raise JSONRPCError(
1348 'failed to delete repository `%s`' % (repo_name,)
1349 'failed to delete repository `%s`' % (repo_name,)
1349 )
1350 )
1350
1351
1351
1352
1352 #TODO: marcink, change name ?
1353 #TODO: marcink, change name ?
1353 @jsonrpc_method()
1354 @jsonrpc_method()
1354 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 """
1356 """
1356 Invalidates the cache for the specified repository.
1357 Invalidates the cache for the specified repository.
1357
1358
1358 This command can only be run using an |authtoken| with admin rights to
1359 This command can only be run using an |authtoken| with admin rights to
1359 the specified repository.
1360 the specified repository.
1360
1361
1361 This command takes the following options:
1362 This command takes the following options:
1362
1363
1363 :param apiuser: This is filled automatically from |authtoken|.
1364 :param apiuser: This is filled automatically from |authtoken|.
1364 :type apiuser: AuthUser
1365 :type apiuser: AuthUser
1365 :param repoid: Sets the repository name or repository ID.
1366 :param repoid: Sets the repository name or repository ID.
1366 :type repoid: str or int
1367 :type repoid: str or int
1367 :param delete_keys: This deletes the invalidated keys instead of
1368 :param delete_keys: This deletes the invalidated keys instead of
1368 just flagging them.
1369 just flagging them.
1369 :type delete_keys: Optional(``True`` | ``False``)
1370 :type delete_keys: Optional(``True`` | ``False``)
1370
1371
1371 Example output:
1372 Example output:
1372
1373
1373 .. code-block:: bash
1374 .. code-block:: bash
1374
1375
1375 id : <id_given_in_input>
1376 id : <id_given_in_input>
1376 result : {
1377 result : {
1377 'msg': Cache for repository `<repository name>` was invalidated,
1378 'msg': Cache for repository `<repository name>` was invalidated,
1378 'repository': <repository name>
1379 'repository': <repository name>
1379 }
1380 }
1380 error : null
1381 error : null
1381
1382
1382 Example error output:
1383 Example error output:
1383
1384
1384 .. code-block:: bash
1385 .. code-block:: bash
1385
1386
1386 id : <id_given_in_input>
1387 id : <id_given_in_input>
1387 result : null
1388 result : null
1388 error : {
1389 error : {
1389 'Error occurred during cache invalidation action'
1390 'Error occurred during cache invalidation action'
1390 }
1391 }
1391
1392
1392 """
1393 """
1393
1394
1394 repo = get_repo_or_error(repoid)
1395 repo = get_repo_or_error(repoid)
1395 if not has_superadmin_permission(apiuser):
1396 if not has_superadmin_permission(apiuser):
1396 _perms = ('repository.admin', 'repository.write',)
1397 _perms = ('repository.admin', 'repository.write',)
1397 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398
1399
1399 delete = Optional.extract(delete_keys)
1400 delete = Optional.extract(delete_keys)
1400 try:
1401 try:
1401 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 return {
1403 return {
1403 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 'repository': repo.repo_name
1405 'repository': repo.repo_name
1405 }
1406 }
1406 except Exception:
1407 except Exception:
1407 log.exception(
1408 log.exception(
1408 "Exception occurred while trying to invalidate repo cache")
1409 "Exception occurred while trying to invalidate repo cache")
1409 raise JSONRPCError(
1410 raise JSONRPCError(
1410 'Error occurred during cache invalidation action'
1411 'Error occurred during cache invalidation action'
1411 )
1412 )
1412
1413
1413
1414
1414 #TODO: marcink, change name ?
1415 #TODO: marcink, change name ?
1415 @jsonrpc_method()
1416 @jsonrpc_method()
1416 def lock(request, apiuser, repoid, locked=Optional(None),
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1417 userid=Optional(OAttr('apiuser'))):
1418 userid=Optional(OAttr('apiuser'))):
1418 """
1419 """
1419 Sets the lock state of the specified |repo| by the given user.
1420 Sets the lock state of the specified |repo| by the given user.
1420 From more information, see :ref:`repo-locking`.
1421 From more information, see :ref:`repo-locking`.
1421
1422
1422 * If the ``userid`` option is not set, the repository is locked to the
1423 * If the ``userid`` option is not set, the repository is locked to the
1423 user who called the method.
1424 user who called the method.
1424 * If the ``locked`` parameter is not set, the current lock state of the
1425 * If the ``locked`` parameter is not set, the current lock state of the
1425 repository is displayed.
1426 repository is displayed.
1426
1427
1427 This command can only be run using an |authtoken| with admin rights to
1428 This command can only be run using an |authtoken| with admin rights to
1428 the specified repository.
1429 the specified repository.
1429
1430
1430 This command takes the following options:
1431 This command takes the following options:
1431
1432
1432 :param apiuser: This is filled automatically from the |authtoken|.
1433 :param apiuser: This is filled automatically from the |authtoken|.
1433 :type apiuser: AuthUser
1434 :type apiuser: AuthUser
1434 :param repoid: Sets the repository name or repository ID.
1435 :param repoid: Sets the repository name or repository ID.
1435 :type repoid: str or int
1436 :type repoid: str or int
1436 :param locked: Sets the lock state.
1437 :param locked: Sets the lock state.
1437 :type locked: Optional(``True`` | ``False``)
1438 :type locked: Optional(``True`` | ``False``)
1438 :param userid: Set the repository lock to this user.
1439 :param userid: Set the repository lock to this user.
1439 :type userid: Optional(str or int)
1440 :type userid: Optional(str or int)
1440
1441
1441 Example error output:
1442 Example error output:
1442
1443
1443 .. code-block:: bash
1444 .. code-block:: bash
1444
1445
1445 id : <id_given_in_input>
1446 id : <id_given_in_input>
1446 result : {
1447 result : {
1447 'repo': '<reponame>',
1448 'repo': '<reponame>',
1448 'locked': <bool: lock state>,
1449 'locked': <bool: lock state>,
1449 'locked_since': <int: lock timestamp>,
1450 'locked_since': <int: lock timestamp>,
1450 'locked_by': <username of person who made the lock>,
1451 'locked_by': <username of person who made the lock>,
1451 'lock_reason': <str: reason for locking>,
1452 'lock_reason': <str: reason for locking>,
1452 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 or
1455 or
1455 'msg': 'Repo `<repository name>` not locked.'
1456 'msg': 'Repo `<repository name>` not locked.'
1456 or
1457 or
1457 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 }
1459 }
1459 error : null
1460 error : null
1460
1461
1461 Example error output:
1462 Example error output:
1462
1463
1463 .. code-block:: bash
1464 .. code-block:: bash
1464
1465
1465 id : <id_given_in_input>
1466 id : <id_given_in_input>
1466 result : null
1467 result : null
1467 error : {
1468 error : {
1468 'Error occurred locking repository `<reponame>`'
1469 'Error occurred locking repository `<reponame>`'
1469 }
1470 }
1470 """
1471 """
1471
1472
1472 repo = get_repo_or_error(repoid)
1473 repo = get_repo_or_error(repoid)
1473 if not has_superadmin_permission(apiuser):
1474 if not has_superadmin_permission(apiuser):
1474 # check if we have at least write permission for this repo !
1475 # check if we have at least write permission for this repo !
1475 _perms = ('repository.admin', 'repository.write',)
1476 _perms = ('repository.admin', 'repository.write',)
1476 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477
1478
1478 # make sure normal user does not pass someone else userid,
1479 # make sure normal user does not pass someone else userid,
1479 # he is not allowed to do that
1480 # he is not allowed to do that
1480 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 raise JSONRPCError('userid is not the same as your user')
1482 raise JSONRPCError('userid is not the same as your user')
1482
1483
1483 if isinstance(userid, Optional):
1484 if isinstance(userid, Optional):
1484 userid = apiuser.user_id
1485 userid = apiuser.user_id
1485
1486
1486 user = get_user_or_error(userid)
1487 user = get_user_or_error(userid)
1487
1488
1488 if isinstance(locked, Optional):
1489 if isinstance(locked, Optional):
1489 lockobj = repo.locked
1490 lockobj = repo.locked
1490
1491
1491 if lockobj[0] is None:
1492 if lockobj[0] is None:
1492 _d = {
1493 _d = {
1493 'repo': repo.repo_name,
1494 'repo': repo.repo_name,
1494 'locked': False,
1495 'locked': False,
1495 'locked_since': None,
1496 'locked_since': None,
1496 'locked_by': None,
1497 'locked_by': None,
1497 'lock_reason': None,
1498 'lock_reason': None,
1498 'lock_state_changed': False,
1499 'lock_state_changed': False,
1499 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 }
1501 }
1501 return _d
1502 return _d
1502 else:
1503 else:
1503 _user_id, _time, _reason = lockobj
1504 _user_id, _time, _reason = lockobj
1504 lock_user = get_user_or_error(userid)
1505 lock_user = get_user_or_error(userid)
1505 _d = {
1506 _d = {
1506 'repo': repo.repo_name,
1507 'repo': repo.repo_name,
1507 'locked': True,
1508 'locked': True,
1508 'locked_since': _time,
1509 'locked_since': _time,
1509 'locked_by': lock_user.username,
1510 'locked_by': lock_user.username,
1510 'lock_reason': _reason,
1511 'lock_reason': _reason,
1511 'lock_state_changed': False,
1512 'lock_state_changed': False,
1512 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 % (repo.repo_name, lock_user.username,
1514 % (repo.repo_name, lock_user.username,
1514 json.dumps(time_to_datetime(_time))))
1515 json.dumps(time_to_datetime(_time))))
1515 }
1516 }
1516 return _d
1517 return _d
1517
1518
1518 # force locked state through a flag
1519 # force locked state through a flag
1519 else:
1520 else:
1520 locked = str2bool(locked)
1521 locked = str2bool(locked)
1521 lock_reason = Repository.LOCK_API
1522 lock_reason = Repository.LOCK_API
1522 try:
1523 try:
1523 if locked:
1524 if locked:
1524 lock_time = time.time()
1525 lock_time = time.time()
1525 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 else:
1527 else:
1527 lock_time = None
1528 lock_time = None
1528 Repository.unlock(repo)
1529 Repository.unlock(repo)
1529 _d = {
1530 _d = {
1530 'repo': repo.repo_name,
1531 'repo': repo.repo_name,
1531 'locked': locked,
1532 'locked': locked,
1532 'locked_since': lock_time,
1533 'locked_since': lock_time,
1533 'locked_by': user.username,
1534 'locked_by': user.username,
1534 'lock_reason': lock_reason,
1535 'lock_reason': lock_reason,
1535 'lock_state_changed': True,
1536 'lock_state_changed': True,
1536 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 % (user.username, repo.repo_name, locked))
1538 % (user.username, repo.repo_name, locked))
1538 }
1539 }
1539 return _d
1540 return _d
1540 except Exception:
1541 except Exception:
1541 log.exception(
1542 log.exception(
1542 "Exception occurred while trying to lock repository")
1543 "Exception occurred while trying to lock repository")
1543 raise JSONRPCError(
1544 raise JSONRPCError(
1544 'Error occurred locking repository `%s`' % repo.repo_name
1545 'Error occurred locking repository `%s`' % repo.repo_name
1545 )
1546 )
1546
1547
1547
1548
1548 @jsonrpc_method()
1549 @jsonrpc_method()
1549 def comment_commit(
1550 def comment_commit(
1550 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 resolves_comment_id=Optional(None),
1553 resolves_comment_id=Optional(None),
1553 userid=Optional(OAttr('apiuser'))):
1554 userid=Optional(OAttr('apiuser'))):
1554 """
1555 """
1555 Set a commit comment, and optionally change the status of the commit.
1556 Set a commit comment, and optionally change the status of the commit.
1556
1557
1557 :param apiuser: This is filled automatically from the |authtoken|.
1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 :type apiuser: AuthUser
1559 :type apiuser: AuthUser
1559 :param repoid: Set the repository name or repository ID.
1560 :param repoid: Set the repository name or repository ID.
1560 :type repoid: str or int
1561 :type repoid: str or int
1561 :param commit_id: Specify the commit_id for which to set a comment.
1562 :param commit_id: Specify the commit_id for which to set a comment.
1562 :type commit_id: str
1563 :type commit_id: str
1563 :param message: The comment text.
1564 :param message: The comment text.
1564 :type message: str
1565 :type message: str
1565 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 'approved', 'rejected', 'under_review'
1567 'approved', 'rejected', 'under_review'
1567 :type status: str
1568 :type status: str
1568 :param comment_type: Comment type, one of: 'note', 'todo'
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1569 :type comment_type: Optional(str), default: 'note'
1570 :type comment_type: Optional(str), default: 'note'
1570 :param userid: Set the user name of the comment creator.
1571 :param userid: Set the user name of the comment creator.
1571 :type userid: Optional(str or int)
1572 :type userid: Optional(str or int)
1572
1573
1573 Example error output:
1574 Example error output:
1574
1575
1575 .. code-block:: bash
1576 .. code-block:: bash
1576
1577
1577 {
1578 {
1578 "id" : <id_given_in_input>,
1579 "id" : <id_given_in_input>,
1579 "result" : {
1580 "result" : {
1580 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1581 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1581 "status_change": null or <status>,
1582 "status_change": null or <status>,
1582 "success": true
1583 "success": true
1583 },
1584 },
1584 "error" : null
1585 "error" : null
1585 }
1586 }
1586
1587
1587 """
1588 """
1588 repo = get_repo_or_error(repoid)
1589 repo = get_repo_or_error(repoid)
1589 if not has_superadmin_permission(apiuser):
1590 if not has_superadmin_permission(apiuser):
1590 _perms = ('repository.read', 'repository.write', 'repository.admin')
1591 _perms = ('repository.read', 'repository.write', 'repository.admin')
1591 validate_repo_permissions(apiuser, repoid, repo, _perms)
1592 validate_repo_permissions(apiuser, repoid, repo, _perms)
1592
1593
1593 try:
1594 try:
1594 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1595 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1595 except Exception as e:
1596 except Exception as e:
1596 log.exception('Failed to fetch commit')
1597 log.exception('Failed to fetch commit')
1597 raise JSONRPCError(safe_str(e))
1598 raise JSONRPCError(safe_str(e))
1598
1599
1599 if isinstance(userid, Optional):
1600 if isinstance(userid, Optional):
1600 userid = apiuser.user_id
1601 userid = apiuser.user_id
1601
1602
1602 user = get_user_or_error(userid)
1603 user = get_user_or_error(userid)
1603 status = Optional.extract(status)
1604 status = Optional.extract(status)
1604 comment_type = Optional.extract(comment_type)
1605 comment_type = Optional.extract(comment_type)
1605 resolves_comment_id = Optional.extract(resolves_comment_id)
1606 resolves_comment_id = Optional.extract(resolves_comment_id)
1606
1607
1607 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1608 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1608 if status and status not in allowed_statuses:
1609 if status and status not in allowed_statuses:
1609 raise JSONRPCError('Bad status, must be on '
1610 raise JSONRPCError('Bad status, must be on '
1610 'of %s got %s' % (allowed_statuses, status,))
1611 'of %s got %s' % (allowed_statuses, status,))
1611
1612
1612 if resolves_comment_id:
1613 if resolves_comment_id:
1613 comment = ChangesetComment.get(resolves_comment_id)
1614 comment = ChangesetComment.get(resolves_comment_id)
1614 if not comment:
1615 if not comment:
1615 raise JSONRPCError(
1616 raise JSONRPCError(
1616 'Invalid resolves_comment_id `%s` for this commit.'
1617 'Invalid resolves_comment_id `%s` for this commit.'
1617 % resolves_comment_id)
1618 % resolves_comment_id)
1618 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1619 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1619 raise JSONRPCError(
1620 raise JSONRPCError(
1620 'Comment `%s` is wrong type for setting status to resolved.'
1621 'Comment `%s` is wrong type for setting status to resolved.'
1621 % resolves_comment_id)
1622 % resolves_comment_id)
1622
1623
1623 try:
1624 try:
1624 rc_config = SettingsModel().get_all_settings()
1625 rc_config = SettingsModel().get_all_settings()
1625 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1626 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1626 status_change_label = ChangesetStatus.get_status_lbl(status)
1627 status_change_label = ChangesetStatus.get_status_lbl(status)
1627 comment = CommentsModel().create(
1628 comment = CommentsModel().create(
1628 message, repo, user, commit_id=commit_id,
1629 message, repo, user, commit_id=commit_id,
1629 status_change=status_change_label,
1630 status_change=status_change_label,
1630 status_change_type=status,
1631 status_change_type=status,
1631 renderer=renderer,
1632 renderer=renderer,
1632 comment_type=comment_type,
1633 comment_type=comment_type,
1633 resolves_comment_id=resolves_comment_id,
1634 resolves_comment_id=resolves_comment_id,
1634 auth_user=apiuser
1635 auth_user=apiuser
1635 )
1636 )
1636 if status:
1637 if status:
1637 # also do a status change
1638 # also do a status change
1638 try:
1639 try:
1639 ChangesetStatusModel().set_status(
1640 ChangesetStatusModel().set_status(
1640 repo, status, user, comment, revision=commit_id,
1641 repo, status, user, comment, revision=commit_id,
1641 dont_allow_on_closed_pull_request=True
1642 dont_allow_on_closed_pull_request=True
1642 )
1643 )
1643 except StatusChangeOnClosedPullRequestError:
1644 except StatusChangeOnClosedPullRequestError:
1644 log.exception(
1645 log.exception(
1645 "Exception occurred while trying to change repo commit status")
1646 "Exception occurred while trying to change repo commit status")
1646 msg = ('Changing status on a changeset associated with '
1647 msg = ('Changing status on a changeset associated with '
1647 'a closed pull request is not allowed')
1648 'a closed pull request is not allowed')
1648 raise JSONRPCError(msg)
1649 raise JSONRPCError(msg)
1649
1650
1650 Session().commit()
1651 Session().commit()
1651 return {
1652 return {
1652 'msg': (
1653 'msg': (
1653 'Commented on commit `%s` for repository `%s`' % (
1654 'Commented on commit `%s` for repository `%s`' % (
1654 comment.revision, repo.repo_name)),
1655 comment.revision, repo.repo_name)),
1655 'status_change': status,
1656 'status_change': status,
1656 'success': True,
1657 'success': True,
1657 }
1658 }
1658 except JSONRPCError:
1659 except JSONRPCError:
1659 # catch any inside errors, and re-raise them to prevent from
1660 # catch any inside errors, and re-raise them to prevent from
1660 # below global catch to silence them
1661 # below global catch to silence them
1661 raise
1662 raise
1662 except Exception:
1663 except Exception:
1663 log.exception("Exception occurred while trying to comment on commit")
1664 log.exception("Exception occurred while trying to comment on commit")
1664 raise JSONRPCError(
1665 raise JSONRPCError(
1665 'failed to set comment on repository `%s`' % (repo.repo_name,)
1666 'failed to set comment on repository `%s`' % (repo.repo_name,)
1666 )
1667 )
1667
1668
1668
1669
1669 @jsonrpc_method()
1670 @jsonrpc_method()
1670 def get_repo_comments(request, apiuser, repoid,
1671 def get_repo_comments(request, apiuser, repoid,
1671 commit_id=Optional(None), comment_type=Optional(None),
1672 commit_id=Optional(None), comment_type=Optional(None),
1672 userid=Optional(None)):
1673 userid=Optional(None)):
1673 """
1674 """
1674 Get all comments for a repository
1675 Get all comments for a repository
1675
1676
1676 :param apiuser: This is filled automatically from the |authtoken|.
1677 :param apiuser: This is filled automatically from the |authtoken|.
1677 :type apiuser: AuthUser
1678 :type apiuser: AuthUser
1678 :param repoid: Set the repository name or repository ID.
1679 :param repoid: Set the repository name or repository ID.
1679 :type repoid: str or int
1680 :type repoid: str or int
1680 :param commit_id: Optionally filter the comments by the commit_id
1681 :param commit_id: Optionally filter the comments by the commit_id
1681 :type commit_id: Optional(str), default: None
1682 :type commit_id: Optional(str), default: None
1682 :param comment_type: Optionally filter the comments by the comment_type
1683 :param comment_type: Optionally filter the comments by the comment_type
1683 one of: 'note', 'todo'
1684 one of: 'note', 'todo'
1684 :type comment_type: Optional(str), default: None
1685 :type comment_type: Optional(str), default: None
1685 :param userid: Optionally filter the comments by the author of comment
1686 :param userid: Optionally filter the comments by the author of comment
1686 :type userid: Optional(str or int), Default: None
1687 :type userid: Optional(str or int), Default: None
1687
1688
1688 Example error output:
1689 Example error output:
1689
1690
1690 .. code-block:: bash
1691 .. code-block:: bash
1691
1692
1692 {
1693 {
1693 "id" : <id_given_in_input>,
1694 "id" : <id_given_in_input>,
1694 "result" : [
1695 "result" : [
1695 {
1696 {
1696 "comment_author": <USER_DETAILS>,
1697 "comment_author": <USER_DETAILS>,
1697 "comment_created_on": "2017-02-01T14:38:16.309",
1698 "comment_created_on": "2017-02-01T14:38:16.309",
1698 "comment_f_path": "file.txt",
1699 "comment_f_path": "file.txt",
1699 "comment_id": 282,
1700 "comment_id": 282,
1700 "comment_lineno": "n1",
1701 "comment_lineno": "n1",
1701 "comment_resolved_by": null,
1702 "comment_resolved_by": null,
1702 "comment_status": [],
1703 "comment_status": [],
1703 "comment_text": "This file needs a header",
1704 "comment_text": "This file needs a header",
1704 "comment_type": "todo"
1705 "comment_type": "todo"
1705 }
1706 }
1706 ],
1707 ],
1707 "error" : null
1708 "error" : null
1708 }
1709 }
1709
1710
1710 """
1711 """
1711 repo = get_repo_or_error(repoid)
1712 repo = get_repo_or_error(repoid)
1712 if not has_superadmin_permission(apiuser):
1713 if not has_superadmin_permission(apiuser):
1713 _perms = ('repository.read', 'repository.write', 'repository.admin')
1714 _perms = ('repository.read', 'repository.write', 'repository.admin')
1714 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715
1716
1716 commit_id = Optional.extract(commit_id)
1717 commit_id = Optional.extract(commit_id)
1717
1718
1718 userid = Optional.extract(userid)
1719 userid = Optional.extract(userid)
1719 if userid:
1720 if userid:
1720 user = get_user_or_error(userid)
1721 user = get_user_or_error(userid)
1721 else:
1722 else:
1722 user = None
1723 user = None
1723
1724
1724 comment_type = Optional.extract(comment_type)
1725 comment_type = Optional.extract(comment_type)
1725 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1726 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1726 raise JSONRPCError(
1727 raise JSONRPCError(
1727 'comment_type must be one of `{}` got {}'.format(
1728 'comment_type must be one of `{}` got {}'.format(
1728 ChangesetComment.COMMENT_TYPES, comment_type)
1729 ChangesetComment.COMMENT_TYPES, comment_type)
1729 )
1730 )
1730
1731
1731 comments = CommentsModel().get_repository_comments(
1732 comments = CommentsModel().get_repository_comments(
1732 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1733 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1733 return comments
1734 return comments
1734
1735
1735
1736
1736 @jsonrpc_method()
1737 @jsonrpc_method()
1737 def grant_user_permission(request, apiuser, repoid, userid, perm):
1738 def grant_user_permission(request, apiuser, repoid, userid, perm):
1738 """
1739 """
1739 Grant permissions for the specified user on the given repository,
1740 Grant permissions for the specified user on the given repository,
1740 or update existing permissions if found.
1741 or update existing permissions if found.
1741
1742
1742 This command can only be run using an |authtoken| with admin
1743 This command can only be run using an |authtoken| with admin
1743 permissions on the |repo|.
1744 permissions on the |repo|.
1744
1745
1745 :param apiuser: This is filled automatically from the |authtoken|.
1746 :param apiuser: This is filled automatically from the |authtoken|.
1746 :type apiuser: AuthUser
1747 :type apiuser: AuthUser
1747 :param repoid: Set the repository name or repository ID.
1748 :param repoid: Set the repository name or repository ID.
1748 :type repoid: str or int
1749 :type repoid: str or int
1749 :param userid: Set the user name.
1750 :param userid: Set the user name.
1750 :type userid: str
1751 :type userid: str
1751 :param perm: Set the user permissions, using the following format
1752 :param perm: Set the user permissions, using the following format
1752 ``(repository.(none|read|write|admin))``
1753 ``(repository.(none|read|write|admin))``
1753 :type perm: str
1754 :type perm: str
1754
1755
1755 Example output:
1756 Example output:
1756
1757
1757 .. code-block:: bash
1758 .. code-block:: bash
1758
1759
1759 id : <id_given_in_input>
1760 id : <id_given_in_input>
1760 result: {
1761 result: {
1761 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1762 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1762 "success": true
1763 "success": true
1763 }
1764 }
1764 error: null
1765 error: null
1765 """
1766 """
1766
1767
1767 repo = get_repo_or_error(repoid)
1768 repo = get_repo_or_error(repoid)
1768 user = get_user_or_error(userid)
1769 user = get_user_or_error(userid)
1769 perm = get_perm_or_error(perm)
1770 perm = get_perm_or_error(perm)
1770 if not has_superadmin_permission(apiuser):
1771 if not has_superadmin_permission(apiuser):
1771 _perms = ('repository.admin',)
1772 _perms = ('repository.admin',)
1772 validate_repo_permissions(apiuser, repoid, repo, _perms)
1773 validate_repo_permissions(apiuser, repoid, repo, _perms)
1773
1774
1774 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1775 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1775 try:
1776 try:
1776 changes = RepoModel().update_permissions(
1777 changes = RepoModel().update_permissions(
1777 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1778 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1778
1779
1779 action_data = {
1780 action_data = {
1780 'added': changes['added'],
1781 'added': changes['added'],
1781 'updated': changes['updated'],
1782 'updated': changes['updated'],
1782 'deleted': changes['deleted'],
1783 'deleted': changes['deleted'],
1783 }
1784 }
1784 audit_logger.store_api(
1785 audit_logger.store_api(
1785 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1786 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1787 Session().commit()
1788 PermissionModel().flush_user_permission_caches(changes)
1786
1789
1787 Session().commit()
1788 return {
1790 return {
1789 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1791 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1790 perm.permission_name, user.username, repo.repo_name
1792 perm.permission_name, user.username, repo.repo_name
1791 ),
1793 ),
1792 'success': True
1794 'success': True
1793 }
1795 }
1794 except Exception:
1796 except Exception:
1795 log.exception("Exception occurred while trying edit permissions for repo")
1797 log.exception("Exception occurred while trying edit permissions for repo")
1796 raise JSONRPCError(
1798 raise JSONRPCError(
1797 'failed to edit permission for user: `%s` in repo: `%s`' % (
1799 'failed to edit permission for user: `%s` in repo: `%s`' % (
1798 userid, repoid
1800 userid, repoid
1799 )
1801 )
1800 )
1802 )
1801
1803
1802
1804
1803 @jsonrpc_method()
1805 @jsonrpc_method()
1804 def revoke_user_permission(request, apiuser, repoid, userid):
1806 def revoke_user_permission(request, apiuser, repoid, userid):
1805 """
1807 """
1806 Revoke permission for a user on the specified repository.
1808 Revoke permission for a user on the specified repository.
1807
1809
1808 This command can only be run using an |authtoken| with admin
1810 This command can only be run using an |authtoken| with admin
1809 permissions on the |repo|.
1811 permissions on the |repo|.
1810
1812
1811 :param apiuser: This is filled automatically from the |authtoken|.
1813 :param apiuser: This is filled automatically from the |authtoken|.
1812 :type apiuser: AuthUser
1814 :type apiuser: AuthUser
1813 :param repoid: Set the repository name or repository ID.
1815 :param repoid: Set the repository name or repository ID.
1814 :type repoid: str or int
1816 :type repoid: str or int
1815 :param userid: Set the user name of revoked user.
1817 :param userid: Set the user name of revoked user.
1816 :type userid: str or int
1818 :type userid: str or int
1817
1819
1818 Example error output:
1820 Example error output:
1819
1821
1820 .. code-block:: bash
1822 .. code-block:: bash
1821
1823
1822 id : <id_given_in_input>
1824 id : <id_given_in_input>
1823 result: {
1825 result: {
1824 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1826 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1825 "success": true
1827 "success": true
1826 }
1828 }
1827 error: null
1829 error: null
1828 """
1830 """
1829
1831
1830 repo = get_repo_or_error(repoid)
1832 repo = get_repo_or_error(repoid)
1831 user = get_user_or_error(userid)
1833 user = get_user_or_error(userid)
1832 if not has_superadmin_permission(apiuser):
1834 if not has_superadmin_permission(apiuser):
1833 _perms = ('repository.admin',)
1835 _perms = ('repository.admin',)
1834 validate_repo_permissions(apiuser, repoid, repo, _perms)
1836 validate_repo_permissions(apiuser, repoid, repo, _perms)
1835
1837
1836 perm_deletions = [[user.user_id, None, "user"]]
1838 perm_deletions = [[user.user_id, None, "user"]]
1837 try:
1839 try:
1838 changes = RepoModel().update_permissions(
1840 changes = RepoModel().update_permissions(
1839 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1841 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1840
1842
1841 action_data = {
1843 action_data = {
1842 'added': changes['added'],
1844 'added': changes['added'],
1843 'updated': changes['updated'],
1845 'updated': changes['updated'],
1844 'deleted': changes['deleted'],
1846 'deleted': changes['deleted'],
1845 }
1847 }
1846 audit_logger.store_api(
1848 audit_logger.store_api(
1847 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1849 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1850 Session().commit()
1851 PermissionModel().flush_user_permission_caches(changes)
1848
1852
1849 Session().commit()
1850 return {
1853 return {
1851 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1854 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1852 user.username, repo.repo_name
1855 user.username, repo.repo_name
1853 ),
1856 ),
1854 'success': True
1857 'success': True
1855 }
1858 }
1856 except Exception:
1859 except Exception:
1857 log.exception("Exception occurred while trying revoke permissions to repo")
1860 log.exception("Exception occurred while trying revoke permissions to repo")
1858 raise JSONRPCError(
1861 raise JSONRPCError(
1859 'failed to edit permission for user: `%s` in repo: `%s`' % (
1862 'failed to edit permission for user: `%s` in repo: `%s`' % (
1860 userid, repoid
1863 userid, repoid
1861 )
1864 )
1862 )
1865 )
1863
1866
1864
1867
1865 @jsonrpc_method()
1868 @jsonrpc_method()
1866 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1869 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1867 """
1870 """
1868 Grant permission for a user group on the specified repository,
1871 Grant permission for a user group on the specified repository,
1869 or update existing permissions.
1872 or update existing permissions.
1870
1873
1871 This command can only be run using an |authtoken| with admin
1874 This command can only be run using an |authtoken| with admin
1872 permissions on the |repo|.
1875 permissions on the |repo|.
1873
1876
1874 :param apiuser: This is filled automatically from the |authtoken|.
1877 :param apiuser: This is filled automatically from the |authtoken|.
1875 :type apiuser: AuthUser
1878 :type apiuser: AuthUser
1876 :param repoid: Set the repository name or repository ID.
1879 :param repoid: Set the repository name or repository ID.
1877 :type repoid: str or int
1880 :type repoid: str or int
1878 :param usergroupid: Specify the ID of the user group.
1881 :param usergroupid: Specify the ID of the user group.
1879 :type usergroupid: str or int
1882 :type usergroupid: str or int
1880 :param perm: Set the user group permissions using the following
1883 :param perm: Set the user group permissions using the following
1881 format: (repository.(none|read|write|admin))
1884 format: (repository.(none|read|write|admin))
1882 :type perm: str
1885 :type perm: str
1883
1886
1884 Example output:
1887 Example 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 : {
1892 result : {
1890 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1893 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1891 "success": true
1894 "success": true
1892
1895
1893 }
1896 }
1894 error : null
1897 error : null
1895
1898
1896 Example error output:
1899 Example error output:
1897
1900
1898 .. code-block:: bash
1901 .. code-block:: bash
1899
1902
1900 id : <id_given_in_input>
1903 id : <id_given_in_input>
1901 result : null
1904 result : null
1902 error : {
1905 error : {
1903 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1906 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1904 }
1907 }
1905
1908
1906 """
1909 """
1907
1910
1908 repo = get_repo_or_error(repoid)
1911 repo = get_repo_or_error(repoid)
1909 perm = get_perm_or_error(perm)
1912 perm = get_perm_or_error(perm)
1910 if not has_superadmin_permission(apiuser):
1913 if not has_superadmin_permission(apiuser):
1911 _perms = ('repository.admin',)
1914 _perms = ('repository.admin',)
1912 validate_repo_permissions(apiuser, repoid, repo, _perms)
1915 validate_repo_permissions(apiuser, repoid, repo, _perms)
1913
1916
1914 user_group = get_user_group_or_error(usergroupid)
1917 user_group = get_user_group_or_error(usergroupid)
1915 if not has_superadmin_permission(apiuser):
1918 if not has_superadmin_permission(apiuser):
1916 # check if we have at least read permission for this user group !
1919 # check if we have at least read permission for this user group !
1917 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1920 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1918 if not HasUserGroupPermissionAnyApi(*_perms)(
1921 if not HasUserGroupPermissionAnyApi(*_perms)(
1919 user=apiuser, user_group_name=user_group.users_group_name):
1922 user=apiuser, user_group_name=user_group.users_group_name):
1920 raise JSONRPCError(
1923 raise JSONRPCError(
1921 'user group `%s` does not exist' % (usergroupid,))
1924 'user group `%s` does not exist' % (usergroupid,))
1922
1925
1923 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1926 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1924 try:
1927 try:
1925 changes = RepoModel().update_permissions(
1928 changes = RepoModel().update_permissions(
1926 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1929 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1927 action_data = {
1930 action_data = {
1928 'added': changes['added'],
1931 'added': changes['added'],
1929 'updated': changes['updated'],
1932 'updated': changes['updated'],
1930 'deleted': changes['deleted'],
1933 'deleted': changes['deleted'],
1931 }
1934 }
1932 audit_logger.store_api(
1935 audit_logger.store_api(
1933 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1936 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1937 Session().commit()
1938 PermissionModel().flush_user_permission_caches(changes)
1934
1939
1935 Session().commit()
1936 return {
1940 return {
1937 'msg': 'Granted perm: `%s` for user group: `%s` in '
1941 'msg': 'Granted perm: `%s` for user group: `%s` in '
1938 'repo: `%s`' % (
1942 'repo: `%s`' % (
1939 perm.permission_name, user_group.users_group_name,
1943 perm.permission_name, user_group.users_group_name,
1940 repo.repo_name
1944 repo.repo_name
1941 ),
1945 ),
1942 'success': True
1946 'success': True
1943 }
1947 }
1944 except Exception:
1948 except Exception:
1945 log.exception(
1949 log.exception(
1946 "Exception occurred while trying change permission on repo")
1950 "Exception occurred while trying change permission on repo")
1947 raise JSONRPCError(
1951 raise JSONRPCError(
1948 'failed to edit permission for user group: `%s` in '
1952 'failed to edit permission for user group: `%s` in '
1949 'repo: `%s`' % (
1953 'repo: `%s`' % (
1950 usergroupid, repo.repo_name
1954 usergroupid, repo.repo_name
1951 )
1955 )
1952 )
1956 )
1953
1957
1954
1958
1955 @jsonrpc_method()
1959 @jsonrpc_method()
1956 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1960 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1957 """
1961 """
1958 Revoke the permissions of a user group on a given repository.
1962 Revoke the permissions of a user group on a given repository.
1959
1963
1960 This command can only be run using an |authtoken| with admin
1964 This command can only be run using an |authtoken| with admin
1961 permissions on the |repo|.
1965 permissions on the |repo|.
1962
1966
1963 :param apiuser: This is filled automatically from the |authtoken|.
1967 :param apiuser: This is filled automatically from the |authtoken|.
1964 :type apiuser: AuthUser
1968 :type apiuser: AuthUser
1965 :param repoid: Set the repository name or repository ID.
1969 :param repoid: Set the repository name or repository ID.
1966 :type repoid: str or int
1970 :type repoid: str or int
1967 :param usergroupid: Specify the user group ID.
1971 :param usergroupid: Specify the user group ID.
1968 :type usergroupid: str or int
1972 :type usergroupid: str or int
1969
1973
1970 Example output:
1974 Example output:
1971
1975
1972 .. code-block:: bash
1976 .. code-block:: bash
1973
1977
1974 id : <id_given_in_input>
1978 id : <id_given_in_input>
1975 result: {
1979 result: {
1976 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1980 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1977 "success": true
1981 "success": true
1978 }
1982 }
1979 error: null
1983 error: null
1980 """
1984 """
1981
1985
1982 repo = get_repo_or_error(repoid)
1986 repo = get_repo_or_error(repoid)
1983 if not has_superadmin_permission(apiuser):
1987 if not has_superadmin_permission(apiuser):
1984 _perms = ('repository.admin',)
1988 _perms = ('repository.admin',)
1985 validate_repo_permissions(apiuser, repoid, repo, _perms)
1989 validate_repo_permissions(apiuser, repoid, repo, _perms)
1986
1990
1987 user_group = get_user_group_or_error(usergroupid)
1991 user_group = get_user_group_or_error(usergroupid)
1988 if not has_superadmin_permission(apiuser):
1992 if not has_superadmin_permission(apiuser):
1989 # check if we have at least read permission for this user group !
1993 # check if we have at least read permission for this user group !
1990 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1994 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1991 if not HasUserGroupPermissionAnyApi(*_perms)(
1995 if not HasUserGroupPermissionAnyApi(*_perms)(
1992 user=apiuser, user_group_name=user_group.users_group_name):
1996 user=apiuser, user_group_name=user_group.users_group_name):
1993 raise JSONRPCError(
1997 raise JSONRPCError(
1994 'user group `%s` does not exist' % (usergroupid,))
1998 'user group `%s` does not exist' % (usergroupid,))
1995
1999
1996 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2000 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1997 try:
2001 try:
1998 changes = RepoModel().update_permissions(
2002 changes = RepoModel().update_permissions(
1999 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2003 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2000 action_data = {
2004 action_data = {
2001 'added': changes['added'],
2005 'added': changes['added'],
2002 'updated': changes['updated'],
2006 'updated': changes['updated'],
2003 'deleted': changes['deleted'],
2007 'deleted': changes['deleted'],
2004 }
2008 }
2005 audit_logger.store_api(
2009 audit_logger.store_api(
2006 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2010 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2011 Session().commit()
2012 PermissionModel().flush_user_permission_caches(changes)
2007
2013
2008 Session().commit()
2009 return {
2014 return {
2010 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2015 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2011 user_group.users_group_name, repo.repo_name
2016 user_group.users_group_name, repo.repo_name
2012 ),
2017 ),
2013 'success': True
2018 'success': True
2014 }
2019 }
2015 except Exception:
2020 except Exception:
2016 log.exception("Exception occurred while trying revoke "
2021 log.exception("Exception occurred while trying revoke "
2017 "user group permission on repo")
2022 "user group permission on repo")
2018 raise JSONRPCError(
2023 raise JSONRPCError(
2019 'failed to edit permission for user group: `%s` in '
2024 'failed to edit permission for user group: `%s` in '
2020 'repo: `%s`' % (
2025 'repo: `%s`' % (
2021 user_group.users_group_name, repo.repo_name
2026 user_group.users_group_name, repo.repo_name
2022 )
2027 )
2023 )
2028 )
2024
2029
2025
2030
2026 @jsonrpc_method()
2031 @jsonrpc_method()
2027 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2032 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2028 """
2033 """
2029 Triggers a pull on the given repository from a remote location. You
2034 Triggers a pull on the given repository from a remote location. You
2030 can use this to keep remote repositories up-to-date.
2035 can use this to keep remote repositories up-to-date.
2031
2036
2032 This command can only be run using an |authtoken| with admin
2037 This command can only be run using an |authtoken| with admin
2033 rights to the specified repository. For more information,
2038 rights to the specified repository. For more information,
2034 see :ref:`config-token-ref`.
2039 see :ref:`config-token-ref`.
2035
2040
2036 This command takes the following options:
2041 This command takes the following options:
2037
2042
2038 :param apiuser: This is filled automatically from the |authtoken|.
2043 :param apiuser: This is filled automatically from the |authtoken|.
2039 :type apiuser: AuthUser
2044 :type apiuser: AuthUser
2040 :param repoid: The repository name or repository ID.
2045 :param repoid: The repository name or repository ID.
2041 :type repoid: str or int
2046 :type repoid: str or int
2042 :param remote_uri: Optional remote URI to pass in for pull
2047 :param remote_uri: Optional remote URI to pass in for pull
2043 :type remote_uri: str
2048 :type remote_uri: str
2044
2049
2045 Example output:
2050 Example output:
2046
2051
2047 .. code-block:: bash
2052 .. code-block:: bash
2048
2053
2049 id : <id_given_in_input>
2054 id : <id_given_in_input>
2050 result : {
2055 result : {
2051 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2056 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2052 "repository": "<repository name>"
2057 "repository": "<repository name>"
2053 }
2058 }
2054 error : null
2059 error : null
2055
2060
2056 Example error output:
2061 Example error output:
2057
2062
2058 .. code-block:: bash
2063 .. code-block:: bash
2059
2064
2060 id : <id_given_in_input>
2065 id : <id_given_in_input>
2061 result : null
2066 result : null
2062 error : {
2067 error : {
2063 "Unable to push changes from `<remote_url>`"
2068 "Unable to push changes from `<remote_url>`"
2064 }
2069 }
2065
2070
2066 """
2071 """
2067
2072
2068 repo = get_repo_or_error(repoid)
2073 repo = get_repo_or_error(repoid)
2069 remote_uri = Optional.extract(remote_uri)
2074 remote_uri = Optional.extract(remote_uri)
2070 remote_uri_display = remote_uri or repo.clone_uri_hidden
2075 remote_uri_display = remote_uri or repo.clone_uri_hidden
2071 if not has_superadmin_permission(apiuser):
2076 if not has_superadmin_permission(apiuser):
2072 _perms = ('repository.admin',)
2077 _perms = ('repository.admin',)
2073 validate_repo_permissions(apiuser, repoid, repo, _perms)
2078 validate_repo_permissions(apiuser, repoid, repo, _perms)
2074
2079
2075 try:
2080 try:
2076 ScmModel().pull_changes(
2081 ScmModel().pull_changes(
2077 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2082 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2078 return {
2083 return {
2079 'msg': 'Pulled from url `%s` on repo `%s`' % (
2084 'msg': 'Pulled from url `%s` on repo `%s`' % (
2080 remote_uri_display, repo.repo_name),
2085 remote_uri_display, repo.repo_name),
2081 'repository': repo.repo_name
2086 'repository': repo.repo_name
2082 }
2087 }
2083 except Exception:
2088 except Exception:
2084 log.exception("Exception occurred while trying to "
2089 log.exception("Exception occurred while trying to "
2085 "pull changes from remote location")
2090 "pull changes from remote location")
2086 raise JSONRPCError(
2091 raise JSONRPCError(
2087 'Unable to pull changes from `%s`' % remote_uri_display
2092 'Unable to pull changes from `%s`' % remote_uri_display
2088 )
2093 )
2089
2094
2090
2095
2091 @jsonrpc_method()
2096 @jsonrpc_method()
2092 def strip(request, apiuser, repoid, revision, branch):
2097 def strip(request, apiuser, repoid, revision, branch):
2093 """
2098 """
2094 Strips the given revision from the specified repository.
2099 Strips the given revision from the specified repository.
2095
2100
2096 * This will remove the revision and all of its decendants.
2101 * This will remove the revision and all of its decendants.
2097
2102
2098 This command can only be run using an |authtoken| with admin rights to
2103 This command can only be run using an |authtoken| with admin rights to
2099 the specified repository.
2104 the specified repository.
2100
2105
2101 This command takes the following options:
2106 This command takes the following options:
2102
2107
2103 :param apiuser: This is filled automatically from the |authtoken|.
2108 :param apiuser: This is filled automatically from the |authtoken|.
2104 :type apiuser: AuthUser
2109 :type apiuser: AuthUser
2105 :param repoid: The repository name or repository ID.
2110 :param repoid: The repository name or repository ID.
2106 :type repoid: str or int
2111 :type repoid: str or int
2107 :param revision: The revision you wish to strip.
2112 :param revision: The revision you wish to strip.
2108 :type revision: str
2113 :type revision: str
2109 :param branch: The branch from which to strip the revision.
2114 :param branch: The branch from which to strip the revision.
2110 :type branch: str
2115 :type branch: str
2111
2116
2112 Example output:
2117 Example output:
2113
2118
2114 .. code-block:: bash
2119 .. code-block:: bash
2115
2120
2116 id : <id_given_in_input>
2121 id : <id_given_in_input>
2117 result : {
2122 result : {
2118 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2123 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2119 "repository": "<repository name>"
2124 "repository": "<repository name>"
2120 }
2125 }
2121 error : null
2126 error : null
2122
2127
2123 Example error output:
2128 Example error output:
2124
2129
2125 .. code-block:: bash
2130 .. code-block:: bash
2126
2131
2127 id : <id_given_in_input>
2132 id : <id_given_in_input>
2128 result : null
2133 result : null
2129 error : {
2134 error : {
2130 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2135 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2131 }
2136 }
2132
2137
2133 """
2138 """
2134
2139
2135 repo = get_repo_or_error(repoid)
2140 repo = get_repo_or_error(repoid)
2136 if not has_superadmin_permission(apiuser):
2141 if not has_superadmin_permission(apiuser):
2137 _perms = ('repository.admin',)
2142 _perms = ('repository.admin',)
2138 validate_repo_permissions(apiuser, repoid, repo, _perms)
2143 validate_repo_permissions(apiuser, repoid, repo, _perms)
2139
2144
2140 try:
2145 try:
2141 ScmModel().strip(repo, revision, branch)
2146 ScmModel().strip(repo, revision, branch)
2142 audit_logger.store_api(
2147 audit_logger.store_api(
2143 'repo.commit.strip', action_data={'commit_id': revision},
2148 'repo.commit.strip', action_data={'commit_id': revision},
2144 repo=repo,
2149 repo=repo,
2145 user=apiuser, commit=True)
2150 user=apiuser, commit=True)
2146
2151
2147 return {
2152 return {
2148 'msg': 'Stripped commit %s from repo `%s`' % (
2153 'msg': 'Stripped commit %s from repo `%s`' % (
2149 revision, repo.repo_name),
2154 revision, repo.repo_name),
2150 'repository': repo.repo_name
2155 'repository': repo.repo_name
2151 }
2156 }
2152 except Exception:
2157 except Exception:
2153 log.exception("Exception while trying to strip")
2158 log.exception("Exception while trying to strip")
2154 raise JSONRPCError(
2159 raise JSONRPCError(
2155 'Unable to strip commit %s from repo `%s`' % (
2160 'Unable to strip commit %s from repo `%s`' % (
2156 revision, repo.repo_name)
2161 revision, repo.repo_name)
2157 )
2162 )
2158
2163
2159
2164
2160 @jsonrpc_method()
2165 @jsonrpc_method()
2161 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2166 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2162 """
2167 """
2163 Returns all settings for a repository. If key is given it only returns the
2168 Returns all settings for a repository. If key is given it only returns the
2164 setting identified by the key or null.
2169 setting identified by the key or null.
2165
2170
2166 :param apiuser: This is filled automatically from the |authtoken|.
2171 :param apiuser: This is filled automatically from the |authtoken|.
2167 :type apiuser: AuthUser
2172 :type apiuser: AuthUser
2168 :param repoid: The repository name or repository id.
2173 :param repoid: The repository name or repository id.
2169 :type repoid: str or int
2174 :type repoid: str or int
2170 :param key: Key of the setting to return.
2175 :param key: Key of the setting to return.
2171 :type: key: Optional(str)
2176 :type: key: Optional(str)
2172
2177
2173 Example output:
2178 Example output:
2174
2179
2175 .. code-block:: bash
2180 .. code-block:: bash
2176
2181
2177 {
2182 {
2178 "error": null,
2183 "error": null,
2179 "id": 237,
2184 "id": 237,
2180 "result": {
2185 "result": {
2181 "extensions_largefiles": true,
2186 "extensions_largefiles": true,
2182 "extensions_evolve": true,
2187 "extensions_evolve": true,
2183 "hooks_changegroup_push_logger": true,
2188 "hooks_changegroup_push_logger": true,
2184 "hooks_changegroup_repo_size": false,
2189 "hooks_changegroup_repo_size": false,
2185 "hooks_outgoing_pull_logger": true,
2190 "hooks_outgoing_pull_logger": true,
2186 "phases_publish": "True",
2191 "phases_publish": "True",
2187 "rhodecode_hg_use_rebase_for_merging": true,
2192 "rhodecode_hg_use_rebase_for_merging": true,
2188 "rhodecode_pr_merge_enabled": true,
2193 "rhodecode_pr_merge_enabled": true,
2189 "rhodecode_use_outdated_comments": true
2194 "rhodecode_use_outdated_comments": true
2190 }
2195 }
2191 }
2196 }
2192 """
2197 """
2193
2198
2194 # Restrict access to this api method to admins only.
2199 # Restrict access to this api method to admins only.
2195 if not has_superadmin_permission(apiuser):
2200 if not has_superadmin_permission(apiuser):
2196 raise JSONRPCForbidden()
2201 raise JSONRPCForbidden()
2197
2202
2198 try:
2203 try:
2199 repo = get_repo_or_error(repoid)
2204 repo = get_repo_or_error(repoid)
2200 settings_model = VcsSettingsModel(repo=repo)
2205 settings_model = VcsSettingsModel(repo=repo)
2201 settings = settings_model.get_global_settings()
2206 settings = settings_model.get_global_settings()
2202 settings.update(settings_model.get_repo_settings())
2207 settings.update(settings_model.get_repo_settings())
2203
2208
2204 # If only a single setting is requested fetch it from all settings.
2209 # If only a single setting is requested fetch it from all settings.
2205 key = Optional.extract(key)
2210 key = Optional.extract(key)
2206 if key is not None:
2211 if key is not None:
2207 settings = settings.get(key, None)
2212 settings = settings.get(key, None)
2208 except Exception:
2213 except Exception:
2209 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2214 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2210 log.exception(msg)
2215 log.exception(msg)
2211 raise JSONRPCError(msg)
2216 raise JSONRPCError(msg)
2212
2217
2213 return settings
2218 return settings
2214
2219
2215
2220
2216 @jsonrpc_method()
2221 @jsonrpc_method()
2217 def set_repo_settings(request, apiuser, repoid, settings):
2222 def set_repo_settings(request, apiuser, repoid, settings):
2218 """
2223 """
2219 Update repository settings. Returns true on success.
2224 Update repository settings. Returns true on success.
2220
2225
2221 :param apiuser: This is filled automatically from the |authtoken|.
2226 :param apiuser: This is filled automatically from the |authtoken|.
2222 :type apiuser: AuthUser
2227 :type apiuser: AuthUser
2223 :param repoid: The repository name or repository id.
2228 :param repoid: The repository name or repository id.
2224 :type repoid: str or int
2229 :type repoid: str or int
2225 :param settings: The new settings for the repository.
2230 :param settings: The new settings for the repository.
2226 :type: settings: dict
2231 :type: settings: dict
2227
2232
2228 Example output:
2233 Example output:
2229
2234
2230 .. code-block:: bash
2235 .. code-block:: bash
2231
2236
2232 {
2237 {
2233 "error": null,
2238 "error": null,
2234 "id": 237,
2239 "id": 237,
2235 "result": true
2240 "result": true
2236 }
2241 }
2237 """
2242 """
2238 # Restrict access to this api method to admins only.
2243 # Restrict access to this api method to admins only.
2239 if not has_superadmin_permission(apiuser):
2244 if not has_superadmin_permission(apiuser):
2240 raise JSONRPCForbidden()
2245 raise JSONRPCForbidden()
2241
2246
2242 if type(settings) is not dict:
2247 if type(settings) is not dict:
2243 raise JSONRPCError('Settings have to be a JSON Object.')
2248 raise JSONRPCError('Settings have to be a JSON Object.')
2244
2249
2245 try:
2250 try:
2246 settings_model = VcsSettingsModel(repo=repoid)
2251 settings_model = VcsSettingsModel(repo=repoid)
2247
2252
2248 # Merge global, repo and incoming settings.
2253 # Merge global, repo and incoming settings.
2249 new_settings = settings_model.get_global_settings()
2254 new_settings = settings_model.get_global_settings()
2250 new_settings.update(settings_model.get_repo_settings())
2255 new_settings.update(settings_model.get_repo_settings())
2251 new_settings.update(settings)
2256 new_settings.update(settings)
2252
2257
2253 # Update the settings.
2258 # Update the settings.
2254 inherit_global_settings = new_settings.get(
2259 inherit_global_settings = new_settings.get(
2255 'inherit_global_settings', False)
2260 'inherit_global_settings', False)
2256 settings_model.create_or_update_repo_settings(
2261 settings_model.create_or_update_repo_settings(
2257 new_settings, inherit_global_settings=inherit_global_settings)
2262 new_settings, inherit_global_settings=inherit_global_settings)
2258 Session().commit()
2263 Session().commit()
2259 except Exception:
2264 except Exception:
2260 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2265 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2261 log.exception(msg)
2266 log.exception(msg)
2262 raise JSONRPCError(msg)
2267 raise JSONRPCError(msg)
2263
2268
2264 # Indicate success.
2269 # Indicate success.
2265 return True
2270 return True
2266
2271
2267
2272
2268 @jsonrpc_method()
2273 @jsonrpc_method()
2269 def maintenance(request, apiuser, repoid):
2274 def maintenance(request, apiuser, repoid):
2270 """
2275 """
2271 Triggers a maintenance on the given repository.
2276 Triggers a maintenance on the given repository.
2272
2277
2273 This command can only be run using an |authtoken| with admin
2278 This command can only be run using an |authtoken| with admin
2274 rights to the specified repository. For more information,
2279 rights to the specified repository. For more information,
2275 see :ref:`config-token-ref`.
2280 see :ref:`config-token-ref`.
2276
2281
2277 This command takes the following options:
2282 This command takes the following options:
2278
2283
2279 :param apiuser: This is filled automatically from the |authtoken|.
2284 :param apiuser: This is filled automatically from the |authtoken|.
2280 :type apiuser: AuthUser
2285 :type apiuser: AuthUser
2281 :param repoid: The repository name or repository ID.
2286 :param repoid: The repository name or repository ID.
2282 :type repoid: str or int
2287 :type repoid: str or int
2283
2288
2284 Example output:
2289 Example output:
2285
2290
2286 .. code-block:: bash
2291 .. code-block:: bash
2287
2292
2288 id : <id_given_in_input>
2293 id : <id_given_in_input>
2289 result : {
2294 result : {
2290 "msg": "executed maintenance command",
2295 "msg": "executed maintenance command",
2291 "executed_actions": [
2296 "executed_actions": [
2292 <action_message>, <action_message2>...
2297 <action_message>, <action_message2>...
2293 ],
2298 ],
2294 "repository": "<repository name>"
2299 "repository": "<repository name>"
2295 }
2300 }
2296 error : null
2301 error : null
2297
2302
2298 Example error output:
2303 Example error output:
2299
2304
2300 .. code-block:: bash
2305 .. code-block:: bash
2301
2306
2302 id : <id_given_in_input>
2307 id : <id_given_in_input>
2303 result : null
2308 result : null
2304 error : {
2309 error : {
2305 "Unable to execute maintenance on `<reponame>`"
2310 "Unable to execute maintenance on `<reponame>`"
2306 }
2311 }
2307
2312
2308 """
2313 """
2309
2314
2310 repo = get_repo_or_error(repoid)
2315 repo = get_repo_or_error(repoid)
2311 if not has_superadmin_permission(apiuser):
2316 if not has_superadmin_permission(apiuser):
2312 _perms = ('repository.admin',)
2317 _perms = ('repository.admin',)
2313 validate_repo_permissions(apiuser, repoid, repo, _perms)
2318 validate_repo_permissions(apiuser, repoid, repo, _perms)
2314
2319
2315 try:
2320 try:
2316 maintenance = repo_maintenance.RepoMaintenance()
2321 maintenance = repo_maintenance.RepoMaintenance()
2317 executed_actions = maintenance.execute(repo)
2322 executed_actions = maintenance.execute(repo)
2318
2323
2319 return {
2324 return {
2320 'msg': 'executed maintenance command',
2325 'msg': 'executed maintenance command',
2321 'executed_actions': executed_actions,
2326 'executed_actions': executed_actions,
2322 'repository': repo.repo_name
2327 'repository': repo.repo_name
2323 }
2328 }
2324 except Exception:
2329 except Exception:
2325 log.exception("Exception occurred while trying to run maintenance")
2330 log.exception("Exception occurred while trying to run maintenance")
2326 raise JSONRPCError(
2331 raise JSONRPCError(
2327 'Unable to execute maintenance on `%s`' % repo.repo_name)
2332 '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,899 +1,907 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. If User Group will be named like
194 external authentication types like ldap. If User Group will be named like
194 one from e.g ldap and sync flag is enabled members will be synced automatically.
195 one from e.g ldap and sync flag is enabled members will be synced automatically.
195 Sync type when enabled via API is set to `manual_api`
196 Sync type when enabled via API is set to `manual_api`
196 :type sync: Optional(``True`` | ``False``)
197 :type sync: Optional(``True`` | ``False``)
197
198
198 Example output:
199 Example output:
199
200
200 .. code-block:: bash
201 .. code-block:: bash
201
202
202 id : <id_given_in_input>
203 id : <id_given_in_input>
203 result: {
204 result: {
204 "msg": "created new user group `<groupname>`",
205 "msg": "created new user group `<groupname>`",
205 "user_group": <user_group_object>
206 "user_group": <user_group_object>
206 }
207 }
207 error: null
208 error: null
208
209
209 Example error output:
210 Example error output:
210
211
211 .. code-block:: bash
212 .. code-block:: bash
212
213
213 id : <id_given_in_input>
214 id : <id_given_in_input>
214 result : null
215 result : null
215 error : {
216 error : {
216 "user group `<group name>` already exist"
217 "user group `<group name>` already exist"
217 or
218 or
218 "failed to create group `<group name>`"
219 "failed to create group `<group name>`"
219 }
220 }
220
221
221 """
222 """
222
223
223 if not has_superadmin_permission(apiuser):
224 if not has_superadmin_permission(apiuser):
224 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
225 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
225 raise JSONRPCForbidden()
226 raise JSONRPCForbidden()
226
227
227 if UserGroupModel().get_by_name(group_name):
228 if UserGroupModel().get_by_name(group_name):
228 raise JSONRPCError("user group `%s` already exist" % (group_name,))
229 raise JSONRPCError("user group `%s` already exist" % (group_name,))
229
230
230 if isinstance(owner, Optional):
231 if isinstance(owner, Optional):
231 owner = apiuser.user_id
232 owner = apiuser.user_id
232
233
233 owner = get_user_or_error(owner)
234 owner = get_user_or_error(owner)
234 active = Optional.extract(active)
235 active = Optional.extract(active)
235 description = Optional.extract(description)
236 description = Optional.extract(description)
236 sync = Optional.extract(sync)
237 sync = Optional.extract(sync)
237
238
238 # set the sync option based on group_data
239 # set the sync option based on group_data
239 group_data = None
240 group_data = None
240 if sync:
241 if sync:
241 group_data = {
242 group_data = {
242 'extern_type': 'manual_api',
243 'extern_type': 'manual_api',
243 'extern_type_set_by': apiuser.username
244 'extern_type_set_by': apiuser.username
244 }
245 }
245
246
246 schema = user_group_schema.UserGroupSchema().bind(
247 schema = user_group_schema.UserGroupSchema().bind(
247 # user caller
248 # user caller
248 user=apiuser)
249 user=apiuser)
249 try:
250 try:
250 schema_data = schema.deserialize(dict(
251 schema_data = schema.deserialize(dict(
251 user_group_name=group_name,
252 user_group_name=group_name,
252 user_group_description=description,
253 user_group_description=description,
253 user_group_owner=owner.username,
254 user_group_owner=owner.username,
254 user_group_active=active,
255 user_group_active=active,
255 ))
256 ))
256 except validation_schema.Invalid as err:
257 except validation_schema.Invalid as err:
257 raise JSONRPCValidationError(colander_exc=err)
258 raise JSONRPCValidationError(colander_exc=err)
258
259
259 try:
260 try:
260 user_group = UserGroupModel().create(
261 user_group = UserGroupModel().create(
261 name=schema_data['user_group_name'],
262 name=schema_data['user_group_name'],
262 description=schema_data['user_group_description'],
263 description=schema_data['user_group_description'],
263 owner=owner,
264 owner=owner,
264 active=schema_data['user_group_active'], group_data=group_data)
265 active=schema_data['user_group_active'], group_data=group_data)
265 Session().flush()
266 Session().flush()
266 creation_data = user_group.get_api_data()
267 creation_data = user_group.get_api_data()
267 audit_logger.store_api(
268 audit_logger.store_api(
268 'user_group.create', action_data={'data': creation_data},
269 'user_group.create', action_data={'data': creation_data},
269 user=apiuser)
270 user=apiuser)
270 Session().commit()
271 Session().commit()
272
273 affected_user_ids = [apiuser.user_id, owner.user_id]
274 PermissionModel().trigger_permission_flush(affected_user_ids)
275
271 return {
276 return {
272 'msg': 'created new user group `%s`' % group_name,
277 'msg': 'created new user group `%s`' % group_name,
273 'user_group': creation_data
278 'user_group': creation_data
274 }
279 }
275 except Exception:
280 except Exception:
276 log.exception("Error occurred during creation of user group")
281 log.exception("Error occurred during creation of user group")
277 raise JSONRPCError('failed to create group `%s`' % (group_name,))
282 raise JSONRPCError('failed to create group `%s`' % (group_name,))
278
283
279
284
280 @jsonrpc_method()
285 @jsonrpc_method()
281 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
286 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
282 description=Optional(''), owner=Optional(None),
287 description=Optional(''), owner=Optional(None),
283 active=Optional(True), sync=Optional(None)):
288 active=Optional(True), sync=Optional(None)):
284 """
289 """
285 Updates the specified `user group` with the details provided.
290 Updates the specified `user group` with the details provided.
286
291
287 This command can only be run using an |authtoken| with admin rights to
292 This command can only be run using an |authtoken| with admin rights to
288 the specified repository.
293 the specified repository.
289
294
290 :param apiuser: This is filled automatically from the |authtoken|.
295 :param apiuser: This is filled automatically from the |authtoken|.
291 :type apiuser: AuthUser
296 :type apiuser: AuthUser
292 :param usergroupid: Set the id of the `user group` to update.
297 :param usergroupid: Set the id of the `user group` to update.
293 :type usergroupid: str or int
298 :type usergroupid: str or int
294 :param group_name: Set the new name the `user group`
299 :param group_name: Set the new name the `user group`
295 :type group_name: str
300 :type group_name: str
296 :param description: Give a description for the `user group`
301 :param description: Give a description for the `user group`
297 :type description: str
302 :type description: str
298 :param owner: Set the owner of the `user group`.
303 :param owner: Set the owner of the `user group`.
299 :type owner: Optional(str or int)
304 :type owner: Optional(str or int)
300 :param active: Set the group as active.
305 :param active: Set the group as active.
301 :type active: Optional(``True`` | ``False``)
306 :type active: Optional(``True`` | ``False``)
302 :param sync: Set enabled or disabled the automatically sync from
307 :param sync: Set enabled or disabled the automatically sync from
303 external authentication types like ldap. If User Group will be named like
308 external authentication types like ldap. If User Group will be named like
304 one from e.g ldap and sync flag is enabled members will be synced automatically.
309 one from e.g ldap and sync flag is enabled members will be synced automatically.
305 Sync type when enabled via API is set to `manual_api`
310 Sync type when enabled via API is set to `manual_api`
306 :type sync: Optional(``True`` | ``False``)
311 :type sync: Optional(``True`` | ``False``)
307
312
308 Example output:
313 Example output:
309
314
310 .. code-block:: bash
315 .. code-block:: bash
311
316
312 id : <id_given_in_input>
317 id : <id_given_in_input>
313 result : {
318 result : {
314 "msg": 'updated user group ID:<user group id> <user group name>',
319 "msg": 'updated user group ID:<user group id> <user group name>',
315 "user_group": <user_group_object>
320 "user_group": <user_group_object>
316 }
321 }
317 error : null
322 error : null
318
323
319 Example error output:
324 Example error output:
320
325
321 .. code-block:: bash
326 .. code-block:: bash
322
327
323 id : <id_given_in_input>
328 id : <id_given_in_input>
324 result : null
329 result : null
325 error : {
330 error : {
326 "failed to update user group `<user group name>`"
331 "failed to update user group `<user group name>`"
327 }
332 }
328
333
329 """
334 """
330
335
331 user_group = get_user_group_or_error(usergroupid)
336 user_group = get_user_group_or_error(usergroupid)
332 include_secrets = False
337 include_secrets = False
333 if not has_superadmin_permission(apiuser):
338 if not has_superadmin_permission(apiuser):
334 # check if we have admin permission for this user group !
339 # check if we have admin permission for this user group !
335 _perms = ('usergroup.admin',)
340 _perms = ('usergroup.admin',)
336 if not HasUserGroupPermissionAnyApi(*_perms)(
341 if not HasUserGroupPermissionAnyApi(*_perms)(
337 user=apiuser, user_group_name=user_group.users_group_name):
342 user=apiuser, user_group_name=user_group.users_group_name):
338 raise JSONRPCError(
343 raise JSONRPCError(
339 'user group `%s` does not exist' % (usergroupid,))
344 'user group `%s` does not exist' % (usergroupid,))
340 else:
345 else:
341 include_secrets = True
346 include_secrets = True
342
347
343 if not isinstance(owner, Optional):
348 if not isinstance(owner, Optional):
344 owner = get_user_or_error(owner)
349 owner = get_user_or_error(owner)
345
350
346 old_data = user_group.get_api_data()
351 old_data = user_group.get_api_data()
347 updates = {}
352 updates = {}
348 store_update(updates, group_name, 'users_group_name')
353 store_update(updates, group_name, 'users_group_name')
349 store_update(updates, description, 'user_group_description')
354 store_update(updates, description, 'user_group_description')
350 store_update(updates, owner, 'user')
355 store_update(updates, owner, 'user')
351 store_update(updates, active, 'users_group_active')
356 store_update(updates, active, 'users_group_active')
352
357
353 sync = Optional.extract(sync)
358 sync = Optional.extract(sync)
354 group_data = None
359 group_data = None
355 if sync is True:
360 if sync is True:
356 group_data = {
361 group_data = {
357 'extern_type': 'manual_api',
362 'extern_type': 'manual_api',
358 'extern_type_set_by': apiuser.username
363 'extern_type_set_by': apiuser.username
359 }
364 }
360 if sync is False:
365 if sync is False:
361 group_data = user_group.group_data
366 group_data = user_group.group_data
362 if group_data and "extern_type" in group_data:
367 if group_data and "extern_type" in group_data:
363 del group_data["extern_type"]
368 del group_data["extern_type"]
364
369
365 try:
370 try:
366 UserGroupModel().update(user_group, updates, group_data=group_data)
371 UserGroupModel().update(user_group, updates, group_data=group_data)
367 audit_logger.store_api(
372 audit_logger.store_api(
368 'user_group.edit', action_data={'old_data': old_data},
373 'user_group.edit', action_data={'old_data': old_data},
369 user=apiuser)
374 user=apiuser)
370 Session().commit()
375 Session().commit()
371 return {
376 return {
372 'msg': 'updated user group ID:%s %s' % (
377 'msg': 'updated user group ID:%s %s' % (
373 user_group.users_group_id, user_group.users_group_name),
378 user_group.users_group_id, user_group.users_group_name),
374 'user_group': user_group.get_api_data(
379 'user_group': user_group.get_api_data(
375 include_secrets=include_secrets)
380 include_secrets=include_secrets)
376 }
381 }
377 except Exception:
382 except Exception:
378 log.exception("Error occurred during update of user group")
383 log.exception("Error occurred during update of user group")
379 raise JSONRPCError(
384 raise JSONRPCError(
380 'failed to update user group `%s`' % (usergroupid,))
385 'failed to update user group `%s`' % (usergroupid,))
381
386
382
387
383 @jsonrpc_method()
388 @jsonrpc_method()
384 def delete_user_group(request, apiuser, usergroupid):
389 def delete_user_group(request, apiuser, usergroupid):
385 """
390 """
386 Deletes the specified `user group`.
391 Deletes the specified `user group`.
387
392
388 This command can only be run using an |authtoken| with admin rights to
393 This command can only be run using an |authtoken| with admin rights to
389 the specified repository.
394 the specified repository.
390
395
391 This command takes the following options:
396 This command takes the following options:
392
397
393 :param apiuser: filled automatically from apikey
398 :param apiuser: filled automatically from apikey
394 :type apiuser: AuthUser
399 :type apiuser: AuthUser
395 :param usergroupid:
400 :param usergroupid:
396 :type usergroupid: int
401 :type usergroupid: int
397
402
398 Example output:
403 Example output:
399
404
400 .. code-block:: bash
405 .. code-block:: bash
401
406
402 id : <id_given_in_input>
407 id : <id_given_in_input>
403 result : {
408 result : {
404 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
409 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
405 }
410 }
406 error : null
411 error : null
407
412
408 Example error output:
413 Example error output:
409
414
410 .. code-block:: bash
415 .. code-block:: bash
411
416
412 id : <id_given_in_input>
417 id : <id_given_in_input>
413 result : null
418 result : null
414 error : {
419 error : {
415 "failed to delete user group ID:<user_group_id> <user_group_name>"
420 "failed to delete user group ID:<user_group_id> <user_group_name>"
416 or
421 or
417 "RepoGroup assigned to <repo_groups_list>"
422 "RepoGroup assigned to <repo_groups_list>"
418 }
423 }
419
424
420 """
425 """
421
426
422 user_group = get_user_group_or_error(usergroupid)
427 user_group = get_user_group_or_error(usergroupid)
423 if not has_superadmin_permission(apiuser):
428 if not has_superadmin_permission(apiuser):
424 # check if we have admin permission for this user group !
429 # check if we have admin permission for this user group !
425 _perms = ('usergroup.admin',)
430 _perms = ('usergroup.admin',)
426 if not HasUserGroupPermissionAnyApi(*_perms)(
431 if not HasUserGroupPermissionAnyApi(*_perms)(
427 user=apiuser, user_group_name=user_group.users_group_name):
432 user=apiuser, user_group_name=user_group.users_group_name):
428 raise JSONRPCError(
433 raise JSONRPCError(
429 'user group `%s` does not exist' % (usergroupid,))
434 'user group `%s` does not exist' % (usergroupid,))
430
435
431 old_data = user_group.get_api_data()
436 old_data = user_group.get_api_data()
432 try:
437 try:
433 UserGroupModel().delete(user_group)
438 UserGroupModel().delete(user_group)
434 audit_logger.store_api(
439 audit_logger.store_api(
435 'user_group.delete', action_data={'old_data': old_data},
440 'user_group.delete', action_data={'old_data': old_data},
436 user=apiuser)
441 user=apiuser)
437 Session().commit()
442 Session().commit()
438 return {
443 return {
439 'msg': 'deleted user group ID:%s %s' % (
444 'msg': 'deleted user group ID:%s %s' % (
440 user_group.users_group_id, user_group.users_group_name),
445 user_group.users_group_id, user_group.users_group_name),
441 'user_group': None
446 'user_group': None
442 }
447 }
443 except UserGroupAssignedException as e:
448 except UserGroupAssignedException as e:
444 log.exception("UserGroupAssigned error")
449 log.exception("UserGroupAssigned error")
445 raise JSONRPCError(str(e))
450 raise JSONRPCError(str(e))
446 except Exception:
451 except Exception:
447 log.exception("Error occurred during deletion of user group")
452 log.exception("Error occurred during deletion of user group")
448 raise JSONRPCError(
453 raise JSONRPCError(
449 'failed to delete user group ID:%s %s' %(
454 'failed to delete user group ID:%s %s' %(
450 user_group.users_group_id, user_group.users_group_name))
455 user_group.users_group_id, user_group.users_group_name))
451
456
452
457
453 @jsonrpc_method()
458 @jsonrpc_method()
454 def add_user_to_user_group(request, apiuser, usergroupid, userid):
459 def add_user_to_user_group(request, apiuser, usergroupid, userid):
455 """
460 """
456 Adds a user to a `user group`. If the user already exists in the group
461 Adds a user to a `user group`. If the user already exists in the group
457 this command will return false.
462 this command will return false.
458
463
459 This command can only be run using an |authtoken| with admin rights to
464 This command can only be run using an |authtoken| with admin rights to
460 the specified user group.
465 the specified user group.
461
466
462 This command takes the following options:
467 This command takes the following options:
463
468
464 :param apiuser: This is filled automatically from the |authtoken|.
469 :param apiuser: This is filled automatically from the |authtoken|.
465 :type apiuser: AuthUser
470 :type apiuser: AuthUser
466 :param usergroupid: Set the name of the `user group` to which a
471 :param usergroupid: Set the name of the `user group` to which a
467 user will be added.
472 user will be added.
468 :type usergroupid: int
473 :type usergroupid: int
469 :param userid: Set the `user_id` of the user to add to the group.
474 :param userid: Set the `user_id` of the user to add to the group.
470 :type userid: int
475 :type userid: int
471
476
472 Example output:
477 Example output:
473
478
474 .. code-block:: bash
479 .. code-block:: bash
475
480
476 id : <id_given_in_input>
481 id : <id_given_in_input>
477 result : {
482 result : {
478 "success": True|False # depends on if member is in group
483 "success": True|False # depends on if member is in group
479 "msg": "added member `<username>` to user group `<groupname>` |
484 "msg": "added member `<username>` to user group `<groupname>` |
480 User is already in that group"
485 User is already in that group"
481
486
482 }
487 }
483 error : null
488 error : null
484
489
485 Example error output:
490 Example error output:
486
491
487 .. code-block:: bash
492 .. code-block:: bash
488
493
489 id : <id_given_in_input>
494 id : <id_given_in_input>
490 result : null
495 result : null
491 error : {
496 error : {
492 "failed to add member to user group `<user_group_name>`"
497 "failed to add member to user group `<user_group_name>`"
493 }
498 }
494
499
495 """
500 """
496
501
497 user = get_user_or_error(userid)
502 user = get_user_or_error(userid)
498 user_group = get_user_group_or_error(usergroupid)
503 user_group = get_user_group_or_error(usergroupid)
499 if not has_superadmin_permission(apiuser):
504 if not has_superadmin_permission(apiuser):
500 # check if we have admin permission for this user group !
505 # check if we have admin permission for this user group !
501 _perms = ('usergroup.admin',)
506 _perms = ('usergroup.admin',)
502 if not HasUserGroupPermissionAnyApi(*_perms)(
507 if not HasUserGroupPermissionAnyApi(*_perms)(
503 user=apiuser, user_group_name=user_group.users_group_name):
508 user=apiuser, user_group_name=user_group.users_group_name):
504 raise JSONRPCError('user group `%s` does not exist' % (
509 raise JSONRPCError('user group `%s` does not exist' % (
505 usergroupid,))
510 usergroupid,))
506
511
507 old_values = user_group.get_api_data()
512 old_values = user_group.get_api_data()
508 try:
513 try:
509 ugm = UserGroupModel().add_user_to_group(user_group, user)
514 ugm = UserGroupModel().add_user_to_group(user_group, user)
510 success = True if ugm is not True else False
515 success = True if ugm is not True else False
511 msg = 'added member `%s` to user group `%s`' % (
516 msg = 'added member `%s` to user group `%s`' % (
512 user.username, user_group.users_group_name
517 user.username, user_group.users_group_name
513 )
518 )
514 msg = msg if success else 'User is already in that group'
519 msg = msg if success else 'User is already in that group'
515 if success:
520 if success:
516 user_data = user.get_api_data()
521 user_data = user.get_api_data()
517 audit_logger.store_api(
522 audit_logger.store_api(
518 'user_group.edit.member.add',
523 'user_group.edit.member.add',
519 action_data={'user': user_data, 'old_data': old_values},
524 action_data={'user': user_data, 'old_data': old_values},
520 user=apiuser)
525 user=apiuser)
521
526
522 Session().commit()
527 Session().commit()
523
528
524 return {
529 return {
525 'success': success,
530 'success': success,
526 'msg': msg
531 'msg': msg
527 }
532 }
528 except Exception:
533 except Exception:
529 log.exception("Error occurred during adding a member to user group")
534 log.exception("Error occurred during adding a member to user group")
530 raise JSONRPCError(
535 raise JSONRPCError(
531 'failed to add member to user group `%s`' % (
536 'failed to add member to user group `%s`' % (
532 user_group.users_group_name,
537 user_group.users_group_name,
533 )
538 )
534 )
539 )
535
540
536
541
537 @jsonrpc_method()
542 @jsonrpc_method()
538 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
543 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
539 """
544 """
540 Removes a user from a user group.
545 Removes a user from a user group.
541
546
542 * If the specified user is not in the group, this command will return
547 * If the specified user is not in the group, this command will return
543 `false`.
548 `false`.
544
549
545 This command can only be run using an |authtoken| with admin rights to
550 This command can only be run using an |authtoken| with admin rights to
546 the specified user group.
551 the specified user group.
547
552
548 :param apiuser: This is filled automatically from the |authtoken|.
553 :param apiuser: This is filled automatically from the |authtoken|.
549 :type apiuser: AuthUser
554 :type apiuser: AuthUser
550 :param usergroupid: Sets the user group name.
555 :param usergroupid: Sets the user group name.
551 :type usergroupid: str or int
556 :type usergroupid: str or int
552 :param userid: The user you wish to remove from |RCE|.
557 :param userid: The user you wish to remove from |RCE|.
553 :type userid: str or int
558 :type userid: str or int
554
559
555 Example output:
560 Example output:
556
561
557 .. code-block:: bash
562 .. code-block:: bash
558
563
559 id : <id_given_in_input>
564 id : <id_given_in_input>
560 result: {
565 result: {
561 "success": True|False, # depends on if member is in group
566 "success": True|False, # depends on if member is in group
562 "msg": "removed member <username> from user group <groupname> |
567 "msg": "removed member <username> from user group <groupname> |
563 User wasn't in group"
568 User wasn't in group"
564 }
569 }
565 error: null
570 error: null
566
571
567 """
572 """
568
573
569 user = get_user_or_error(userid)
574 user = get_user_or_error(userid)
570 user_group = get_user_group_or_error(usergroupid)
575 user_group = get_user_group_or_error(usergroupid)
571 if not has_superadmin_permission(apiuser):
576 if not has_superadmin_permission(apiuser):
572 # check if we have admin permission for this user group !
577 # check if we have admin permission for this user group !
573 _perms = ('usergroup.admin',)
578 _perms = ('usergroup.admin',)
574 if not HasUserGroupPermissionAnyApi(*_perms)(
579 if not HasUserGroupPermissionAnyApi(*_perms)(
575 user=apiuser, user_group_name=user_group.users_group_name):
580 user=apiuser, user_group_name=user_group.users_group_name):
576 raise JSONRPCError(
581 raise JSONRPCError(
577 'user group `%s` does not exist' % (usergroupid,))
582 'user group `%s` does not exist' % (usergroupid,))
578
583
579 old_values = user_group.get_api_data()
584 old_values = user_group.get_api_data()
580 try:
585 try:
581 success = UserGroupModel().remove_user_from_group(user_group, user)
586 success = UserGroupModel().remove_user_from_group(user_group, user)
582 msg = 'removed member `%s` from user group `%s`' % (
587 msg = 'removed member `%s` from user group `%s`' % (
583 user.username, user_group.users_group_name
588 user.username, user_group.users_group_name
584 )
589 )
585 msg = msg if success else "User wasn't in group"
590 msg = msg if success else "User wasn't in group"
586 if success:
591 if success:
587 user_data = user.get_api_data()
592 user_data = user.get_api_data()
588 audit_logger.store_api(
593 audit_logger.store_api(
589 'user_group.edit.member.delete',
594 'user_group.edit.member.delete',
590 action_data={'user': user_data, 'old_data': old_values},
595 action_data={'user': user_data, 'old_data': old_values},
591 user=apiuser)
596 user=apiuser)
592
597
593 Session().commit()
598 Session().commit()
594 return {'success': success, 'msg': msg}
599 return {'success': success, 'msg': msg}
595 except Exception:
600 except Exception:
596 log.exception("Error occurred during removing an member from user group")
601 log.exception("Error occurred during removing an member from user group")
597 raise JSONRPCError(
602 raise JSONRPCError(
598 'failed to remove member from user group `%s`' % (
603 'failed to remove member from user group `%s`' % (
599 user_group.users_group_name,
604 user_group.users_group_name,
600 )
605 )
601 )
606 )
602
607
603
608
604 @jsonrpc_method()
609 @jsonrpc_method()
605 def grant_user_permission_to_user_group(
610 def grant_user_permission_to_user_group(
606 request, apiuser, usergroupid, userid, perm):
611 request, apiuser, usergroupid, userid, perm):
607 """
612 """
608 Set permissions for a user in a user group.
613 Set permissions for a user in a user group.
609
614
610 :param apiuser: This is filled automatically from the |authtoken|.
615 :param apiuser: This is filled automatically from the |authtoken|.
611 :type apiuser: AuthUser
616 :type apiuser: AuthUser
612 :param usergroupid: Set the user group to edit permissions on.
617 :param usergroupid: Set the user group to edit permissions on.
613 :type usergroupid: str or int
618 :type usergroupid: str or int
614 :param userid: Set the user from whom you wish to set permissions.
619 :param userid: Set the user from whom you wish to set permissions.
615 :type userid: str
620 :type userid: str
616 :param perm: (usergroup.(none|read|write|admin))
621 :param perm: (usergroup.(none|read|write|admin))
617 :type perm: str
622 :type perm: str
618
623
619 Example output:
624 Example output:
620
625
621 .. code-block:: bash
626 .. code-block:: bash
622
627
623 id : <id_given_in_input>
628 id : <id_given_in_input>
624 result : {
629 result : {
625 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
630 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
626 "success": true
631 "success": true
627 }
632 }
628 error : null
633 error : null
629 """
634 """
630
635
631 user_group = get_user_group_or_error(usergroupid)
636 user_group = get_user_group_or_error(usergroupid)
632
637
633 if not has_superadmin_permission(apiuser):
638 if not has_superadmin_permission(apiuser):
634 # check if we have admin permission for this user group !
639 # check if we have admin permission for this user group !
635 _perms = ('usergroup.admin',)
640 _perms = ('usergroup.admin',)
636 if not HasUserGroupPermissionAnyApi(*_perms)(
641 if not HasUserGroupPermissionAnyApi(*_perms)(
637 user=apiuser, user_group_name=user_group.users_group_name):
642 user=apiuser, user_group_name=user_group.users_group_name):
638 raise JSONRPCError(
643 raise JSONRPCError(
639 'user group `%s` does not exist' % (usergroupid,))
644 'user group `%s` does not exist' % (usergroupid,))
640
645
641 user = get_user_or_error(userid)
646 user = get_user_or_error(userid)
642 perm = get_perm_or_error(perm, prefix='usergroup.')
647 perm = get_perm_or_error(perm, prefix='usergroup.')
643
648
644 try:
649 try:
645 changes = UserGroupModel().grant_user_permission(
650 changes = UserGroupModel().grant_user_permission(
646 user_group=user_group, user=user, perm=perm)
651 user_group=user_group, user=user, perm=perm)
647
652
648 action_data = {
653 action_data = {
649 'added': changes['added'],
654 'added': changes['added'],
650 'updated': changes['updated'],
655 'updated': changes['updated'],
651 'deleted': changes['deleted'],
656 'deleted': changes['deleted'],
652 }
657 }
653 audit_logger.store_api(
658 audit_logger.store_api(
654 'user_group.edit.permissions', action_data=action_data,
659 'user_group.edit.permissions', action_data=action_data,
655 user=apiuser)
660 user=apiuser)
661 Session().commit()
662 PermissionModel().flush_user_permission_caches(changes)
656
663
657 Session().commit()
658 return {
664 return {
659 'msg':
665 'msg':
660 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
666 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
661 perm.permission_name, user.username,
667 perm.permission_name, user.username,
662 user_group.users_group_name
668 user_group.users_group_name
663 ),
669 ),
664 'success': True
670 'success': True
665 }
671 }
666 except Exception:
672 except Exception:
667 log.exception("Error occurred during editing permissions "
673 log.exception("Error occurred during editing permissions "
668 "for user in user group")
674 "for user in user group")
669 raise JSONRPCError(
675 raise JSONRPCError(
670 'failed to edit permission for user: '
676 'failed to edit permission for user: '
671 '`%s` in user group: `%s`' % (
677 '`%s` in user group: `%s`' % (
672 userid, user_group.users_group_name))
678 userid, user_group.users_group_name))
673
679
674
680
675 @jsonrpc_method()
681 @jsonrpc_method()
676 def revoke_user_permission_from_user_group(
682 def revoke_user_permission_from_user_group(
677 request, apiuser, usergroupid, userid):
683 request, apiuser, usergroupid, userid):
678 """
684 """
679 Revoke a users permissions in a user group.
685 Revoke a users permissions in a user group.
680
686
681 :param apiuser: This is filled automatically from the |authtoken|.
687 :param apiuser: This is filled automatically from the |authtoken|.
682 :type apiuser: AuthUser
688 :type apiuser: AuthUser
683 :param usergroupid: Set the user group from which to revoke the user
689 :param usergroupid: Set the user group from which to revoke the user
684 permissions.
690 permissions.
685 :type: usergroupid: str or int
691 :type: usergroupid: str or int
686 :param userid: Set the userid of the user whose permissions will be
692 :param userid: Set the userid of the user whose permissions will be
687 revoked.
693 revoked.
688 :type userid: str
694 :type userid: str
689
695
690 Example output:
696 Example output:
691
697
692 .. code-block:: bash
698 .. code-block:: bash
693
699
694 id : <id_given_in_input>
700 id : <id_given_in_input>
695 result : {
701 result : {
696 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
702 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
697 "success": true
703 "success": true
698 }
704 }
699 error : null
705 error : null
700 """
706 """
701
707
702 user_group = get_user_group_or_error(usergroupid)
708 user_group = get_user_group_or_error(usergroupid)
703
709
704 if not has_superadmin_permission(apiuser):
710 if not has_superadmin_permission(apiuser):
705 # check if we have admin permission for this user group !
711 # check if we have admin permission for this user group !
706 _perms = ('usergroup.admin',)
712 _perms = ('usergroup.admin',)
707 if not HasUserGroupPermissionAnyApi(*_perms)(
713 if not HasUserGroupPermissionAnyApi(*_perms)(
708 user=apiuser, user_group_name=user_group.users_group_name):
714 user=apiuser, user_group_name=user_group.users_group_name):
709 raise JSONRPCError(
715 raise JSONRPCError(
710 'user group `%s` does not exist' % (usergroupid,))
716 'user group `%s` does not exist' % (usergroupid,))
711
717
712 user = get_user_or_error(userid)
718 user = get_user_or_error(userid)
713
719
714 try:
720 try:
715 changes = UserGroupModel().revoke_user_permission(
721 changes = UserGroupModel().revoke_user_permission(
716 user_group=user_group, user=user)
722 user_group=user_group, user=user)
717 action_data = {
723 action_data = {
718 'added': changes['added'],
724 'added': changes['added'],
719 'updated': changes['updated'],
725 'updated': changes['updated'],
720 'deleted': changes['deleted'],
726 'deleted': changes['deleted'],
721 }
727 }
722 audit_logger.store_api(
728 audit_logger.store_api(
723 'user_group.edit.permissions', action_data=action_data,
729 'user_group.edit.permissions', action_data=action_data,
724 user=apiuser)
730 user=apiuser)
731 Session().commit()
732 PermissionModel().flush_user_permission_caches(changes)
725
733
726 Session().commit()
727 return {
734 return {
728 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
735 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
729 user.username, user_group.users_group_name
736 user.username, user_group.users_group_name
730 ),
737 ),
731 'success': True
738 'success': True
732 }
739 }
733 except Exception:
740 except Exception:
734 log.exception("Error occurred during editing permissions "
741 log.exception("Error occurred during editing permissions "
735 "for user in user group")
742 "for user in user group")
736 raise JSONRPCError(
743 raise JSONRPCError(
737 'failed to edit permission for user: `%s` in user group: `%s`'
744 'failed to edit permission for user: `%s` in user group: `%s`'
738 % (userid, user_group.users_group_name))
745 % (userid, user_group.users_group_name))
739
746
740
747
741 @jsonrpc_method()
748 @jsonrpc_method()
742 def grant_user_group_permission_to_user_group(
749 def grant_user_group_permission_to_user_group(
743 request, apiuser, usergroupid, sourceusergroupid, perm):
750 request, apiuser, usergroupid, sourceusergroupid, perm):
744 """
751 """
745 Give one user group permissions to another user group.
752 Give one user group permissions to another user group.
746
753
747 :param apiuser: This is filled automatically from the |authtoken|.
754 :param apiuser: This is filled automatically from the |authtoken|.
748 :type apiuser: AuthUser
755 :type apiuser: AuthUser
749 :param usergroupid: Set the user group on which to edit permissions.
756 :param usergroupid: Set the user group on which to edit permissions.
750 :type usergroupid: str or int
757 :type usergroupid: str or int
751 :param sourceusergroupid: Set the source user group to which
758 :param sourceusergroupid: Set the source user group to which
752 access/permissions will be granted.
759 access/permissions will be granted.
753 :type sourceusergroupid: str or int
760 :type sourceusergroupid: str or int
754 :param perm: (usergroup.(none|read|write|admin))
761 :param perm: (usergroup.(none|read|write|admin))
755 :type perm: str
762 :type perm: str
756
763
757 Example output:
764 Example output:
758
765
759 .. code-block:: bash
766 .. code-block:: bash
760
767
761 id : <id_given_in_input>
768 id : <id_given_in_input>
762 result : {
769 result : {
763 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
770 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
764 "success": true
771 "success": true
765 }
772 }
766 error : null
773 error : null
767 """
774 """
768
775
769 user_group = get_user_group_or_error(sourceusergroupid)
776 user_group = get_user_group_or_error(sourceusergroupid)
770 target_user_group = get_user_group_or_error(usergroupid)
777 target_user_group = get_user_group_or_error(usergroupid)
771 perm = get_perm_or_error(perm, prefix='usergroup.')
778 perm = get_perm_or_error(perm, prefix='usergroup.')
772
779
773 if not has_superadmin_permission(apiuser):
780 if not has_superadmin_permission(apiuser):
774 # check if we have admin permission for this user group !
781 # check if we have admin permission for this user group !
775 _perms = ('usergroup.admin',)
782 _perms = ('usergroup.admin',)
776 if not HasUserGroupPermissionAnyApi(*_perms)(
783 if not HasUserGroupPermissionAnyApi(*_perms)(
777 user=apiuser,
784 user=apiuser,
778 user_group_name=target_user_group.users_group_name):
785 user_group_name=target_user_group.users_group_name):
779 raise JSONRPCError(
786 raise JSONRPCError(
780 'to user group `%s` does not exist' % (usergroupid,))
787 'to user group `%s` does not exist' % (usergroupid,))
781
788
782 # check if we have at least read permission for source user group !
789 # check if we have at least read permission for source user group !
783 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
790 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
784 if not HasUserGroupPermissionAnyApi(*_perms)(
791 if not HasUserGroupPermissionAnyApi(*_perms)(
785 user=apiuser, user_group_name=user_group.users_group_name):
792 user=apiuser, user_group_name=user_group.users_group_name):
786 raise JSONRPCError(
793 raise JSONRPCError(
787 'user group `%s` does not exist' % (sourceusergroupid,))
794 'user group `%s` does not exist' % (sourceusergroupid,))
788
795
789 try:
796 try:
790 changes = UserGroupModel().grant_user_group_permission(
797 changes = UserGroupModel().grant_user_group_permission(
791 target_user_group=target_user_group,
798 target_user_group=target_user_group,
792 user_group=user_group, perm=perm)
799 user_group=user_group, perm=perm)
793
800
794 action_data = {
801 action_data = {
795 'added': changes['added'],
802 'added': changes['added'],
796 'updated': changes['updated'],
803 'updated': changes['updated'],
797 'deleted': changes['deleted'],
804 'deleted': changes['deleted'],
798 }
805 }
799 audit_logger.store_api(
806 audit_logger.store_api(
800 'user_group.edit.permissions', action_data=action_data,
807 'user_group.edit.permissions', action_data=action_data,
801 user=apiuser)
808 user=apiuser)
809 Session().commit()
810 PermissionModel().flush_user_permission_caches(changes)
802
811
803 Session().commit()
804 return {
812 return {
805 'msg': 'Granted perm: `%s` for user group: `%s` '
813 'msg': 'Granted perm: `%s` for user group: `%s` '
806 'in user group: `%s`' % (
814 'in user group: `%s`' % (
807 perm.permission_name, user_group.users_group_name,
815 perm.permission_name, user_group.users_group_name,
808 target_user_group.users_group_name
816 target_user_group.users_group_name
809 ),
817 ),
810 'success': True
818 'success': True
811 }
819 }
812 except Exception:
820 except Exception:
813 log.exception("Error occurred during editing permissions "
821 log.exception("Error occurred during editing permissions "
814 "for user group in user group")
822 "for user group in user group")
815 raise JSONRPCError(
823 raise JSONRPCError(
816 'failed to edit permission for user group: `%s` in '
824 'failed to edit permission for user group: `%s` in '
817 'user group: `%s`' % (
825 'user group: `%s`' % (
818 sourceusergroupid, target_user_group.users_group_name
826 sourceusergroupid, target_user_group.users_group_name
819 )
827 )
820 )
828 )
821
829
822
830
823 @jsonrpc_method()
831 @jsonrpc_method()
824 def revoke_user_group_permission_from_user_group(
832 def revoke_user_group_permission_from_user_group(
825 request, apiuser, usergroupid, sourceusergroupid):
833 request, apiuser, usergroupid, sourceusergroupid):
826 """
834 """
827 Revoke the permissions that one user group has to another.
835 Revoke the permissions that one user group has to another.
828
836
829 :param apiuser: This is filled automatically from the |authtoken|.
837 :param apiuser: This is filled automatically from the |authtoken|.
830 :type apiuser: AuthUser
838 :type apiuser: AuthUser
831 :param usergroupid: Set the user group on which to edit permissions.
839 :param usergroupid: Set the user group on which to edit permissions.
832 :type usergroupid: str or int
840 :type usergroupid: str or int
833 :param sourceusergroupid: Set the user group from which permissions
841 :param sourceusergroupid: Set the user group from which permissions
834 are revoked.
842 are revoked.
835 :type sourceusergroupid: str or int
843 :type sourceusergroupid: str or int
836
844
837 Example output:
845 Example output:
838
846
839 .. code-block:: bash
847 .. code-block:: bash
840
848
841 id : <id_given_in_input>
849 id : <id_given_in_input>
842 result : {
850 result : {
843 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
851 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
844 "success": true
852 "success": true
845 }
853 }
846 error : null
854 error : null
847 """
855 """
848
856
849 user_group = get_user_group_or_error(sourceusergroupid)
857 user_group = get_user_group_or_error(sourceusergroupid)
850 target_user_group = get_user_group_or_error(usergroupid)
858 target_user_group = get_user_group_or_error(usergroupid)
851
859
852 if not has_superadmin_permission(apiuser):
860 if not has_superadmin_permission(apiuser):
853 # check if we have admin permission for this user group !
861 # check if we have admin permission for this user group !
854 _perms = ('usergroup.admin',)
862 _perms = ('usergroup.admin',)
855 if not HasUserGroupPermissionAnyApi(*_perms)(
863 if not HasUserGroupPermissionAnyApi(*_perms)(
856 user=apiuser,
864 user=apiuser,
857 user_group_name=target_user_group.users_group_name):
865 user_group_name=target_user_group.users_group_name):
858 raise JSONRPCError(
866 raise JSONRPCError(
859 'to user group `%s` does not exist' % (usergroupid,))
867 'to user group `%s` does not exist' % (usergroupid,))
860
868
861 # check if we have at least read permission
869 # check if we have at least read permission
862 # for the source user group !
870 # for the source user group !
863 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
871 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
864 if not HasUserGroupPermissionAnyApi(*_perms)(
872 if not HasUserGroupPermissionAnyApi(*_perms)(
865 user=apiuser, user_group_name=user_group.users_group_name):
873 user=apiuser, user_group_name=user_group.users_group_name):
866 raise JSONRPCError(
874 raise JSONRPCError(
867 'user group `%s` does not exist' % (sourceusergroupid,))
875 'user group `%s` does not exist' % (sourceusergroupid,))
868
876
869 try:
877 try:
870 changes = UserGroupModel().revoke_user_group_permission(
878 changes = UserGroupModel().revoke_user_group_permission(
871 target_user_group=target_user_group, user_group=user_group)
879 target_user_group=target_user_group, user_group=user_group)
872 action_data = {
880 action_data = {
873 'added': changes['added'],
881 'added': changes['added'],
874 'updated': changes['updated'],
882 'updated': changes['updated'],
875 'deleted': changes['deleted'],
883 'deleted': changes['deleted'],
876 }
884 }
877 audit_logger.store_api(
885 audit_logger.store_api(
878 'user_group.edit.permissions', action_data=action_data,
886 'user_group.edit.permissions', action_data=action_data,
879 user=apiuser)
887 user=apiuser)
880
881 Session().commit()
888 Session().commit()
889 PermissionModel().flush_user_permission_caches(changes)
882
890
883 return {
891 return {
884 'msg': 'Revoked perm for user group: '
892 'msg': 'Revoked perm for user group: '
885 '`%s` in user group: `%s`' % (
893 '`%s` in user group: `%s`' % (
886 user_group.users_group_name,
894 user_group.users_group_name,
887 target_user_group.users_group_name
895 target_user_group.users_group_name
888 ),
896 ),
889 'success': True
897 'success': True
890 }
898 }
891 except Exception:
899 except Exception:
892 log.exception("Error occurred during editing permissions "
900 log.exception("Error occurred during editing permissions "
893 "for user group in user group")
901 "for user group in user group")
894 raise JSONRPCError(
902 raise JSONRPCError(
895 'failed to edit permission for user group: '
903 'failed to edit permission for user group: '
896 '`%s` in user group: `%s`' % (
904 '`%s` in user group: `%s`' % (
897 sourceusergroupid, target_user_group.users_group_name
905 sourceusergroupid, target_user_group.users_group_name
898 )
906 )
899 )
907 )
@@ -1,519 +1,519 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 = c.rc_config
71 app_settings = c.rc_config
72
72
73 defaults = {
73 defaults = {
74 'anonymous': c.user.active,
74 'anonymous': c.user.active,
75 'default_register_message': app_settings.get(
75 'default_register_message': app_settings.get(
76 'rhodecode_register_message')
76 'rhodecode_register_message')
77 }
77 }
78 defaults.update(c.user.get_default_perms())
78 defaults.update(c.user.get_default_perms())
79
79
80 data = render('rhodecode:templates/admin/permissions/permissions.mako',
80 data = render('rhodecode:templates/admin/permissions/permissions.mako',
81 self._get_template_context(c), self.request)
81 self._get_template_context(c), self.request)
82 html = formencode.htmlfill.render(
82 html = formencode.htmlfill.render(
83 data,
83 data,
84 defaults=defaults,
84 defaults=defaults,
85 encoding="UTF-8",
85 encoding="UTF-8",
86 force_defaults=False
86 force_defaults=False
87 )
87 )
88 return Response(html)
88 return Response(html)
89
89
90 @LoginRequired()
90 @LoginRequired()
91 @HasPermissionAllDecorator('hg.admin')
91 @HasPermissionAllDecorator('hg.admin')
92 @CSRFRequired()
92 @CSRFRequired()
93 @view_config(
93 @view_config(
94 route_name='admin_permissions_application_update', request_method='POST',
94 route_name='admin_permissions_application_update', request_method='POST',
95 renderer='rhodecode:templates/admin/permissions/permissions.mako')
95 renderer='rhodecode:templates/admin/permissions/permissions.mako')
96 def permissions_application_update(self):
96 def permissions_application_update(self):
97 _ = self.request.translate
97 _ = self.request.translate
98 c = self.load_default_context()
98 c = self.load_default_context()
99 c.active = 'application'
99 c.active = 'application'
100
100
101 _form = ApplicationPermissionsForm(
101 _form = ApplicationPermissionsForm(
102 self.request.translate,
102 self.request.translate,
103 [x[0] for x in c.register_choices],
103 [x[0] for x in c.register_choices],
104 [x[0] for x in c.password_reset_choices],
104 [x[0] for x in c.password_reset_choices],
105 [x[0] for x in c.extern_activate_choices])()
105 [x[0] for x in c.extern_activate_choices])()
106
106
107 try:
107 try:
108 form_result = _form.to_python(dict(self.request.POST))
108 form_result = _form.to_python(dict(self.request.POST))
109 form_result.update({'perm_user_name': User.DEFAULT_USER})
109 form_result.update({'perm_user_name': User.DEFAULT_USER})
110 PermissionModel().update_application_permissions(form_result)
110 PermissionModel().update_application_permissions(form_result)
111
111
112 settings = [
112 settings = [
113 ('register_message', 'default_register_message'),
113 ('register_message', 'default_register_message'),
114 ]
114 ]
115 for setting, form_key in settings:
115 for setting, form_key in settings:
116 sett = SettingsModel().create_or_update_setting(
116 sett = SettingsModel().create_or_update_setting(
117 setting, form_result[form_key])
117 setting, form_result[form_key])
118 Session().add(sett)
118 Session().add(sett)
119
119
120 Session().commit()
120 Session().commit()
121 h.flash(_('Application permissions updated successfully'),
121 h.flash(_('Application permissions updated successfully'),
122 category='success')
122 category='success')
123
123
124 except formencode.Invalid as errors:
124 except formencode.Invalid as errors:
125 defaults = errors.value
125 defaults = errors.value
126
126
127 data = render(
127 data = render(
128 'rhodecode:templates/admin/permissions/permissions.mako',
128 'rhodecode:templates/admin/permissions/permissions.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 errors=errors.error_dict or {},
133 errors=errors.error_dict or {},
134 prefix_error=False,
134 prefix_error=False,
135 encoding="UTF-8",
135 encoding="UTF-8",
136 force_defaults=False
136 force_defaults=False
137 )
137 )
138 return Response(html)
138 return Response(html)
139
139
140 except Exception:
140 except Exception:
141 log.exception("Exception during update of permissions")
141 log.exception("Exception during update of permissions")
142 h.flash(_('Error occurred during update of permissions'),
142 h.flash(_('Error occurred during update of permissions'),
143 category='error')
143 category='error')
144
144
145 affected_user_ids = [User.get_default_user().user_id]
145 affected_user_ids = [User.get_default_user().user_id]
146 events.trigger(events.UserPermissionsChange(affected_user_ids))
146 PermissionModel().trigger_permission_flush(affected_user_ids)
147
147
148 raise HTTPFound(h.route_path('admin_permissions_application'))
148 raise HTTPFound(h.route_path('admin_permissions_application'))
149
149
150 @LoginRequired()
150 @LoginRequired()
151 @HasPermissionAllDecorator('hg.admin')
151 @HasPermissionAllDecorator('hg.admin')
152 @view_config(
152 @view_config(
153 route_name='admin_permissions_object', request_method='GET',
153 route_name='admin_permissions_object', request_method='GET',
154 renderer='rhodecode:templates/admin/permissions/permissions.mako')
154 renderer='rhodecode:templates/admin/permissions/permissions.mako')
155 def permissions_objects(self):
155 def permissions_objects(self):
156 c = self.load_default_context()
156 c = self.load_default_context()
157 c.active = 'objects'
157 c.active = 'objects'
158
158
159 c.user = User.get_default_user(refresh=True)
159 c.user = User.get_default_user(refresh=True)
160 defaults = {}
160 defaults = {}
161 defaults.update(c.user.get_default_perms())
161 defaults.update(c.user.get_default_perms())
162
162
163 data = render(
163 data = render(
164 'rhodecode:templates/admin/permissions/permissions.mako',
164 'rhodecode:templates/admin/permissions/permissions.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=defaults,
168 defaults=defaults,
169 encoding="UTF-8",
169 encoding="UTF-8",
170 force_defaults=False
170 force_defaults=False
171 )
171 )
172 return Response(html)
172 return Response(html)
173
173
174 @LoginRequired()
174 @LoginRequired()
175 @HasPermissionAllDecorator('hg.admin')
175 @HasPermissionAllDecorator('hg.admin')
176 @CSRFRequired()
176 @CSRFRequired()
177 @view_config(
177 @view_config(
178 route_name='admin_permissions_object_update', request_method='POST',
178 route_name='admin_permissions_object_update', request_method='POST',
179 renderer='rhodecode:templates/admin/permissions/permissions.mako')
179 renderer='rhodecode:templates/admin/permissions/permissions.mako')
180 def permissions_objects_update(self):
180 def permissions_objects_update(self):
181 _ = self.request.translate
181 _ = self.request.translate
182 c = self.load_default_context()
182 c = self.load_default_context()
183 c.active = 'objects'
183 c.active = 'objects'
184
184
185 _form = ObjectPermissionsForm(
185 _form = ObjectPermissionsForm(
186 self.request.translate,
186 self.request.translate,
187 [x[0] for x in c.repo_perms_choices],
187 [x[0] for x in c.repo_perms_choices],
188 [x[0] for x in c.group_perms_choices],
188 [x[0] for x in c.group_perms_choices],
189 [x[0] for x in c.user_group_perms_choices],
189 [x[0] for x in c.user_group_perms_choices],
190 )()
190 )()
191
191
192 try:
192 try:
193 form_result = _form.to_python(dict(self.request.POST))
193 form_result = _form.to_python(dict(self.request.POST))
194 form_result.update({'perm_user_name': User.DEFAULT_USER})
194 form_result.update({'perm_user_name': User.DEFAULT_USER})
195 PermissionModel().update_object_permissions(form_result)
195 PermissionModel().update_object_permissions(form_result)
196
196
197 Session().commit()
197 Session().commit()
198 h.flash(_('Object permissions updated successfully'),
198 h.flash(_('Object permissions updated successfully'),
199 category='success')
199 category='success')
200
200
201 except formencode.Invalid as errors:
201 except formencode.Invalid as errors:
202 defaults = errors.value
202 defaults = errors.value
203
203
204 data = render(
204 data = render(
205 'rhodecode:templates/admin/permissions/permissions.mako',
205 'rhodecode:templates/admin/permissions/permissions.mako',
206 self._get_template_context(c), self.request)
206 self._get_template_context(c), self.request)
207 html = formencode.htmlfill.render(
207 html = formencode.htmlfill.render(
208 data,
208 data,
209 defaults=defaults,
209 defaults=defaults,
210 errors=errors.error_dict or {},
210 errors=errors.error_dict or {},
211 prefix_error=False,
211 prefix_error=False,
212 encoding="UTF-8",
212 encoding="UTF-8",
213 force_defaults=False
213 force_defaults=False
214 )
214 )
215 return Response(html)
215 return Response(html)
216 except Exception:
216 except Exception:
217 log.exception("Exception during update of permissions")
217 log.exception("Exception during update of permissions")
218 h.flash(_('Error occurred during update of permissions'),
218 h.flash(_('Error occurred during update of permissions'),
219 category='error')
219 category='error')
220
220
221 affected_user_ids = [User.get_default_user().user_id]
221 affected_user_ids = [User.get_default_user().user_id]
222 events.trigger(events.UserPermissionsChange(affected_user_ids))
222 PermissionModel().trigger_permission_flush(affected_user_ids)
223
223
224 raise HTTPFound(h.route_path('admin_permissions_object'))
224 raise HTTPFound(h.route_path('admin_permissions_object'))
225
225
226 @LoginRequired()
226 @LoginRequired()
227 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
228 @view_config(
229 route_name='admin_permissions_branch', request_method='GET',
229 route_name='admin_permissions_branch', request_method='GET',
230 renderer='rhodecode:templates/admin/permissions/permissions.mako')
230 renderer='rhodecode:templates/admin/permissions/permissions.mako')
231 def permissions_branch(self):
231 def permissions_branch(self):
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'branch'
233 c.active = 'branch'
234
234
235 c.user = User.get_default_user(refresh=True)
235 c.user = User.get_default_user(refresh=True)
236 defaults = {}
236 defaults = {}
237 defaults.update(c.user.get_default_perms())
237 defaults.update(c.user.get_default_perms())
238
238
239 data = render(
239 data = render(
240 'rhodecode:templates/admin/permissions/permissions.mako',
240 'rhodecode:templates/admin/permissions/permissions.mako',
241 self._get_template_context(c), self.request)
241 self._get_template_context(c), self.request)
242 html = formencode.htmlfill.render(
242 html = formencode.htmlfill.render(
243 data,
243 data,
244 defaults=defaults,
244 defaults=defaults,
245 encoding="UTF-8",
245 encoding="UTF-8",
246 force_defaults=False
246 force_defaults=False
247 )
247 )
248 return Response(html)
248 return Response(html)
249
249
250 @LoginRequired()
250 @LoginRequired()
251 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
252 @view_config(
253 route_name='admin_permissions_global', request_method='GET',
253 route_name='admin_permissions_global', request_method='GET',
254 renderer='rhodecode:templates/admin/permissions/permissions.mako')
254 renderer='rhodecode:templates/admin/permissions/permissions.mako')
255 def permissions_global(self):
255 def permissions_global(self):
256 c = self.load_default_context()
256 c = self.load_default_context()
257 c.active = 'global'
257 c.active = 'global'
258
258
259 c.user = User.get_default_user(refresh=True)
259 c.user = User.get_default_user(refresh=True)
260 defaults = {}
260 defaults = {}
261 defaults.update(c.user.get_default_perms())
261 defaults.update(c.user.get_default_perms())
262
262
263 data = render(
263 data = render(
264 'rhodecode:templates/admin/permissions/permissions.mako',
264 'rhodecode:templates/admin/permissions/permissions.mako',
265 self._get_template_context(c), self.request)
265 self._get_template_context(c), self.request)
266 html = formencode.htmlfill.render(
266 html = formencode.htmlfill.render(
267 data,
267 data,
268 defaults=defaults,
268 defaults=defaults,
269 encoding="UTF-8",
269 encoding="UTF-8",
270 force_defaults=False
270 force_defaults=False
271 )
271 )
272 return Response(html)
272 return Response(html)
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='admin_permissions_global_update', request_method='POST',
278 route_name='admin_permissions_global_update', request_method='POST',
279 renderer='rhodecode:templates/admin/permissions/permissions.mako')
279 renderer='rhodecode:templates/admin/permissions/permissions.mako')
280 def permissions_global_update(self):
280 def permissions_global_update(self):
281 _ = self.request.translate
281 _ = self.request.translate
282 c = self.load_default_context()
282 c = self.load_default_context()
283 c.active = 'global'
283 c.active = 'global'
284
284
285 _form = UserPermissionsForm(
285 _form = UserPermissionsForm(
286 self.request.translate,
286 self.request.translate,
287 [x[0] for x in c.repo_create_choices],
287 [x[0] for x in c.repo_create_choices],
288 [x[0] for x in c.repo_create_on_write_choices],
288 [x[0] for x in c.repo_create_on_write_choices],
289 [x[0] for x in c.repo_group_create_choices],
289 [x[0] for x in c.repo_group_create_choices],
290 [x[0] for x in c.user_group_create_choices],
290 [x[0] for x in c.user_group_create_choices],
291 [x[0] for x in c.fork_choices],
291 [x[0] for x in c.fork_choices],
292 [x[0] for x in c.inherit_default_permission_choices])()
292 [x[0] for x in c.inherit_default_permission_choices])()
293
293
294 try:
294 try:
295 form_result = _form.to_python(dict(self.request.POST))
295 form_result = _form.to_python(dict(self.request.POST))
296 form_result.update({'perm_user_name': User.DEFAULT_USER})
296 form_result.update({'perm_user_name': User.DEFAULT_USER})
297 PermissionModel().update_user_permissions(form_result)
297 PermissionModel().update_user_permissions(form_result)
298
298
299 Session().commit()
299 Session().commit()
300 h.flash(_('Global permissions updated successfully'),
300 h.flash(_('Global permissions updated successfully'),
301 category='success')
301 category='success')
302
302
303 except formencode.Invalid as errors:
303 except formencode.Invalid as errors:
304 defaults = errors.value
304 defaults = errors.value
305
305
306 data = render(
306 data = render(
307 'rhodecode:templates/admin/permissions/permissions.mako',
307 'rhodecode:templates/admin/permissions/permissions.mako',
308 self._get_template_context(c), self.request)
308 self._get_template_context(c), self.request)
309 html = formencode.htmlfill.render(
309 html = formencode.htmlfill.render(
310 data,
310 data,
311 defaults=defaults,
311 defaults=defaults,
312 errors=errors.error_dict or {},
312 errors=errors.error_dict or {},
313 prefix_error=False,
313 prefix_error=False,
314 encoding="UTF-8",
314 encoding="UTF-8",
315 force_defaults=False
315 force_defaults=False
316 )
316 )
317 return Response(html)
317 return Response(html)
318 except Exception:
318 except Exception:
319 log.exception("Exception during update of permissions")
319 log.exception("Exception during update of permissions")
320 h.flash(_('Error occurred during update of permissions'),
320 h.flash(_('Error occurred during update of permissions'),
321 category='error')
321 category='error')
322
322
323 affected_user_ids = [User.get_default_user().user_id]
323 affected_user_ids = [User.get_default_user().user_id]
324 events.trigger(events.UserPermissionsChange(affected_user_ids))
324 PermissionModel().trigger_permission_flush(affected_user_ids)
325
325
326 raise HTTPFound(h.route_path('admin_permissions_global'))
326 raise HTTPFound(h.route_path('admin_permissions_global'))
327
327
328 @LoginRequired()
328 @LoginRequired()
329 @HasPermissionAllDecorator('hg.admin')
329 @HasPermissionAllDecorator('hg.admin')
330 @view_config(
330 @view_config(
331 route_name='admin_permissions_ips', request_method='GET',
331 route_name='admin_permissions_ips', request_method='GET',
332 renderer='rhodecode:templates/admin/permissions/permissions.mako')
332 renderer='rhodecode:templates/admin/permissions/permissions.mako')
333 def permissions_ips(self):
333 def permissions_ips(self):
334 c = self.load_default_context()
334 c = self.load_default_context()
335 c.active = 'ips'
335 c.active = 'ips'
336
336
337 c.user = User.get_default_user(refresh=True)
337 c.user = User.get_default_user(refresh=True)
338 c.user_ip_map = (
338 c.user_ip_map = (
339 UserIpMap.query().filter(UserIpMap.user == c.user).all())
339 UserIpMap.query().filter(UserIpMap.user == c.user).all())
340
340
341 return self._get_template_context(c)
341 return self._get_template_context(c)
342
342
343 @LoginRequired()
343 @LoginRequired()
344 @HasPermissionAllDecorator('hg.admin')
344 @HasPermissionAllDecorator('hg.admin')
345 @view_config(
345 @view_config(
346 route_name='admin_permissions_overview', request_method='GET',
346 route_name='admin_permissions_overview', request_method='GET',
347 renderer='rhodecode:templates/admin/permissions/permissions.mako')
347 renderer='rhodecode:templates/admin/permissions/permissions.mako')
348 def permissions_overview(self):
348 def permissions_overview(self):
349 c = self.load_default_context()
349 c = self.load_default_context()
350 c.active = 'perms'
350 c.active = 'perms'
351
351
352 c.user = User.get_default_user(refresh=True)
352 c.user = User.get_default_user(refresh=True)
353 c.perm_user = c.user.AuthUser()
353 c.perm_user = c.user.AuthUser()
354 return self._get_template_context(c)
354 return self._get_template_context(c)
355
355
356 @LoginRequired()
356 @LoginRequired()
357 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
358 @view_config(
358 @view_config(
359 route_name='admin_permissions_auth_token_access', request_method='GET',
359 route_name='admin_permissions_auth_token_access', request_method='GET',
360 renderer='rhodecode:templates/admin/permissions/permissions.mako')
360 renderer='rhodecode:templates/admin/permissions/permissions.mako')
361 def auth_token_access(self):
361 def auth_token_access(self):
362 from rhodecode import CONFIG
362 from rhodecode import CONFIG
363
363
364 c = self.load_default_context()
364 c = self.load_default_context()
365 c.active = 'auth_token_access'
365 c.active = 'auth_token_access'
366
366
367 c.user = User.get_default_user(refresh=True)
367 c.user = User.get_default_user(refresh=True)
368 c.perm_user = c.user.AuthUser()
368 c.perm_user = c.user.AuthUser()
369
369
370 mapper = self.request.registry.queryUtility(IRoutesMapper)
370 mapper = self.request.registry.queryUtility(IRoutesMapper)
371 c.view_data = []
371 c.view_data = []
372
372
373 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
373 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
374 introspector = self.request.registry.introspector
374 introspector = self.request.registry.introspector
375
375
376 view_intr = {}
376 view_intr = {}
377 for view_data in introspector.get_category('views'):
377 for view_data in introspector.get_category('views'):
378 intr = view_data['introspectable']
378 intr = view_data['introspectable']
379
379
380 if 'route_name' in intr and intr['attr']:
380 if 'route_name' in intr and intr['attr']:
381 view_intr[intr['route_name']] = '{}:{}'.format(
381 view_intr[intr['route_name']] = '{}:{}'.format(
382 str(intr['derived_callable'].func_name), intr['attr']
382 str(intr['derived_callable'].func_name), intr['attr']
383 )
383 )
384
384
385 c.whitelist_key = 'api_access_controllers_whitelist'
385 c.whitelist_key = 'api_access_controllers_whitelist'
386 c.whitelist_file = CONFIG.get('__file__')
386 c.whitelist_file = CONFIG.get('__file__')
387 whitelist_views = aslist(
387 whitelist_views = aslist(
388 CONFIG.get(c.whitelist_key), sep=',')
388 CONFIG.get(c.whitelist_key), sep=',')
389
389
390 for route_info in mapper.get_routes():
390 for route_info in mapper.get_routes():
391 if not route_info.name.startswith('__'):
391 if not route_info.name.startswith('__'):
392 routepath = route_info.pattern
392 routepath = route_info.pattern
393
393
394 def replace(matchobj):
394 def replace(matchobj):
395 if matchobj.group(1):
395 if matchobj.group(1):
396 return "{%s}" % matchobj.group(1).split(':')[0]
396 return "{%s}" % matchobj.group(1).split(':')[0]
397 else:
397 else:
398 return "{%s}" % matchobj.group(2)
398 return "{%s}" % matchobj.group(2)
399
399
400 routepath = _argument_prog.sub(replace, routepath)
400 routepath = _argument_prog.sub(replace, routepath)
401
401
402 if not routepath.startswith('/'):
402 if not routepath.startswith('/'):
403 routepath = '/' + routepath
403 routepath = '/' + routepath
404
404
405 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
405 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
406 active = view_fqn in whitelist_views
406 active = view_fqn in whitelist_views
407 c.view_data.append((route_info.name, view_fqn, routepath, active))
407 c.view_data.append((route_info.name, view_fqn, routepath, active))
408
408
409 c.whitelist_views = whitelist_views
409 c.whitelist_views = whitelist_views
410 return self._get_template_context(c)
410 return self._get_template_context(c)
411
411
412 def ssh_enabled(self):
412 def ssh_enabled(self):
413 return self.request.registry.settings.get(
413 return self.request.registry.settings.get(
414 'ssh.generate_authorized_keyfile')
414 'ssh.generate_authorized_keyfile')
415
415
416 @LoginRequired()
416 @LoginRequired()
417 @HasPermissionAllDecorator('hg.admin')
417 @HasPermissionAllDecorator('hg.admin')
418 @view_config(
418 @view_config(
419 route_name='admin_permissions_ssh_keys', request_method='GET',
419 route_name='admin_permissions_ssh_keys', request_method='GET',
420 renderer='rhodecode:templates/admin/permissions/permissions.mako')
420 renderer='rhodecode:templates/admin/permissions/permissions.mako')
421 def ssh_keys(self):
421 def ssh_keys(self):
422 c = self.load_default_context()
422 c = self.load_default_context()
423 c.active = 'ssh_keys'
423 c.active = 'ssh_keys'
424 c.ssh_enabled = self.ssh_enabled()
424 c.ssh_enabled = self.ssh_enabled()
425 return self._get_template_context(c)
425 return self._get_template_context(c)
426
426
427 @LoginRequired()
427 @LoginRequired()
428 @HasPermissionAllDecorator('hg.admin')
428 @HasPermissionAllDecorator('hg.admin')
429 @view_config(
429 @view_config(
430 route_name='admin_permissions_ssh_keys_data', request_method='GET',
430 route_name='admin_permissions_ssh_keys_data', request_method='GET',
431 renderer='json_ext', xhr=True)
431 renderer='json_ext', xhr=True)
432 def ssh_keys_data(self):
432 def ssh_keys_data(self):
433 _ = self.request.translate
433 _ = self.request.translate
434 self.load_default_context()
434 self.load_default_context()
435 column_map = {
435 column_map = {
436 'fingerprint': 'ssh_key_fingerprint',
436 'fingerprint': 'ssh_key_fingerprint',
437 'username': User.username
437 'username': User.username
438 }
438 }
439 draw, start, limit = self._extract_chunk(self.request)
439 draw, start, limit = self._extract_chunk(self.request)
440 search_q, order_by, order_dir = self._extract_ordering(
440 search_q, order_by, order_dir = self._extract_ordering(
441 self.request, column_map=column_map)
441 self.request, column_map=column_map)
442
442
443 ssh_keys_data_total_count = UserSshKeys.query()\
443 ssh_keys_data_total_count = UserSshKeys.query()\
444 .count()
444 .count()
445
445
446 # json generate
446 # json generate
447 base_q = UserSshKeys.query().join(UserSshKeys.user)
447 base_q = UserSshKeys.query().join(UserSshKeys.user)
448
448
449 if search_q:
449 if search_q:
450 like_expression = u'%{}%'.format(safe_unicode(search_q))
450 like_expression = u'%{}%'.format(safe_unicode(search_q))
451 base_q = base_q.filter(or_(
451 base_q = base_q.filter(or_(
452 User.username.ilike(like_expression),
452 User.username.ilike(like_expression),
453 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
453 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
454 ))
454 ))
455
455
456 users_data_total_filtered_count = base_q.count()
456 users_data_total_filtered_count = base_q.count()
457
457
458 sort_col = self._get_order_col(order_by, UserSshKeys)
458 sort_col = self._get_order_col(order_by, UserSshKeys)
459 if sort_col:
459 if sort_col:
460 if order_dir == 'asc':
460 if order_dir == 'asc':
461 # handle null values properly to order by NULL last
461 # handle null values properly to order by NULL last
462 if order_by in ['created_on']:
462 if order_by in ['created_on']:
463 sort_col = coalesce(sort_col, datetime.date.max)
463 sort_col = coalesce(sort_col, datetime.date.max)
464 sort_col = sort_col.asc()
464 sort_col = sort_col.asc()
465 else:
465 else:
466 # handle null values properly to order by NULL last
466 # handle null values properly to order by NULL last
467 if order_by in ['created_on']:
467 if order_by in ['created_on']:
468 sort_col = coalesce(sort_col, datetime.date.min)
468 sort_col = coalesce(sort_col, datetime.date.min)
469 sort_col = sort_col.desc()
469 sort_col = sort_col.desc()
470
470
471 base_q = base_q.order_by(sort_col)
471 base_q = base_q.order_by(sort_col)
472 base_q = base_q.offset(start).limit(limit)
472 base_q = base_q.offset(start).limit(limit)
473
473
474 ssh_keys = base_q.all()
474 ssh_keys = base_q.all()
475
475
476 ssh_keys_data = []
476 ssh_keys_data = []
477 for ssh_key in ssh_keys:
477 for ssh_key in ssh_keys:
478 ssh_keys_data.append({
478 ssh_keys_data.append({
479 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
479 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
480 "fingerprint": ssh_key.ssh_key_fingerprint,
480 "fingerprint": ssh_key.ssh_key_fingerprint,
481 "description": ssh_key.description,
481 "description": ssh_key.description,
482 "created_on": h.format_date(ssh_key.created_on),
482 "created_on": h.format_date(ssh_key.created_on),
483 "accessed_on": h.format_date(ssh_key.accessed_on),
483 "accessed_on": h.format_date(ssh_key.accessed_on),
484 "action": h.link_to(
484 "action": h.link_to(
485 _('Edit'), h.route_path('edit_user_ssh_keys',
485 _('Edit'), h.route_path('edit_user_ssh_keys',
486 user_id=ssh_key.user.user_id))
486 user_id=ssh_key.user.user_id))
487 })
487 })
488
488
489 data = ({
489 data = ({
490 'draw': draw,
490 'draw': draw,
491 'data': ssh_keys_data,
491 'data': ssh_keys_data,
492 'recordsTotal': ssh_keys_data_total_count,
492 'recordsTotal': ssh_keys_data_total_count,
493 'recordsFiltered': users_data_total_filtered_count,
493 'recordsFiltered': users_data_total_filtered_count,
494 })
494 })
495
495
496 return data
496 return data
497
497
498 @LoginRequired()
498 @LoginRequired()
499 @HasPermissionAllDecorator('hg.admin')
499 @HasPermissionAllDecorator('hg.admin')
500 @CSRFRequired()
500 @CSRFRequired()
501 @view_config(
501 @view_config(
502 route_name='admin_permissions_ssh_keys_update', request_method='POST',
502 route_name='admin_permissions_ssh_keys_update', request_method='POST',
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
503 renderer='rhodecode:templates/admin/permissions/permissions.mako')
504 def ssh_keys_update(self):
504 def ssh_keys_update(self):
505 _ = self.request.translate
505 _ = self.request.translate
506 self.load_default_context()
506 self.load_default_context()
507
507
508 ssh_enabled = self.ssh_enabled()
508 ssh_enabled = self.ssh_enabled()
509 key_file = self.request.registry.settings.get(
509 key_file = self.request.registry.settings.get(
510 'ssh.authorized_keys_file_path')
510 'ssh.authorized_keys_file_path')
511 if ssh_enabled:
511 if ssh_enabled:
512 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
512 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
513 h.flash(_('Updated SSH keys file: {}').format(key_file),
513 h.flash(_('Updated SSH keys file: {}').format(key_file),
514 category='success')
514 category='success')
515 else:
515 else:
516 h.flash(_('SSH key support is disabled in .ini file'),
516 h.flash(_('SSH key support is disabled in .ini file'),
517 category='warning')
517 category='warning')
518
518
519 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
519 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,186 +1,187 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.personal_repo_group = self._rhodecode_user.personal_repo_group
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63
64
64 @LoginRequired()
65 @LoginRequired()
65 @NotAnonymous()
66 @NotAnonymous()
66 # perms check inside
67 # perms check inside
67 @view_config(
68 @view_config(
68 route_name='repos', request_method='GET',
69 route_name='repos', request_method='GET',
69 renderer='rhodecode:templates/admin/repos/repos.mako')
70 renderer='rhodecode:templates/admin/repos/repos.mako')
70 def repository_list(self):
71 def repository_list(self):
71 c = self.load_default_context()
72 c = self.load_default_context()
72
73
73 repo_list = Repository.get_all_repos()
74 repo_list = Repository.get_all_repos()
74 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
75 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
75 repos_data = RepoModel().get_repos_as_dict(
76 repos_data = RepoModel().get_repos_as_dict(
76 repo_list=c.repo_list, admin=True, super_user_actions=True)
77 repo_list=c.repo_list, admin=True, super_user_actions=True)
77 # json used to render the grid
78 # json used to render the grid
78 c.data = json.dumps(repos_data)
79 c.data = json.dumps(repos_data)
79
80
80 return self._get_template_context(c)
81 return self._get_template_context(c)
81
82
82 @LoginRequired()
83 @LoginRequired()
83 @NotAnonymous()
84 @NotAnonymous()
84 # perms check inside
85 # perms check inside
85 @view_config(
86 @view_config(
86 route_name='repo_new', request_method='GET',
87 route_name='repo_new', request_method='GET',
87 renderer='rhodecode:templates/admin/repos/repo_add.mako')
88 renderer='rhodecode:templates/admin/repos/repo_add.mako')
88 def repository_new(self):
89 def repository_new(self):
89 c = self.load_default_context()
90 c = self.load_default_context()
90
91
91 new_repo = self.request.GET.get('repo', '')
92 new_repo = self.request.GET.get('repo', '')
92 parent_group = safe_int(self.request.GET.get('parent_group'))
93 parent_group = safe_int(self.request.GET.get('parent_group'))
93 _gr = RepoGroup.get(parent_group)
94 _gr = RepoGroup.get(parent_group)
94
95
95 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
96 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
96 # you're not super admin nor have global create permissions,
97 # you're not super admin nor have global create permissions,
97 # but maybe you have at least write permission to a parent group ?
98 # but maybe you have at least write permission to a parent group ?
98
99
99 gr_name = _gr.group_name if _gr else None
100 gr_name = _gr.group_name if _gr else None
100 # create repositories with write permission on group is set to true
101 # create repositories with write permission on group is set to true
101 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
102 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
102 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
103 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
103 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
104 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
104 if not (group_admin or (group_write and create_on_write)):
105 if not (group_admin or (group_write and create_on_write)):
105 raise HTTPForbidden()
106 raise HTTPForbidden()
106
107
107 self._load_form_data(c)
108 self._load_form_data(c)
108 c.new_repo = repo_name_slug(new_repo)
109 c.new_repo = repo_name_slug(new_repo)
109
110
110 # apply the defaults from defaults page
111 # apply the defaults from defaults page
111 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
112 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
112 # set checkbox to autochecked
113 # set checkbox to autochecked
113 defaults['repo_copy_permissions'] = True
114 defaults['repo_copy_permissions'] = True
114
115
115 parent_group_choice = '-1'
116 parent_group_choice = '-1'
116 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
117 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
117 parent_group_choice = self._rhodecode_user.personal_repo_group
118 parent_group_choice = self._rhodecode_user.personal_repo_group
118
119
119 if parent_group and _gr:
120 if parent_group and _gr:
120 if parent_group in [x[0] for x in c.repo_groups]:
121 if parent_group in [x[0] for x in c.repo_groups]:
121 parent_group_choice = safe_unicode(parent_group)
122 parent_group_choice = safe_unicode(parent_group)
122
123
123 defaults.update({'repo_group': parent_group_choice})
124 defaults.update({'repo_group': parent_group_choice})
124
125
125 data = render('rhodecode:templates/admin/repos/repo_add.mako',
126 data = render('rhodecode:templates/admin/repos/repo_add.mako',
126 self._get_template_context(c), self.request)
127 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
128 html = formencode.htmlfill.render(
128 data,
129 data,
129 defaults=defaults,
130 defaults=defaults,
130 encoding="UTF-8",
131 encoding="UTF-8",
131 force_defaults=False
132 force_defaults=False
132 )
133 )
133 return Response(html)
134 return Response(html)
134
135
135 @LoginRequired()
136 @LoginRequired()
136 @NotAnonymous()
137 @NotAnonymous()
137 @CSRFRequired()
138 @CSRFRequired()
138 # perms check inside
139 # perms check inside
139 @view_config(
140 @view_config(
140 route_name='repo_create', request_method='POST',
141 route_name='repo_create', request_method='POST',
141 renderer='rhodecode:templates/admin/repos/repos.mako')
142 renderer='rhodecode:templates/admin/repos/repos.mako')
142 def repository_create(self):
143 def repository_create(self):
143 c = self.load_default_context()
144 c = self.load_default_context()
144
145
145 form_result = {}
146 form_result = {}
146 self._load_form_data(c)
147 self._load_form_data(c)
147
148
148 try:
149 try:
149 # CanWriteToGroup validators checks permissions of this POST
150 # CanWriteToGroup validators checks permissions of this POST
150 form = RepoForm(
151 form = RepoForm(
151 self.request.translate, repo_groups=c.repo_groups_choices)()
152 self.request.translate, repo_groups=c.repo_groups_choices)()
152 form_result = form.to_python(dict(self.request.POST))
153 form_result = form.to_python(dict(self.request.POST))
153 copy_permissions = form_result.get('repo_copy_permissions')
154 copy_permissions = form_result.get('repo_copy_permissions')
154 # create is done sometimes async on celery, db transaction
155 # create is done sometimes async on celery, db transaction
155 # management is handled there.
156 # management is handled there.
156 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
157 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
157 task_id = get_task_id(task)
158 task_id = get_task_id(task)
158 except formencode.Invalid as errors:
159 except formencode.Invalid as errors:
159 data = render('rhodecode:templates/admin/repos/repo_add.mako',
160 data = render('rhodecode:templates/admin/repos/repo_add.mako',
160 self._get_template_context(c), self.request)
161 self._get_template_context(c), self.request)
161 html = formencode.htmlfill.render(
162 html = formencode.htmlfill.render(
162 data,
163 data,
163 defaults=errors.value,
164 defaults=errors.value,
164 errors=errors.error_dict or {},
165 errors=errors.error_dict or {},
165 prefix_error=False,
166 prefix_error=False,
166 encoding="UTF-8",
167 encoding="UTF-8",
167 force_defaults=False
168 force_defaults=False
168 )
169 )
169 return Response(html)
170 return Response(html)
170
171
171 except Exception as e:
172 except Exception as e:
172 msg = self._log_creation_exception(e, form_result.get('repo_name'))
173 msg = self._log_creation_exception(e, form_result.get('repo_name'))
173 h.flash(msg, category='error')
174 h.flash(msg, category='error')
174 raise HTTPFound(h.route_path('home'))
175 raise HTTPFound(h.route_path('home'))
175
176
176 repo_name = form_result.get('repo_name_full')
177 repo_name = form_result.get('repo_name_full')
177
178
178 affected_user_ids = [self._rhodecode_user.user_id]
179 affected_user_ids = [self._rhodecode_user.user_id]
179 if copy_permissions:
180 if copy_permissions:
180 # permission flush is done in repo creating
181 # permission flush is done in repo creating
181 pass
182 pass
182 events.trigger(events.UserPermissionsChange(affected_user_ids))
183 PermissionModel().trigger_permission_flush(affected_user_ids)
183
184
184 raise HTTPFound(
185 raise HTTPFound(
185 h.route_path('repo_creating', repo_name=repo_name,
186 h.route_path('repo_creating', repo_name=repo_name,
186 _query=dict(task_id=task_id)))
187 _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,264 +1,265 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
59
59 c.personal_repo_group = c.rhodecode_user.personal_repo_group
60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
60
61
61 return c
62 return c
62
63
63 @LoginRequired()
64 @LoginRequired()
64 @HasRepoPermissionAnyDecorator(
65 @HasRepoPermissionAnyDecorator(
65 'repository.read', 'repository.write', 'repository.admin')
66 'repository.read', 'repository.write', 'repository.admin')
66 @view_config(
67 @view_config(
67 route_name='repo_forks_show_all', request_method='GET',
68 route_name='repo_forks_show_all', request_method='GET',
68 renderer='rhodecode:templates/forks/forks.mako')
69 renderer='rhodecode:templates/forks/forks.mako')
69 def repo_forks_show_all(self):
70 def repo_forks_show_all(self):
70 c = self.load_default_context()
71 c = self.load_default_context()
71 return self._get_template_context(c)
72 return self._get_template_context(c)
72
73
73 @LoginRequired()
74 @LoginRequired()
74 @HasRepoPermissionAnyDecorator(
75 @HasRepoPermissionAnyDecorator(
75 'repository.read', 'repository.write', 'repository.admin')
76 'repository.read', 'repository.write', 'repository.admin')
76 @view_config(
77 @view_config(
77 route_name='repo_forks_data', request_method='GET',
78 route_name='repo_forks_data', request_method='GET',
78 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
79 def repo_forks_data(self):
80 def repo_forks_data(self):
80 _ = self.request.translate
81 _ = self.request.translate
81 self.load_default_context()
82 self.load_default_context()
82 column_map = {
83 column_map = {
83 'fork_name': 'repo_name',
84 'fork_name': 'repo_name',
84 'fork_date': 'created_on',
85 'fork_date': 'created_on',
85 'last_activity': 'updated_on'
86 'last_activity': 'updated_on'
86 }
87 }
87 draw, start, limit = self._extract_chunk(self.request)
88 draw, start, limit = self._extract_chunk(self.request)
88 search_q, order_by, order_dir = self._extract_ordering(
89 search_q, order_by, order_dir = self._extract_ordering(
89 self.request, column_map=column_map)
90 self.request, column_map=column_map)
90
91
91 acl_check = HasRepoPermissionAny(
92 acl_check = HasRepoPermissionAny(
92 'repository.read', 'repository.write', 'repository.admin')
93 'repository.read', 'repository.write', 'repository.admin')
93 repo_id = self.db_repo.repo_id
94 repo_id = self.db_repo.repo_id
94 allowed_ids = [-1]
95 allowed_ids = [-1]
95 for f in Repository.query().filter(Repository.fork_id == repo_id):
96 for f in Repository.query().filter(Repository.fork_id == repo_id):
96 if acl_check(f.repo_name, 'get forks check'):
97 if acl_check(f.repo_name, 'get forks check'):
97 allowed_ids.append(f.repo_id)
98 allowed_ids.append(f.repo_id)
98
99
99 forks_data_total_count = Repository.query()\
100 forks_data_total_count = Repository.query()\
100 .filter(Repository.fork_id == repo_id)\
101 .filter(Repository.fork_id == repo_id)\
101 .filter(Repository.repo_id.in_(allowed_ids))\
102 .filter(Repository.repo_id.in_(allowed_ids))\
102 .count()
103 .count()
103
104
104 # json generate
105 # json generate
105 base_q = Repository.query()\
106 base_q = Repository.query()\
106 .filter(Repository.fork_id == repo_id)\
107 .filter(Repository.fork_id == repo_id)\
107 .filter(Repository.repo_id.in_(allowed_ids))\
108 .filter(Repository.repo_id.in_(allowed_ids))\
108
109
109 if search_q:
110 if search_q:
110 like_expression = u'%{}%'.format(safe_unicode(search_q))
111 like_expression = u'%{}%'.format(safe_unicode(search_q))
111 base_q = base_q.filter(or_(
112 base_q = base_q.filter(or_(
112 Repository.repo_name.ilike(like_expression),
113 Repository.repo_name.ilike(like_expression),
113 Repository.description.ilike(like_expression),
114 Repository.description.ilike(like_expression),
114 ))
115 ))
115
116
116 forks_data_total_filtered_count = base_q.count()
117 forks_data_total_filtered_count = base_q.count()
117
118
118 sort_col = getattr(Repository, order_by, None)
119 sort_col = getattr(Repository, order_by, None)
119 if sort_col:
120 if sort_col:
120 if order_dir == 'asc':
121 if order_dir == 'asc':
121 # handle null values properly to order by NULL last
122 # handle null values properly to order by NULL last
122 if order_by in ['last_activity']:
123 if order_by in ['last_activity']:
123 sort_col = coalesce(sort_col, datetime.date.max)
124 sort_col = coalesce(sort_col, datetime.date.max)
124 sort_col = sort_col.asc()
125 sort_col = sort_col.asc()
125 else:
126 else:
126 # handle null values properly to order by NULL last
127 # handle null values properly to order by NULL last
127 if order_by in ['last_activity']:
128 if order_by in ['last_activity']:
128 sort_col = coalesce(sort_col, datetime.date.min)
129 sort_col = coalesce(sort_col, datetime.date.min)
129 sort_col = sort_col.desc()
130 sort_col = sort_col.desc()
130
131
131 base_q = base_q.order_by(sort_col)
132 base_q = base_q.order_by(sort_col)
132 base_q = base_q.offset(start).limit(limit)
133 base_q = base_q.offset(start).limit(limit)
133
134
134 fork_list = base_q.all()
135 fork_list = base_q.all()
135
136
136 def fork_actions(fork):
137 def fork_actions(fork):
137 url_link = h.route_path(
138 url_link = h.route_path(
138 'repo_compare',
139 'repo_compare',
139 repo_name=fork.repo_name,
140 repo_name=fork.repo_name,
140 source_ref_type=self.db_repo.landing_rev[0],
141 source_ref_type=self.db_repo.landing_rev[0],
141 source_ref=self.db_repo.landing_rev[1],
142 source_ref=self.db_repo.landing_rev[1],
142 target_ref_type=self.db_repo.landing_rev[0],
143 target_ref_type=self.db_repo.landing_rev[0],
143 target_ref=self.db_repo.landing_rev[1],
144 target_ref=self.db_repo.landing_rev[1],
144 _query=dict(merge=1, target_repo=f.repo_name))
145 _query=dict(merge=1, target_repo=f.repo_name))
145 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
146 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
146
147
147 def fork_name(fork):
148 def fork_name(fork):
148 return h.link_to(fork.repo_name,
149 return h.link_to(fork.repo_name,
149 h.route_path('repo_summary', repo_name=fork.repo_name))
150 h.route_path('repo_summary', repo_name=fork.repo_name))
150
151
151 forks_data = []
152 forks_data = []
152 for fork in fork_list:
153 for fork in fork_list:
153 forks_data.append({
154 forks_data.append({
154 "username": h.gravatar_with_user(self.request, fork.user.username),
155 "username": h.gravatar_with_user(self.request, fork.user.username),
155 "fork_name": fork_name(fork),
156 "fork_name": fork_name(fork),
156 "description": fork.description_safe,
157 "description": fork.description_safe,
157 "fork_date": h.age_component(fork.created_on, time_is_local=True),
158 "fork_date": h.age_component(fork.created_on, time_is_local=True),
158 "last_activity": h.format_date(fork.updated_on),
159 "last_activity": h.format_date(fork.updated_on),
159 "action": fork_actions(fork),
160 "action": fork_actions(fork),
160 })
161 })
161
162
162 data = ({
163 data = ({
163 'draw': draw,
164 'draw': draw,
164 'data': forks_data,
165 'data': forks_data,
165 'recordsTotal': forks_data_total_count,
166 'recordsTotal': forks_data_total_count,
166 'recordsFiltered': forks_data_total_filtered_count,
167 'recordsFiltered': forks_data_total_filtered_count,
167 })
168 })
168
169
169 return data
170 return data
170
171
171 @LoginRequired()
172 @LoginRequired()
172 @NotAnonymous()
173 @NotAnonymous()
173 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
174 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
174 @HasRepoPermissionAnyDecorator(
175 @HasRepoPermissionAnyDecorator(
175 'repository.read', 'repository.write', 'repository.admin')
176 'repository.read', 'repository.write', 'repository.admin')
176 @view_config(
177 @view_config(
177 route_name='repo_fork_new', request_method='GET',
178 route_name='repo_fork_new', request_method='GET',
178 renderer='rhodecode:templates/forks/forks.mako')
179 renderer='rhodecode:templates/forks/forks.mako')
179 def repo_fork_new(self):
180 def repo_fork_new(self):
180 c = self.load_default_context()
181 c = self.load_default_context()
181
182
182 defaults = RepoModel()._get_defaults(self.db_repo_name)
183 defaults = RepoModel()._get_defaults(self.db_repo_name)
183 # alter the description to indicate a fork
184 # alter the description to indicate a fork
184 defaults['description'] = (
185 defaults['description'] = (
185 'fork of repository: %s \n%s' % (
186 'fork of repository: %s \n%s' % (
186 defaults['repo_name'], defaults['description']))
187 defaults['repo_name'], defaults['description']))
187 # add suffix to fork
188 # add suffix to fork
188 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
189 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
189
190
190 data = render('rhodecode:templates/forks/fork.mako',
191 data = render('rhodecode:templates/forks/fork.mako',
191 self._get_template_context(c), self.request)
192 self._get_template_context(c), self.request)
192 html = formencode.htmlfill.render(
193 html = formencode.htmlfill.render(
193 data,
194 data,
194 defaults=defaults,
195 defaults=defaults,
195 encoding="UTF-8",
196 encoding="UTF-8",
196 force_defaults=False
197 force_defaults=False
197 )
198 )
198 return Response(html)
199 return Response(html)
199
200
200 @LoginRequired()
201 @LoginRequired()
201 @NotAnonymous()
202 @NotAnonymous()
202 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
203 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
203 @HasRepoPermissionAnyDecorator(
204 @HasRepoPermissionAnyDecorator(
204 'repository.read', 'repository.write', 'repository.admin')
205 'repository.read', 'repository.write', 'repository.admin')
205 @CSRFRequired()
206 @CSRFRequired()
206 @view_config(
207 @view_config(
207 route_name='repo_fork_create', request_method='POST',
208 route_name='repo_fork_create', request_method='POST',
208 renderer='rhodecode:templates/forks/fork.mako')
209 renderer='rhodecode:templates/forks/fork.mako')
209 def repo_fork_create(self):
210 def repo_fork_create(self):
210 _ = self.request.translate
211 _ = self.request.translate
211 c = self.load_default_context()
212 c = self.load_default_context()
212
213
213 _form = RepoForkForm(self.request.translate,
214 _form = RepoForkForm(self.request.translate,
214 old_data={'repo_type': self.db_repo.repo_type},
215 old_data={'repo_type': self.db_repo.repo_type},
215 repo_groups=c.repo_groups_choices)()
216 repo_groups=c.repo_groups_choices)()
216 post_data = dict(self.request.POST)
217 post_data = dict(self.request.POST)
217
218
218 # forbid injecting other repo by forging a request
219 # forbid injecting other repo by forging a request
219 post_data['fork_parent_id'] = self.db_repo.repo_id
220 post_data['fork_parent_id'] = self.db_repo.repo_id
220
221
221 form_result = {}
222 form_result = {}
222 task_id = None
223 task_id = None
223 try:
224 try:
224 form_result = _form.to_python(post_data)
225 form_result = _form.to_python(post_data)
225 copy_permissions = form_result.get('copy_permissions')
226 copy_permissions = form_result.get('copy_permissions')
226 # create fork is done sometimes async on celery, db transaction
227 # create fork is done sometimes async on celery, db transaction
227 # management is handled there.
228 # management is handled there.
228 task = RepoModel().create_fork(
229 task = RepoModel().create_fork(
229 form_result, c.rhodecode_user.user_id)
230 form_result, c.rhodecode_user.user_id)
230
231
231 task_id = get_task_id(task)
232 task_id = get_task_id(task)
232 except formencode.Invalid as errors:
233 except formencode.Invalid as errors:
233 c.rhodecode_db_repo = self.db_repo
234 c.rhodecode_db_repo = self.db_repo
234
235
235 data = render('rhodecode:templates/forks/fork.mako',
236 data = render('rhodecode:templates/forks/fork.mako',
236 self._get_template_context(c), self.request)
237 self._get_template_context(c), self.request)
237 html = formencode.htmlfill.render(
238 html = formencode.htmlfill.render(
238 data,
239 data,
239 defaults=errors.value,
240 defaults=errors.value,
240 errors=errors.error_dict or {},
241 errors=errors.error_dict or {},
241 prefix_error=False,
242 prefix_error=False,
242 encoding="UTF-8",
243 encoding="UTF-8",
243 force_defaults=False
244 force_defaults=False
244 )
245 )
245 return Response(html)
246 return Response(html)
246 except Exception:
247 except Exception:
247 log.exception(
248 log.exception(
248 u'Exception while trying to fork the repository %s', self.db_repo_name)
249 u'Exception while trying to fork the repository %s', self.db_repo_name)
249 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
250 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
250 h.flash(msg, category='error')
251 h.flash(msg, category='error')
251 raise HTTPFound(h.route_path('home'))
252 raise HTTPFound(h.route_path('home'))
252
253
253 repo_name = form_result.get('repo_name_full', self.db_repo_name)
254 repo_name = form_result.get('repo_name_full', self.db_repo_name)
254
255
255 affected_user_ids = [self._rhodecode_user.user_id]
256 affected_user_ids = [self._rhodecode_user.user_id]
256 if copy_permissions:
257 if copy_permissions:
257 # permission flush is done in repo creating
258 # permission flush is done in repo creating
258 pass
259 pass
259
260
260 events.trigger(events.UserPermissionsChange(affected_user_ids))
261 PermissionModel().trigger_permission_flush(affected_user_ids)
261
262
262 raise HTTPFound(
263 raise HTTPFound(
263 h.route_path('repo_creating', repo_name=repo_name,
264 h.route_path('repo_creating', repo_name=repo_name,
264 _query=dict(task_id=task_id)))
265 _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