##// END OF EJS Templates
audit-logs: implemented full audit logs across application....
marcink -
r1829:ff4add41 default
parent child Browse files
Show More
@@ -1,2062 +1,2070 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
37 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.model.changeset_status import ChangesetStatusModel
38 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.comment import CommentsModel
39 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.db import (
40 from rhodecode.model.db import (
41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
41 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
42 ChangesetComment)
42 ChangesetComment)
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.scm import ScmModel, RepoList
44 from rhodecode.model.scm import ScmModel, RepoList
45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
45 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
46 from rhodecode.model import validation_schema
46 from rhodecode.model import validation_schema
47 from rhodecode.model.validation_schema.schemas import repo_schema
47 from rhodecode.model.validation_schema.schemas import repo_schema
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 @jsonrpc_method()
52 @jsonrpc_method()
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
53 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 """
54 """
55 Gets an existing repository by its name or repository_id.
55 Gets an existing repository by its name or repository_id.
56
56
57 The members section so the output returns users groups or users
57 The members section so the output returns users groups or users
58 associated with that repository.
58 associated with that repository.
59
59
60 This command can only be run using an |authtoken| with admin rights,
60 This command can only be run using an |authtoken| with admin rights,
61 or users with at least read rights to the |repo|.
61 or users with at least read rights to the |repo|.
62
62
63 :param apiuser: This is filled automatically from the |authtoken|.
63 :param apiuser: This is filled automatically from the |authtoken|.
64 :type apiuser: AuthUser
64 :type apiuser: AuthUser
65 :param repoid: The repository name or repository id.
65 :param repoid: The repository name or repository id.
66 :type repoid: str or int
66 :type repoid: str or int
67 :param cache: use the cached value for last changeset
67 :param cache: use the cached value for last changeset
68 :type: cache: Optional(bool)
68 :type: cache: Optional(bool)
69
69
70 Example output:
70 Example output:
71
71
72 .. code-block:: bash
72 .. code-block:: bash
73
73
74 {
74 {
75 "error": null,
75 "error": null,
76 "id": <repo_id>,
76 "id": <repo_id>,
77 "result": {
77 "result": {
78 "clone_uri": null,
78 "clone_uri": null,
79 "created_on": "timestamp",
79 "created_on": "timestamp",
80 "description": "repo description",
80 "description": "repo description",
81 "enable_downloads": false,
81 "enable_downloads": false,
82 "enable_locking": false,
82 "enable_locking": false,
83 "enable_statistics": false,
83 "enable_statistics": false,
84 "followers": [
84 "followers": [
85 {
85 {
86 "active": true,
86 "active": true,
87 "admin": false,
87 "admin": false,
88 "api_key": "****************************************",
88 "api_key": "****************************************",
89 "api_keys": [
89 "api_keys": [
90 "****************************************"
90 "****************************************"
91 ],
91 ],
92 "email": "user@example.com",
92 "email": "user@example.com",
93 "emails": [
93 "emails": [
94 "user@example.com"
94 "user@example.com"
95 ],
95 ],
96 "extern_name": "rhodecode",
96 "extern_name": "rhodecode",
97 "extern_type": "rhodecode",
97 "extern_type": "rhodecode",
98 "firstname": "username",
98 "firstname": "username",
99 "ip_addresses": [],
99 "ip_addresses": [],
100 "language": null,
100 "language": null,
101 "last_login": "2015-09-16T17:16:35.854",
101 "last_login": "2015-09-16T17:16:35.854",
102 "lastname": "surname",
102 "lastname": "surname",
103 "user_id": <user_id>,
103 "user_id": <user_id>,
104 "username": "name"
104 "username": "name"
105 }
105 }
106 ],
106 ],
107 "fork_of": "parent-repo",
107 "fork_of": "parent-repo",
108 "landing_rev": [
108 "landing_rev": [
109 "rev",
109 "rev",
110 "tip"
110 "tip"
111 ],
111 ],
112 "last_changeset": {
112 "last_changeset": {
113 "author": "User <user@example.com>",
113 "author": "User <user@example.com>",
114 "branch": "default",
114 "branch": "default",
115 "date": "timestamp",
115 "date": "timestamp",
116 "message": "last commit message",
116 "message": "last commit message",
117 "parents": [
117 "parents": [
118 {
118 {
119 "raw_id": "commit-id"
119 "raw_id": "commit-id"
120 }
120 }
121 ],
121 ],
122 "raw_id": "commit-id",
122 "raw_id": "commit-id",
123 "revision": <revision number>,
123 "revision": <revision number>,
124 "short_id": "short id"
124 "short_id": "short id"
125 },
125 },
126 "lock_reason": null,
126 "lock_reason": null,
127 "locked_by": null,
127 "locked_by": null,
128 "locked_date": null,
128 "locked_date": null,
129 "members": [
129 "members": [
130 {
130 {
131 "name": "super-admin-name",
131 "name": "super-admin-name",
132 "origin": "super-admin",
132 "origin": "super-admin",
133 "permission": "repository.admin",
133 "permission": "repository.admin",
134 "type": "user"
134 "type": "user"
135 },
135 },
136 {
136 {
137 "name": "owner-name",
137 "name": "owner-name",
138 "origin": "owner",
138 "origin": "owner",
139 "permission": "repository.admin",
139 "permission": "repository.admin",
140 "type": "user"
140 "type": "user"
141 },
141 },
142 {
142 {
143 "name": "user-group-name",
143 "name": "user-group-name",
144 "origin": "permission",
144 "origin": "permission",
145 "permission": "repository.write",
145 "permission": "repository.write",
146 "type": "user_group"
146 "type": "user_group"
147 }
147 }
148 ],
148 ],
149 "owner": "owner-name",
149 "owner": "owner-name",
150 "permissions": [
150 "permissions": [
151 {
151 {
152 "name": "super-admin-name",
152 "name": "super-admin-name",
153 "origin": "super-admin",
153 "origin": "super-admin",
154 "permission": "repository.admin",
154 "permission": "repository.admin",
155 "type": "user"
155 "type": "user"
156 },
156 },
157 {
157 {
158 "name": "owner-name",
158 "name": "owner-name",
159 "origin": "owner",
159 "origin": "owner",
160 "permission": "repository.admin",
160 "permission": "repository.admin",
161 "type": "user"
161 "type": "user"
162 },
162 },
163 {
163 {
164 "name": "user-group-name",
164 "name": "user-group-name",
165 "origin": "permission",
165 "origin": "permission",
166 "permission": "repository.write",
166 "permission": "repository.write",
167 "type": "user_group"
167 "type": "user_group"
168 }
168 }
169 ],
169 ],
170 "private": true,
170 "private": true,
171 "repo_id": 676,
171 "repo_id": 676,
172 "repo_name": "user-group/repo-name",
172 "repo_name": "user-group/repo-name",
173 "repo_type": "hg"
173 "repo_type": "hg"
174 }
174 }
175 }
175 }
176 """
176 """
177
177
178 repo = get_repo_or_error(repoid)
178 repo = get_repo_or_error(repoid)
179 cache = Optional.extract(cache)
179 cache = Optional.extract(cache)
180
180
181 include_secrets = False
181 include_secrets = False
182 if has_superadmin_permission(apiuser):
182 if has_superadmin_permission(apiuser):
183 include_secrets = True
183 include_secrets = True
184 else:
184 else:
185 # check if we have at least read permission for this repo !
185 # check if we have at least read permission for this repo !
186 _perms = (
186 _perms = (
187 'repository.admin', 'repository.write', 'repository.read',)
187 'repository.admin', 'repository.write', 'repository.read',)
188 validate_repo_permissions(apiuser, repoid, repo, _perms)
188 validate_repo_permissions(apiuser, repoid, repo, _perms)
189
189
190 permissions = []
190 permissions = []
191 for _user in repo.permissions():
191 for _user in repo.permissions():
192 user_data = {
192 user_data = {
193 'name': _user.username,
193 'name': _user.username,
194 'permission': _user.permission,
194 'permission': _user.permission,
195 'origin': get_origin(_user),
195 'origin': get_origin(_user),
196 'type': "user",
196 'type': "user",
197 }
197 }
198 permissions.append(user_data)
198 permissions.append(user_data)
199
199
200 for _user_group in repo.permission_user_groups():
200 for _user_group in repo.permission_user_groups():
201 user_group_data = {
201 user_group_data = {
202 'name': _user_group.users_group_name,
202 'name': _user_group.users_group_name,
203 'permission': _user_group.permission,
203 'permission': _user_group.permission,
204 'origin': get_origin(_user_group),
204 'origin': get_origin(_user_group),
205 'type': "user_group",
205 'type': "user_group",
206 }
206 }
207 permissions.append(user_group_data)
207 permissions.append(user_group_data)
208
208
209 following_users = [
209 following_users = [
210 user.user.get_api_data(include_secrets=include_secrets)
210 user.user.get_api_data(include_secrets=include_secrets)
211 for user in repo.followers]
211 for user in repo.followers]
212
212
213 if not cache:
213 if not cache:
214 repo.update_commit_cache()
214 repo.update_commit_cache()
215 data = repo.get_api_data(include_secrets=include_secrets)
215 data = repo.get_api_data(include_secrets=include_secrets)
216 data['members'] = permissions # TODO: this should be deprecated soon
216 data['members'] = permissions # TODO: this should be deprecated soon
217 data['permissions'] = permissions
217 data['permissions'] = permissions
218 data['followers'] = following_users
218 data['followers'] = following_users
219 return data
219 return data
220
220
221
221
222 @jsonrpc_method()
222 @jsonrpc_method()
223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
223 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
224 """
224 """
225 Lists all existing repositories.
225 Lists all existing repositories.
226
226
227 This command can only be run using an |authtoken| with admin rights,
227 This command can only be run using an |authtoken| with admin rights,
228 or users with at least read rights to |repos|.
228 or users with at least read rights to |repos|.
229
229
230 :param apiuser: This is filled automatically from the |authtoken|.
230 :param apiuser: This is filled automatically from the |authtoken|.
231 :type apiuser: AuthUser
231 :type apiuser: AuthUser
232 :param root: specify root repository group to fetch repositories.
232 :param root: specify root repository group to fetch repositories.
233 filters the returned repositories to be members of given root group.
233 filters the returned repositories to be members of given root group.
234 :type root: Optional(None)
234 :type root: Optional(None)
235 :param traverse: traverse given root into subrepositories. With this flag
235 :param traverse: traverse given root into subrepositories. With this flag
236 set to False, it will only return top-level repositories from `root`.
236 set to False, it will only return top-level repositories from `root`.
237 if root is empty it will return just top-level repositories.
237 if root is empty it will return just top-level repositories.
238 :type traverse: Optional(True)
238 :type traverse: Optional(True)
239
239
240
240
241 Example output:
241 Example output:
242
242
243 .. code-block:: bash
243 .. code-block:: bash
244
244
245 id : <id_given_in_input>
245 id : <id_given_in_input>
246 result: [
246 result: [
247 {
247 {
248 "repo_id" : "<repo_id>",
248 "repo_id" : "<repo_id>",
249 "repo_name" : "<reponame>"
249 "repo_name" : "<reponame>"
250 "repo_type" : "<repo_type>",
250 "repo_type" : "<repo_type>",
251 "clone_uri" : "<clone_uri>",
251 "clone_uri" : "<clone_uri>",
252 "private": : "<bool>",
252 "private": : "<bool>",
253 "created_on" : "<datetimecreated>",
253 "created_on" : "<datetimecreated>",
254 "description" : "<description>",
254 "description" : "<description>",
255 "landing_rev": "<landing_rev>",
255 "landing_rev": "<landing_rev>",
256 "owner": "<repo_owner>",
256 "owner": "<repo_owner>",
257 "fork_of": "<name_of_fork_parent>",
257 "fork_of": "<name_of_fork_parent>",
258 "enable_downloads": "<bool>",
258 "enable_downloads": "<bool>",
259 "enable_locking": "<bool>",
259 "enable_locking": "<bool>",
260 "enable_statistics": "<bool>",
260 "enable_statistics": "<bool>",
261 },
261 },
262 ...
262 ...
263 ]
263 ]
264 error: null
264 error: null
265 """
265 """
266
266
267 include_secrets = has_superadmin_permission(apiuser)
267 include_secrets = has_superadmin_permission(apiuser)
268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
268 _perms = ('repository.read', 'repository.write', 'repository.admin',)
269 extras = {'user': apiuser}
269 extras = {'user': apiuser}
270
270
271 root = Optional.extract(root)
271 root = Optional.extract(root)
272 traverse = Optional.extract(traverse, binary=True)
272 traverse = Optional.extract(traverse, binary=True)
273
273
274 if root:
274 if root:
275 # verify parent existance, if it's empty return an error
275 # verify parent existance, if it's empty return an error
276 parent = RepoGroup.get_by_group_name(root)
276 parent = RepoGroup.get_by_group_name(root)
277 if not parent:
277 if not parent:
278 raise JSONRPCError(
278 raise JSONRPCError(
279 'Root repository group `{}` does not exist'.format(root))
279 'Root repository group `{}` does not exist'.format(root))
280
280
281 if traverse:
281 if traverse:
282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
282 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
283 else:
283 else:
284 repos = RepoModel().get_repos_for_root(root=parent)
284 repos = RepoModel().get_repos_for_root(root=parent)
285 else:
285 else:
286 if traverse:
286 if traverse:
287 repos = RepoModel().get_all()
287 repos = RepoModel().get_all()
288 else:
288 else:
289 # return just top-level
289 # return just top-level
290 repos = RepoModel().get_repos_for_root(root=None)
290 repos = RepoModel().get_repos_for_root(root=None)
291
291
292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
292 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
293 return [repo.get_api_data(include_secrets=include_secrets)
293 return [repo.get_api_data(include_secrets=include_secrets)
294 for repo in repo_list]
294 for repo in repo_list]
295
295
296
296
297 @jsonrpc_method()
297 @jsonrpc_method()
298 def get_repo_changeset(request, apiuser, repoid, revision,
298 def get_repo_changeset(request, apiuser, repoid, revision,
299 details=Optional('basic')):
299 details=Optional('basic')):
300 """
300 """
301 Returns information about a changeset.
301 Returns information about a changeset.
302
302
303 Additionally parameters define the amount of details returned by
303 Additionally parameters define the amount of details returned by
304 this function.
304 this function.
305
305
306 This command can only be run using an |authtoken| with admin rights,
306 This command can only be run using an |authtoken| with admin rights,
307 or users with at least read rights to the |repo|.
307 or users with at least read rights to the |repo|.
308
308
309 :param apiuser: This is filled automatically from the |authtoken|.
309 :param apiuser: This is filled automatically from the |authtoken|.
310 :type apiuser: AuthUser
310 :type apiuser: AuthUser
311 :param repoid: The repository name or repository id
311 :param repoid: The repository name or repository id
312 :type repoid: str or int
312 :type repoid: str or int
313 :param revision: revision for which listing should be done
313 :param revision: revision for which listing should be done
314 :type revision: str
314 :type revision: str
315 :param details: details can be 'basic|extended|full' full gives diff
315 :param details: details can be 'basic|extended|full' full gives diff
316 info details like the diff itself, and number of changed files etc.
316 info details like the diff itself, and number of changed files etc.
317 :type details: Optional(str)
317 :type details: Optional(str)
318
318
319 """
319 """
320 repo = get_repo_or_error(repoid)
320 repo = get_repo_or_error(repoid)
321 if not has_superadmin_permission(apiuser):
321 if not has_superadmin_permission(apiuser):
322 _perms = (
322 _perms = (
323 'repository.admin', 'repository.write', 'repository.read',)
323 'repository.admin', 'repository.write', 'repository.read',)
324 validate_repo_permissions(apiuser, repoid, repo, _perms)
324 validate_repo_permissions(apiuser, repoid, repo, _perms)
325
325
326 changes_details = Optional.extract(details)
326 changes_details = Optional.extract(details)
327 _changes_details_types = ['basic', 'extended', 'full']
327 _changes_details_types = ['basic', 'extended', 'full']
328 if changes_details not in _changes_details_types:
328 if changes_details not in _changes_details_types:
329 raise JSONRPCError(
329 raise JSONRPCError(
330 'ret_type must be one of %s' % (
330 'ret_type must be one of %s' % (
331 ','.join(_changes_details_types)))
331 ','.join(_changes_details_types)))
332
332
333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
333 pre_load = ['author', 'branch', 'date', 'message', 'parents',
334 'status', '_commit', '_file_paths']
334 'status', '_commit', '_file_paths']
335
335
336 try:
336 try:
337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
337 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
338 except TypeError as e:
338 except TypeError as e:
339 raise JSONRPCError(e.message)
339 raise JSONRPCError(e.message)
340 _cs_json = cs.__json__()
340 _cs_json = cs.__json__()
341 _cs_json['diff'] = build_commit_data(cs, changes_details)
341 _cs_json['diff'] = build_commit_data(cs, changes_details)
342 if changes_details == 'full':
342 if changes_details == 'full':
343 _cs_json['refs'] = {
343 _cs_json['refs'] = {
344 'branches': [cs.branch],
344 'branches': [cs.branch],
345 'bookmarks': getattr(cs, 'bookmarks', []),
345 'bookmarks': getattr(cs, 'bookmarks', []),
346 'tags': cs.tags
346 'tags': cs.tags
347 }
347 }
348 return _cs_json
348 return _cs_json
349
349
350
350
351 @jsonrpc_method()
351 @jsonrpc_method()
352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
352 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
353 details=Optional('basic')):
353 details=Optional('basic')):
354 """
354 """
355 Returns a set of commits limited by the number starting
355 Returns a set of commits limited by the number starting
356 from the `start_rev` option.
356 from the `start_rev` option.
357
357
358 Additional parameters define the amount of details returned by this
358 Additional parameters define the amount of details returned by this
359 function.
359 function.
360
360
361 This command can only be run using an |authtoken| with admin rights,
361 This command can only be run using an |authtoken| with admin rights,
362 or users with at least read rights to |repos|.
362 or users with at least read rights to |repos|.
363
363
364 :param apiuser: This is filled automatically from the |authtoken|.
364 :param apiuser: This is filled automatically from the |authtoken|.
365 :type apiuser: AuthUser
365 :type apiuser: AuthUser
366 :param repoid: The repository name or repository ID.
366 :param repoid: The repository name or repository ID.
367 :type repoid: str or int
367 :type repoid: str or int
368 :param start_rev: The starting revision from where to get changesets.
368 :param start_rev: The starting revision from where to get changesets.
369 :type start_rev: str
369 :type start_rev: str
370 :param limit: Limit the number of commits to this amount
370 :param limit: Limit the number of commits to this amount
371 :type limit: str or int
371 :type limit: str or int
372 :param details: Set the level of detail returned. Valid option are:
372 :param details: Set the level of detail returned. Valid option are:
373 ``basic``, ``extended`` and ``full``.
373 ``basic``, ``extended`` and ``full``.
374 :type details: Optional(str)
374 :type details: Optional(str)
375
375
376 .. note::
376 .. note::
377
377
378 Setting the parameter `details` to the value ``full`` is extensive
378 Setting the parameter `details` to the value ``full`` is extensive
379 and returns details like the diff itself, and the number
379 and returns details like the diff itself, and the number
380 of changed files.
380 of changed files.
381
381
382 """
382 """
383 repo = get_repo_or_error(repoid)
383 repo = get_repo_or_error(repoid)
384 if not has_superadmin_permission(apiuser):
384 if not has_superadmin_permission(apiuser):
385 _perms = (
385 _perms = (
386 'repository.admin', 'repository.write', 'repository.read',)
386 'repository.admin', 'repository.write', 'repository.read',)
387 validate_repo_permissions(apiuser, repoid, repo, _perms)
387 validate_repo_permissions(apiuser, repoid, repo, _perms)
388
388
389 changes_details = Optional.extract(details)
389 changes_details = Optional.extract(details)
390 _changes_details_types = ['basic', 'extended', 'full']
390 _changes_details_types = ['basic', 'extended', 'full']
391 if changes_details not in _changes_details_types:
391 if changes_details not in _changes_details_types:
392 raise JSONRPCError(
392 raise JSONRPCError(
393 'ret_type must be one of %s' % (
393 'ret_type must be one of %s' % (
394 ','.join(_changes_details_types)))
394 ','.join(_changes_details_types)))
395
395
396 limit = int(limit)
396 limit = int(limit)
397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
397 pre_load = ['author', 'branch', 'date', 'message', 'parents',
398 'status', '_commit', '_file_paths']
398 'status', '_commit', '_file_paths']
399
399
400 vcs_repo = repo.scm_instance()
400 vcs_repo = repo.scm_instance()
401 # SVN needs a special case to distinguish its index and commit id
401 # SVN needs a special case to distinguish its index and commit id
402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
402 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
403 start_rev = vcs_repo.commit_ids[0]
403 start_rev = vcs_repo.commit_ids[0]
404
404
405 try:
405 try:
406 commits = vcs_repo.get_commits(
406 commits = vcs_repo.get_commits(
407 start_id=start_rev, pre_load=pre_load)
407 start_id=start_rev, pre_load=pre_load)
408 except TypeError as e:
408 except TypeError as e:
409 raise JSONRPCError(e.message)
409 raise JSONRPCError(e.message)
410 except Exception:
410 except Exception:
411 log.exception('Fetching of commits failed')
411 log.exception('Fetching of commits failed')
412 raise JSONRPCError('Error occurred during commit fetching')
412 raise JSONRPCError('Error occurred during commit fetching')
413
413
414 ret = []
414 ret = []
415 for cnt, commit in enumerate(commits):
415 for cnt, commit in enumerate(commits):
416 if cnt >= limit != -1:
416 if cnt >= limit != -1:
417 break
417 break
418 _cs_json = commit.__json__()
418 _cs_json = commit.__json__()
419 _cs_json['diff'] = build_commit_data(commit, changes_details)
419 _cs_json['diff'] = build_commit_data(commit, changes_details)
420 if changes_details == 'full':
420 if changes_details == 'full':
421 _cs_json['refs'] = {
421 _cs_json['refs'] = {
422 'branches': [commit.branch],
422 'branches': [commit.branch],
423 'bookmarks': getattr(commit, 'bookmarks', []),
423 'bookmarks': getattr(commit, 'bookmarks', []),
424 'tags': commit.tags
424 'tags': commit.tags
425 }
425 }
426 ret.append(_cs_json)
426 ret.append(_cs_json)
427 return ret
427 return ret
428
428
429
429
430 @jsonrpc_method()
430 @jsonrpc_method()
431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
431 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
432 ret_type=Optional('all'), details=Optional('basic'),
432 ret_type=Optional('all'), details=Optional('basic'),
433 max_file_bytes=Optional(None)):
433 max_file_bytes=Optional(None)):
434 """
434 """
435 Returns a list of nodes and children in a flat list for a given
435 Returns a list of nodes and children in a flat list for a given
436 path at given revision.
436 path at given revision.
437
437
438 It's possible to specify ret_type to show only `files` or `dirs`.
438 It's possible to specify ret_type to show only `files` or `dirs`.
439
439
440 This command can only be run using an |authtoken| with admin rights,
440 This command can only be run using an |authtoken| with admin rights,
441 or users with at least read rights to |repos|.
441 or users with at least read rights to |repos|.
442
442
443 :param apiuser: This is filled automatically from the |authtoken|.
443 :param apiuser: This is filled automatically from the |authtoken|.
444 :type apiuser: AuthUser
444 :type apiuser: AuthUser
445 :param repoid: The repository name or repository ID.
445 :param repoid: The repository name or repository ID.
446 :type repoid: str or int
446 :type repoid: str or int
447 :param revision: The revision for which listing should be done.
447 :param revision: The revision for which listing should be done.
448 :type revision: str
448 :type revision: str
449 :param root_path: The path from which to start displaying.
449 :param root_path: The path from which to start displaying.
450 :type root_path: str
450 :type root_path: str
451 :param ret_type: Set the return type. Valid options are
451 :param ret_type: Set the return type. Valid options are
452 ``all`` (default), ``files`` and ``dirs``.
452 ``all`` (default), ``files`` and ``dirs``.
453 :type ret_type: Optional(str)
453 :type ret_type: Optional(str)
454 :param details: Returns extended information about nodes, such as
454 :param details: Returns extended information about nodes, such as
455 md5, binary, and or content. The valid options are ``basic`` and
455 md5, binary, and or content. The valid options are ``basic`` and
456 ``full``.
456 ``full``.
457 :type details: Optional(str)
457 :type details: Optional(str)
458 :param max_file_bytes: Only return file content under this file size bytes
458 :param max_file_bytes: Only return file content under this file size bytes
459 :type details: Optional(int)
459 :type details: Optional(int)
460
460
461 Example output:
461 Example output:
462
462
463 .. code-block:: bash
463 .. code-block:: bash
464
464
465 id : <id_given_in_input>
465 id : <id_given_in_input>
466 result: [
466 result: [
467 {
467 {
468 "name" : "<name>"
468 "name" : "<name>"
469 "type" : "<type>",
469 "type" : "<type>",
470 "binary": "<true|false>" (only in extended mode)
470 "binary": "<true|false>" (only in extended mode)
471 "md5" : "<md5 of file content>" (only in extended mode)
471 "md5" : "<md5 of file content>" (only in extended mode)
472 },
472 },
473 ...
473 ...
474 ]
474 ]
475 error: null
475 error: null
476 """
476 """
477
477
478 repo = get_repo_or_error(repoid)
478 repo = get_repo_or_error(repoid)
479 if not has_superadmin_permission(apiuser):
479 if not has_superadmin_permission(apiuser):
480 _perms = (
480 _perms = (
481 'repository.admin', 'repository.write', 'repository.read',)
481 'repository.admin', 'repository.write', 'repository.read',)
482 validate_repo_permissions(apiuser, repoid, repo, _perms)
482 validate_repo_permissions(apiuser, repoid, repo, _perms)
483
483
484 ret_type = Optional.extract(ret_type)
484 ret_type = Optional.extract(ret_type)
485 details = Optional.extract(details)
485 details = Optional.extract(details)
486 _extended_types = ['basic', 'full']
486 _extended_types = ['basic', 'full']
487 if details not in _extended_types:
487 if details not in _extended_types:
488 raise JSONRPCError(
488 raise JSONRPCError(
489 'ret_type must be one of %s' % (','.join(_extended_types)))
489 'ret_type must be one of %s' % (','.join(_extended_types)))
490 extended_info = False
490 extended_info = False
491 content = False
491 content = False
492 if details == 'basic':
492 if details == 'basic':
493 extended_info = True
493 extended_info = True
494
494
495 if details == 'full':
495 if details == 'full':
496 extended_info = content = True
496 extended_info = content = True
497
497
498 _map = {}
498 _map = {}
499 try:
499 try:
500 # check if repo is not empty by any chance, skip quicker if it is.
500 # check if repo is not empty by any chance, skip quicker if it is.
501 _scm = repo.scm_instance()
501 _scm = repo.scm_instance()
502 if _scm.is_empty():
502 if _scm.is_empty():
503 return []
503 return []
504
504
505 _d, _f = ScmModel().get_nodes(
505 _d, _f = ScmModel().get_nodes(
506 repo, revision, root_path, flat=False,
506 repo, revision, root_path, flat=False,
507 extended_info=extended_info, content=content,
507 extended_info=extended_info, content=content,
508 max_file_bytes=max_file_bytes)
508 max_file_bytes=max_file_bytes)
509 _map = {
509 _map = {
510 'all': _d + _f,
510 'all': _d + _f,
511 'files': _f,
511 'files': _f,
512 'dirs': _d,
512 'dirs': _d,
513 }
513 }
514 return _map[ret_type]
514 return _map[ret_type]
515 except KeyError:
515 except KeyError:
516 raise JSONRPCError(
516 raise JSONRPCError(
517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
517 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
518 except Exception:
518 except Exception:
519 log.exception("Exception occurred while trying to get repo nodes")
519 log.exception("Exception occurred while trying to get repo nodes")
520 raise JSONRPCError(
520 raise JSONRPCError(
521 'failed to get repo: `%s` nodes' % repo.repo_name
521 'failed to get repo: `%s` nodes' % repo.repo_name
522 )
522 )
523
523
524
524
525 @jsonrpc_method()
525 @jsonrpc_method()
526 def get_repo_refs(request, apiuser, repoid):
526 def get_repo_refs(request, apiuser, repoid):
527 """
527 """
528 Returns a dictionary of current references. It returns
528 Returns a dictionary of current references. It returns
529 bookmarks, branches, closed_branches, and tags for given repository
529 bookmarks, branches, closed_branches, and tags for given repository
530
530
531 It's possible to specify ret_type to show only `files` or `dirs`.
531 It's possible to specify ret_type to show only `files` or `dirs`.
532
532
533 This command can only be run using an |authtoken| with admin rights,
533 This command can only be run using an |authtoken| with admin rights,
534 or users with at least read rights to |repos|.
534 or users with at least read rights to |repos|.
535
535
536 :param apiuser: This is filled automatically from the |authtoken|.
536 :param apiuser: This is filled automatically from the |authtoken|.
537 :type apiuser: AuthUser
537 :type apiuser: AuthUser
538 :param repoid: The repository name or repository ID.
538 :param repoid: The repository name or repository ID.
539 :type repoid: str or int
539 :type repoid: str or int
540
540
541 Example output:
541 Example output:
542
542
543 .. code-block:: bash
543 .. code-block:: bash
544
544
545 id : <id_given_in_input>
545 id : <id_given_in_input>
546 "result": {
546 "result": {
547 "bookmarks": {
547 "bookmarks": {
548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
548 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
549 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 },
550 },
551 "branches": {
551 "branches": {
552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
552 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
553 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
554 },
554 },
555 "branches_closed": {},
555 "branches_closed": {},
556 "tags": {
556 "tags": {
557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
557 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
558 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
559 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
560 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
561 }
561 }
562 }
562 }
563 error: null
563 error: null
564 """
564 """
565
565
566 repo = get_repo_or_error(repoid)
566 repo = get_repo_or_error(repoid)
567 if not has_superadmin_permission(apiuser):
567 if not has_superadmin_permission(apiuser):
568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
568 _perms = ('repository.admin', 'repository.write', 'repository.read',)
569 validate_repo_permissions(apiuser, repoid, repo, _perms)
569 validate_repo_permissions(apiuser, repoid, repo, _perms)
570
570
571 try:
571 try:
572 # check if repo is not empty by any chance, skip quicker if it is.
572 # check if repo is not empty by any chance, skip quicker if it is.
573 vcs_instance = repo.scm_instance()
573 vcs_instance = repo.scm_instance()
574 refs = vcs_instance.refs()
574 refs = vcs_instance.refs()
575 return refs
575 return refs
576 except Exception:
576 except Exception:
577 log.exception("Exception occurred while trying to get repo refs")
577 log.exception("Exception occurred while trying to get repo refs")
578 raise JSONRPCError(
578 raise JSONRPCError(
579 'failed to get repo: `%s` references' % repo.repo_name
579 'failed to get repo: `%s` references' % repo.repo_name
580 )
580 )
581
581
582
582
583 @jsonrpc_method()
583 @jsonrpc_method()
584 def create_repo(
584 def create_repo(
585 request, apiuser, repo_name, repo_type,
585 request, apiuser, repo_name, repo_type,
586 owner=Optional(OAttr('apiuser')),
586 owner=Optional(OAttr('apiuser')),
587 description=Optional(''),
587 description=Optional(''),
588 private=Optional(False),
588 private=Optional(False),
589 clone_uri=Optional(None),
589 clone_uri=Optional(None),
590 landing_rev=Optional('rev:tip'),
590 landing_rev=Optional('rev:tip'),
591 enable_statistics=Optional(False),
591 enable_statistics=Optional(False),
592 enable_locking=Optional(False),
592 enable_locking=Optional(False),
593 enable_downloads=Optional(False),
593 enable_downloads=Optional(False),
594 copy_permissions=Optional(False)):
594 copy_permissions=Optional(False)):
595 """
595 """
596 Creates a repository.
596 Creates a repository.
597
597
598 * If the repository name contains "/", repository will be created inside
598 * If the repository name contains "/", repository will be created inside
599 a repository group or nested repository groups
599 a repository group or nested repository groups
600
600
601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
601 For example "foo/bar/repo1" will create |repo| called "repo1" inside
602 group "foo/bar". You have to have permissions to access and write to
602 group "foo/bar". You have to have permissions to access and write to
603 the last repository group ("bar" in this example)
603 the last repository group ("bar" in this example)
604
604
605 This command can only be run using an |authtoken| with at least
605 This command can only be run using an |authtoken| with at least
606 permissions to create repositories, or write permissions to
606 permissions to create repositories, or write permissions to
607 parent repository groups.
607 parent repository groups.
608
608
609 :param apiuser: This is filled automatically from the |authtoken|.
609 :param apiuser: This is filled automatically from the |authtoken|.
610 :type apiuser: AuthUser
610 :type apiuser: AuthUser
611 :param repo_name: Set the repository name.
611 :param repo_name: Set the repository name.
612 :type repo_name: str
612 :type repo_name: str
613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
613 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
614 :type repo_type: str
614 :type repo_type: str
615 :param owner: user_id or username
615 :param owner: user_id or username
616 :type owner: Optional(str)
616 :type owner: Optional(str)
617 :param description: Set the repository description.
617 :param description: Set the repository description.
618 :type description: Optional(str)
618 :type description: Optional(str)
619 :param private: set repository as private
619 :param private: set repository as private
620 :type private: bool
620 :type private: bool
621 :param clone_uri: set clone_uri
621 :param clone_uri: set clone_uri
622 :type clone_uri: str
622 :type clone_uri: str
623 :param landing_rev: <rev_type>:<rev>
623 :param landing_rev: <rev_type>:<rev>
624 :type landing_rev: str
624 :type landing_rev: str
625 :param enable_locking:
625 :param enable_locking:
626 :type enable_locking: bool
626 :type enable_locking: bool
627 :param enable_downloads:
627 :param enable_downloads:
628 :type enable_downloads: bool
628 :type enable_downloads: bool
629 :param enable_statistics:
629 :param enable_statistics:
630 :type enable_statistics: bool
630 :type enable_statistics: bool
631 :param copy_permissions: Copy permission from group in which the
631 :param copy_permissions: Copy permission from group in which the
632 repository is being created.
632 repository is being created.
633 :type copy_permissions: bool
633 :type copy_permissions: bool
634
634
635
635
636 Example output:
636 Example output:
637
637
638 .. code-block:: bash
638 .. code-block:: bash
639
639
640 id : <id_given_in_input>
640 id : <id_given_in_input>
641 result: {
641 result: {
642 "msg": "Created new repository `<reponame>`",
642 "msg": "Created new repository `<reponame>`",
643 "success": true,
643 "success": true,
644 "task": "<celery task id or None if done sync>"
644 "task": "<celery task id or None if done sync>"
645 }
645 }
646 error: null
646 error: null
647
647
648
648
649 Example error output:
649 Example error output:
650
650
651 .. code-block:: bash
651 .. code-block:: bash
652
652
653 id : <id_given_in_input>
653 id : <id_given_in_input>
654 result : null
654 result : null
655 error : {
655 error : {
656 'failed to create repository `<repo_name>`'
656 'failed to create repository `<repo_name>`'
657 }
657 }
658
658
659 """
659 """
660
660
661 owner = validate_set_owner_permissions(apiuser, owner)
661 owner = validate_set_owner_permissions(apiuser, owner)
662
662
663 description = Optional.extract(description)
663 description = Optional.extract(description)
664 copy_permissions = Optional.extract(copy_permissions)
664 copy_permissions = Optional.extract(copy_permissions)
665 clone_uri = Optional.extract(clone_uri)
665 clone_uri = Optional.extract(clone_uri)
666 landing_commit_ref = Optional.extract(landing_rev)
666 landing_commit_ref = Optional.extract(landing_rev)
667
667
668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
668 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
669 if isinstance(private, Optional):
669 if isinstance(private, Optional):
670 private = defs.get('repo_private') or Optional.extract(private)
670 private = defs.get('repo_private') or Optional.extract(private)
671 if isinstance(repo_type, Optional):
671 if isinstance(repo_type, Optional):
672 repo_type = defs.get('repo_type')
672 repo_type = defs.get('repo_type')
673 if isinstance(enable_statistics, Optional):
673 if isinstance(enable_statistics, Optional):
674 enable_statistics = defs.get('repo_enable_statistics')
674 enable_statistics = defs.get('repo_enable_statistics')
675 if isinstance(enable_locking, Optional):
675 if isinstance(enable_locking, Optional):
676 enable_locking = defs.get('repo_enable_locking')
676 enable_locking = defs.get('repo_enable_locking')
677 if isinstance(enable_downloads, Optional):
677 if isinstance(enable_downloads, Optional):
678 enable_downloads = defs.get('repo_enable_downloads')
678 enable_downloads = defs.get('repo_enable_downloads')
679
679
680 schema = repo_schema.RepoSchema().bind(
680 schema = repo_schema.RepoSchema().bind(
681 repo_type_options=rhodecode.BACKENDS.keys(),
681 repo_type_options=rhodecode.BACKENDS.keys(),
682 # user caller
682 # user caller
683 user=apiuser)
683 user=apiuser)
684
684
685 try:
685 try:
686 schema_data = schema.deserialize(dict(
686 schema_data = schema.deserialize(dict(
687 repo_name=repo_name,
687 repo_name=repo_name,
688 repo_type=repo_type,
688 repo_type=repo_type,
689 repo_owner=owner.username,
689 repo_owner=owner.username,
690 repo_description=description,
690 repo_description=description,
691 repo_landing_commit_ref=landing_commit_ref,
691 repo_landing_commit_ref=landing_commit_ref,
692 repo_clone_uri=clone_uri,
692 repo_clone_uri=clone_uri,
693 repo_private=private,
693 repo_private=private,
694 repo_copy_permissions=copy_permissions,
694 repo_copy_permissions=copy_permissions,
695 repo_enable_statistics=enable_statistics,
695 repo_enable_statistics=enable_statistics,
696 repo_enable_downloads=enable_downloads,
696 repo_enable_downloads=enable_downloads,
697 repo_enable_locking=enable_locking))
697 repo_enable_locking=enable_locking))
698 except validation_schema.Invalid as err:
698 except validation_schema.Invalid as err:
699 raise JSONRPCValidationError(colander_exc=err)
699 raise JSONRPCValidationError(colander_exc=err)
700
700
701 try:
701 try:
702 data = {
702 data = {
703 'owner': owner,
703 'owner': owner,
704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
704 'repo_name': schema_data['repo_group']['repo_name_without_group'],
705 'repo_name_full': schema_data['repo_name'],
705 'repo_name_full': schema_data['repo_name'],
706 'repo_group': schema_data['repo_group']['repo_group_id'],
706 'repo_group': schema_data['repo_group']['repo_group_id'],
707 'repo_type': schema_data['repo_type'],
707 'repo_type': schema_data['repo_type'],
708 'repo_description': schema_data['repo_description'],
708 'repo_description': schema_data['repo_description'],
709 'repo_private': schema_data['repo_private'],
709 'repo_private': schema_data['repo_private'],
710 'clone_uri': schema_data['repo_clone_uri'],
710 'clone_uri': schema_data['repo_clone_uri'],
711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
711 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
712 'enable_statistics': schema_data['repo_enable_statistics'],
712 'enable_statistics': schema_data['repo_enable_statistics'],
713 'enable_locking': schema_data['repo_enable_locking'],
713 'enable_locking': schema_data['repo_enable_locking'],
714 'enable_downloads': schema_data['repo_enable_downloads'],
714 'enable_downloads': schema_data['repo_enable_downloads'],
715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
715 'repo_copy_permissions': schema_data['repo_copy_permissions'],
716 }
716 }
717
717
718 task = RepoModel().create(form_data=data, cur_user=owner)
718 task = RepoModel().create(form_data=data, cur_user=owner)
719 from celery.result import BaseAsyncResult
719 from celery.result import BaseAsyncResult
720 task_id = None
720 task_id = None
721 if isinstance(task, BaseAsyncResult):
721 if isinstance(task, BaseAsyncResult):
722 task_id = task.task_id
722 task_id = task.task_id
723 # no commit, it's done in RepoModel, or async via celery
723 # no commit, it's done in RepoModel, or async via celery
724 return {
724 return {
725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
725 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
726 'success': True, # cannot return the repo data here since fork
726 'success': True, # cannot return the repo data here since fork
727 # can be done async
727 # can be done async
728 'task': task_id
728 'task': task_id
729 }
729 }
730 except Exception:
730 except Exception:
731 log.exception(
731 log.exception(
732 u"Exception while trying to create the repository %s",
732 u"Exception while trying to create the repository %s",
733 schema_data['repo_name'])
733 schema_data['repo_name'])
734 raise JSONRPCError(
734 raise JSONRPCError(
735 'failed to create repository `%s`' % (schema_data['repo_name'],))
735 'failed to create repository `%s`' % (schema_data['repo_name'],))
736
736
737
737
738 @jsonrpc_method()
738 @jsonrpc_method()
739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
739 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
740 description=Optional('')):
740 description=Optional('')):
741 """
741 """
742 Adds an extra field to a repository.
742 Adds an extra field to a repository.
743
743
744 This command can only be run using an |authtoken| with at least
744 This command can only be run using an |authtoken| with at least
745 write permissions to the |repo|.
745 write permissions to the |repo|.
746
746
747 :param apiuser: This is filled automatically from the |authtoken|.
747 :param apiuser: This is filled automatically from the |authtoken|.
748 :type apiuser: AuthUser
748 :type apiuser: AuthUser
749 :param repoid: Set the repository name or repository id.
749 :param repoid: Set the repository name or repository id.
750 :type repoid: str or int
750 :type repoid: str or int
751 :param key: Create a unique field key for this repository.
751 :param key: Create a unique field key for this repository.
752 :type key: str
752 :type key: str
753 :param label:
753 :param label:
754 :type label: Optional(str)
754 :type label: Optional(str)
755 :param description:
755 :param description:
756 :type description: Optional(str)
756 :type description: Optional(str)
757 """
757 """
758 repo = get_repo_or_error(repoid)
758 repo = get_repo_or_error(repoid)
759 if not has_superadmin_permission(apiuser):
759 if not has_superadmin_permission(apiuser):
760 _perms = ('repository.admin',)
760 _perms = ('repository.admin',)
761 validate_repo_permissions(apiuser, repoid, repo, _perms)
761 validate_repo_permissions(apiuser, repoid, repo, _perms)
762
762
763 label = Optional.extract(label) or key
763 label = Optional.extract(label) or key
764 description = Optional.extract(description)
764 description = Optional.extract(description)
765
765
766 field = RepositoryField.get_by_key_name(key, repo)
766 field = RepositoryField.get_by_key_name(key, repo)
767 if field:
767 if field:
768 raise JSONRPCError('Field with key '
768 raise JSONRPCError('Field with key '
769 '`%s` exists for repo `%s`' % (key, repoid))
769 '`%s` exists for repo `%s`' % (key, repoid))
770
770
771 try:
771 try:
772 RepoModel().add_repo_field(repo, key, field_label=label,
772 RepoModel().add_repo_field(repo, key, field_label=label,
773 field_desc=description)
773 field_desc=description)
774 Session().commit()
774 Session().commit()
775 return {
775 return {
776 'msg': "Added new repository field `%s`" % (key,),
776 'msg': "Added new repository field `%s`" % (key,),
777 'success': True,
777 'success': True,
778 }
778 }
779 except Exception:
779 except Exception:
780 log.exception("Exception occurred while trying to add field to repo")
780 log.exception("Exception occurred while trying to add field to repo")
781 raise JSONRPCError(
781 raise JSONRPCError(
782 'failed to create new field for repository `%s`' % (repoid,))
782 'failed to create new field for repository `%s`' % (repoid,))
783
783
784
784
785 @jsonrpc_method()
785 @jsonrpc_method()
786 def remove_field_from_repo(request, apiuser, repoid, key):
786 def remove_field_from_repo(request, apiuser, repoid, key):
787 """
787 """
788 Removes an extra field from a repository.
788 Removes an extra field from a repository.
789
789
790 This command can only be run using an |authtoken| with at least
790 This command can only be run using an |authtoken| with at least
791 write permissions to the |repo|.
791 write permissions to the |repo|.
792
792
793 :param apiuser: This is filled automatically from the |authtoken|.
793 :param apiuser: This is filled automatically from the |authtoken|.
794 :type apiuser: AuthUser
794 :type apiuser: AuthUser
795 :param repoid: Set the repository name or repository ID.
795 :param repoid: Set the repository name or repository ID.
796 :type repoid: str or int
796 :type repoid: str or int
797 :param key: Set the unique field key for this repository.
797 :param key: Set the unique field key for this repository.
798 :type key: str
798 :type key: str
799 """
799 """
800
800
801 repo = get_repo_or_error(repoid)
801 repo = get_repo_or_error(repoid)
802 if not has_superadmin_permission(apiuser):
802 if not has_superadmin_permission(apiuser):
803 _perms = ('repository.admin',)
803 _perms = ('repository.admin',)
804 validate_repo_permissions(apiuser, repoid, repo, _perms)
804 validate_repo_permissions(apiuser, repoid, repo, _perms)
805
805
806 field = RepositoryField.get_by_key_name(key, repo)
806 field = RepositoryField.get_by_key_name(key, repo)
807 if not field:
807 if not field:
808 raise JSONRPCError('Field with key `%s` does not '
808 raise JSONRPCError('Field with key `%s` does not '
809 'exists for repo `%s`' % (key, repoid))
809 'exists for repo `%s`' % (key, repoid))
810
810
811 try:
811 try:
812 RepoModel().delete_repo_field(repo, field_key=key)
812 RepoModel().delete_repo_field(repo, field_key=key)
813 Session().commit()
813 Session().commit()
814 return {
814 return {
815 'msg': "Deleted repository field `%s`" % (key,),
815 'msg': "Deleted repository field `%s`" % (key,),
816 'success': True,
816 'success': True,
817 }
817 }
818 except Exception:
818 except Exception:
819 log.exception(
819 log.exception(
820 "Exception occurred while trying to delete field from repo")
820 "Exception occurred while trying to delete field from repo")
821 raise JSONRPCError(
821 raise JSONRPCError(
822 'failed to delete field for repository `%s`' % (repoid,))
822 'failed to delete field for repository `%s`' % (repoid,))
823
823
824
824
825 @jsonrpc_method()
825 @jsonrpc_method()
826 def update_repo(
826 def update_repo(
827 request, apiuser, repoid, repo_name=Optional(None),
827 request, apiuser, repoid, repo_name=Optional(None),
828 owner=Optional(OAttr('apiuser')), description=Optional(''),
828 owner=Optional(OAttr('apiuser')), description=Optional(''),
829 private=Optional(False), clone_uri=Optional(None),
829 private=Optional(False), clone_uri=Optional(None),
830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
830 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
831 enable_statistics=Optional(False),
831 enable_statistics=Optional(False),
832 enable_locking=Optional(False),
832 enable_locking=Optional(False),
833 enable_downloads=Optional(False), fields=Optional('')):
833 enable_downloads=Optional(False), fields=Optional('')):
834 """
834 """
835 Updates a repository with the given information.
835 Updates a repository with the given information.
836
836
837 This command can only be run using an |authtoken| with at least
837 This command can only be run using an |authtoken| with at least
838 admin permissions to the |repo|.
838 admin permissions to the |repo|.
839
839
840 * If the repository name contains "/", repository will be updated
840 * If the repository name contains "/", repository will be updated
841 accordingly with a repository group or nested repository groups
841 accordingly with a repository group or nested repository groups
842
842
843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
843 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
844 called "repo-test" and place it inside group "foo/bar".
844 called "repo-test" and place it inside group "foo/bar".
845 You have to have permissions to access and write to the last repository
845 You have to have permissions to access and write to the last repository
846 group ("bar" in this example)
846 group ("bar" in this example)
847
847
848 :param apiuser: This is filled automatically from the |authtoken|.
848 :param apiuser: This is filled automatically from the |authtoken|.
849 :type apiuser: AuthUser
849 :type apiuser: AuthUser
850 :param repoid: repository name or repository ID.
850 :param repoid: repository name or repository ID.
851 :type repoid: str or int
851 :type repoid: str or int
852 :param repo_name: Update the |repo| name, including the
852 :param repo_name: Update the |repo| name, including the
853 repository group it's in.
853 repository group it's in.
854 :type repo_name: str
854 :type repo_name: str
855 :param owner: Set the |repo| owner.
855 :param owner: Set the |repo| owner.
856 :type owner: str
856 :type owner: str
857 :param fork_of: Set the |repo| as fork of another |repo|.
857 :param fork_of: Set the |repo| as fork of another |repo|.
858 :type fork_of: str
858 :type fork_of: str
859 :param description: Update the |repo| description.
859 :param description: Update the |repo| description.
860 :type description: str
860 :type description: str
861 :param private: Set the |repo| as private. (True | False)
861 :param private: Set the |repo| as private. (True | False)
862 :type private: bool
862 :type private: bool
863 :param clone_uri: Update the |repo| clone URI.
863 :param clone_uri: Update the |repo| clone URI.
864 :type clone_uri: str
864 :type clone_uri: str
865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
865 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
866 :type landing_rev: str
866 :type landing_rev: str
867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
867 :param enable_statistics: Enable statistics on the |repo|, (True | False).
868 :type enable_statistics: bool
868 :type enable_statistics: bool
869 :param enable_locking: Enable |repo| locking.
869 :param enable_locking: Enable |repo| locking.
870 :type enable_locking: bool
870 :type enable_locking: bool
871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
871 :param enable_downloads: Enable downloads from the |repo|, (True | False).
872 :type enable_downloads: bool
872 :type enable_downloads: bool
873 :param fields: Add extra fields to the |repo|. Use the following
873 :param fields: Add extra fields to the |repo|. Use the following
874 example format: ``field_key=field_val,field_key2=fieldval2``.
874 example format: ``field_key=field_val,field_key2=fieldval2``.
875 Escape ', ' with \,
875 Escape ', ' with \,
876 :type fields: str
876 :type fields: str
877 """
877 """
878
878
879 repo = get_repo_or_error(repoid)
879 repo = get_repo_or_error(repoid)
880
880
881 include_secrets = False
881 include_secrets = False
882 if not has_superadmin_permission(apiuser):
882 if not has_superadmin_permission(apiuser):
883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
883 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
884 else:
884 else:
885 include_secrets = True
885 include_secrets = True
886
886
887 updates = dict(
887 updates = dict(
888 repo_name=repo_name
888 repo_name=repo_name
889 if not isinstance(repo_name, Optional) else repo.repo_name,
889 if not isinstance(repo_name, Optional) else repo.repo_name,
890
890
891 fork_id=fork_of
891 fork_id=fork_of
892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
892 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
893
893
894 user=owner
894 user=owner
895 if not isinstance(owner, Optional) else repo.user.username,
895 if not isinstance(owner, Optional) else repo.user.username,
896
896
897 repo_description=description
897 repo_description=description
898 if not isinstance(description, Optional) else repo.description,
898 if not isinstance(description, Optional) else repo.description,
899
899
900 repo_private=private
900 repo_private=private
901 if not isinstance(private, Optional) else repo.private,
901 if not isinstance(private, Optional) else repo.private,
902
902
903 clone_uri=clone_uri
903 clone_uri=clone_uri
904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
904 if not isinstance(clone_uri, Optional) else repo.clone_uri,
905
905
906 repo_landing_rev=landing_rev
906 repo_landing_rev=landing_rev
907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
907 if not isinstance(landing_rev, Optional) else repo._landing_revision,
908
908
909 repo_enable_statistics=enable_statistics
909 repo_enable_statistics=enable_statistics
910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
910 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
911
911
912 repo_enable_locking=enable_locking
912 repo_enable_locking=enable_locking
913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
913 if not isinstance(enable_locking, Optional) else repo.enable_locking,
914
914
915 repo_enable_downloads=enable_downloads
915 repo_enable_downloads=enable_downloads
916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
916 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
917
917
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
918 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
919
919
920 old_values = repo.get_api_data()
920 schema = repo_schema.RepoSchema().bind(
921 schema = repo_schema.RepoSchema().bind(
921 repo_type_options=rhodecode.BACKENDS.keys(),
922 repo_type_options=rhodecode.BACKENDS.keys(),
922 repo_ref_options=ref_choices,
923 repo_ref_options=ref_choices,
923 # user caller
924 # user caller
924 user=apiuser,
925 user=apiuser,
925 old_values=repo.get_api_data())
926 old_values=old_values)
926 try:
927 try:
927 schema_data = schema.deserialize(dict(
928 schema_data = schema.deserialize(dict(
928 # we save old value, users cannot change type
929 # we save old value, users cannot change type
929 repo_type=repo.repo_type,
930 repo_type=repo.repo_type,
930
931
931 repo_name=updates['repo_name'],
932 repo_name=updates['repo_name'],
932 repo_owner=updates['user'],
933 repo_owner=updates['user'],
933 repo_description=updates['repo_description'],
934 repo_description=updates['repo_description'],
934 repo_clone_uri=updates['clone_uri'],
935 repo_clone_uri=updates['clone_uri'],
935 repo_fork_of=updates['fork_id'],
936 repo_fork_of=updates['fork_id'],
936 repo_private=updates['repo_private'],
937 repo_private=updates['repo_private'],
937 repo_landing_commit_ref=updates['repo_landing_rev'],
938 repo_landing_commit_ref=updates['repo_landing_rev'],
938 repo_enable_statistics=updates['repo_enable_statistics'],
939 repo_enable_statistics=updates['repo_enable_statistics'],
939 repo_enable_downloads=updates['repo_enable_downloads'],
940 repo_enable_downloads=updates['repo_enable_downloads'],
940 repo_enable_locking=updates['repo_enable_locking']))
941 repo_enable_locking=updates['repo_enable_locking']))
941 except validation_schema.Invalid as err:
942 except validation_schema.Invalid as err:
942 raise JSONRPCValidationError(colander_exc=err)
943 raise JSONRPCValidationError(colander_exc=err)
943
944
944 # save validated data back into the updates dict
945 # save validated data back into the updates dict
945 validated_updates = dict(
946 validated_updates = dict(
946 repo_name=schema_data['repo_group']['repo_name_without_group'],
947 repo_name=schema_data['repo_group']['repo_name_without_group'],
947 repo_group=schema_data['repo_group']['repo_group_id'],
948 repo_group=schema_data['repo_group']['repo_group_id'],
948
949
949 user=schema_data['repo_owner'],
950 user=schema_data['repo_owner'],
950 repo_description=schema_data['repo_description'],
951 repo_description=schema_data['repo_description'],
951 repo_private=schema_data['repo_private'],
952 repo_private=schema_data['repo_private'],
952 clone_uri=schema_data['repo_clone_uri'],
953 clone_uri=schema_data['repo_clone_uri'],
953 repo_landing_rev=schema_data['repo_landing_commit_ref'],
954 repo_landing_rev=schema_data['repo_landing_commit_ref'],
954 repo_enable_statistics=schema_data['repo_enable_statistics'],
955 repo_enable_statistics=schema_data['repo_enable_statistics'],
955 repo_enable_locking=schema_data['repo_enable_locking'],
956 repo_enable_locking=schema_data['repo_enable_locking'],
956 repo_enable_downloads=schema_data['repo_enable_downloads'],
957 repo_enable_downloads=schema_data['repo_enable_downloads'],
957 )
958 )
958
959
959 if schema_data['repo_fork_of']:
960 if schema_data['repo_fork_of']:
960 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
961 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
961 validated_updates['fork_id'] = fork_repo.repo_id
962 validated_updates['fork_id'] = fork_repo.repo_id
962
963
963 # extra fields
964 # extra fields
964 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
965 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
965 if fields:
966 if fields:
966 validated_updates.update(fields)
967 validated_updates.update(fields)
967
968
968 try:
969 try:
969 RepoModel().update(repo, **validated_updates)
970 RepoModel().update(repo, **validated_updates)
971 audit_logger.store_api(
972 'repo.edit', action_data={'old_data': old_values},
973 user=apiuser, repo=repo)
970 Session().commit()
974 Session().commit()
971 return {
975 return {
972 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
976 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
973 'repository': repo.get_api_data(include_secrets=include_secrets)
977 'repository': repo.get_api_data(include_secrets=include_secrets)
974 }
978 }
975 except Exception:
979 except Exception:
976 log.exception(
980 log.exception(
977 u"Exception while trying to update the repository %s",
981 u"Exception while trying to update the repository %s",
978 repoid)
982 repoid)
979 raise JSONRPCError('failed to update repo `%s`' % repoid)
983 raise JSONRPCError('failed to update repo `%s`' % repoid)
980
984
981
985
982 @jsonrpc_method()
986 @jsonrpc_method()
983 def fork_repo(request, apiuser, repoid, fork_name,
987 def fork_repo(request, apiuser, repoid, fork_name,
984 owner=Optional(OAttr('apiuser')),
988 owner=Optional(OAttr('apiuser')),
985 description=Optional(''),
989 description=Optional(''),
986 private=Optional(False),
990 private=Optional(False),
987 clone_uri=Optional(None),
991 clone_uri=Optional(None),
988 landing_rev=Optional('rev:tip'),
992 landing_rev=Optional('rev:tip'),
989 copy_permissions=Optional(False)):
993 copy_permissions=Optional(False)):
990 """
994 """
991 Creates a fork of the specified |repo|.
995 Creates a fork of the specified |repo|.
992
996
993 * If the fork_name contains "/", fork will be created inside
997 * If the fork_name contains "/", fork will be created inside
994 a repository group or nested repository groups
998 a repository group or nested repository groups
995
999
996 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1000 For example "foo/bar/fork-repo" will create fork called "fork-repo"
997 inside group "foo/bar". You have to have permissions to access and
1001 inside group "foo/bar". You have to have permissions to access and
998 write to the last repository group ("bar" in this example)
1002 write to the last repository group ("bar" in this example)
999
1003
1000 This command can only be run using an |authtoken| with minimum
1004 This command can only be run using an |authtoken| with minimum
1001 read permissions of the forked repo, create fork permissions for an user.
1005 read permissions of the forked repo, create fork permissions for an user.
1002
1006
1003 :param apiuser: This is filled automatically from the |authtoken|.
1007 :param apiuser: This is filled automatically from the |authtoken|.
1004 :type apiuser: AuthUser
1008 :type apiuser: AuthUser
1005 :param repoid: Set repository name or repository ID.
1009 :param repoid: Set repository name or repository ID.
1006 :type repoid: str or int
1010 :type repoid: str or int
1007 :param fork_name: Set the fork name, including it's repository group membership.
1011 :param fork_name: Set the fork name, including it's repository group membership.
1008 :type fork_name: str
1012 :type fork_name: str
1009 :param owner: Set the fork owner.
1013 :param owner: Set the fork owner.
1010 :type owner: str
1014 :type owner: str
1011 :param description: Set the fork description.
1015 :param description: Set the fork description.
1012 :type description: str
1016 :type description: str
1013 :param copy_permissions: Copy permissions from parent |repo|. The
1017 :param copy_permissions: Copy permissions from parent |repo|. The
1014 default is False.
1018 default is False.
1015 :type copy_permissions: bool
1019 :type copy_permissions: bool
1016 :param private: Make the fork private. The default is False.
1020 :param private: Make the fork private. The default is False.
1017 :type private: bool
1021 :type private: bool
1018 :param landing_rev: Set the landing revision. The default is tip.
1022 :param landing_rev: Set the landing revision. The default is tip.
1019
1023
1020 Example output:
1024 Example output:
1021
1025
1022 .. code-block:: bash
1026 .. code-block:: bash
1023
1027
1024 id : <id_for_response>
1028 id : <id_for_response>
1025 api_key : "<api_key>"
1029 api_key : "<api_key>"
1026 args: {
1030 args: {
1027 "repoid" : "<reponame or repo_id>",
1031 "repoid" : "<reponame or repo_id>",
1028 "fork_name": "<forkname>",
1032 "fork_name": "<forkname>",
1029 "owner": "<username or user_id = Optional(=apiuser)>",
1033 "owner": "<username or user_id = Optional(=apiuser)>",
1030 "description": "<description>",
1034 "description": "<description>",
1031 "copy_permissions": "<bool>",
1035 "copy_permissions": "<bool>",
1032 "private": "<bool>",
1036 "private": "<bool>",
1033 "landing_rev": "<landing_rev>"
1037 "landing_rev": "<landing_rev>"
1034 }
1038 }
1035
1039
1036 Example error output:
1040 Example error output:
1037
1041
1038 .. code-block:: bash
1042 .. code-block:: bash
1039
1043
1040 id : <id_given_in_input>
1044 id : <id_given_in_input>
1041 result: {
1045 result: {
1042 "msg": "Created fork of `<reponame>` as `<forkname>`",
1046 "msg": "Created fork of `<reponame>` as `<forkname>`",
1043 "success": true,
1047 "success": true,
1044 "task": "<celery task id or None if done sync>"
1048 "task": "<celery task id or None if done sync>"
1045 }
1049 }
1046 error: null
1050 error: null
1047
1051
1048 """
1052 """
1049
1053
1050 repo = get_repo_or_error(repoid)
1054 repo = get_repo_or_error(repoid)
1051 repo_name = repo.repo_name
1055 repo_name = repo.repo_name
1052
1056
1053 if not has_superadmin_permission(apiuser):
1057 if not has_superadmin_permission(apiuser):
1054 # check if we have at least read permission for
1058 # check if we have at least read permission for
1055 # this repo that we fork !
1059 # this repo that we fork !
1056 _perms = (
1060 _perms = (
1057 'repository.admin', 'repository.write', 'repository.read')
1061 'repository.admin', 'repository.write', 'repository.read')
1058 validate_repo_permissions(apiuser, repoid, repo, _perms)
1062 validate_repo_permissions(apiuser, repoid, repo, _perms)
1059
1063
1060 # check if the regular user has at least fork permissions as well
1064 # check if the regular user has at least fork permissions as well
1061 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1065 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1062 raise JSONRPCForbidden()
1066 raise JSONRPCForbidden()
1063
1067
1064 # check if user can set owner parameter
1068 # check if user can set owner parameter
1065 owner = validate_set_owner_permissions(apiuser, owner)
1069 owner = validate_set_owner_permissions(apiuser, owner)
1066
1070
1067 description = Optional.extract(description)
1071 description = Optional.extract(description)
1068 copy_permissions = Optional.extract(copy_permissions)
1072 copy_permissions = Optional.extract(copy_permissions)
1069 clone_uri = Optional.extract(clone_uri)
1073 clone_uri = Optional.extract(clone_uri)
1070 landing_commit_ref = Optional.extract(landing_rev)
1074 landing_commit_ref = Optional.extract(landing_rev)
1071 private = Optional.extract(private)
1075 private = Optional.extract(private)
1072
1076
1073 schema = repo_schema.RepoSchema().bind(
1077 schema = repo_schema.RepoSchema().bind(
1074 repo_type_options=rhodecode.BACKENDS.keys(),
1078 repo_type_options=rhodecode.BACKENDS.keys(),
1075 # user caller
1079 # user caller
1076 user=apiuser)
1080 user=apiuser)
1077
1081
1078 try:
1082 try:
1079 schema_data = schema.deserialize(dict(
1083 schema_data = schema.deserialize(dict(
1080 repo_name=fork_name,
1084 repo_name=fork_name,
1081 repo_type=repo.repo_type,
1085 repo_type=repo.repo_type,
1082 repo_owner=owner.username,
1086 repo_owner=owner.username,
1083 repo_description=description,
1087 repo_description=description,
1084 repo_landing_commit_ref=landing_commit_ref,
1088 repo_landing_commit_ref=landing_commit_ref,
1085 repo_clone_uri=clone_uri,
1089 repo_clone_uri=clone_uri,
1086 repo_private=private,
1090 repo_private=private,
1087 repo_copy_permissions=copy_permissions))
1091 repo_copy_permissions=copy_permissions))
1088 except validation_schema.Invalid as err:
1092 except validation_schema.Invalid as err:
1089 raise JSONRPCValidationError(colander_exc=err)
1093 raise JSONRPCValidationError(colander_exc=err)
1090
1094
1091 try:
1095 try:
1092 data = {
1096 data = {
1093 'fork_parent_id': repo.repo_id,
1097 'fork_parent_id': repo.repo_id,
1094
1098
1095 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1099 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1096 'repo_name_full': schema_data['repo_name'],
1100 'repo_name_full': schema_data['repo_name'],
1097 'repo_group': schema_data['repo_group']['repo_group_id'],
1101 'repo_group': schema_data['repo_group']['repo_group_id'],
1098 'repo_type': schema_data['repo_type'],
1102 'repo_type': schema_data['repo_type'],
1099 'description': schema_data['repo_description'],
1103 'description': schema_data['repo_description'],
1100 'private': schema_data['repo_private'],
1104 'private': schema_data['repo_private'],
1101 'copy_permissions': schema_data['repo_copy_permissions'],
1105 'copy_permissions': schema_data['repo_copy_permissions'],
1102 'landing_rev': schema_data['repo_landing_commit_ref'],
1106 'landing_rev': schema_data['repo_landing_commit_ref'],
1103 }
1107 }
1104
1108
1105 task = RepoModel().create_fork(data, cur_user=owner)
1109 task = RepoModel().create_fork(data, cur_user=owner)
1106 # no commit, it's done in RepoModel, or async via celery
1110 # no commit, it's done in RepoModel, or async via celery
1107 from celery.result import BaseAsyncResult
1111 from celery.result import BaseAsyncResult
1108 task_id = None
1112 task_id = None
1109 if isinstance(task, BaseAsyncResult):
1113 if isinstance(task, BaseAsyncResult):
1110 task_id = task.task_id
1114 task_id = task.task_id
1111 return {
1115 return {
1112 'msg': 'Created fork of `%s` as `%s`' % (
1116 'msg': 'Created fork of `%s` as `%s`' % (
1113 repo.repo_name, schema_data['repo_name']),
1117 repo.repo_name, schema_data['repo_name']),
1114 'success': True, # cannot return the repo data here since fork
1118 'success': True, # cannot return the repo data here since fork
1115 # can be done async
1119 # can be done async
1116 'task': task_id
1120 'task': task_id
1117 }
1121 }
1118 except Exception:
1122 except Exception:
1119 log.exception(
1123 log.exception(
1120 u"Exception while trying to create fork %s",
1124 u"Exception while trying to create fork %s",
1121 schema_data['repo_name'])
1125 schema_data['repo_name'])
1122 raise JSONRPCError(
1126 raise JSONRPCError(
1123 'failed to fork repository `%s` as `%s`' % (
1127 'failed to fork repository `%s` as `%s`' % (
1124 repo_name, schema_data['repo_name']))
1128 repo_name, schema_data['repo_name']))
1125
1129
1126
1130
1127 @jsonrpc_method()
1131 @jsonrpc_method()
1128 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1132 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1129 """
1133 """
1130 Deletes a repository.
1134 Deletes a repository.
1131
1135
1132 * When the `forks` parameter is set it's possible to detach or delete
1136 * When the `forks` parameter is set it's possible to detach or delete
1133 forks of deleted repository.
1137 forks of deleted repository.
1134
1138
1135 This command can only be run using an |authtoken| with admin
1139 This command can only be run using an |authtoken| with admin
1136 permissions on the |repo|.
1140 permissions on the |repo|.
1137
1141
1138 :param apiuser: This is filled automatically from the |authtoken|.
1142 :param apiuser: This is filled automatically from the |authtoken|.
1139 :type apiuser: AuthUser
1143 :type apiuser: AuthUser
1140 :param repoid: Set the repository name or repository ID.
1144 :param repoid: Set the repository name or repository ID.
1141 :type repoid: str or int
1145 :type repoid: str or int
1142 :param forks: Set to `detach` or `delete` forks from the |repo|.
1146 :param forks: Set to `detach` or `delete` forks from the |repo|.
1143 :type forks: Optional(str)
1147 :type forks: Optional(str)
1144
1148
1145 Example error output:
1149 Example error output:
1146
1150
1147 .. code-block:: bash
1151 .. code-block:: bash
1148
1152
1149 id : <id_given_in_input>
1153 id : <id_given_in_input>
1150 result: {
1154 result: {
1151 "msg": "Deleted repository `<reponame>`",
1155 "msg": "Deleted repository `<reponame>`",
1152 "success": true
1156 "success": true
1153 }
1157 }
1154 error: null
1158 error: null
1155 """
1159 """
1156
1160
1157 repo = get_repo_or_error(repoid)
1161 repo = get_repo_or_error(repoid)
1158 repo_name = repo.repo_name
1162 repo_name = repo.repo_name
1159 if not has_superadmin_permission(apiuser):
1163 if not has_superadmin_permission(apiuser):
1160 _perms = ('repository.admin',)
1164 _perms = ('repository.admin',)
1161 validate_repo_permissions(apiuser, repoid, repo, _perms)
1165 validate_repo_permissions(apiuser, repoid, repo, _perms)
1162
1166
1163 try:
1167 try:
1164 handle_forks = Optional.extract(forks)
1168 handle_forks = Optional.extract(forks)
1165 _forks_msg = ''
1169 _forks_msg = ''
1166 _forks = [f for f in repo.forks]
1170 _forks = [f for f in repo.forks]
1167 if handle_forks == 'detach':
1171 if handle_forks == 'detach':
1168 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1172 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1169 elif handle_forks == 'delete':
1173 elif handle_forks == 'delete':
1170 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1174 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1171 elif _forks:
1175 elif _forks:
1172 raise JSONRPCError(
1176 raise JSONRPCError(
1173 'Cannot delete `%s` it still contains attached forks' %
1177 'Cannot delete `%s` it still contains attached forks' %
1174 (repo.repo_name,)
1178 (repo.repo_name,)
1175 )
1179 )
1176 repo_data = repo.get_api_data()
1180 old_data = repo.get_api_data()
1177 RepoModel().delete(repo, forks=forks)
1181 RepoModel().delete(repo, forks=forks)
1178
1182
1179 repo = audit_logger.RepoWrap(repo_id=None,
1183 repo = audit_logger.RepoWrap(repo_id=None,
1180 repo_name=repo.repo_name)
1184 repo_name=repo.repo_name)
1181
1185
1182 audit_logger.store_api(
1186 audit_logger.store_api(
1183 action='repo.delete',
1187 'repo.delete', action_data={'old_data': old_data},
1184 action_data={'data': repo_data},
1185 user=apiuser, repo=repo)
1188 user=apiuser, repo=repo)
1186
1189
1187 ScmModel().mark_for_invalidation(repo_name, delete=True)
1190 ScmModel().mark_for_invalidation(repo_name, delete=True)
1188 Session().commit()
1191 Session().commit()
1189 return {
1192 return {
1190 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1193 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1191 'success': True
1194 'success': True
1192 }
1195 }
1193 except Exception:
1196 except Exception:
1194 log.exception("Exception occurred while trying to delete repo")
1197 log.exception("Exception occurred while trying to delete repo")
1195 raise JSONRPCError(
1198 raise JSONRPCError(
1196 'failed to delete repository `%s`' % (repo_name,)
1199 'failed to delete repository `%s`' % (repo_name,)
1197 )
1200 )
1198
1201
1199
1202
1200 #TODO: marcink, change name ?
1203 #TODO: marcink, change name ?
1201 @jsonrpc_method()
1204 @jsonrpc_method()
1202 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1205 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1203 """
1206 """
1204 Invalidates the cache for the specified repository.
1207 Invalidates the cache for the specified repository.
1205
1208
1206 This command can only be run using an |authtoken| with admin rights to
1209 This command can only be run using an |authtoken| with admin rights to
1207 the specified repository.
1210 the specified repository.
1208
1211
1209 This command takes the following options:
1212 This command takes the following options:
1210
1213
1211 :param apiuser: This is filled automatically from |authtoken|.
1214 :param apiuser: This is filled automatically from |authtoken|.
1212 :type apiuser: AuthUser
1215 :type apiuser: AuthUser
1213 :param repoid: Sets the repository name or repository ID.
1216 :param repoid: Sets the repository name or repository ID.
1214 :type repoid: str or int
1217 :type repoid: str or int
1215 :param delete_keys: This deletes the invalidated keys instead of
1218 :param delete_keys: This deletes the invalidated keys instead of
1216 just flagging them.
1219 just flagging them.
1217 :type delete_keys: Optional(``True`` | ``False``)
1220 :type delete_keys: Optional(``True`` | ``False``)
1218
1221
1219 Example output:
1222 Example output:
1220
1223
1221 .. code-block:: bash
1224 .. code-block:: bash
1222
1225
1223 id : <id_given_in_input>
1226 id : <id_given_in_input>
1224 result : {
1227 result : {
1225 'msg': Cache for repository `<repository name>` was invalidated,
1228 'msg': Cache for repository `<repository name>` was invalidated,
1226 'repository': <repository name>
1229 'repository': <repository name>
1227 }
1230 }
1228 error : null
1231 error : null
1229
1232
1230 Example error output:
1233 Example error output:
1231
1234
1232 .. code-block:: bash
1235 .. code-block:: bash
1233
1236
1234 id : <id_given_in_input>
1237 id : <id_given_in_input>
1235 result : null
1238 result : null
1236 error : {
1239 error : {
1237 'Error occurred during cache invalidation action'
1240 'Error occurred during cache invalidation action'
1238 }
1241 }
1239
1242
1240 """
1243 """
1241
1244
1242 repo = get_repo_or_error(repoid)
1245 repo = get_repo_or_error(repoid)
1243 if not has_superadmin_permission(apiuser):
1246 if not has_superadmin_permission(apiuser):
1244 _perms = ('repository.admin', 'repository.write',)
1247 _perms = ('repository.admin', 'repository.write',)
1245 validate_repo_permissions(apiuser, repoid, repo, _perms)
1248 validate_repo_permissions(apiuser, repoid, repo, _perms)
1246
1249
1247 delete = Optional.extract(delete_keys)
1250 delete = Optional.extract(delete_keys)
1248 try:
1251 try:
1249 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1252 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1250 return {
1253 return {
1251 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1254 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1252 'repository': repo.repo_name
1255 'repository': repo.repo_name
1253 }
1256 }
1254 except Exception:
1257 except Exception:
1255 log.exception(
1258 log.exception(
1256 "Exception occurred while trying to invalidate repo cache")
1259 "Exception occurred while trying to invalidate repo cache")
1257 raise JSONRPCError(
1260 raise JSONRPCError(
1258 'Error occurred during cache invalidation action'
1261 'Error occurred during cache invalidation action'
1259 )
1262 )
1260
1263
1261
1264
1262 #TODO: marcink, change name ?
1265 #TODO: marcink, change name ?
1263 @jsonrpc_method()
1266 @jsonrpc_method()
1264 def lock(request, apiuser, repoid, locked=Optional(None),
1267 def lock(request, apiuser, repoid, locked=Optional(None),
1265 userid=Optional(OAttr('apiuser'))):
1268 userid=Optional(OAttr('apiuser'))):
1266 """
1269 """
1267 Sets the lock state of the specified |repo| by the given user.
1270 Sets the lock state of the specified |repo| by the given user.
1268 From more information, see :ref:`repo-locking`.
1271 From more information, see :ref:`repo-locking`.
1269
1272
1270 * If the ``userid`` option is not set, the repository is locked to the
1273 * If the ``userid`` option is not set, the repository is locked to the
1271 user who called the method.
1274 user who called the method.
1272 * If the ``locked`` parameter is not set, the current lock state of the
1275 * If the ``locked`` parameter is not set, the current lock state of the
1273 repository is displayed.
1276 repository is displayed.
1274
1277
1275 This command can only be run using an |authtoken| with admin rights to
1278 This command can only be run using an |authtoken| with admin rights to
1276 the specified repository.
1279 the specified repository.
1277
1280
1278 This command takes the following options:
1281 This command takes the following options:
1279
1282
1280 :param apiuser: This is filled automatically from the |authtoken|.
1283 :param apiuser: This is filled automatically from the |authtoken|.
1281 :type apiuser: AuthUser
1284 :type apiuser: AuthUser
1282 :param repoid: Sets the repository name or repository ID.
1285 :param repoid: Sets the repository name or repository ID.
1283 :type repoid: str or int
1286 :type repoid: str or int
1284 :param locked: Sets the lock state.
1287 :param locked: Sets the lock state.
1285 :type locked: Optional(``True`` | ``False``)
1288 :type locked: Optional(``True`` | ``False``)
1286 :param userid: Set the repository lock to this user.
1289 :param userid: Set the repository lock to this user.
1287 :type userid: Optional(str or int)
1290 :type userid: Optional(str or int)
1288
1291
1289 Example error output:
1292 Example error output:
1290
1293
1291 .. code-block:: bash
1294 .. code-block:: bash
1292
1295
1293 id : <id_given_in_input>
1296 id : <id_given_in_input>
1294 result : {
1297 result : {
1295 'repo': '<reponame>',
1298 'repo': '<reponame>',
1296 'locked': <bool: lock state>,
1299 'locked': <bool: lock state>,
1297 'locked_since': <int: lock timestamp>,
1300 'locked_since': <int: lock timestamp>,
1298 'locked_by': <username of person who made the lock>,
1301 'locked_by': <username of person who made the lock>,
1299 'lock_reason': <str: reason for locking>,
1302 'lock_reason': <str: reason for locking>,
1300 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1303 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1301 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1304 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1302 or
1305 or
1303 'msg': 'Repo `<repository name>` not locked.'
1306 'msg': 'Repo `<repository name>` not locked.'
1304 or
1307 or
1305 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1308 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1306 }
1309 }
1307 error : null
1310 error : null
1308
1311
1309 Example error output:
1312 Example error output:
1310
1313
1311 .. code-block:: bash
1314 .. code-block:: bash
1312
1315
1313 id : <id_given_in_input>
1316 id : <id_given_in_input>
1314 result : null
1317 result : null
1315 error : {
1318 error : {
1316 'Error occurred locking repository `<reponame>`'
1319 'Error occurred locking repository `<reponame>`'
1317 }
1320 }
1318 """
1321 """
1319
1322
1320 repo = get_repo_or_error(repoid)
1323 repo = get_repo_or_error(repoid)
1321 if not has_superadmin_permission(apiuser):
1324 if not has_superadmin_permission(apiuser):
1322 # check if we have at least write permission for this repo !
1325 # check if we have at least write permission for this repo !
1323 _perms = ('repository.admin', 'repository.write',)
1326 _perms = ('repository.admin', 'repository.write',)
1324 validate_repo_permissions(apiuser, repoid, repo, _perms)
1327 validate_repo_permissions(apiuser, repoid, repo, _perms)
1325
1328
1326 # make sure normal user does not pass someone else userid,
1329 # make sure normal user does not pass someone else userid,
1327 # he is not allowed to do that
1330 # he is not allowed to do that
1328 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1331 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1329 raise JSONRPCError('userid is not the same as your user')
1332 raise JSONRPCError('userid is not the same as your user')
1330
1333
1331 if isinstance(userid, Optional):
1334 if isinstance(userid, Optional):
1332 userid = apiuser.user_id
1335 userid = apiuser.user_id
1333
1336
1334 user = get_user_or_error(userid)
1337 user = get_user_or_error(userid)
1335
1338
1336 if isinstance(locked, Optional):
1339 if isinstance(locked, Optional):
1337 lockobj = repo.locked
1340 lockobj = repo.locked
1338
1341
1339 if lockobj[0] is None:
1342 if lockobj[0] is None:
1340 _d = {
1343 _d = {
1341 'repo': repo.repo_name,
1344 'repo': repo.repo_name,
1342 'locked': False,
1345 'locked': False,
1343 'locked_since': None,
1346 'locked_since': None,
1344 'locked_by': None,
1347 'locked_by': None,
1345 'lock_reason': None,
1348 'lock_reason': None,
1346 'lock_state_changed': False,
1349 'lock_state_changed': False,
1347 'msg': 'Repo `%s` not locked.' % repo.repo_name
1350 'msg': 'Repo `%s` not locked.' % repo.repo_name
1348 }
1351 }
1349 return _d
1352 return _d
1350 else:
1353 else:
1351 _user_id, _time, _reason = lockobj
1354 _user_id, _time, _reason = lockobj
1352 lock_user = get_user_or_error(userid)
1355 lock_user = get_user_or_error(userid)
1353 _d = {
1356 _d = {
1354 'repo': repo.repo_name,
1357 'repo': repo.repo_name,
1355 'locked': True,
1358 'locked': True,
1356 'locked_since': _time,
1359 'locked_since': _time,
1357 'locked_by': lock_user.username,
1360 'locked_by': lock_user.username,
1358 'lock_reason': _reason,
1361 'lock_reason': _reason,
1359 'lock_state_changed': False,
1362 'lock_state_changed': False,
1360 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1363 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1361 % (repo.repo_name, lock_user.username,
1364 % (repo.repo_name, lock_user.username,
1362 json.dumps(time_to_datetime(_time))))
1365 json.dumps(time_to_datetime(_time))))
1363 }
1366 }
1364 return _d
1367 return _d
1365
1368
1366 # force locked state through a flag
1369 # force locked state through a flag
1367 else:
1370 else:
1368 locked = str2bool(locked)
1371 locked = str2bool(locked)
1369 lock_reason = Repository.LOCK_API
1372 lock_reason = Repository.LOCK_API
1370 try:
1373 try:
1371 if locked:
1374 if locked:
1372 lock_time = time.time()
1375 lock_time = time.time()
1373 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1376 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1374 else:
1377 else:
1375 lock_time = None
1378 lock_time = None
1376 Repository.unlock(repo)
1379 Repository.unlock(repo)
1377 _d = {
1380 _d = {
1378 'repo': repo.repo_name,
1381 'repo': repo.repo_name,
1379 'locked': locked,
1382 'locked': locked,
1380 'locked_since': lock_time,
1383 'locked_since': lock_time,
1381 'locked_by': user.username,
1384 'locked_by': user.username,
1382 'lock_reason': lock_reason,
1385 'lock_reason': lock_reason,
1383 'lock_state_changed': True,
1386 'lock_state_changed': True,
1384 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1387 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1385 % (user.username, repo.repo_name, locked))
1388 % (user.username, repo.repo_name, locked))
1386 }
1389 }
1387 return _d
1390 return _d
1388 except Exception:
1391 except Exception:
1389 log.exception(
1392 log.exception(
1390 "Exception occurred while trying to lock repository")
1393 "Exception occurred while trying to lock repository")
1391 raise JSONRPCError(
1394 raise JSONRPCError(
1392 'Error occurred locking repository `%s`' % repo.repo_name
1395 'Error occurred locking repository `%s`' % repo.repo_name
1393 )
1396 )
1394
1397
1395
1398
1396 @jsonrpc_method()
1399 @jsonrpc_method()
1397 def comment_commit(
1400 def comment_commit(
1398 request, apiuser, repoid, commit_id, message, status=Optional(None),
1401 request, apiuser, repoid, commit_id, message, status=Optional(None),
1399 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1402 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1400 resolves_comment_id=Optional(None),
1403 resolves_comment_id=Optional(None),
1401 userid=Optional(OAttr('apiuser'))):
1404 userid=Optional(OAttr('apiuser'))):
1402 """
1405 """
1403 Set a commit comment, and optionally change the status of the commit.
1406 Set a commit comment, and optionally change the status of the commit.
1404
1407
1405 :param apiuser: This is filled automatically from the |authtoken|.
1408 :param apiuser: This is filled automatically from the |authtoken|.
1406 :type apiuser: AuthUser
1409 :type apiuser: AuthUser
1407 :param repoid: Set the repository name or repository ID.
1410 :param repoid: Set the repository name or repository ID.
1408 :type repoid: str or int
1411 :type repoid: str or int
1409 :param commit_id: Specify the commit_id for which to set a comment.
1412 :param commit_id: Specify the commit_id for which to set a comment.
1410 :type commit_id: str
1413 :type commit_id: str
1411 :param message: The comment text.
1414 :param message: The comment text.
1412 :type message: str
1415 :type message: str
1413 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1416 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1414 'approved', 'rejected', 'under_review'
1417 'approved', 'rejected', 'under_review'
1415 :type status: str
1418 :type status: str
1416 :param comment_type: Comment type, one of: 'note', 'todo'
1419 :param comment_type: Comment type, one of: 'note', 'todo'
1417 :type comment_type: Optional(str), default: 'note'
1420 :type comment_type: Optional(str), default: 'note'
1418 :param userid: Set the user name of the comment creator.
1421 :param userid: Set the user name of the comment creator.
1419 :type userid: Optional(str or int)
1422 :type userid: Optional(str or int)
1420
1423
1421 Example error output:
1424 Example error output:
1422
1425
1423 .. code-block:: bash
1426 .. code-block:: bash
1424
1427
1425 {
1428 {
1426 "id" : <id_given_in_input>,
1429 "id" : <id_given_in_input>,
1427 "result" : {
1430 "result" : {
1428 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1431 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1429 "status_change": null or <status>,
1432 "status_change": null or <status>,
1430 "success": true
1433 "success": true
1431 },
1434 },
1432 "error" : null
1435 "error" : null
1433 }
1436 }
1434
1437
1435 """
1438 """
1436 repo = get_repo_or_error(repoid)
1439 repo = get_repo_or_error(repoid)
1437 if not has_superadmin_permission(apiuser):
1440 if not has_superadmin_permission(apiuser):
1438 _perms = ('repository.read', 'repository.write', 'repository.admin')
1441 _perms = ('repository.read', 'repository.write', 'repository.admin')
1439 validate_repo_permissions(apiuser, repoid, repo, _perms)
1442 validate_repo_permissions(apiuser, repoid, repo, _perms)
1440
1443
1441 try:
1444 try:
1442 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1445 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1443 except Exception as e:
1446 except Exception as e:
1444 log.exception('Failed to fetch commit')
1447 log.exception('Failed to fetch commit')
1445 raise JSONRPCError(e.message)
1448 raise JSONRPCError(e.message)
1446
1449
1447 if isinstance(userid, Optional):
1450 if isinstance(userid, Optional):
1448 userid = apiuser.user_id
1451 userid = apiuser.user_id
1449
1452
1450 user = get_user_or_error(userid)
1453 user = get_user_or_error(userid)
1451 status = Optional.extract(status)
1454 status = Optional.extract(status)
1452 comment_type = Optional.extract(comment_type)
1455 comment_type = Optional.extract(comment_type)
1453 resolves_comment_id = Optional.extract(resolves_comment_id)
1456 resolves_comment_id = Optional.extract(resolves_comment_id)
1454
1457
1455 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1458 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1456 if status and status not in allowed_statuses:
1459 if status and status not in allowed_statuses:
1457 raise JSONRPCError('Bad status, must be on '
1460 raise JSONRPCError('Bad status, must be on '
1458 'of %s got %s' % (allowed_statuses, status,))
1461 'of %s got %s' % (allowed_statuses, status,))
1459
1462
1460 if resolves_comment_id:
1463 if resolves_comment_id:
1461 comment = ChangesetComment.get(resolves_comment_id)
1464 comment = ChangesetComment.get(resolves_comment_id)
1462 if not comment:
1465 if not comment:
1463 raise JSONRPCError(
1466 raise JSONRPCError(
1464 'Invalid resolves_comment_id `%s` for this commit.'
1467 'Invalid resolves_comment_id `%s` for this commit.'
1465 % resolves_comment_id)
1468 % resolves_comment_id)
1466 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1469 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1467 raise JSONRPCError(
1470 raise JSONRPCError(
1468 'Comment `%s` is wrong type for setting status to resolved.'
1471 'Comment `%s` is wrong type for setting status to resolved.'
1469 % resolves_comment_id)
1472 % resolves_comment_id)
1470
1473
1471 try:
1474 try:
1472 rc_config = SettingsModel().get_all_settings()
1475 rc_config = SettingsModel().get_all_settings()
1473 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1476 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1474 status_change_label = ChangesetStatus.get_status_lbl(status)
1477 status_change_label = ChangesetStatus.get_status_lbl(status)
1475 comm = CommentsModel().create(
1478 comment = CommentsModel().create(
1476 message, repo, user, commit_id=commit_id,
1479 message, repo, user, commit_id=commit_id,
1477 status_change=status_change_label,
1480 status_change=status_change_label,
1478 status_change_type=status,
1481 status_change_type=status,
1479 renderer=renderer,
1482 renderer=renderer,
1480 comment_type=comment_type,
1483 comment_type=comment_type,
1481 resolves_comment_id=resolves_comment_id
1484 resolves_comment_id=resolves_comment_id
1482 )
1485 )
1483 if status:
1486 if status:
1484 # also do a status change
1487 # also do a status change
1485 try:
1488 try:
1486 ChangesetStatusModel().set_status(
1489 ChangesetStatusModel().set_status(
1487 repo, status, user, comm, revision=commit_id,
1490 repo, status, user, comment, revision=commit_id,
1488 dont_allow_on_closed_pull_request=True
1491 dont_allow_on_closed_pull_request=True
1489 )
1492 )
1490 except StatusChangeOnClosedPullRequestError:
1493 except StatusChangeOnClosedPullRequestError:
1491 log.exception(
1494 log.exception(
1492 "Exception occurred while trying to change repo commit status")
1495 "Exception occurred while trying to change repo commit status")
1493 msg = ('Changing status on a changeset associated with '
1496 msg = ('Changing status on a changeset associated with '
1494 'a closed pull request is not allowed')
1497 'a closed pull request is not allowed')
1495 raise JSONRPCError(msg)
1498 raise JSONRPCError(msg)
1496
1499
1497 Session().commit()
1500 Session().commit()
1498 return {
1501 return {
1499 'msg': (
1502 'msg': (
1500 'Commented on commit `%s` for repository `%s`' % (
1503 'Commented on commit `%s` for repository `%s`' % (
1501 comm.revision, repo.repo_name)),
1504 comment.revision, repo.repo_name)),
1502 'status_change': status,
1505 'status_change': status,
1503 'success': True,
1506 'success': True,
1504 }
1507 }
1505 except JSONRPCError:
1508 except JSONRPCError:
1506 # catch any inside errors, and re-raise them to prevent from
1509 # catch any inside errors, and re-raise them to prevent from
1507 # below global catch to silence them
1510 # below global catch to silence them
1508 raise
1511 raise
1509 except Exception:
1512 except Exception:
1510 log.exception("Exception occurred while trying to comment on commit")
1513 log.exception("Exception occurred while trying to comment on commit")
1511 raise JSONRPCError(
1514 raise JSONRPCError(
1512 'failed to set comment on repository `%s`' % (repo.repo_name,)
1515 'failed to set comment on repository `%s`' % (repo.repo_name,)
1513 )
1516 )
1514
1517
1515
1518
1516 @jsonrpc_method()
1519 @jsonrpc_method()
1517 def grant_user_permission(request, apiuser, repoid, userid, perm):
1520 def grant_user_permission(request, apiuser, repoid, userid, perm):
1518 """
1521 """
1519 Grant permissions for the specified user on the given repository,
1522 Grant permissions for the specified user on the given repository,
1520 or update existing permissions if found.
1523 or update existing permissions if found.
1521
1524
1522 This command can only be run using an |authtoken| with admin
1525 This command can only be run using an |authtoken| with admin
1523 permissions on the |repo|.
1526 permissions on the |repo|.
1524
1527
1525 :param apiuser: This is filled automatically from the |authtoken|.
1528 :param apiuser: This is filled automatically from the |authtoken|.
1526 :type apiuser: AuthUser
1529 :type apiuser: AuthUser
1527 :param repoid: Set the repository name or repository ID.
1530 :param repoid: Set the repository name or repository ID.
1528 :type repoid: str or int
1531 :type repoid: str or int
1529 :param userid: Set the user name.
1532 :param userid: Set the user name.
1530 :type userid: str
1533 :type userid: str
1531 :param perm: Set the user permissions, using the following format
1534 :param perm: Set the user permissions, using the following format
1532 ``(repository.(none|read|write|admin))``
1535 ``(repository.(none|read|write|admin))``
1533 :type perm: str
1536 :type perm: str
1534
1537
1535 Example output:
1538 Example output:
1536
1539
1537 .. code-block:: bash
1540 .. code-block:: bash
1538
1541
1539 id : <id_given_in_input>
1542 id : <id_given_in_input>
1540 result: {
1543 result: {
1541 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1544 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1542 "success": true
1545 "success": true
1543 }
1546 }
1544 error: null
1547 error: null
1545 """
1548 """
1546
1549
1547 repo = get_repo_or_error(repoid)
1550 repo = get_repo_or_error(repoid)
1548 user = get_user_or_error(userid)
1551 user = get_user_or_error(userid)
1549 perm = get_perm_or_error(perm)
1552 perm = get_perm_or_error(perm)
1550 if not has_superadmin_permission(apiuser):
1553 if not has_superadmin_permission(apiuser):
1551 _perms = ('repository.admin',)
1554 _perms = ('repository.admin',)
1552 validate_repo_permissions(apiuser, repoid, repo, _perms)
1555 validate_repo_permissions(apiuser, repoid, repo, _perms)
1553
1556
1554 try:
1557 try:
1555
1558
1556 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1559 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1557
1560
1558 Session().commit()
1561 Session().commit()
1559 return {
1562 return {
1560 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1563 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1561 perm.permission_name, user.username, repo.repo_name
1564 perm.permission_name, user.username, repo.repo_name
1562 ),
1565 ),
1563 'success': True
1566 'success': True
1564 }
1567 }
1565 except Exception:
1568 except Exception:
1566 log.exception(
1569 log.exception(
1567 "Exception occurred while trying edit permissions for repo")
1570 "Exception occurred while trying edit permissions for repo")
1568 raise JSONRPCError(
1571 raise JSONRPCError(
1569 'failed to edit permission for user: `%s` in repo: `%s`' % (
1572 'failed to edit permission for user: `%s` in repo: `%s`' % (
1570 userid, repoid
1573 userid, repoid
1571 )
1574 )
1572 )
1575 )
1573
1576
1574
1577
1575 @jsonrpc_method()
1578 @jsonrpc_method()
1576 def revoke_user_permission(request, apiuser, repoid, userid):
1579 def revoke_user_permission(request, apiuser, repoid, userid):
1577 """
1580 """
1578 Revoke permission for a user on the specified repository.
1581 Revoke permission for a user on the specified repository.
1579
1582
1580 This command can only be run using an |authtoken| with admin
1583 This command can only be run using an |authtoken| with admin
1581 permissions on the |repo|.
1584 permissions on the |repo|.
1582
1585
1583 :param apiuser: This is filled automatically from the |authtoken|.
1586 :param apiuser: This is filled automatically from the |authtoken|.
1584 :type apiuser: AuthUser
1587 :type apiuser: AuthUser
1585 :param repoid: Set the repository name or repository ID.
1588 :param repoid: Set the repository name or repository ID.
1586 :type repoid: str or int
1589 :type repoid: str or int
1587 :param userid: Set the user name of revoked user.
1590 :param userid: Set the user name of revoked user.
1588 :type userid: str or int
1591 :type userid: str or int
1589
1592
1590 Example error output:
1593 Example error output:
1591
1594
1592 .. code-block:: bash
1595 .. code-block:: bash
1593
1596
1594 id : <id_given_in_input>
1597 id : <id_given_in_input>
1595 result: {
1598 result: {
1596 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1599 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1597 "success": true
1600 "success": true
1598 }
1601 }
1599 error: null
1602 error: null
1600 """
1603 """
1601
1604
1602 repo = get_repo_or_error(repoid)
1605 repo = get_repo_or_error(repoid)
1603 user = get_user_or_error(userid)
1606 user = get_user_or_error(userid)
1604 if not has_superadmin_permission(apiuser):
1607 if not has_superadmin_permission(apiuser):
1605 _perms = ('repository.admin',)
1608 _perms = ('repository.admin',)
1606 validate_repo_permissions(apiuser, repoid, repo, _perms)
1609 validate_repo_permissions(apiuser, repoid, repo, _perms)
1607
1610
1608 try:
1611 try:
1609 RepoModel().revoke_user_permission(repo=repo, user=user)
1612 RepoModel().revoke_user_permission(repo=repo, user=user)
1610 Session().commit()
1613 Session().commit()
1611 return {
1614 return {
1612 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1615 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1613 user.username, repo.repo_name
1616 user.username, repo.repo_name
1614 ),
1617 ),
1615 'success': True
1618 'success': True
1616 }
1619 }
1617 except Exception:
1620 except Exception:
1618 log.exception(
1621 log.exception(
1619 "Exception occurred while trying revoke permissions to repo")
1622 "Exception occurred while trying revoke permissions to repo")
1620 raise JSONRPCError(
1623 raise JSONRPCError(
1621 'failed to edit permission for user: `%s` in repo: `%s`' % (
1624 'failed to edit permission for user: `%s` in repo: `%s`' % (
1622 userid, repoid
1625 userid, repoid
1623 )
1626 )
1624 )
1627 )
1625
1628
1626
1629
1627 @jsonrpc_method()
1630 @jsonrpc_method()
1628 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1631 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1629 """
1632 """
1630 Grant permission for a user group on the specified repository,
1633 Grant permission for a user group on the specified repository,
1631 or update existing permissions.
1634 or update existing permissions.
1632
1635
1633 This command can only be run using an |authtoken| with admin
1636 This command can only be run using an |authtoken| with admin
1634 permissions on the |repo|.
1637 permissions on the |repo|.
1635
1638
1636 :param apiuser: This is filled automatically from the |authtoken|.
1639 :param apiuser: This is filled automatically from the |authtoken|.
1637 :type apiuser: AuthUser
1640 :type apiuser: AuthUser
1638 :param repoid: Set the repository name or repository ID.
1641 :param repoid: Set the repository name or repository ID.
1639 :type repoid: str or int
1642 :type repoid: str or int
1640 :param usergroupid: Specify the ID of the user group.
1643 :param usergroupid: Specify the ID of the user group.
1641 :type usergroupid: str or int
1644 :type usergroupid: str or int
1642 :param perm: Set the user group permissions using the following
1645 :param perm: Set the user group permissions using the following
1643 format: (repository.(none|read|write|admin))
1646 format: (repository.(none|read|write|admin))
1644 :type perm: str
1647 :type perm: str
1645
1648
1646 Example output:
1649 Example output:
1647
1650
1648 .. code-block:: bash
1651 .. code-block:: bash
1649
1652
1650 id : <id_given_in_input>
1653 id : <id_given_in_input>
1651 result : {
1654 result : {
1652 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1655 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1653 "success": true
1656 "success": true
1654
1657
1655 }
1658 }
1656 error : null
1659 error : null
1657
1660
1658 Example error output:
1661 Example error output:
1659
1662
1660 .. code-block:: bash
1663 .. code-block:: bash
1661
1664
1662 id : <id_given_in_input>
1665 id : <id_given_in_input>
1663 result : null
1666 result : null
1664 error : {
1667 error : {
1665 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1668 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1666 }
1669 }
1667
1670
1668 """
1671 """
1669
1672
1670 repo = get_repo_or_error(repoid)
1673 repo = get_repo_or_error(repoid)
1671 perm = get_perm_or_error(perm)
1674 perm = get_perm_or_error(perm)
1672 if not has_superadmin_permission(apiuser):
1675 if not has_superadmin_permission(apiuser):
1673 _perms = ('repository.admin',)
1676 _perms = ('repository.admin',)
1674 validate_repo_permissions(apiuser, repoid, repo, _perms)
1677 validate_repo_permissions(apiuser, repoid, repo, _perms)
1675
1678
1676 user_group = get_user_group_or_error(usergroupid)
1679 user_group = get_user_group_or_error(usergroupid)
1677 if not has_superadmin_permission(apiuser):
1680 if not has_superadmin_permission(apiuser):
1678 # check if we have at least read permission for this user group !
1681 # check if we have at least read permission for this user group !
1679 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1682 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1680 if not HasUserGroupPermissionAnyApi(*_perms)(
1683 if not HasUserGroupPermissionAnyApi(*_perms)(
1681 user=apiuser, user_group_name=user_group.users_group_name):
1684 user=apiuser, user_group_name=user_group.users_group_name):
1682 raise JSONRPCError(
1685 raise JSONRPCError(
1683 'user group `%s` does not exist' % (usergroupid,))
1686 'user group `%s` does not exist' % (usergroupid,))
1684
1687
1685 try:
1688 try:
1686 RepoModel().grant_user_group_permission(
1689 RepoModel().grant_user_group_permission(
1687 repo=repo, group_name=user_group, perm=perm)
1690 repo=repo, group_name=user_group, perm=perm)
1688
1691
1689 Session().commit()
1692 Session().commit()
1690 return {
1693 return {
1691 'msg': 'Granted perm: `%s` for user group: `%s` in '
1694 'msg': 'Granted perm: `%s` for user group: `%s` in '
1692 'repo: `%s`' % (
1695 'repo: `%s`' % (
1693 perm.permission_name, user_group.users_group_name,
1696 perm.permission_name, user_group.users_group_name,
1694 repo.repo_name
1697 repo.repo_name
1695 ),
1698 ),
1696 'success': True
1699 'success': True
1697 }
1700 }
1698 except Exception:
1701 except Exception:
1699 log.exception(
1702 log.exception(
1700 "Exception occurred while trying change permission on repo")
1703 "Exception occurred while trying change permission on repo")
1701 raise JSONRPCError(
1704 raise JSONRPCError(
1702 'failed to edit permission for user group: `%s` in '
1705 'failed to edit permission for user group: `%s` in '
1703 'repo: `%s`' % (
1706 'repo: `%s`' % (
1704 usergroupid, repo.repo_name
1707 usergroupid, repo.repo_name
1705 )
1708 )
1706 )
1709 )
1707
1710
1708
1711
1709 @jsonrpc_method()
1712 @jsonrpc_method()
1710 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1713 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1711 """
1714 """
1712 Revoke the permissions of a user group on a given repository.
1715 Revoke the permissions of a user group on a given repository.
1713
1716
1714 This command can only be run using an |authtoken| with admin
1717 This command can only be run using an |authtoken| with admin
1715 permissions on the |repo|.
1718 permissions on the |repo|.
1716
1719
1717 :param apiuser: This is filled automatically from the |authtoken|.
1720 :param apiuser: This is filled automatically from the |authtoken|.
1718 :type apiuser: AuthUser
1721 :type apiuser: AuthUser
1719 :param repoid: Set the repository name or repository ID.
1722 :param repoid: Set the repository name or repository ID.
1720 :type repoid: str or int
1723 :type repoid: str or int
1721 :param usergroupid: Specify the user group ID.
1724 :param usergroupid: Specify the user group ID.
1722 :type usergroupid: str or int
1725 :type usergroupid: str or int
1723
1726
1724 Example output:
1727 Example output:
1725
1728
1726 .. code-block:: bash
1729 .. code-block:: bash
1727
1730
1728 id : <id_given_in_input>
1731 id : <id_given_in_input>
1729 result: {
1732 result: {
1730 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1733 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1731 "success": true
1734 "success": true
1732 }
1735 }
1733 error: null
1736 error: null
1734 """
1737 """
1735
1738
1736 repo = get_repo_or_error(repoid)
1739 repo = get_repo_or_error(repoid)
1737 if not has_superadmin_permission(apiuser):
1740 if not has_superadmin_permission(apiuser):
1738 _perms = ('repository.admin',)
1741 _perms = ('repository.admin',)
1739 validate_repo_permissions(apiuser, repoid, repo, _perms)
1742 validate_repo_permissions(apiuser, repoid, repo, _perms)
1740
1743
1741 user_group = get_user_group_or_error(usergroupid)
1744 user_group = get_user_group_or_error(usergroupid)
1742 if not has_superadmin_permission(apiuser):
1745 if not has_superadmin_permission(apiuser):
1743 # check if we have at least read permission for this user group !
1746 # check if we have at least read permission for this user group !
1744 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1747 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1745 if not HasUserGroupPermissionAnyApi(*_perms)(
1748 if not HasUserGroupPermissionAnyApi(*_perms)(
1746 user=apiuser, user_group_name=user_group.users_group_name):
1749 user=apiuser, user_group_name=user_group.users_group_name):
1747 raise JSONRPCError(
1750 raise JSONRPCError(
1748 'user group `%s` does not exist' % (usergroupid,))
1751 'user group `%s` does not exist' % (usergroupid,))
1749
1752
1750 try:
1753 try:
1751 RepoModel().revoke_user_group_permission(
1754 RepoModel().revoke_user_group_permission(
1752 repo=repo, group_name=user_group)
1755 repo=repo, group_name=user_group)
1753
1756
1754 Session().commit()
1757 Session().commit()
1755 return {
1758 return {
1756 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1759 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1757 user_group.users_group_name, repo.repo_name
1760 user_group.users_group_name, repo.repo_name
1758 ),
1761 ),
1759 'success': True
1762 'success': True
1760 }
1763 }
1761 except Exception:
1764 except Exception:
1762 log.exception("Exception occurred while trying revoke "
1765 log.exception("Exception occurred while trying revoke "
1763 "user group permission on repo")
1766 "user group permission on repo")
1764 raise JSONRPCError(
1767 raise JSONRPCError(
1765 'failed to edit permission for user group: `%s` in '
1768 'failed to edit permission for user group: `%s` in '
1766 'repo: `%s`' % (
1769 'repo: `%s`' % (
1767 user_group.users_group_name, repo.repo_name
1770 user_group.users_group_name, repo.repo_name
1768 )
1771 )
1769 )
1772 )
1770
1773
1771
1774
1772 @jsonrpc_method()
1775 @jsonrpc_method()
1773 def pull(request, apiuser, repoid):
1776 def pull(request, apiuser, repoid):
1774 """
1777 """
1775 Triggers a pull on the given repository from a remote location. You
1778 Triggers a pull on the given repository from a remote location. You
1776 can use this to keep remote repositories up-to-date.
1779 can use this to keep remote repositories up-to-date.
1777
1780
1778 This command can only be run using an |authtoken| with admin
1781 This command can only be run using an |authtoken| with admin
1779 rights to the specified repository. For more information,
1782 rights to the specified repository. For more information,
1780 see :ref:`config-token-ref`.
1783 see :ref:`config-token-ref`.
1781
1784
1782 This command takes the following options:
1785 This command takes the following options:
1783
1786
1784 :param apiuser: This is filled automatically from the |authtoken|.
1787 :param apiuser: This is filled automatically from the |authtoken|.
1785 :type apiuser: AuthUser
1788 :type apiuser: AuthUser
1786 :param repoid: The repository name or repository ID.
1789 :param repoid: The repository name or repository ID.
1787 :type repoid: str or int
1790 :type repoid: str or int
1788
1791
1789 Example output:
1792 Example output:
1790
1793
1791 .. code-block:: bash
1794 .. code-block:: bash
1792
1795
1793 id : <id_given_in_input>
1796 id : <id_given_in_input>
1794 result : {
1797 result : {
1795 "msg": "Pulled from `<repository name>`"
1798 "msg": "Pulled from `<repository name>`"
1796 "repository": "<repository name>"
1799 "repository": "<repository name>"
1797 }
1800 }
1798 error : null
1801 error : null
1799
1802
1800 Example error output:
1803 Example error output:
1801
1804
1802 .. code-block:: bash
1805 .. code-block:: bash
1803
1806
1804 id : <id_given_in_input>
1807 id : <id_given_in_input>
1805 result : null
1808 result : null
1806 error : {
1809 error : {
1807 "Unable to pull changes from `<reponame>`"
1810 "Unable to pull changes from `<reponame>`"
1808 }
1811 }
1809
1812
1810 """
1813 """
1811
1814
1812 repo = get_repo_or_error(repoid)
1815 repo = get_repo_or_error(repoid)
1813 if not has_superadmin_permission(apiuser):
1816 if not has_superadmin_permission(apiuser):
1814 _perms = ('repository.admin',)
1817 _perms = ('repository.admin',)
1815 validate_repo_permissions(apiuser, repoid, repo, _perms)
1818 validate_repo_permissions(apiuser, repoid, repo, _perms)
1816
1819
1817 try:
1820 try:
1818 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1821 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1819 return {
1822 return {
1820 'msg': 'Pulled from `%s`' % repo.repo_name,
1823 'msg': 'Pulled from `%s`' % repo.repo_name,
1821 'repository': repo.repo_name
1824 'repository': repo.repo_name
1822 }
1825 }
1823 except Exception:
1826 except Exception:
1824 log.exception("Exception occurred while trying to "
1827 log.exception("Exception occurred while trying to "
1825 "pull changes from remote location")
1828 "pull changes from remote location")
1826 raise JSONRPCError(
1829 raise JSONRPCError(
1827 'Unable to pull changes from `%s`' % repo.repo_name
1830 'Unable to pull changes from `%s`' % repo.repo_name
1828 )
1831 )
1829
1832
1830
1833
1831 @jsonrpc_method()
1834 @jsonrpc_method()
1832 def strip(request, apiuser, repoid, revision, branch):
1835 def strip(request, apiuser, repoid, revision, branch):
1833 """
1836 """
1834 Strips the given revision from the specified repository.
1837 Strips the given revision from the specified repository.
1835
1838
1836 * This will remove the revision and all of its decendants.
1839 * This will remove the revision and all of its decendants.
1837
1840
1838 This command can only be run using an |authtoken| with admin rights to
1841 This command can only be run using an |authtoken| with admin rights to
1839 the specified repository.
1842 the specified repository.
1840
1843
1841 This command takes the following options:
1844 This command takes the following options:
1842
1845
1843 :param apiuser: This is filled automatically from the |authtoken|.
1846 :param apiuser: This is filled automatically from the |authtoken|.
1844 :type apiuser: AuthUser
1847 :type apiuser: AuthUser
1845 :param repoid: The repository name or repository ID.
1848 :param repoid: The repository name or repository ID.
1846 :type repoid: str or int
1849 :type repoid: str or int
1847 :param revision: The revision you wish to strip.
1850 :param revision: The revision you wish to strip.
1848 :type revision: str
1851 :type revision: str
1849 :param branch: The branch from which to strip the revision.
1852 :param branch: The branch from which to strip the revision.
1850 :type branch: str
1853 :type branch: str
1851
1854
1852 Example output:
1855 Example output:
1853
1856
1854 .. code-block:: bash
1857 .. code-block:: bash
1855
1858
1856 id : <id_given_in_input>
1859 id : <id_given_in_input>
1857 result : {
1860 result : {
1858 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1861 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1859 "repository": "<repository name>"
1862 "repository": "<repository name>"
1860 }
1863 }
1861 error : null
1864 error : null
1862
1865
1863 Example error output:
1866 Example error output:
1864
1867
1865 .. code-block:: bash
1868 .. code-block:: bash
1866
1869
1867 id : <id_given_in_input>
1870 id : <id_given_in_input>
1868 result : null
1871 result : null
1869 error : {
1872 error : {
1870 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1873 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1871 }
1874 }
1872
1875
1873 """
1876 """
1874
1877
1875 repo = get_repo_or_error(repoid)
1878 repo = get_repo_or_error(repoid)
1876 if not has_superadmin_permission(apiuser):
1879 if not has_superadmin_permission(apiuser):
1877 _perms = ('repository.admin',)
1880 _perms = ('repository.admin',)
1878 validate_repo_permissions(apiuser, repoid, repo, _perms)
1881 validate_repo_permissions(apiuser, repoid, repo, _perms)
1879
1882
1880 try:
1883 try:
1881 ScmModel().strip(repo, revision, branch)
1884 ScmModel().strip(repo, revision, branch)
1885 audit_logger.store_api(
1886 'repo.commit.strip', action_data={'commit_id': revision},
1887 repo=repo,
1888 user=apiuser, commit=True)
1889
1882 return {
1890 return {
1883 'msg': 'Stripped commit %s from repo `%s`' % (
1891 'msg': 'Stripped commit %s from repo `%s`' % (
1884 revision, repo.repo_name),
1892 revision, repo.repo_name),
1885 'repository': repo.repo_name
1893 'repository': repo.repo_name
1886 }
1894 }
1887 except Exception:
1895 except Exception:
1888 log.exception("Exception while trying to strip")
1896 log.exception("Exception while trying to strip")
1889 raise JSONRPCError(
1897 raise JSONRPCError(
1890 'Unable to strip commit %s from repo `%s`' % (
1898 'Unable to strip commit %s from repo `%s`' % (
1891 revision, repo.repo_name)
1899 revision, repo.repo_name)
1892 )
1900 )
1893
1901
1894
1902
1895 @jsonrpc_method()
1903 @jsonrpc_method()
1896 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1904 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1897 """
1905 """
1898 Returns all settings for a repository. If key is given it only returns the
1906 Returns all settings for a repository. If key is given it only returns the
1899 setting identified by the key or null.
1907 setting identified by the key or null.
1900
1908
1901 :param apiuser: This is filled automatically from the |authtoken|.
1909 :param apiuser: This is filled automatically from the |authtoken|.
1902 :type apiuser: AuthUser
1910 :type apiuser: AuthUser
1903 :param repoid: The repository name or repository id.
1911 :param repoid: The repository name or repository id.
1904 :type repoid: str or int
1912 :type repoid: str or int
1905 :param key: Key of the setting to return.
1913 :param key: Key of the setting to return.
1906 :type: key: Optional(str)
1914 :type: key: Optional(str)
1907
1915
1908 Example output:
1916 Example output:
1909
1917
1910 .. code-block:: bash
1918 .. code-block:: bash
1911
1919
1912 {
1920 {
1913 "error": null,
1921 "error": null,
1914 "id": 237,
1922 "id": 237,
1915 "result": {
1923 "result": {
1916 "extensions_largefiles": true,
1924 "extensions_largefiles": true,
1917 "extensions_evolve": true,
1925 "extensions_evolve": true,
1918 "hooks_changegroup_push_logger": true,
1926 "hooks_changegroup_push_logger": true,
1919 "hooks_changegroup_repo_size": false,
1927 "hooks_changegroup_repo_size": false,
1920 "hooks_outgoing_pull_logger": true,
1928 "hooks_outgoing_pull_logger": true,
1921 "phases_publish": "True",
1929 "phases_publish": "True",
1922 "rhodecode_hg_use_rebase_for_merging": true,
1930 "rhodecode_hg_use_rebase_for_merging": true,
1923 "rhodecode_pr_merge_enabled": true,
1931 "rhodecode_pr_merge_enabled": true,
1924 "rhodecode_use_outdated_comments": true
1932 "rhodecode_use_outdated_comments": true
1925 }
1933 }
1926 }
1934 }
1927 """
1935 """
1928
1936
1929 # Restrict access to this api method to admins only.
1937 # Restrict access to this api method to admins only.
1930 if not has_superadmin_permission(apiuser):
1938 if not has_superadmin_permission(apiuser):
1931 raise JSONRPCForbidden()
1939 raise JSONRPCForbidden()
1932
1940
1933 try:
1941 try:
1934 repo = get_repo_or_error(repoid)
1942 repo = get_repo_or_error(repoid)
1935 settings_model = VcsSettingsModel(repo=repo)
1943 settings_model = VcsSettingsModel(repo=repo)
1936 settings = settings_model.get_global_settings()
1944 settings = settings_model.get_global_settings()
1937 settings.update(settings_model.get_repo_settings())
1945 settings.update(settings_model.get_repo_settings())
1938
1946
1939 # If only a single setting is requested fetch it from all settings.
1947 # If only a single setting is requested fetch it from all settings.
1940 key = Optional.extract(key)
1948 key = Optional.extract(key)
1941 if key is not None:
1949 if key is not None:
1942 settings = settings.get(key, None)
1950 settings = settings.get(key, None)
1943 except Exception:
1951 except Exception:
1944 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1952 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1945 log.exception(msg)
1953 log.exception(msg)
1946 raise JSONRPCError(msg)
1954 raise JSONRPCError(msg)
1947
1955
1948 return settings
1956 return settings
1949
1957
1950
1958
1951 @jsonrpc_method()
1959 @jsonrpc_method()
1952 def set_repo_settings(request, apiuser, repoid, settings):
1960 def set_repo_settings(request, apiuser, repoid, settings):
1953 """
1961 """
1954 Update repository settings. Returns true on success.
1962 Update repository settings. Returns true on success.
1955
1963
1956 :param apiuser: This is filled automatically from the |authtoken|.
1964 :param apiuser: This is filled automatically from the |authtoken|.
1957 :type apiuser: AuthUser
1965 :type apiuser: AuthUser
1958 :param repoid: The repository name or repository id.
1966 :param repoid: The repository name or repository id.
1959 :type repoid: str or int
1967 :type repoid: str or int
1960 :param settings: The new settings for the repository.
1968 :param settings: The new settings for the repository.
1961 :type: settings: dict
1969 :type: settings: dict
1962
1970
1963 Example output:
1971 Example output:
1964
1972
1965 .. code-block:: bash
1973 .. code-block:: bash
1966
1974
1967 {
1975 {
1968 "error": null,
1976 "error": null,
1969 "id": 237,
1977 "id": 237,
1970 "result": true
1978 "result": true
1971 }
1979 }
1972 """
1980 """
1973 # Restrict access to this api method to admins only.
1981 # Restrict access to this api method to admins only.
1974 if not has_superadmin_permission(apiuser):
1982 if not has_superadmin_permission(apiuser):
1975 raise JSONRPCForbidden()
1983 raise JSONRPCForbidden()
1976
1984
1977 if type(settings) is not dict:
1985 if type(settings) is not dict:
1978 raise JSONRPCError('Settings have to be a JSON Object.')
1986 raise JSONRPCError('Settings have to be a JSON Object.')
1979
1987
1980 try:
1988 try:
1981 settings_model = VcsSettingsModel(repo=repoid)
1989 settings_model = VcsSettingsModel(repo=repoid)
1982
1990
1983 # Merge global, repo and incoming settings.
1991 # Merge global, repo and incoming settings.
1984 new_settings = settings_model.get_global_settings()
1992 new_settings = settings_model.get_global_settings()
1985 new_settings.update(settings_model.get_repo_settings())
1993 new_settings.update(settings_model.get_repo_settings())
1986 new_settings.update(settings)
1994 new_settings.update(settings)
1987
1995
1988 # Update the settings.
1996 # Update the settings.
1989 inherit_global_settings = new_settings.get(
1997 inherit_global_settings = new_settings.get(
1990 'inherit_global_settings', False)
1998 'inherit_global_settings', False)
1991 settings_model.create_or_update_repo_settings(
1999 settings_model.create_or_update_repo_settings(
1992 new_settings, inherit_global_settings=inherit_global_settings)
2000 new_settings, inherit_global_settings=inherit_global_settings)
1993 Session().commit()
2001 Session().commit()
1994 except Exception:
2002 except Exception:
1995 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2003 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1996 log.exception(msg)
2004 log.exception(msg)
1997 raise JSONRPCError(msg)
2005 raise JSONRPCError(msg)
1998
2006
1999 # Indicate success.
2007 # Indicate success.
2000 return True
2008 return True
2001
2009
2002
2010
2003 @jsonrpc_method()
2011 @jsonrpc_method()
2004 def maintenance(request, apiuser, repoid):
2012 def maintenance(request, apiuser, repoid):
2005 """
2013 """
2006 Triggers a maintenance on the given repository.
2014 Triggers a maintenance on the given repository.
2007
2015
2008 This command can only be run using an |authtoken| with admin
2016 This command can only be run using an |authtoken| with admin
2009 rights to the specified repository. For more information,
2017 rights to the specified repository. For more information,
2010 see :ref:`config-token-ref`.
2018 see :ref:`config-token-ref`.
2011
2019
2012 This command takes the following options:
2020 This command takes the following options:
2013
2021
2014 :param apiuser: This is filled automatically from the |authtoken|.
2022 :param apiuser: This is filled automatically from the |authtoken|.
2015 :type apiuser: AuthUser
2023 :type apiuser: AuthUser
2016 :param repoid: The repository name or repository ID.
2024 :param repoid: The repository name or repository ID.
2017 :type repoid: str or int
2025 :type repoid: str or int
2018
2026
2019 Example output:
2027 Example output:
2020
2028
2021 .. code-block:: bash
2029 .. code-block:: bash
2022
2030
2023 id : <id_given_in_input>
2031 id : <id_given_in_input>
2024 result : {
2032 result : {
2025 "msg": "executed maintenance command",
2033 "msg": "executed maintenance command",
2026 "executed_actions": [
2034 "executed_actions": [
2027 <action_message>, <action_message2>...
2035 <action_message>, <action_message2>...
2028 ],
2036 ],
2029 "repository": "<repository name>"
2037 "repository": "<repository name>"
2030 }
2038 }
2031 error : null
2039 error : null
2032
2040
2033 Example error output:
2041 Example error output:
2034
2042
2035 .. code-block:: bash
2043 .. code-block:: bash
2036
2044
2037 id : <id_given_in_input>
2045 id : <id_given_in_input>
2038 result : null
2046 result : null
2039 error : {
2047 error : {
2040 "Unable to execute maintenance on `<reponame>`"
2048 "Unable to execute maintenance on `<reponame>`"
2041 }
2049 }
2042
2050
2043 """
2051 """
2044
2052
2045 repo = get_repo_or_error(repoid)
2053 repo = get_repo_or_error(repoid)
2046 if not has_superadmin_permission(apiuser):
2054 if not has_superadmin_permission(apiuser):
2047 _perms = ('repository.admin',)
2055 _perms = ('repository.admin',)
2048 validate_repo_permissions(apiuser, repoid, repo, _perms)
2056 validate_repo_permissions(apiuser, repoid, repo, _perms)
2049
2057
2050 try:
2058 try:
2051 maintenance = repo_maintenance.RepoMaintenance()
2059 maintenance = repo_maintenance.RepoMaintenance()
2052 executed_actions = maintenance.execute(repo)
2060 executed_actions = maintenance.execute(repo)
2053
2061
2054 return {
2062 return {
2055 'msg': 'executed maintenance command',
2063 'msg': 'executed maintenance command',
2056 'executed_actions': executed_actions,
2064 'executed_actions': executed_actions,
2057 'repository': repo.repo_name
2065 'repository': repo.repo_name
2058 }
2066 }
2059 except Exception:
2067 except Exception:
2060 log.exception("Exception occurred while trying to run maintenance")
2068 log.exception("Exception occurred while trying to run maintenance")
2061 raise JSONRPCError(
2069 raise JSONRPCError(
2062 'Unable to execute maintenance on `%s`' % repo.repo_name)
2070 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,702 +1,719 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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.auth import (
31 from rhodecode.lib.auth import (
31 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model import validation_schema
36 from rhodecode.model import validation_schema
36 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
37
38
38
39
39 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
40
41
41
42
42 @jsonrpc_method()
43 @jsonrpc_method()
43 def get_repo_group(request, apiuser, repogroupid):
44 def get_repo_group(request, apiuser, repogroupid):
44 """
45 """
45 Return the specified |repo| group, along with permissions,
46 Return the specified |repo| group, along with permissions,
46 and repositories inside the group
47 and repositories inside the group
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 repogroupid: Specify the name of ID of the repository group.
51 :param repogroupid: Specify the name of ID of the repository group.
51 :type repogroupid: str or int
52 :type repogroupid: str or int
52
53
53
54
54 Example output:
55 Example output:
55
56
56 .. code-block:: bash
57 .. code-block:: bash
57
58
58 {
59 {
59 "error": null,
60 "error": null,
60 "id": repo-group-id,
61 "id": repo-group-id,
61 "result": {
62 "result": {
62 "group_description": "repo group description",
63 "group_description": "repo group description",
63 "group_id": 14,
64 "group_id": 14,
64 "group_name": "group name",
65 "group_name": "group name",
65 "members": [
66 "members": [
66 {
67 {
67 "name": "super-admin-username",
68 "name": "super-admin-username",
68 "origin": "super-admin",
69 "origin": "super-admin",
69 "permission": "group.admin",
70 "permission": "group.admin",
70 "type": "user"
71 "type": "user"
71 },
72 },
72 {
73 {
73 "name": "owner-name",
74 "name": "owner-name",
74 "origin": "owner",
75 "origin": "owner",
75 "permission": "group.admin",
76 "permission": "group.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": "group.write",
82 "permission": "group.write",
82 "type": "user_group"
83 "type": "user_group"
83 }
84 }
84 ],
85 ],
85 "owner": "owner-name",
86 "owner": "owner-name",
86 "parent_group": null,
87 "parent_group": null,
87 "repositories": [ repo-list ]
88 "repositories": [ repo-list ]
88 }
89 }
89 }
90 }
90 """
91 """
91
92
92 repo_group = get_repo_group_or_error(repogroupid)
93 repo_group = get_repo_group_or_error(repogroupid)
93 if not has_superadmin_permission(apiuser):
94 if not has_superadmin_permission(apiuser):
94 # check if we have at least read permission for this repo group !
95 # check if we have at least read permission for this repo group !
95 _perms = ('group.admin', 'group.write', 'group.read',)
96 _perms = ('group.admin', 'group.write', 'group.read',)
96 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 user=apiuser, group_name=repo_group.group_name):
98 user=apiuser, group_name=repo_group.group_name):
98 raise JSONRPCError(
99 raise JSONRPCError(
99 'repository group `%s` does not exist' % (repogroupid,))
100 'repository group `%s` does not exist' % (repogroupid,))
100
101
101 permissions = []
102 permissions = []
102 for _user in repo_group.permissions():
103 for _user in repo_group.permissions():
103 user_data = {
104 user_data = {
104 'name': _user.username,
105 'name': _user.username,
105 'permission': _user.permission,
106 'permission': _user.permission,
106 'origin': get_origin(_user),
107 'origin': get_origin(_user),
107 'type': "user",
108 'type': "user",
108 }
109 }
109 permissions.append(user_data)
110 permissions.append(user_data)
110
111
111 for _user_group in repo_group.permission_user_groups():
112 for _user_group in repo_group.permission_user_groups():
112 user_group_data = {
113 user_group_data = {
113 'name': _user_group.users_group_name,
114 'name': _user_group.users_group_name,
114 'permission': _user_group.permission,
115 'permission': _user_group.permission,
115 'origin': get_origin(_user_group),
116 'origin': get_origin(_user_group),
116 'type': "user_group",
117 'type': "user_group",
117 }
118 }
118 permissions.append(user_group_data)
119 permissions.append(user_group_data)
119
120
120 data = repo_group.get_api_data()
121 data = repo_group.get_api_data()
121 data["members"] = permissions # TODO: this should be named permissions
122 data["members"] = permissions # TODO: this should be named permissions
122 return data
123 return data
123
124
124
125
125 @jsonrpc_method()
126 @jsonrpc_method()
126 def get_repo_groups(request, apiuser):
127 def get_repo_groups(request, apiuser):
127 """
128 """
128 Returns all repository groups.
129 Returns all repository groups.
129
130
130 :param apiuser: This is filled automatically from the |authtoken|.
131 :param apiuser: This is filled automatically from the |authtoken|.
131 :type apiuser: AuthUser
132 :type apiuser: AuthUser
132 """
133 """
133
134
134 result = []
135 result = []
135 _perms = ('group.read', 'group.write', 'group.admin',)
136 _perms = ('group.read', 'group.write', 'group.admin',)
136 extras = {'user': apiuser}
137 extras = {'user': apiuser}
137 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 perm_set=_perms, extra_kwargs=extras):
139 perm_set=_perms, extra_kwargs=extras):
139 result.append(repo_group.get_api_data())
140 result.append(repo_group.get_api_data())
140 return result
141 return result
141
142
142
143
143 @jsonrpc_method()
144 @jsonrpc_method()
144 def create_repo_group(
145 def create_repo_group(
145 request, apiuser, group_name,
146 request, apiuser, group_name,
146 owner=Optional(OAttr('apiuser')),
147 owner=Optional(OAttr('apiuser')),
147 description=Optional(''),
148 description=Optional(''),
148 copy_permissions=Optional(False)):
149 copy_permissions=Optional(False)):
149 """
150 """
150 Creates a repository group.
151 Creates a repository group.
151
152
152 * If the repository group name contains "/", repository group will be
153 * If the repository group name contains "/", repository group will be
153 created inside a repository group or nested repository groups
154 created inside a repository group or nested repository groups
154
155
155 For example "foo/bar/group1" will create repository group called "group1"
156 For example "foo/bar/group1" will create repository group called "group1"
156 inside group "foo/bar". You have to have permissions to access and
157 inside group "foo/bar". You have to have permissions to access and
157 write to the last repository group ("bar" in this example)
158 write to the last repository group ("bar" in this example)
158
159
159 This command can only be run using an |authtoken| with at least
160 This command can only be run using an |authtoken| with at least
160 permissions to create repository groups, or admin permissions to
161 permissions to create repository groups, or admin permissions to
161 parent repository groups.
162 parent repository groups.
162
163
163 :param apiuser: This is filled automatically from the |authtoken|.
164 :param apiuser: This is filled automatically from the |authtoken|.
164 :type apiuser: AuthUser
165 :type apiuser: AuthUser
165 :param group_name: Set the repository group name.
166 :param group_name: Set the repository group name.
166 :type group_name: str
167 :type group_name: str
167 :param description: Set the |repo| group description.
168 :param description: Set the |repo| group description.
168 :type description: str
169 :type description: str
169 :param owner: Set the |repo| group owner.
170 :param owner: Set the |repo| group owner.
170 :type owner: str
171 :type owner: str
171 :param copy_permissions:
172 :param copy_permissions:
172 :type copy_permissions:
173 :type copy_permissions:
173
174
174 Example output:
175 Example output:
175
176
176 .. code-block:: bash
177 .. code-block:: bash
177
178
178 id : <id_given_in_input>
179 id : <id_given_in_input>
179 result : {
180 result : {
180 "msg": "Created new repo group `<repo_group_name>`"
181 "msg": "Created new repo group `<repo_group_name>`"
181 "repo_group": <repogroup_object>
182 "repo_group": <repogroup_object>
182 }
183 }
183 error : null
184 error : null
184
185
185
186
186 Example error output:
187 Example error output:
187
188
188 .. code-block:: bash
189 .. code-block:: bash
189
190
190 id : <id_given_in_input>
191 id : <id_given_in_input>
191 result : null
192 result : null
192 error : {
193 error : {
193 failed to create repo group `<repogroupid>`
194 failed to create repo group `<repogroupid>`
194 }
195 }
195
196
196 """
197 """
197
198
198 owner = validate_set_owner_permissions(apiuser, owner)
199 owner = validate_set_owner_permissions(apiuser, owner)
199
200
200 description = Optional.extract(description)
201 description = Optional.extract(description)
201 copy_permissions = Optional.extract(copy_permissions)
202 copy_permissions = Optional.extract(copy_permissions)
202
203
203 schema = repo_group_schema.RepoGroupSchema().bind(
204 schema = repo_group_schema.RepoGroupSchema().bind(
204 # user caller
205 # user caller
205 user=apiuser)
206 user=apiuser)
206
207
207 try:
208 try:
208 schema_data = schema.deserialize(dict(
209 schema_data = schema.deserialize(dict(
209 repo_group_name=group_name,
210 repo_group_name=group_name,
210 repo_group_owner=owner.username,
211 repo_group_owner=owner.username,
211 repo_group_description=description,
212 repo_group_description=description,
212 repo_group_copy_permissions=copy_permissions,
213 repo_group_copy_permissions=copy_permissions,
213 ))
214 ))
214 except validation_schema.Invalid as err:
215 except validation_schema.Invalid as err:
215 raise JSONRPCValidationError(colander_exc=err)
216 raise JSONRPCValidationError(colander_exc=err)
216
217
217 validated_group_name = schema_data['repo_group_name']
218 validated_group_name = schema_data['repo_group_name']
218
219
219 try:
220 try:
220 repo_group = RepoGroupModel().create(
221 repo_group = RepoGroupModel().create(
221 owner=owner,
222 owner=owner,
222 group_name=validated_group_name,
223 group_name=validated_group_name,
223 group_description=schema_data['repo_group_name'],
224 group_description=schema_data['repo_group_name'],
224 copy_permissions=schema_data['repo_group_copy_permissions'])
225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 Session().flush()
227
228 repo_group_data = repo_group.get_api_data()
229 audit_logger.store_api(
230 'repo_group.create', action_data={'data': repo_group_data},
231 user=apiuser)
232
225 Session().commit()
233 Session().commit()
226 return {
234 return {
227 'msg': 'Created new repo group `%s`' % validated_group_name,
235 'msg': 'Created new repo group `%s`' % validated_group_name,
228 'repo_group': repo_group.get_api_data()
236 'repo_group': repo_group.get_api_data()
229 }
237 }
230 except Exception:
238 except Exception:
231 log.exception("Exception occurred while trying create repo group")
239 log.exception("Exception occurred while trying create repo group")
232 raise JSONRPCError(
240 raise JSONRPCError(
233 'failed to create repo group `%s`' % (validated_group_name,))
241 'failed to create repo group `%s`' % (validated_group_name,))
234
242
235
243
236 @jsonrpc_method()
244 @jsonrpc_method()
237 def update_repo_group(
245 def update_repo_group(
238 request, apiuser, repogroupid, group_name=Optional(''),
246 request, apiuser, repogroupid, group_name=Optional(''),
239 description=Optional(''), owner=Optional(OAttr('apiuser')),
247 description=Optional(''), owner=Optional(OAttr('apiuser')),
240 enable_locking=Optional(False)):
248 enable_locking=Optional(False)):
241 """
249 """
242 Updates repository group with the details given.
250 Updates repository group with the details given.
243
251
244 This command can only be run using an |authtoken| with admin
252 This command can only be run using an |authtoken| with admin
245 permissions.
253 permissions.
246
254
247 * If the group_name name contains "/", repository group will be updated
255 * If the group_name name contains "/", repository group will be updated
248 accordingly with a repository group or nested repository groups
256 accordingly with a repository group or nested repository groups
249
257
250 For example repogroupid=group-test group_name="foo/bar/group-test"
258 For example repogroupid=group-test group_name="foo/bar/group-test"
251 will update repository group called "group-test" and place it
259 will update repository group called "group-test" and place it
252 inside group "foo/bar".
260 inside group "foo/bar".
253 You have to have permissions to access and write to the last repository
261 You have to have permissions to access and write to the last repository
254 group ("bar" in this example)
262 group ("bar" in this example)
255
263
256 :param apiuser: This is filled automatically from the |authtoken|.
264 :param apiuser: This is filled automatically from the |authtoken|.
257 :type apiuser: AuthUser
265 :type apiuser: AuthUser
258 :param repogroupid: Set the ID of repository group.
266 :param repogroupid: Set the ID of repository group.
259 :type repogroupid: str or int
267 :type repogroupid: str or int
260 :param group_name: Set the name of the |repo| group.
268 :param group_name: Set the name of the |repo| group.
261 :type group_name: str
269 :type group_name: str
262 :param description: Set a description for the group.
270 :param description: Set a description for the group.
263 :type description: str
271 :type description: str
264 :param owner: Set the |repo| group owner.
272 :param owner: Set the |repo| group owner.
265 :type owner: str
273 :type owner: str
266 :param enable_locking: Enable |repo| locking. The default is false.
274 :param enable_locking: Enable |repo| locking. The default is false.
267 :type enable_locking: bool
275 :type enable_locking: bool
268 """
276 """
269
277
270 repo_group = get_repo_group_or_error(repogroupid)
278 repo_group = get_repo_group_or_error(repogroupid)
271
279
272 if not has_superadmin_permission(apiuser):
280 if not has_superadmin_permission(apiuser):
273 validate_repo_group_permissions(
281 validate_repo_group_permissions(
274 apiuser, repogroupid, repo_group, ('group.admin',))
282 apiuser, repogroupid, repo_group, ('group.admin',))
275
283
276 updates = dict(
284 updates = dict(
277 group_name=group_name
285 group_name=group_name
278 if not isinstance(group_name, Optional) else repo_group.group_name,
286 if not isinstance(group_name, Optional) else repo_group.group_name,
279
287
280 group_description=description
288 group_description=description
281 if not isinstance(description, Optional) else repo_group.group_description,
289 if not isinstance(description, Optional) else repo_group.group_description,
282
290
283 user=owner
291 user=owner
284 if not isinstance(owner, Optional) else repo_group.user.username,
292 if not isinstance(owner, Optional) else repo_group.user.username,
285
293
286 enable_locking=enable_locking
294 enable_locking=enable_locking
287 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
288 )
296 )
289
297
290 schema = repo_group_schema.RepoGroupSchema().bind(
298 schema = repo_group_schema.RepoGroupSchema().bind(
291 # user caller
299 # user caller
292 user=apiuser,
300 user=apiuser,
293 old_values=repo_group.get_api_data())
301 old_values=repo_group.get_api_data())
294
302
295 try:
303 try:
296 schema_data = schema.deserialize(dict(
304 schema_data = schema.deserialize(dict(
297 repo_group_name=updates['group_name'],
305 repo_group_name=updates['group_name'],
298 repo_group_owner=updates['user'],
306 repo_group_owner=updates['user'],
299 repo_group_description=updates['group_description'],
307 repo_group_description=updates['group_description'],
300 repo_group_enable_locking=updates['enable_locking'],
308 repo_group_enable_locking=updates['enable_locking'],
301 ))
309 ))
302 except validation_schema.Invalid as err:
310 except validation_schema.Invalid as err:
303 raise JSONRPCValidationError(colander_exc=err)
311 raise JSONRPCValidationError(colander_exc=err)
304
312
305 validated_updates = dict(
313 validated_updates = dict(
306 group_name=schema_data['repo_group']['repo_group_name_without_group'],
314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
307 group_parent_id=schema_data['repo_group']['repo_group_id'],
315 group_parent_id=schema_data['repo_group']['repo_group_id'],
308 user=schema_data['repo_group_owner'],
316 user=schema_data['repo_group_owner'],
309 group_description=schema_data['repo_group_description'],
317 group_description=schema_data['repo_group_description'],
310 enable_locking=schema_data['repo_group_enable_locking'],
318 enable_locking=schema_data['repo_group_enable_locking'],
311 )
319 )
312
320
321 old_data = repo_group.get_api_data()
313 try:
322 try:
314 RepoGroupModel().update(repo_group, validated_updates)
323 RepoGroupModel().update(repo_group, validated_updates)
324 audit_logger.store_api(
325 'repo_group.edit', action_data={'old_data': old_data},
326 user=apiuser)
327
315 Session().commit()
328 Session().commit()
316 return {
329 return {
317 'msg': 'updated repository group ID:%s %s' % (
330 'msg': 'updated repository group ID:%s %s' % (
318 repo_group.group_id, repo_group.group_name),
331 repo_group.group_id, repo_group.group_name),
319 'repo_group': repo_group.get_api_data()
332 'repo_group': repo_group.get_api_data()
320 }
333 }
321 except Exception:
334 except Exception:
322 log.exception(
335 log.exception(
323 u"Exception occurred while trying update repo group %s",
336 u"Exception occurred while trying update repo group %s",
324 repogroupid)
337 repogroupid)
325 raise JSONRPCError('failed to update repository group `%s`'
338 raise JSONRPCError('failed to update repository group `%s`'
326 % (repogroupid,))
339 % (repogroupid,))
327
340
328
341
329 @jsonrpc_method()
342 @jsonrpc_method()
330 def delete_repo_group(request, apiuser, repogroupid):
343 def delete_repo_group(request, apiuser, repogroupid):
331 """
344 """
332 Deletes a |repo| group.
345 Deletes a |repo| group.
333
346
334 :param apiuser: This is filled automatically from the |authtoken|.
347 :param apiuser: This is filled automatically from the |authtoken|.
335 :type apiuser: AuthUser
348 :type apiuser: AuthUser
336 :param repogroupid: Set the name or ID of repository group to be
349 :param repogroupid: Set the name or ID of repository group to be
337 deleted.
350 deleted.
338 :type repogroupid: str or int
351 :type repogroupid: str or int
339
352
340 Example output:
353 Example output:
341
354
342 .. code-block:: bash
355 .. code-block:: bash
343
356
344 id : <id_given_in_input>
357 id : <id_given_in_input>
345 result : {
358 result : {
346 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
347 'repo_group': null
360 'repo_group': null
348 }
361 }
349 error : null
362 error : null
350
363
351 Example error output:
364 Example error output:
352
365
353 .. code-block:: bash
366 .. code-block:: bash
354
367
355 id : <id_given_in_input>
368 id : <id_given_in_input>
356 result : null
369 result : null
357 error : {
370 error : {
358 "failed to delete repo group ID:<repogroupid> <repogroupname>"
371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
359 }
372 }
360
373
361 """
374 """
362
375
363 repo_group = get_repo_group_or_error(repogroupid)
376 repo_group = get_repo_group_or_error(repogroupid)
364 if not has_superadmin_permission(apiuser):
377 if not has_superadmin_permission(apiuser):
365 validate_repo_group_permissions(
378 validate_repo_group_permissions(
366 apiuser, repogroupid, repo_group, ('group.admin',))
379 apiuser, repogroupid, repo_group, ('group.admin',))
367
380
381 old_data = repo_group.get_api_data()
368 try:
382 try:
369 RepoGroupModel().delete(repo_group)
383 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
370 Session().commit()
387 Session().commit()
371 return {
388 return {
372 'msg': 'deleted repo group ID:%s %s' %
389 'msg': 'deleted repo group ID:%s %s' %
373 (repo_group.group_id, repo_group.group_name),
390 (repo_group.group_id, repo_group.group_name),
374 'repo_group': None
391 'repo_group': None
375 }
392 }
376 except Exception:
393 except Exception:
377 log.exception("Exception occurred while trying to delete repo group")
394 log.exception("Exception occurred while trying to delete repo group")
378 raise JSONRPCError('failed to delete repo group ID:%s %s' %
395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
379 (repo_group.group_id, repo_group.group_name))
396 (repo_group.group_id, repo_group.group_name))
380
397
381
398
382 @jsonrpc_method()
399 @jsonrpc_method()
383 def grant_user_permission_to_repo_group(
400 def grant_user_permission_to_repo_group(
384 request, apiuser, repogroupid, userid, perm,
401 request, apiuser, repogroupid, userid, perm,
385 apply_to_children=Optional('none')):
402 apply_to_children=Optional('none')):
386 """
403 """
387 Grant permission for a user on the given repository group, or update
404 Grant permission for a user on the given repository group, or update
388 existing permissions if found.
405 existing permissions if found.
389
406
390 This command can only be run using an |authtoken| with admin
407 This command can only be run using an |authtoken| with admin
391 permissions.
408 permissions.
392
409
393 :param apiuser: This is filled automatically from the |authtoken|.
410 :param apiuser: This is filled automatically from the |authtoken|.
394 :type apiuser: AuthUser
411 :type apiuser: AuthUser
395 :param repogroupid: Set the name or ID of repository group.
412 :param repogroupid: Set the name or ID of repository group.
396 :type repogroupid: str or int
413 :type repogroupid: str or int
397 :param userid: Set the user name.
414 :param userid: Set the user name.
398 :type userid: str
415 :type userid: str
399 :param perm: (group.(none|read|write|admin))
416 :param perm: (group.(none|read|write|admin))
400 :type perm: str
417 :type perm: str
401 :param apply_to_children: 'none', 'repos', 'groups', 'all'
418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
402 :type apply_to_children: str
419 :type apply_to_children: str
403
420
404 Example output:
421 Example output:
405
422
406 .. code-block:: bash
423 .. code-block:: bash
407
424
408 id : <id_given_in_input>
425 id : <id_given_in_input>
409 result: {
426 result: {
410 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
411 "success": true
428 "success": true
412 }
429 }
413 error: null
430 error: null
414
431
415 Example error output:
432 Example error output:
416
433
417 .. code-block:: bash
434 .. code-block:: bash
418
435
419 id : <id_given_in_input>
436 id : <id_given_in_input>
420 result : null
437 result : null
421 error : {
438 error : {
422 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
423 }
440 }
424
441
425 """
442 """
426
443
427 repo_group = get_repo_group_or_error(repogroupid)
444 repo_group = get_repo_group_or_error(repogroupid)
428
445
429 if not has_superadmin_permission(apiuser):
446 if not has_superadmin_permission(apiuser):
430 validate_repo_group_permissions(
447 validate_repo_group_permissions(
431 apiuser, repogroupid, repo_group, ('group.admin',))
448 apiuser, repogroupid, repo_group, ('group.admin',))
432
449
433 user = get_user_or_error(userid)
450 user = get_user_or_error(userid)
434 perm = get_perm_or_error(perm, prefix='group.')
451 perm = get_perm_or_error(perm, prefix='group.')
435 apply_to_children = Optional.extract(apply_to_children)
452 apply_to_children = Optional.extract(apply_to_children)
436
453
437 perm_additions = [[user.user_id, perm, "user"]]
454 perm_additions = [[user.user_id, perm, "user"]]
438 try:
455 try:
439 RepoGroupModel().update_permissions(repo_group=repo_group,
456 RepoGroupModel().update_permissions(repo_group=repo_group,
440 perm_additions=perm_additions,
457 perm_additions=perm_additions,
441 recursive=apply_to_children,
458 recursive=apply_to_children,
442 cur_user=apiuser)
459 cur_user=apiuser)
443 Session().commit()
460 Session().commit()
444 return {
461 return {
445 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
462 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
446 '`%s` in repo group: `%s`' % (
463 '`%s` in repo group: `%s`' % (
447 perm.permission_name, apply_to_children, user.username,
464 perm.permission_name, apply_to_children, user.username,
448 repo_group.name
465 repo_group.name
449 ),
466 ),
450 'success': True
467 'success': True
451 }
468 }
452 except Exception:
469 except Exception:
453 log.exception("Exception occurred while trying to grant "
470 log.exception("Exception occurred while trying to grant "
454 "user permissions to repo group")
471 "user permissions to repo group")
455 raise JSONRPCError(
472 raise JSONRPCError(
456 'failed to edit permission for user: '
473 'failed to edit permission for user: '
457 '`%s` in repo group: `%s`' % (userid, repo_group.name))
474 '`%s` in repo group: `%s`' % (userid, repo_group.name))
458
475
459
476
460 @jsonrpc_method()
477 @jsonrpc_method()
461 def revoke_user_permission_from_repo_group(
478 def revoke_user_permission_from_repo_group(
462 request, apiuser, repogroupid, userid,
479 request, apiuser, repogroupid, userid,
463 apply_to_children=Optional('none')):
480 apply_to_children=Optional('none')):
464 """
481 """
465 Revoke permission for a user in a given repository group.
482 Revoke permission for a user in a given repository group.
466
483
467 This command can only be run using an |authtoken| with admin
484 This command can only be run using an |authtoken| with admin
468 permissions on the |repo| group.
485 permissions on the |repo| group.
469
486
470 :param apiuser: This is filled automatically from the |authtoken|.
487 :param apiuser: This is filled automatically from the |authtoken|.
471 :type apiuser: AuthUser
488 :type apiuser: AuthUser
472 :param repogroupid: Set the name or ID of the repository group.
489 :param repogroupid: Set the name or ID of the repository group.
473 :type repogroupid: str or int
490 :type repogroupid: str or int
474 :param userid: Set the user name to revoke.
491 :param userid: Set the user name to revoke.
475 :type userid: str
492 :type userid: str
476 :param apply_to_children: 'none', 'repos', 'groups', 'all'
493 :param apply_to_children: 'none', 'repos', 'groups', 'all'
477 :type apply_to_children: str
494 :type apply_to_children: str
478
495
479 Example output:
496 Example output:
480
497
481 .. code-block:: bash
498 .. code-block:: bash
482
499
483 id : <id_given_in_input>
500 id : <id_given_in_input>
484 result: {
501 result: {
485 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
502 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
486 "success": true
503 "success": true
487 }
504 }
488 error: null
505 error: null
489
506
490 Example error output:
507 Example error output:
491
508
492 .. code-block:: bash
509 .. code-block:: bash
493
510
494 id : <id_given_in_input>
511 id : <id_given_in_input>
495 result : null
512 result : null
496 error : {
513 error : {
497 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
514 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
498 }
515 }
499
516
500 """
517 """
501
518
502 repo_group = get_repo_group_or_error(repogroupid)
519 repo_group = get_repo_group_or_error(repogroupid)
503
520
504 if not has_superadmin_permission(apiuser):
521 if not has_superadmin_permission(apiuser):
505 validate_repo_group_permissions(
522 validate_repo_group_permissions(
506 apiuser, repogroupid, repo_group, ('group.admin',))
523 apiuser, repogroupid, repo_group, ('group.admin',))
507
524
508 user = get_user_or_error(userid)
525 user = get_user_or_error(userid)
509 apply_to_children = Optional.extract(apply_to_children)
526 apply_to_children = Optional.extract(apply_to_children)
510
527
511 perm_deletions = [[user.user_id, None, "user"]]
528 perm_deletions = [[user.user_id, None, "user"]]
512 try:
529 try:
513 RepoGroupModel().update_permissions(repo_group=repo_group,
530 RepoGroupModel().update_permissions(repo_group=repo_group,
514 perm_deletions=perm_deletions,
531 perm_deletions=perm_deletions,
515 recursive=apply_to_children,
532 recursive=apply_to_children,
516 cur_user=apiuser)
533 cur_user=apiuser)
517 Session().commit()
534 Session().commit()
518 return {
535 return {
519 'msg': 'Revoked perm (recursive:%s) for user: '
536 'msg': 'Revoked perm (recursive:%s) for user: '
520 '`%s` in repo group: `%s`' % (
537 '`%s` in repo group: `%s`' % (
521 apply_to_children, user.username, repo_group.name
538 apply_to_children, user.username, repo_group.name
522 ),
539 ),
523 'success': True
540 'success': True
524 }
541 }
525 except Exception:
542 except Exception:
526 log.exception("Exception occurred while trying revoke user "
543 log.exception("Exception occurred while trying revoke user "
527 "permission from repo group")
544 "permission from repo group")
528 raise JSONRPCError(
545 raise JSONRPCError(
529 'failed to edit permission for user: '
546 'failed to edit permission for user: '
530 '`%s` in repo group: `%s`' % (userid, repo_group.name))
547 '`%s` in repo group: `%s`' % (userid, repo_group.name))
531
548
532
549
533 @jsonrpc_method()
550 @jsonrpc_method()
534 def grant_user_group_permission_to_repo_group(
551 def grant_user_group_permission_to_repo_group(
535 request, apiuser, repogroupid, usergroupid, perm,
552 request, apiuser, repogroupid, usergroupid, perm,
536 apply_to_children=Optional('none'), ):
553 apply_to_children=Optional('none'), ):
537 """
554 """
538 Grant permission for a user group on given repository group, or update
555 Grant permission for a user group on given repository group, or update
539 existing permissions if found.
556 existing permissions if found.
540
557
541 This command can only be run using an |authtoken| with admin
558 This command can only be run using an |authtoken| with admin
542 permissions on the |repo| group.
559 permissions on the |repo| group.
543
560
544 :param apiuser: This is filled automatically from the |authtoken|.
561 :param apiuser: This is filled automatically from the |authtoken|.
545 :type apiuser: AuthUser
562 :type apiuser: AuthUser
546 :param repogroupid: Set the name or id of repository group
563 :param repogroupid: Set the name or id of repository group
547 :type repogroupid: str or int
564 :type repogroupid: str or int
548 :param usergroupid: id of usergroup
565 :param usergroupid: id of usergroup
549 :type usergroupid: str or int
566 :type usergroupid: str or int
550 :param perm: (group.(none|read|write|admin))
567 :param perm: (group.(none|read|write|admin))
551 :type perm: str
568 :type perm: str
552 :param apply_to_children: 'none', 'repos', 'groups', 'all'
569 :param apply_to_children: 'none', 'repos', 'groups', 'all'
553 :type apply_to_children: str
570 :type apply_to_children: str
554
571
555 Example output:
572 Example output:
556
573
557 .. code-block:: bash
574 .. code-block:: bash
558
575
559 id : <id_given_in_input>
576 id : <id_given_in_input>
560 result : {
577 result : {
561 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
578 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
562 "success": true
579 "success": true
563
580
564 }
581 }
565 error : null
582 error : null
566
583
567 Example error output:
584 Example error output:
568
585
569 .. code-block:: bash
586 .. code-block:: bash
570
587
571 id : <id_given_in_input>
588 id : <id_given_in_input>
572 result : null
589 result : null
573 error : {
590 error : {
574 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
591 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
575 }
592 }
576
593
577 """
594 """
578
595
579 repo_group = get_repo_group_or_error(repogroupid)
596 repo_group = get_repo_group_or_error(repogroupid)
580 perm = get_perm_or_error(perm, prefix='group.')
597 perm = get_perm_or_error(perm, prefix='group.')
581 user_group = get_user_group_or_error(usergroupid)
598 user_group = get_user_group_or_error(usergroupid)
582 if not has_superadmin_permission(apiuser):
599 if not has_superadmin_permission(apiuser):
583 validate_repo_group_permissions(
600 validate_repo_group_permissions(
584 apiuser, repogroupid, repo_group, ('group.admin',))
601 apiuser, repogroupid, repo_group, ('group.admin',))
585
602
586 # check if we have at least read permission for this user group !
603 # check if we have at least read permission for this user group !
587 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
604 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
588 if not HasUserGroupPermissionAnyApi(*_perms)(
605 if not HasUserGroupPermissionAnyApi(*_perms)(
589 user=apiuser, user_group_name=user_group.users_group_name):
606 user=apiuser, user_group_name=user_group.users_group_name):
590 raise JSONRPCError(
607 raise JSONRPCError(
591 'user group `%s` does not exist' % (usergroupid,))
608 'user group `%s` does not exist' % (usergroupid,))
592
609
593 apply_to_children = Optional.extract(apply_to_children)
610 apply_to_children = Optional.extract(apply_to_children)
594
611
595 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
612 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
596 try:
613 try:
597 RepoGroupModel().update_permissions(repo_group=repo_group,
614 RepoGroupModel().update_permissions(repo_group=repo_group,
598 perm_additions=perm_additions,
615 perm_additions=perm_additions,
599 recursive=apply_to_children,
616 recursive=apply_to_children,
600 cur_user=apiuser)
617 cur_user=apiuser)
601 Session().commit()
618 Session().commit()
602 return {
619 return {
603 'msg': 'Granted perm: `%s` (recursive:%s) '
620 'msg': 'Granted perm: `%s` (recursive:%s) '
604 'for user group: `%s` in repo group: `%s`' % (
621 'for user group: `%s` in repo group: `%s`' % (
605 perm.permission_name, apply_to_children,
622 perm.permission_name, apply_to_children,
606 user_group.users_group_name, repo_group.name
623 user_group.users_group_name, repo_group.name
607 ),
624 ),
608 'success': True
625 'success': True
609 }
626 }
610 except Exception:
627 except Exception:
611 log.exception("Exception occurred while trying to grant user "
628 log.exception("Exception occurred while trying to grant user "
612 "group permissions to repo group")
629 "group permissions to repo group")
613 raise JSONRPCError(
630 raise JSONRPCError(
614 'failed to edit permission for user group: `%s` in '
631 'failed to edit permission for user group: `%s` in '
615 'repo group: `%s`' % (
632 'repo group: `%s`' % (
616 usergroupid, repo_group.name
633 usergroupid, repo_group.name
617 )
634 )
618 )
635 )
619
636
620
637
621 @jsonrpc_method()
638 @jsonrpc_method()
622 def revoke_user_group_permission_from_repo_group(
639 def revoke_user_group_permission_from_repo_group(
623 request, apiuser, repogroupid, usergroupid,
640 request, apiuser, repogroupid, usergroupid,
624 apply_to_children=Optional('none')):
641 apply_to_children=Optional('none')):
625 """
642 """
626 Revoke permission for user group on given repository.
643 Revoke permission for user group on given repository.
627
644
628 This command can only be run using an |authtoken| with admin
645 This command can only be run using an |authtoken| with admin
629 permissions on the |repo| group.
646 permissions on the |repo| group.
630
647
631 :param apiuser: This is filled automatically from the |authtoken|.
648 :param apiuser: This is filled automatically from the |authtoken|.
632 :type apiuser: AuthUser
649 :type apiuser: AuthUser
633 :param repogroupid: name or id of repository group
650 :param repogroupid: name or id of repository group
634 :type repogroupid: str or int
651 :type repogroupid: str or int
635 :param usergroupid:
652 :param usergroupid:
636 :param apply_to_children: 'none', 'repos', 'groups', 'all'
653 :param apply_to_children: 'none', 'repos', 'groups', 'all'
637 :type apply_to_children: str
654 :type apply_to_children: str
638
655
639 Example output:
656 Example output:
640
657
641 .. code-block:: bash
658 .. code-block:: bash
642
659
643 id : <id_given_in_input>
660 id : <id_given_in_input>
644 result: {
661 result: {
645 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
662 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
646 "success": true
663 "success": true
647 }
664 }
648 error: null
665 error: null
649
666
650 Example error output:
667 Example error output:
651
668
652 .. code-block:: bash
669 .. code-block:: bash
653
670
654 id : <id_given_in_input>
671 id : <id_given_in_input>
655 result : null
672 result : null
656 error : {
673 error : {
657 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
674 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
658 }
675 }
659
676
660
677
661 """
678 """
662
679
663 repo_group = get_repo_group_or_error(repogroupid)
680 repo_group = get_repo_group_or_error(repogroupid)
664 user_group = get_user_group_or_error(usergroupid)
681 user_group = get_user_group_or_error(usergroupid)
665 if not has_superadmin_permission(apiuser):
682 if not has_superadmin_permission(apiuser):
666 validate_repo_group_permissions(
683 validate_repo_group_permissions(
667 apiuser, repogroupid, repo_group, ('group.admin',))
684 apiuser, repogroupid, repo_group, ('group.admin',))
668
685
669 # check if we have at least read permission for this user group !
686 # check if we have at least read permission for this user group !
670 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
687 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
671 if not HasUserGroupPermissionAnyApi(*_perms)(
688 if not HasUserGroupPermissionAnyApi(*_perms)(
672 user=apiuser, user_group_name=user_group.users_group_name):
689 user=apiuser, user_group_name=user_group.users_group_name):
673 raise JSONRPCError(
690 raise JSONRPCError(
674 'user group `%s` does not exist' % (usergroupid,))
691 'user group `%s` does not exist' % (usergroupid,))
675
692
676 apply_to_children = Optional.extract(apply_to_children)
693 apply_to_children = Optional.extract(apply_to_children)
677
694
678 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
695 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
679 try:
696 try:
680 RepoGroupModel().update_permissions(repo_group=repo_group,
697 RepoGroupModel().update_permissions(repo_group=repo_group,
681 perm_deletions=perm_deletions,
698 perm_deletions=perm_deletions,
682 recursive=apply_to_children,
699 recursive=apply_to_children,
683 cur_user=apiuser)
700 cur_user=apiuser)
684 Session().commit()
701 Session().commit()
685 return {
702 return {
686 'msg': 'Revoked perm (recursive:%s) for user group: '
703 'msg': 'Revoked perm (recursive:%s) for user group: '
687 '`%s` in repo group: `%s`' % (
704 '`%s` in repo group: `%s`' % (
688 apply_to_children, user_group.users_group_name,
705 apply_to_children, user_group.users_group_name,
689 repo_group.name
706 repo_group.name
690 ),
707 ),
691 'success': True
708 'success': True
692 }
709 }
693 except Exception:
710 except Exception:
694 log.exception("Exception occurred while trying revoke user group "
711 log.exception("Exception occurred while trying revoke user group "
695 "permissions from repo group")
712 "permissions from repo group")
696 raise JSONRPCError(
713 raise JSONRPCError(
697 'failed to edit permission for user group: '
714 'failed to edit permission for user group: '
698 '`%s` in repo group: `%s`' % (
715 '`%s` in repo group: `%s`' % (
699 user_group.users_group_name, repo_group.name
716 user_group.users_group_name, repo_group.name
700 )
717 )
701 )
718 )
702
719
@@ -1,515 +1,529 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 from rhodecode.api.utils import (
24 from rhodecode.api.utils import (
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
25 Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
26 from rhodecode.lib import audit_logger
26 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.auth import AuthUser, PasswordGenerator
27 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.exceptions import DefaultUserException
28 from rhodecode.lib.utils2 import safe_int, str2bool
29 from rhodecode.lib.utils2 import safe_int, str2bool
29 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
31 from rhodecode.model.user import UserModel
31
32
32 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
33
34
34
35
35 @jsonrpc_method()
36 @jsonrpc_method()
36 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
37 def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
37 """
38 """
38 Returns the information associated with a username or userid.
39 Returns the information associated with a username or userid.
39
40
40 * If the ``userid`` is not set, this command returns the information
41 * If the ``userid`` is not set, this command returns the information
41 for the ``userid`` calling the method.
42 for the ``userid`` calling the method.
42
43
43 .. note::
44 .. note::
44
45
45 Normal users may only run this command against their ``userid``. For
46 Normal users may only run this command against their ``userid``. For
46 full privileges you must run this command using an |authtoken| with
47 full privileges you must run this command using an |authtoken| with
47 admin rights.
48 admin rights.
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 userid: Sets the userid for which data will be returned.
52 :param userid: Sets the userid for which data will be returned.
52 :type userid: Optional(str or int)
53 :type userid: Optional(str or int)
53
54
54 Example output:
55 Example output:
55
56
56 .. code-block:: bash
57 .. code-block:: bash
57
58
58 {
59 {
59 "error": null,
60 "error": null,
60 "id": <id>,
61 "id": <id>,
61 "result": {
62 "result": {
62 "active": true,
63 "active": true,
63 "admin": false,
64 "admin": false,
64 "api_keys": [ list of keys ],
65 "api_keys": [ list of keys ],
65 "auth_tokens": [ list of tokens with details ],
66 "auth_tokens": [ list of tokens with details ],
66 "email": "user@example.com",
67 "email": "user@example.com",
67 "emails": [
68 "emails": [
68 "user@example.com"
69 "user@example.com"
69 ],
70 ],
70 "extern_name": "rhodecode",
71 "extern_name": "rhodecode",
71 "extern_type": "rhodecode",
72 "extern_type": "rhodecode",
72 "firstname": "username",
73 "firstname": "username",
73 "ip_addresses": [],
74 "ip_addresses": [],
74 "language": null,
75 "language": null,
75 "last_login": "Timestamp",
76 "last_login": "Timestamp",
76 "last_activity": "Timestamp",
77 "last_activity": "Timestamp",
77 "lastname": "surnae",
78 "lastname": "surnae",
78 "permissions": {
79 "permissions": {
79 "global": [
80 "global": [
80 "hg.inherit_default_perms.true",
81 "hg.inherit_default_perms.true",
81 "usergroup.read",
82 "usergroup.read",
82 "hg.repogroup.create.false",
83 "hg.repogroup.create.false",
83 "hg.create.none",
84 "hg.create.none",
84 "hg.password_reset.enabled",
85 "hg.password_reset.enabled",
85 "hg.extern_activate.manual",
86 "hg.extern_activate.manual",
86 "hg.create.write_on_repogroup.false",
87 "hg.create.write_on_repogroup.false",
87 "hg.usergroup.create.false",
88 "hg.usergroup.create.false",
88 "group.none",
89 "group.none",
89 "repository.none",
90 "repository.none",
90 "hg.register.none",
91 "hg.register.none",
91 "hg.fork.repository"
92 "hg.fork.repository"
92 ],
93 ],
93 "repositories": { "username/example": "repository.write"},
94 "repositories": { "username/example": "repository.write"},
94 "repositories_groups": { "user-group/repo": "group.none" },
95 "repositories_groups": { "user-group/repo": "group.none" },
95 "user_groups": { "user_group_name": "usergroup.read" }
96 "user_groups": { "user_group_name": "usergroup.read" }
96 },
97 },
97 "user_id": 32,
98 "user_id": 32,
98 "username": "username"
99 "username": "username"
99 }
100 }
100 }
101 }
101 """
102 """
102
103
103 if not has_superadmin_permission(apiuser):
104 if not has_superadmin_permission(apiuser):
104 # make sure normal user does not pass someone else userid,
105 # make sure normal user does not pass someone else userid,
105 # he is not allowed to do that
106 # he is not allowed to do that
106 if not isinstance(userid, Optional) and userid != apiuser.user_id:
107 if not isinstance(userid, Optional) and userid != apiuser.user_id:
107 raise JSONRPCError('userid is not the same as your user')
108 raise JSONRPCError('userid is not the same as your user')
108
109
109 userid = Optional.extract(userid, evaluate_locals=locals())
110 userid = Optional.extract(userid, evaluate_locals=locals())
110 userid = getattr(userid, 'user_id', userid)
111 userid = getattr(userid, 'user_id', userid)
111
112
112 user = get_user_or_error(userid)
113 user = get_user_or_error(userid)
113 data = user.get_api_data(include_secrets=True)
114 data = user.get_api_data(include_secrets=True)
114 data['permissions'] = AuthUser(user_id=user.user_id).permissions
115 data['permissions'] = AuthUser(user_id=user.user_id).permissions
115 return data
116 return data
116
117
117
118
118 @jsonrpc_method()
119 @jsonrpc_method()
119 def get_users(request, apiuser):
120 def get_users(request, apiuser):
120 """
121 """
121 Lists all users in the |RCE| user database.
122 Lists all users in the |RCE| user database.
122
123
123 This command can only be run using an |authtoken| with admin rights to
124 This command can only be run using an |authtoken| with admin rights to
124 the specified repository.
125 the specified repository.
125
126
126 This command takes the following options:
127 This command takes the following options:
127
128
128 :param apiuser: This is filled automatically from the |authtoken|.
129 :param apiuser: This is filled automatically from the |authtoken|.
129 :type apiuser: AuthUser
130 :type apiuser: AuthUser
130
131
131 Example output:
132 Example output:
132
133
133 .. code-block:: bash
134 .. code-block:: bash
134
135
135 id : <id_given_in_input>
136 id : <id_given_in_input>
136 result: [<user_object>, ...]
137 result: [<user_object>, ...]
137 error: null
138 error: null
138 """
139 """
139
140
140 if not has_superadmin_permission(apiuser):
141 if not has_superadmin_permission(apiuser):
141 raise JSONRPCForbidden()
142 raise JSONRPCForbidden()
142
143
143 result = []
144 result = []
144 users_list = User.query().order_by(User.username) \
145 users_list = User.query().order_by(User.username) \
145 .filter(User.username != User.DEFAULT_USER) \
146 .filter(User.username != User.DEFAULT_USER) \
146 .all()
147 .all()
147 for user in users_list:
148 for user in users_list:
148 result.append(user.get_api_data(include_secrets=True))
149 result.append(user.get_api_data(include_secrets=True))
149 return result
150 return result
150
151
151
152
152 @jsonrpc_method()
153 @jsonrpc_method()
153 def create_user(request, apiuser, username, email, password=Optional(''),
154 def create_user(request, apiuser, username, email, password=Optional(''),
154 firstname=Optional(''), lastname=Optional(''),
155 firstname=Optional(''), lastname=Optional(''),
155 active=Optional(True), admin=Optional(False),
156 active=Optional(True), admin=Optional(False),
156 extern_name=Optional('rhodecode'),
157 extern_name=Optional('rhodecode'),
157 extern_type=Optional('rhodecode'),
158 extern_type=Optional('rhodecode'),
158 force_password_change=Optional(False),
159 force_password_change=Optional(False),
159 create_personal_repo_group=Optional(None)):
160 create_personal_repo_group=Optional(None)):
160 """
161 """
161 Creates a new user and returns the new user object.
162 Creates a new user and returns the new user object.
162
163
163 This command can only be run using an |authtoken| with admin rights to
164 This command can only be run using an |authtoken| with admin rights to
164 the specified repository.
165 the specified repository.
165
166
166 This command takes the following options:
167 This command takes the following options:
167
168
168 :param apiuser: This is filled automatically from the |authtoken|.
169 :param apiuser: This is filled automatically from the |authtoken|.
169 :type apiuser: AuthUser
170 :type apiuser: AuthUser
170 :param username: Set the new username.
171 :param username: Set the new username.
171 :type username: str or int
172 :type username: str or int
172 :param email: Set the user email address.
173 :param email: Set the user email address.
173 :type email: str
174 :type email: str
174 :param password: Set the new user password.
175 :param password: Set the new user password.
175 :type password: Optional(str)
176 :type password: Optional(str)
176 :param firstname: Set the new user firstname.
177 :param firstname: Set the new user firstname.
177 :type firstname: Optional(str)
178 :type firstname: Optional(str)
178 :param lastname: Set the new user surname.
179 :param lastname: Set the new user surname.
179 :type lastname: Optional(str)
180 :type lastname: Optional(str)
180 :param active: Set the user as active.
181 :param active: Set the user as active.
181 :type active: Optional(``True`` | ``False``)
182 :type active: Optional(``True`` | ``False``)
182 :param admin: Give the new user admin rights.
183 :param admin: Give the new user admin rights.
183 :type admin: Optional(``True`` | ``False``)
184 :type admin: Optional(``True`` | ``False``)
184 :param extern_name: Set the authentication plugin name.
185 :param extern_name: Set the authentication plugin name.
185 Using LDAP this is filled with LDAP UID.
186 Using LDAP this is filled with LDAP UID.
186 :type extern_name: Optional(str)
187 :type extern_name: Optional(str)
187 :param extern_type: Set the new user authentication plugin.
188 :param extern_type: Set the new user authentication plugin.
188 :type extern_type: Optional(str)
189 :type extern_type: Optional(str)
189 :param force_password_change: Force the new user to change password
190 :param force_password_change: Force the new user to change password
190 on next login.
191 on next login.
191 :type force_password_change: Optional(``True`` | ``False``)
192 :type force_password_change: Optional(``True`` | ``False``)
192 :param create_personal_repo_group: Create personal repo group for this user
193 :param create_personal_repo_group: Create personal repo group for this user
193 :type create_personal_repo_group: Optional(``True`` | ``False``)
194 :type create_personal_repo_group: Optional(``True`` | ``False``)
194
195
195 Example output:
196 Example output:
196
197
197 .. code-block:: bash
198 .. code-block:: bash
198
199
199 id : <id_given_in_input>
200 id : <id_given_in_input>
200 result: {
201 result: {
201 "msg" : "created new user `<username>`",
202 "msg" : "created new user `<username>`",
202 "user": <user_obj>
203 "user": <user_obj>
203 }
204 }
204 error: null
205 error: null
205
206
206 Example error output:
207 Example error output:
207
208
208 .. code-block:: bash
209 .. code-block:: bash
209
210
210 id : <id_given_in_input>
211 id : <id_given_in_input>
211 result : null
212 result : null
212 error : {
213 error : {
213 "user `<username>` already exist"
214 "user `<username>` already exist"
214 or
215 or
215 "email `<email>` already exist"
216 "email `<email>` already exist"
216 or
217 or
217 "failed to create user `<username>`"
218 "failed to create user `<username>`"
218 }
219 }
219
220
220 """
221 """
221 if not has_superadmin_permission(apiuser):
222 if not has_superadmin_permission(apiuser):
222 raise JSONRPCForbidden()
223 raise JSONRPCForbidden()
223
224
224 if UserModel().get_by_username(username):
225 if UserModel().get_by_username(username):
225 raise JSONRPCError("user `%s` already exist" % (username,))
226 raise JSONRPCError("user `%s` already exist" % (username,))
226
227
227 if UserModel().get_by_email(email, case_insensitive=True):
228 if UserModel().get_by_email(email, case_insensitive=True):
228 raise JSONRPCError("email `%s` already exist" % (email,))
229 raise JSONRPCError("email `%s` already exist" % (email,))
229
230
230 # generate random password if we actually given the
231 # generate random password if we actually given the
231 # extern_name and it's not rhodecode
232 # extern_name and it's not rhodecode
232 if (not isinstance(extern_name, Optional) and
233 if (not isinstance(extern_name, Optional) and
233 Optional.extract(extern_name) != 'rhodecode'):
234 Optional.extract(extern_name) != 'rhodecode'):
234 # generate temporary password if user is external
235 # generate temporary password if user is external
235 password = PasswordGenerator().gen_password(length=16)
236 password = PasswordGenerator().gen_password(length=16)
236 create_repo_group = Optional.extract(create_personal_repo_group)
237 create_repo_group = Optional.extract(create_personal_repo_group)
237 if isinstance(create_repo_group, basestring):
238 if isinstance(create_repo_group, basestring):
238 create_repo_group = str2bool(create_repo_group)
239 create_repo_group = str2bool(create_repo_group)
239
240
240 try:
241 try:
241 user = UserModel().create_or_update(
242 user = UserModel().create_or_update(
242 username=Optional.extract(username),
243 username=Optional.extract(username),
243 password=Optional.extract(password),
244 password=Optional.extract(password),
244 email=Optional.extract(email),
245 email=Optional.extract(email),
245 firstname=Optional.extract(firstname),
246 firstname=Optional.extract(firstname),
246 lastname=Optional.extract(lastname),
247 lastname=Optional.extract(lastname),
247 active=Optional.extract(active),
248 active=Optional.extract(active),
248 admin=Optional.extract(admin),
249 admin=Optional.extract(admin),
249 extern_type=Optional.extract(extern_type),
250 extern_type=Optional.extract(extern_type),
250 extern_name=Optional.extract(extern_name),
251 extern_name=Optional.extract(extern_name),
251 force_password_change=Optional.extract(force_password_change),
252 force_password_change=Optional.extract(force_password_change),
252 create_repo_group=create_repo_group
253 create_repo_group=create_repo_group
253 )
254 )
255 Session().flush()
256 creation_data = user.get_api_data()
257 audit_logger.store_api(
258 'user.create', action_data={'data': creation_data},
259 user=apiuser)
260
254 Session().commit()
261 Session().commit()
255 return {
262 return {
256 'msg': 'created new user `%s`' % username,
263 'msg': 'created new user `%s`' % username,
257 'user': user.get_api_data(include_secrets=True)
264 'user': user.get_api_data(include_secrets=True)
258 }
265 }
259 except Exception:
266 except Exception:
260 log.exception('Error occurred during creation of user')
267 log.exception('Error occurred during creation of user')
261 raise JSONRPCError('failed to create user `%s`' % (username,))
268 raise JSONRPCError('failed to create user `%s`' % (username,))
262
269
263
270
264 @jsonrpc_method()
271 @jsonrpc_method()
265 def update_user(request, apiuser, userid, username=Optional(None),
272 def update_user(request, apiuser, userid, username=Optional(None),
266 email=Optional(None), password=Optional(None),
273 email=Optional(None), password=Optional(None),
267 firstname=Optional(None), lastname=Optional(None),
274 firstname=Optional(None), lastname=Optional(None),
268 active=Optional(None), admin=Optional(None),
275 active=Optional(None), admin=Optional(None),
269 extern_type=Optional(None), extern_name=Optional(None), ):
276 extern_type=Optional(None), extern_name=Optional(None), ):
270 """
277 """
271 Updates the details for the specified user, if that user exists.
278 Updates the details for the specified user, if that user exists.
272
279
273 This command can only be run using an |authtoken| with admin rights to
280 This command can only be run using an |authtoken| with admin rights to
274 the specified repository.
281 the specified repository.
275
282
276 This command takes the following options:
283 This command takes the following options:
277
284
278 :param apiuser: This is filled automatically from |authtoken|.
285 :param apiuser: This is filled automatically from |authtoken|.
279 :type apiuser: AuthUser
286 :type apiuser: AuthUser
280 :param userid: Set the ``userid`` to update.
287 :param userid: Set the ``userid`` to update.
281 :type userid: str or int
288 :type userid: str or int
282 :param username: Set the new username.
289 :param username: Set the new username.
283 :type username: str or int
290 :type username: str or int
284 :param email: Set the new email.
291 :param email: Set the new email.
285 :type email: str
292 :type email: str
286 :param password: Set the new password.
293 :param password: Set the new password.
287 :type password: Optional(str)
294 :type password: Optional(str)
288 :param firstname: Set the new first name.
295 :param firstname: Set the new first name.
289 :type firstname: Optional(str)
296 :type firstname: Optional(str)
290 :param lastname: Set the new surname.
297 :param lastname: Set the new surname.
291 :type lastname: Optional(str)
298 :type lastname: Optional(str)
292 :param active: Set the new user as active.
299 :param active: Set the new user as active.
293 :type active: Optional(``True`` | ``False``)
300 :type active: Optional(``True`` | ``False``)
294 :param admin: Give the user admin rights.
301 :param admin: Give the user admin rights.
295 :type admin: Optional(``True`` | ``False``)
302 :type admin: Optional(``True`` | ``False``)
296 :param extern_name: Set the authentication plugin user name.
303 :param extern_name: Set the authentication plugin user name.
297 Using LDAP this is filled with LDAP UID.
304 Using LDAP this is filled with LDAP UID.
298 :type extern_name: Optional(str)
305 :type extern_name: Optional(str)
299 :param extern_type: Set the authentication plugin type.
306 :param extern_type: Set the authentication plugin type.
300 :type extern_type: Optional(str)
307 :type extern_type: Optional(str)
301
308
302
309
303 Example output:
310 Example output:
304
311
305 .. code-block:: bash
312 .. code-block:: bash
306
313
307 id : <id_given_in_input>
314 id : <id_given_in_input>
308 result: {
315 result: {
309 "msg" : "updated user ID:<userid> <username>",
316 "msg" : "updated user ID:<userid> <username>",
310 "user": <user_object>,
317 "user": <user_object>,
311 }
318 }
312 error: null
319 error: null
313
320
314 Example error output:
321 Example error output:
315
322
316 .. code-block:: bash
323 .. code-block:: bash
317
324
318 id : <id_given_in_input>
325 id : <id_given_in_input>
319 result : null
326 result : null
320 error : {
327 error : {
321 "failed to update user `<username>`"
328 "failed to update user `<username>`"
322 }
329 }
323
330
324 """
331 """
325 if not has_superadmin_permission(apiuser):
332 if not has_superadmin_permission(apiuser):
326 raise JSONRPCForbidden()
333 raise JSONRPCForbidden()
327
334
328 user = get_user_or_error(userid)
335 user = get_user_or_error(userid)
329
336 old_data = user.get_api_data()
330 # only non optional arguments will be stored in updates
337 # only non optional arguments will be stored in updates
331 updates = {}
338 updates = {}
332
339
333 try:
340 try:
334
341
335 store_update(updates, username, 'username')
342 store_update(updates, username, 'username')
336 store_update(updates, password, 'password')
343 store_update(updates, password, 'password')
337 store_update(updates, email, 'email')
344 store_update(updates, email, 'email')
338 store_update(updates, firstname, 'name')
345 store_update(updates, firstname, 'name')
339 store_update(updates, lastname, 'lastname')
346 store_update(updates, lastname, 'lastname')
340 store_update(updates, active, 'active')
347 store_update(updates, active, 'active')
341 store_update(updates, admin, 'admin')
348 store_update(updates, admin, 'admin')
342 store_update(updates, extern_name, 'extern_name')
349 store_update(updates, extern_name, 'extern_name')
343 store_update(updates, extern_type, 'extern_type')
350 store_update(updates, extern_type, 'extern_type')
344
351
345 user = UserModel().update_user(user, **updates)
352 user = UserModel().update_user(user, **updates)
353 audit_logger.store_api(
354 'user.edit', action_data={'old_data': old_data},
355 user=apiuser)
346 Session().commit()
356 Session().commit()
347 return {
357 return {
348 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
358 'msg': 'updated user ID:%s %s' % (user.user_id, user.username),
349 'user': user.get_api_data(include_secrets=True)
359 'user': user.get_api_data(include_secrets=True)
350 }
360 }
351 except DefaultUserException:
361 except DefaultUserException:
352 log.exception("Default user edit exception")
362 log.exception("Default user edit exception")
353 raise JSONRPCError('editing default user is forbidden')
363 raise JSONRPCError('editing default user is forbidden')
354 except Exception:
364 except Exception:
355 log.exception("Error occurred during update of user")
365 log.exception("Error occurred during update of user")
356 raise JSONRPCError('failed to update user `%s`' % (userid,))
366 raise JSONRPCError('failed to update user `%s`' % (userid,))
357
367
358
368
359 @jsonrpc_method()
369 @jsonrpc_method()
360 def delete_user(request, apiuser, userid):
370 def delete_user(request, apiuser, userid):
361 """
371 """
362 Deletes the specified user from the |RCE| user database.
372 Deletes the specified user from the |RCE| user database.
363
373
364 This command can only be run using an |authtoken| with admin rights to
374 This command can only be run using an |authtoken| with admin rights to
365 the specified repository.
375 the specified repository.
366
376
367 .. important::
377 .. important::
368
378
369 Ensure all open pull requests and open code review
379 Ensure all open pull requests and open code review
370 requests to this user are close.
380 requests to this user are close.
371
381
372 Also ensure all repositories, or repository groups owned by this
382 Also ensure all repositories, or repository groups owned by this
373 user are reassigned before deletion.
383 user are reassigned before deletion.
374
384
375 This command takes the following options:
385 This command takes the following options:
376
386
377 :param apiuser: This is filled automatically from the |authtoken|.
387 :param apiuser: This is filled automatically from the |authtoken|.
378 :type apiuser: AuthUser
388 :type apiuser: AuthUser
379 :param userid: Set the user to delete.
389 :param userid: Set the user to delete.
380 :type userid: str or int
390 :type userid: str or int
381
391
382 Example output:
392 Example output:
383
393
384 .. code-block:: bash
394 .. code-block:: bash
385
395
386 id : <id_given_in_input>
396 id : <id_given_in_input>
387 result: {
397 result: {
388 "msg" : "deleted user ID:<userid> <username>",
398 "msg" : "deleted user ID:<userid> <username>",
389 "user": null
399 "user": null
390 }
400 }
391 error: null
401 error: null
392
402
393 Example error output:
403 Example error output:
394
404
395 .. code-block:: bash
405 .. code-block:: bash
396
406
397 id : <id_given_in_input>
407 id : <id_given_in_input>
398 result : null
408 result : null
399 error : {
409 error : {
400 "failed to delete user ID:<userid> <username>"
410 "failed to delete user ID:<userid> <username>"
401 }
411 }
402
412
403 """
413 """
404 if not has_superadmin_permission(apiuser):
414 if not has_superadmin_permission(apiuser):
405 raise JSONRPCForbidden()
415 raise JSONRPCForbidden()
406
416
407 user = get_user_or_error(userid)
417 user = get_user_or_error(userid)
408
418 old_data = user.get_api_data()
409 try:
419 try:
410 UserModel().delete(userid)
420 UserModel().delete(userid)
421 audit_logger.store_api(
422 'user.delete', action_data={'old_data': old_data},
423 user=apiuser)
424
411 Session().commit()
425 Session().commit()
412 return {
426 return {
413 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
427 'msg': 'deleted user ID:%s %s' % (user.user_id, user.username),
414 'user': None
428 'user': None
415 }
429 }
416 except Exception:
430 except Exception:
417 log.exception("Error occurred during deleting of user")
431 log.exception("Error occurred during deleting of user")
418 raise JSONRPCError(
432 raise JSONRPCError(
419 'failed to delete user ID:%s %s' % (user.user_id, user.username))
433 'failed to delete user ID:%s %s' % (user.user_id, user.username))
420
434
421
435
422 @jsonrpc_method()
436 @jsonrpc_method()
423 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
437 def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
424 """
438 """
425 Displays all repositories locked by the specified user.
439 Displays all repositories locked by the specified user.
426
440
427 * If this command is run by a non-admin user, it returns
441 * If this command is run by a non-admin user, it returns
428 a list of |repos| locked by that user.
442 a list of |repos| locked by that user.
429
443
430 This command takes the following options:
444 This command takes the following options:
431
445
432 :param apiuser: This is filled automatically from the |authtoken|.
446 :param apiuser: This is filled automatically from the |authtoken|.
433 :type apiuser: AuthUser
447 :type apiuser: AuthUser
434 :param userid: Sets the userid whose list of locked |repos| will be
448 :param userid: Sets the userid whose list of locked |repos| will be
435 displayed.
449 displayed.
436 :type userid: Optional(str or int)
450 :type userid: Optional(str or int)
437
451
438 Example output:
452 Example output:
439
453
440 .. code-block:: bash
454 .. code-block:: bash
441
455
442 id : <id_given_in_input>
456 id : <id_given_in_input>
443 result : {
457 result : {
444 [repo_object, repo_object,...]
458 [repo_object, repo_object,...]
445 }
459 }
446 error : null
460 error : null
447 """
461 """
448
462
449 include_secrets = False
463 include_secrets = False
450 if not has_superadmin_permission(apiuser):
464 if not has_superadmin_permission(apiuser):
451 # make sure normal user does not pass someone else userid,
465 # make sure normal user does not pass someone else userid,
452 # he is not allowed to do that
466 # he is not allowed to do that
453 if not isinstance(userid, Optional) and userid != apiuser.user_id:
467 if not isinstance(userid, Optional) and userid != apiuser.user_id:
454 raise JSONRPCError('userid is not the same as your user')
468 raise JSONRPCError('userid is not the same as your user')
455 else:
469 else:
456 include_secrets = True
470 include_secrets = True
457
471
458 userid = Optional.extract(userid, evaluate_locals=locals())
472 userid = Optional.extract(userid, evaluate_locals=locals())
459 userid = getattr(userid, 'user_id', userid)
473 userid = getattr(userid, 'user_id', userid)
460 user = get_user_or_error(userid)
474 user = get_user_or_error(userid)
461
475
462 ret = []
476 ret = []
463
477
464 # show all locks
478 # show all locks
465 for r in Repository.getAll():
479 for r in Repository.getAll():
466 _user_id, _time, _reason = r.locked
480 _user_id, _time, _reason = r.locked
467 if _user_id and _time:
481 if _user_id and _time:
468 _api_data = r.get_api_data(include_secrets=include_secrets)
482 _api_data = r.get_api_data(include_secrets=include_secrets)
469 # if we use user filter just show the locks for this user
483 # if we use user filter just show the locks for this user
470 if safe_int(_user_id) == user.user_id:
484 if safe_int(_user_id) == user.user_id:
471 ret.append(_api_data)
485 ret.append(_api_data)
472
486
473 return ret
487 return ret
474
488
475
489
476 @jsonrpc_method()
490 @jsonrpc_method()
477 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
491 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
478 """
492 """
479 Fetches all action logs made by the specified user.
493 Fetches all action logs made by the specified user.
480
494
481 This command takes the following options:
495 This command takes the following options:
482
496
483 :param apiuser: This is filled automatically from the |authtoken|.
497 :param apiuser: This is filled automatically from the |authtoken|.
484 :type apiuser: AuthUser
498 :type apiuser: AuthUser
485 :param userid: Sets the userid whose list of locked |repos| will be
499 :param userid: Sets the userid whose list of locked |repos| will be
486 displayed.
500 displayed.
487 :type userid: Optional(str or int)
501 :type userid: Optional(str or int)
488
502
489 Example output:
503 Example output:
490
504
491 .. code-block:: bash
505 .. code-block:: bash
492
506
493 id : <id_given_in_input>
507 id : <id_given_in_input>
494 result : {
508 result : {
495 [action, action,...]
509 [action, action,...]
496 }
510 }
497 error : null
511 error : null
498 """
512 """
499
513
500 if not has_superadmin_permission(apiuser):
514 if not has_superadmin_permission(apiuser):
501 # make sure normal user does not pass someone else userid,
515 # make sure normal user does not pass someone else userid,
502 # he is not allowed to do that
516 # he is not allowed to do that
503 if not isinstance(userid, Optional) and userid != apiuser.user_id:
517 if not isinstance(userid, Optional) and userid != apiuser.user_id:
504 raise JSONRPCError('userid is not the same as your user')
518 raise JSONRPCError('userid is not the same as your user')
505
519
506 userid = Optional.extract(userid, evaluate_locals=locals())
520 userid = Optional.extract(userid, evaluate_locals=locals())
507 userid = getattr(userid, 'user_id', userid)
521 userid = getattr(userid, 'user_id', userid)
508 user = get_user_or_error(userid)
522 user = get_user_or_error(userid)
509
523
510 ret = []
524 ret = []
511
525
512 # show all user actions
526 # show all user actions
513 for entry in UserModel().get_user_log(user, filter_term=None):
527 for entry in UserModel().get_user_log(user, filter_term=None):
514 ret.append(entry)
528 ret.append(entry)
515 return ret
529 return ret
@@ -1,773 +1,799 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 jsonrpc_method, JSONRPCError, JSONRPCForbidden
23 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
24 from rhodecode.api.utils import (
24 from rhodecode.api.utils import (
25 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
25 Optional, OAttr, store_update, has_superadmin_permission, get_origin,
26 get_user_or_error, get_user_group_or_error, get_perm_or_error)
26 get_user_or_error, get_user_group_or_error, get_perm_or_error)
27 from rhodecode.lib import audit_logger
27 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
28 from rhodecode.lib.auth import HasUserGroupPermissionAnyApi, HasPermissionAnyApi
28 from rhodecode.lib.exceptions import UserGroupAssignedException
29 from rhodecode.lib.exceptions import UserGroupAssignedException
29 from rhodecode.model.db import Session
30 from rhodecode.model.db import Session
30 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.scm import UserGroupList
31 from rhodecode.model.user_group import UserGroupModel
32 from rhodecode.model.user_group import UserGroupModel
32
33
33 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
34
35
35
36
36 @jsonrpc_method()
37 @jsonrpc_method()
37 def get_user_group(request, apiuser, usergroupid):
38 def get_user_group(request, apiuser, usergroupid):
38 """
39 """
39 Returns the data of an existing user group.
40 Returns the data of an existing user group.
40
41
41 This command can only be run using an |authtoken| with admin rights to
42 This command can only be run using an |authtoken| with admin rights to
42 the specified repository.
43 the specified repository.
43
44
44 :param apiuser: This is filled automatically from the |authtoken|.
45 :param apiuser: This is filled automatically from the |authtoken|.
45 :type apiuser: AuthUser
46 :type apiuser: AuthUser
46 :param usergroupid: Set the user group from which to return data.
47 :param usergroupid: Set the user group from which to return data.
47 :type usergroupid: str or int
48 :type usergroupid: str or int
48
49
49 Example error output:
50 Example error output:
50
51
51 .. code-block:: bash
52 .. code-block:: bash
52
53
53 {
54 {
54 "error": null,
55 "error": null,
55 "id": <id>,
56 "id": <id>,
56 "result": {
57 "result": {
57 "active": true,
58 "active": true,
58 "group_description": "group description",
59 "group_description": "group description",
59 "group_name": "group name",
60 "group_name": "group name",
60 "members": [
61 "members": [
61 {
62 {
62 "name": "owner-name",
63 "name": "owner-name",
63 "origin": "owner",
64 "origin": "owner",
64 "permission": "usergroup.admin",
65 "permission": "usergroup.admin",
65 "type": "user"
66 "type": "user"
66 },
67 },
67 {
68 {
68 {
69 {
69 "name": "user name",
70 "name": "user name",
70 "origin": "permission",
71 "origin": "permission",
71 "permission": "usergroup.admin",
72 "permission": "usergroup.admin",
72 "type": "user"
73 "type": "user"
73 },
74 },
74 {
75 {
75 "name": "user group name",
76 "name": "user group name",
76 "origin": "permission",
77 "origin": "permission",
77 "permission": "usergroup.write",
78 "permission": "usergroup.write",
78 "type": "user_group"
79 "type": "user_group"
79 }
80 }
80 ],
81 ],
81 "owner": "owner name",
82 "owner": "owner name",
82 "users": [],
83 "users": [],
83 "users_group_id": 2
84 "users_group_id": 2
84 }
85 }
85 }
86 }
86
87
87 """
88 """
88
89
89 user_group = get_user_group_or_error(usergroupid)
90 user_group = get_user_group_or_error(usergroupid)
90 if not has_superadmin_permission(apiuser):
91 if not has_superadmin_permission(apiuser):
91 # check if we have at least read permission for this user group !
92 # check if we have at least read permission for this user group !
92 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
93 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
93 if not HasUserGroupPermissionAnyApi(*_perms)(
94 if not HasUserGroupPermissionAnyApi(*_perms)(
94 user=apiuser, user_group_name=user_group.users_group_name):
95 user=apiuser, user_group_name=user_group.users_group_name):
95 raise JSONRPCError('user group `%s` does not exist' % (
96 raise JSONRPCError('user group `%s` does not exist' % (
96 usergroupid,))
97 usergroupid,))
97
98
98 permissions = []
99 permissions = []
99 for _user in user_group.permissions():
100 for _user in user_group.permissions():
100 user_data = {
101 user_data = {
101 'name': _user.username,
102 'name': _user.username,
102 'permission': _user.permission,
103 'permission': _user.permission,
103 'origin': get_origin(_user),
104 'origin': get_origin(_user),
104 'type': "user",
105 'type': "user",
105 }
106 }
106 permissions.append(user_data)
107 permissions.append(user_data)
107
108
108 for _user_group in user_group.permission_user_groups():
109 for _user_group in user_group.permission_user_groups():
109 user_group_data = {
110 user_group_data = {
110 'name': _user_group.users_group_name,
111 'name': _user_group.users_group_name,
111 'permission': _user_group.permission,
112 'permission': _user_group.permission,
112 'origin': get_origin(_user_group),
113 'origin': get_origin(_user_group),
113 'type': "user_group",
114 'type': "user_group",
114 }
115 }
115 permissions.append(user_group_data)
116 permissions.append(user_group_data)
116
117
117 data = user_group.get_api_data()
118 data = user_group.get_api_data()
118 data['members'] = permissions
119 data['members'] = permissions
119
120
120 return data
121 return data
121
122
122
123
123 @jsonrpc_method()
124 @jsonrpc_method()
124 def get_user_groups(request, apiuser):
125 def get_user_groups(request, apiuser):
125 """
126 """
126 Lists all the existing user groups within RhodeCode.
127 Lists all the existing user groups within RhodeCode.
127
128
128 This command can only be run using an |authtoken| with admin rights to
129 This command can only be run using an |authtoken| with admin rights to
129 the specified repository.
130 the specified repository.
130
131
131 This command takes the following options:
132 This command takes the following options:
132
133
133 :param apiuser: This is filled automatically from the |authtoken|.
134 :param apiuser: This is filled automatically from the |authtoken|.
134 :type apiuser: AuthUser
135 :type apiuser: AuthUser
135
136
136 Example error output:
137 Example error output:
137
138
138 .. code-block:: bash
139 .. code-block:: bash
139
140
140 id : <id_given_in_input>
141 id : <id_given_in_input>
141 result : [<user_group_obj>,...]
142 result : [<user_group_obj>,...]
142 error : null
143 error : null
143 """
144 """
144
145
145 include_secrets = has_superadmin_permission(apiuser)
146 include_secrets = has_superadmin_permission(apiuser)
146
147
147 result = []
148 result = []
148 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
149 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
149 extras = {'user': apiuser}
150 extras = {'user': apiuser}
150 for user_group in UserGroupList(UserGroupModel().get_all(),
151 for user_group in UserGroupList(UserGroupModel().get_all(),
151 perm_set=_perms, extra_kwargs=extras):
152 perm_set=_perms, extra_kwargs=extras):
152 result.append(
153 result.append(
153 user_group.get_api_data(include_secrets=include_secrets))
154 user_group.get_api_data(include_secrets=include_secrets))
154 return result
155 return result
155
156
156
157
157 @jsonrpc_method()
158 @jsonrpc_method()
158 def create_user_group(
159 def create_user_group(
159 request, apiuser, group_name, description=Optional(''),
160 request, apiuser, group_name, description=Optional(''),
160 owner=Optional(OAttr('apiuser')), active=Optional(True)):
161 owner=Optional(OAttr('apiuser')), active=Optional(True)):
161 """
162 """
162 Creates a new user group.
163 Creates a new user group.
163
164
164 This command can only be run using an |authtoken| with admin rights to
165 This command can only be run using an |authtoken| with admin rights to
165 the specified repository.
166 the specified repository.
166
167
167 This command takes the following options:
168 This command takes the following options:
168
169
169 :param apiuser: This is filled automatically from the |authtoken|.
170 :param apiuser: This is filled automatically from the |authtoken|.
170 :type apiuser: AuthUser
171 :type apiuser: AuthUser
171 :param group_name: Set the name of the new user group.
172 :param group_name: Set the name of the new user group.
172 :type group_name: str
173 :type group_name: str
173 :param description: Give a description of the new user group.
174 :param description: Give a description of the new user group.
174 :type description: str
175 :type description: str
175 :param owner: Set the owner of the new user group.
176 :param owner: Set the owner of the new user group.
176 If not set, the owner is the |authtoken| user.
177 If not set, the owner is the |authtoken| user.
177 :type owner: Optional(str or int)
178 :type owner: Optional(str or int)
178 :param active: Set this group as active.
179 :param active: Set this group as active.
179 :type active: Optional(``True`` | ``False``)
180 :type active: Optional(``True`` | ``False``)
180
181
181 Example output:
182 Example output:
182
183
183 .. code-block:: bash
184 .. code-block:: bash
184
185
185 id : <id_given_in_input>
186 id : <id_given_in_input>
186 result: {
187 result: {
187 "msg": "created new user group `<groupname>`",
188 "msg": "created new user group `<groupname>`",
188 "user_group": <user_group_object>
189 "user_group": <user_group_object>
189 }
190 }
190 error: null
191 error: null
191
192
192 Example error output:
193 Example error output:
193
194
194 .. code-block:: bash
195 .. code-block:: bash
195
196
196 id : <id_given_in_input>
197 id : <id_given_in_input>
197 result : null
198 result : null
198 error : {
199 error : {
199 "user group `<group name>` already exist"
200 "user group `<group name>` already exist"
200 or
201 or
201 "failed to create group `<group name>`"
202 "failed to create group `<group name>`"
202 }
203 }
203
204
204 """
205 """
205
206
206 if not has_superadmin_permission(apiuser):
207 if not has_superadmin_permission(apiuser):
207 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
208 if not HasPermissionAnyApi('hg.usergroup.create.true')(user=apiuser):
208 raise JSONRPCForbidden()
209 raise JSONRPCForbidden()
209
210
210 if UserGroupModel().get_by_name(group_name):
211 if UserGroupModel().get_by_name(group_name):
211 raise JSONRPCError("user group `%s` already exist" % (group_name,))
212 raise JSONRPCError("user group `%s` already exist" % (group_name,))
212
213
213 try:
214 try:
214 if isinstance(owner, Optional):
215 if isinstance(owner, Optional):
215 owner = apiuser.user_id
216 owner = apiuser.user_id
216
217
217 owner = get_user_or_error(owner)
218 owner = get_user_or_error(owner)
218 active = Optional.extract(active)
219 active = Optional.extract(active)
219 description = Optional.extract(description)
220 description = Optional.extract(description)
220 ug = UserGroupModel().create(
221 user_group = UserGroupModel().create(
221 name=group_name, description=description, owner=owner,
222 name=group_name, description=description, owner=owner,
222 active=active)
223 active=active)
224 Session().flush()
225 creation_data = user_group.get_api_data()
226 audit_logger.store_api(
227 'user_group.create', action_data={'data': creation_data},
228 user=apiuser)
223 Session().commit()
229 Session().commit()
224 return {
230 return {
225 'msg': 'created new user group `%s`' % group_name,
231 'msg': 'created new user group `%s`' % group_name,
226 'user_group': ug.get_api_data()
232 'user_group': creation_data
227 }
233 }
228 except Exception:
234 except Exception:
229 log.exception("Error occurred during creation of user group")
235 log.exception("Error occurred during creation of user group")
230 raise JSONRPCError('failed to create group `%s`' % (group_name,))
236 raise JSONRPCError('failed to create group `%s`' % (group_name,))
231
237
232
238
233 @jsonrpc_method()
239 @jsonrpc_method()
234 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
240 def update_user_group(request, apiuser, usergroupid, group_name=Optional(''),
235 description=Optional(''), owner=Optional(None),
241 description=Optional(''), owner=Optional(None),
236 active=Optional(True)):
242 active=Optional(True)):
237 """
243 """
238 Updates the specified `user group` with the details provided.
244 Updates the specified `user group` with the details provided.
239
245
240 This command can only be run using an |authtoken| with admin rights to
246 This command can only be run using an |authtoken| with admin rights to
241 the specified repository.
247 the specified repository.
242
248
243 :param apiuser: This is filled automatically from the |authtoken|.
249 :param apiuser: This is filled automatically from the |authtoken|.
244 :type apiuser: AuthUser
250 :type apiuser: AuthUser
245 :param usergroupid: Set the id of the `user group` to update.
251 :param usergroupid: Set the id of the `user group` to update.
246 :type usergroupid: str or int
252 :type usergroupid: str or int
247 :param group_name: Set the new name the `user group`
253 :param group_name: Set the new name the `user group`
248 :type group_name: str
254 :type group_name: str
249 :param description: Give a description for the `user group`
255 :param description: Give a description for the `user group`
250 :type description: str
256 :type description: str
251 :param owner: Set the owner of the `user group`.
257 :param owner: Set the owner of the `user group`.
252 :type owner: Optional(str or int)
258 :type owner: Optional(str or int)
253 :param active: Set the group as active.
259 :param active: Set the group as active.
254 :type active: Optional(``True`` | ``False``)
260 :type active: Optional(``True`` | ``False``)
255
261
256 Example output:
262 Example output:
257
263
258 .. code-block:: bash
264 .. code-block:: bash
259
265
260 id : <id_given_in_input>
266 id : <id_given_in_input>
261 result : {
267 result : {
262 "msg": 'updated user group ID:<user group id> <user group name>',
268 "msg": 'updated user group ID:<user group id> <user group name>',
263 "user_group": <user_group_object>
269 "user_group": <user_group_object>
264 }
270 }
265 error : null
271 error : null
266
272
267 Example error output:
273 Example error output:
268
274
269 .. code-block:: bash
275 .. code-block:: bash
270
276
271 id : <id_given_in_input>
277 id : <id_given_in_input>
272 result : null
278 result : null
273 error : {
279 error : {
274 "failed to update user group `<user group name>`"
280 "failed to update user group `<user group name>`"
275 }
281 }
276
282
277 """
283 """
278
284
279 user_group = get_user_group_or_error(usergroupid)
285 user_group = get_user_group_or_error(usergroupid)
280 include_secrets = False
286 include_secrets = False
281 if not has_superadmin_permission(apiuser):
287 if not has_superadmin_permission(apiuser):
282 # check if we have admin permission for this user group !
288 # check if we have admin permission for this user group !
283 _perms = ('usergroup.admin',)
289 _perms = ('usergroup.admin',)
284 if not HasUserGroupPermissionAnyApi(*_perms)(
290 if not HasUserGroupPermissionAnyApi(*_perms)(
285 user=apiuser, user_group_name=user_group.users_group_name):
291 user=apiuser, user_group_name=user_group.users_group_name):
286 raise JSONRPCError(
292 raise JSONRPCError(
287 'user group `%s` does not exist' % (usergroupid,))
293 'user group `%s` does not exist' % (usergroupid,))
288 else:
294 else:
289 include_secrets = True
295 include_secrets = True
290
296
291 if not isinstance(owner, Optional):
297 if not isinstance(owner, Optional):
292 owner = get_user_or_error(owner)
298 owner = get_user_or_error(owner)
293
299
300 old_data = user_group.get_api_data()
294 updates = {}
301 updates = {}
295 store_update(updates, group_name, 'users_group_name')
302 store_update(updates, group_name, 'users_group_name')
296 store_update(updates, description, 'user_group_description')
303 store_update(updates, description, 'user_group_description')
297 store_update(updates, owner, 'user')
304 store_update(updates, owner, 'user')
298 store_update(updates, active, 'users_group_active')
305 store_update(updates, active, 'users_group_active')
299 try:
306 try:
300 UserGroupModel().update(user_group, updates)
307 UserGroupModel().update(user_group, updates)
308 audit_logger.store_api(
309 'user_group.edit', action_data={'old_data': old_data},
310 user=apiuser)
301 Session().commit()
311 Session().commit()
302 return {
312 return {
303 'msg': 'updated user group ID:%s %s' % (
313 'msg': 'updated user group ID:%s %s' % (
304 user_group.users_group_id, user_group.users_group_name),
314 user_group.users_group_id, user_group.users_group_name),
305 'user_group': user_group.get_api_data(
315 'user_group': user_group.get_api_data(
306 include_secrets=include_secrets)
316 include_secrets=include_secrets)
307 }
317 }
308 except Exception:
318 except Exception:
309 log.exception("Error occurred during update of user group")
319 log.exception("Error occurred during update of user group")
310 raise JSONRPCError(
320 raise JSONRPCError(
311 'failed to update user group `%s`' % (usergroupid,))
321 'failed to update user group `%s`' % (usergroupid,))
312
322
313
323
314 @jsonrpc_method()
324 @jsonrpc_method()
315 def delete_user_group(request, apiuser, usergroupid):
325 def delete_user_group(request, apiuser, usergroupid):
316 """
326 """
317 Deletes the specified `user group`.
327 Deletes the specified `user group`.
318
328
319 This command can only be run using an |authtoken| with admin rights to
329 This command can only be run using an |authtoken| with admin rights to
320 the specified repository.
330 the specified repository.
321
331
322 This command takes the following options:
332 This command takes the following options:
323
333
324 :param apiuser: filled automatically from apikey
334 :param apiuser: filled automatically from apikey
325 :type apiuser: AuthUser
335 :type apiuser: AuthUser
326 :param usergroupid:
336 :param usergroupid:
327 :type usergroupid: int
337 :type usergroupid: int
328
338
329 Example output:
339 Example output:
330
340
331 .. code-block:: bash
341 .. code-block:: bash
332
342
333 id : <id_given_in_input>
343 id : <id_given_in_input>
334 result : {
344 result : {
335 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
345 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
336 }
346 }
337 error : null
347 error : null
338
348
339 Example error output:
349 Example error output:
340
350
341 .. code-block:: bash
351 .. code-block:: bash
342
352
343 id : <id_given_in_input>
353 id : <id_given_in_input>
344 result : null
354 result : null
345 error : {
355 error : {
346 "failed to delete user group ID:<user_group_id> <user_group_name>"
356 "failed to delete user group ID:<user_group_id> <user_group_name>"
347 or
357 or
348 "RepoGroup assigned to <repo_groups_list>"
358 "RepoGroup assigned to <repo_groups_list>"
349 }
359 }
350
360
351 """
361 """
352
362
353 user_group = get_user_group_or_error(usergroupid)
363 user_group = get_user_group_or_error(usergroupid)
354 if not has_superadmin_permission(apiuser):
364 if not has_superadmin_permission(apiuser):
355 # check if we have admin permission for this user group !
365 # check if we have admin permission for this user group !
356 _perms = ('usergroup.admin',)
366 _perms = ('usergroup.admin',)
357 if not HasUserGroupPermissionAnyApi(*_perms)(
367 if not HasUserGroupPermissionAnyApi(*_perms)(
358 user=apiuser, user_group_name=user_group.users_group_name):
368 user=apiuser, user_group_name=user_group.users_group_name):
359 raise JSONRPCError(
369 raise JSONRPCError(
360 'user group `%s` does not exist' % (usergroupid,))
370 'user group `%s` does not exist' % (usergroupid,))
361
371
372 old_data = user_group.get_api_data()
362 try:
373 try:
363 UserGroupModel().delete(user_group)
374 UserGroupModel().delete(user_group)
375 audit_logger.store_api(
376 'user_group.delete', action_data={'old_data': old_data},
377 user=apiuser)
364 Session().commit()
378 Session().commit()
365 return {
379 return {
366 'msg': 'deleted user group ID:%s %s' % (
380 'msg': 'deleted user group ID:%s %s' % (
367 user_group.users_group_id, user_group.users_group_name),
381 user_group.users_group_id, user_group.users_group_name),
368 'user_group': None
382 'user_group': None
369 }
383 }
370 except UserGroupAssignedException as e:
384 except UserGroupAssignedException as e:
371 log.exception("UserGroupAssigned error")
385 log.exception("UserGroupAssigned error")
372 raise JSONRPCError(str(e))
386 raise JSONRPCError(str(e))
373 except Exception:
387 except Exception:
374 log.exception("Error occurred during deletion of user group")
388 log.exception("Error occurred during deletion of user group")
375 raise JSONRPCError(
389 raise JSONRPCError(
376 'failed to delete user group ID:%s %s' %(
390 'failed to delete user group ID:%s %s' %(
377 user_group.users_group_id, user_group.users_group_name))
391 user_group.users_group_id, user_group.users_group_name))
378
392
379
393
380 @jsonrpc_method()
394 @jsonrpc_method()
381 def add_user_to_user_group(request, apiuser, usergroupid, userid):
395 def add_user_to_user_group(request, apiuser, usergroupid, userid):
382 """
396 """
383 Adds a user to a `user group`. If the user already exists in the group
397 Adds a user to a `user group`. If the user already exists in the group
384 this command will return false.
398 this command will return false.
385
399
386 This command can only be run using an |authtoken| with admin rights to
400 This command can only be run using an |authtoken| with admin rights to
387 the specified user group.
401 the specified user group.
388
402
389 This command takes the following options:
403 This command takes the following options:
390
404
391 :param apiuser: This is filled automatically from the |authtoken|.
405 :param apiuser: This is filled automatically from the |authtoken|.
392 :type apiuser: AuthUser
406 :type apiuser: AuthUser
393 :param usergroupid: Set the name of the `user group` to which a
407 :param usergroupid: Set the name of the `user group` to which a
394 user will be added.
408 user will be added.
395 :type usergroupid: int
409 :type usergroupid: int
396 :param userid: Set the `user_id` of the user to add to the group.
410 :param userid: Set the `user_id` of the user to add to the group.
397 :type userid: int
411 :type userid: int
398
412
399 Example output:
413 Example output:
400
414
401 .. code-block:: bash
415 .. code-block:: bash
402
416
403 id : <id_given_in_input>
417 id : <id_given_in_input>
404 result : {
418 result : {
405 "success": True|False # depends on if member is in group
419 "success": True|False # depends on if member is in group
406 "msg": "added member `<username>` to user group `<groupname>` |
420 "msg": "added member `<username>` to user group `<groupname>` |
407 User is already in that group"
421 User is already in that group"
408
422
409 }
423 }
410 error : null
424 error : null
411
425
412 Example error output:
426 Example error output:
413
427
414 .. code-block:: bash
428 .. code-block:: bash
415
429
416 id : <id_given_in_input>
430 id : <id_given_in_input>
417 result : null
431 result : null
418 error : {
432 error : {
419 "failed to add member to user group `<user_group_name>`"
433 "failed to add member to user group `<user_group_name>`"
420 }
434 }
421
435
422 """
436 """
423
437
424 user = get_user_or_error(userid)
438 user = get_user_or_error(userid)
425 user_group = get_user_group_or_error(usergroupid)
439 user_group = get_user_group_or_error(usergroupid)
426 if not has_superadmin_permission(apiuser):
440 if not has_superadmin_permission(apiuser):
427 # check if we have admin permission for this user group !
441 # check if we have admin permission for this user group !
428 _perms = ('usergroup.admin',)
442 _perms = ('usergroup.admin',)
429 if not HasUserGroupPermissionAnyApi(*_perms)(
443 if not HasUserGroupPermissionAnyApi(*_perms)(
430 user=apiuser, user_group_name=user_group.users_group_name):
444 user=apiuser, user_group_name=user_group.users_group_name):
431 raise JSONRPCError('user group `%s` does not exist' % (
445 raise JSONRPCError('user group `%s` does not exist' % (
432 usergroupid,))
446 usergroupid,))
433
447
434 try:
448 try:
435 ugm = UserGroupModel().add_user_to_group(user_group, user)
449 ugm = UserGroupModel().add_user_to_group(user_group, user)
436 success = True if ugm is not True else False
450 success = True if ugm is not True else False
437 msg = 'added member `%s` to user group `%s`' % (
451 msg = 'added member `%s` to user group `%s`' % (
438 user.username, user_group.users_group_name
452 user.username, user_group.users_group_name
439 )
453 )
440 msg = msg if success else 'User is already in that group'
454 msg = msg if success else 'User is already in that group'
455 if success:
456 user_data = user.get_api_data()
457 audit_logger.store_api(
458 'user_group.edit.member.add', action_data={'user': user_data},
459 user=apiuser)
460
441 Session().commit()
461 Session().commit()
442
462
443 return {
463 return {
444 'success': success,
464 'success': success,
445 'msg': msg
465 'msg': msg
446 }
466 }
447 except Exception:
467 except Exception:
448 log.exception("Error occurred during adding a member to user group")
468 log.exception("Error occurred during adding a member to user group")
449 raise JSONRPCError(
469 raise JSONRPCError(
450 'failed to add member to user group `%s`' % (
470 'failed to add member to user group `%s`' % (
451 user_group.users_group_name,
471 user_group.users_group_name,
452 )
472 )
453 )
473 )
454
474
455
475
456 @jsonrpc_method()
476 @jsonrpc_method()
457 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
477 def remove_user_from_user_group(request, apiuser, usergroupid, userid):
458 """
478 """
459 Removes a user from a user group.
479 Removes a user from a user group.
460
480
461 * If the specified user is not in the group, this command will return
481 * If the specified user is not in the group, this command will return
462 `false`.
482 `false`.
463
483
464 This command can only be run using an |authtoken| with admin rights to
484 This command can only be run using an |authtoken| with admin rights to
465 the specified user group.
485 the specified user group.
466
486
467 :param apiuser: This is filled automatically from the |authtoken|.
487 :param apiuser: This is filled automatically from the |authtoken|.
468 :type apiuser: AuthUser
488 :type apiuser: AuthUser
469 :param usergroupid: Sets the user group name.
489 :param usergroupid: Sets the user group name.
470 :type usergroupid: str or int
490 :type usergroupid: str or int
471 :param userid: The user you wish to remove from |RCE|.
491 :param userid: The user you wish to remove from |RCE|.
472 :type userid: str or int
492 :type userid: str or int
473
493
474 Example output:
494 Example output:
475
495
476 .. code-block:: bash
496 .. code-block:: bash
477
497
478 id : <id_given_in_input>
498 id : <id_given_in_input>
479 result: {
499 result: {
480 "success": True|False, # depends on if member is in group
500 "success": True|False, # depends on if member is in group
481 "msg": "removed member <username> from user group <groupname> |
501 "msg": "removed member <username> from user group <groupname> |
482 User wasn't in group"
502 User wasn't in group"
483 }
503 }
484 error: null
504 error: null
485
505
486 """
506 """
487
507
488 user = get_user_or_error(userid)
508 user = get_user_or_error(userid)
489 user_group = get_user_group_or_error(usergroupid)
509 user_group = get_user_group_or_error(usergroupid)
490 if not has_superadmin_permission(apiuser):
510 if not has_superadmin_permission(apiuser):
491 # check if we have admin permission for this user group !
511 # check if we have admin permission for this user group !
492 _perms = ('usergroup.admin',)
512 _perms = ('usergroup.admin',)
493 if not HasUserGroupPermissionAnyApi(*_perms)(
513 if not HasUserGroupPermissionAnyApi(*_perms)(
494 user=apiuser, user_group_name=user_group.users_group_name):
514 user=apiuser, user_group_name=user_group.users_group_name):
495 raise JSONRPCError(
515 raise JSONRPCError(
496 'user group `%s` does not exist' % (usergroupid,))
516 'user group `%s` does not exist' % (usergroupid,))
497
517
498 try:
518 try:
499 success = UserGroupModel().remove_user_from_group(user_group, user)
519 success = UserGroupModel().remove_user_from_group(user_group, user)
500 msg = 'removed member `%s` from user group `%s`' % (
520 msg = 'removed member `%s` from user group `%s`' % (
501 user.username, user_group.users_group_name
521 user.username, user_group.users_group_name
502 )
522 )
503 msg = msg if success else "User wasn't in group"
523 msg = msg if success else "User wasn't in group"
524 if success:
525 user_data = user.get_api_data()
526 audit_logger.store_api(
527 'user_group.edit.member.delete', action_data={'user': user_data},
528 user=apiuser)
529
504 Session().commit()
530 Session().commit()
505 return {'success': success, 'msg': msg}
531 return {'success': success, 'msg': msg}
506 except Exception:
532 except Exception:
507 log.exception("Error occurred during removing an member from user group")
533 log.exception("Error occurred during removing an member from user group")
508 raise JSONRPCError(
534 raise JSONRPCError(
509 'failed to remove member from user group `%s`' % (
535 'failed to remove member from user group `%s`' % (
510 user_group.users_group_name,
536 user_group.users_group_name,
511 )
537 )
512 )
538 )
513
539
514
540
515 @jsonrpc_method()
541 @jsonrpc_method()
516 def grant_user_permission_to_user_group(
542 def grant_user_permission_to_user_group(
517 request, apiuser, usergroupid, userid, perm):
543 request, apiuser, usergroupid, userid, perm):
518 """
544 """
519 Set permissions for a user in a user group.
545 Set permissions for a user in a user group.
520
546
521 :param apiuser: This is filled automatically from the |authtoken|.
547 :param apiuser: This is filled automatically from the |authtoken|.
522 :type apiuser: AuthUser
548 :type apiuser: AuthUser
523 :param usergroupid: Set the user group to edit permissions on.
549 :param usergroupid: Set the user group to edit permissions on.
524 :type usergroupid: str or int
550 :type usergroupid: str or int
525 :param userid: Set the user from whom you wish to set permissions.
551 :param userid: Set the user from whom you wish to set permissions.
526 :type userid: str
552 :type userid: str
527 :param perm: (usergroup.(none|read|write|admin))
553 :param perm: (usergroup.(none|read|write|admin))
528 :type perm: str
554 :type perm: str
529
555
530 Example output:
556 Example output:
531
557
532 .. code-block:: bash
558 .. code-block:: bash
533
559
534 id : <id_given_in_input>
560 id : <id_given_in_input>
535 result : {
561 result : {
536 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
562 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
537 "success": true
563 "success": true
538 }
564 }
539 error : null
565 error : null
540 """
566 """
541
567
542 user_group = get_user_group_or_error(usergroupid)
568 user_group = get_user_group_or_error(usergroupid)
543
569
544 if not has_superadmin_permission(apiuser):
570 if not has_superadmin_permission(apiuser):
545 # check if we have admin permission for this user group !
571 # check if we have admin permission for this user group !
546 _perms = ('usergroup.admin',)
572 _perms = ('usergroup.admin',)
547 if not HasUserGroupPermissionAnyApi(*_perms)(
573 if not HasUserGroupPermissionAnyApi(*_perms)(
548 user=apiuser, user_group_name=user_group.users_group_name):
574 user=apiuser, user_group_name=user_group.users_group_name):
549 raise JSONRPCError(
575 raise JSONRPCError(
550 'user group `%s` does not exist' % (usergroupid,))
576 'user group `%s` does not exist' % (usergroupid,))
551
577
552 user = get_user_or_error(userid)
578 user = get_user_or_error(userid)
553 perm = get_perm_or_error(perm, prefix='usergroup.')
579 perm = get_perm_or_error(perm, prefix='usergroup.')
554
580
555 try:
581 try:
556 UserGroupModel().grant_user_permission(
582 UserGroupModel().grant_user_permission(
557 user_group=user_group, user=user, perm=perm)
583 user_group=user_group, user=user, perm=perm)
558 Session().commit()
584 Session().commit()
559 return {
585 return {
560 'msg':
586 'msg':
561 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
587 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
562 perm.permission_name, user.username,
588 perm.permission_name, user.username,
563 user_group.users_group_name
589 user_group.users_group_name
564 ),
590 ),
565 'success': True
591 'success': True
566 }
592 }
567 except Exception:
593 except Exception:
568 log.exception("Error occurred during editing permissions "
594 log.exception("Error occurred during editing permissions "
569 "for user in user group")
595 "for user in user group")
570 raise JSONRPCError(
596 raise JSONRPCError(
571 'failed to edit permission for user: '
597 'failed to edit permission for user: '
572 '`%s` in user group: `%s`' % (
598 '`%s` in user group: `%s`' % (
573 userid, user_group.users_group_name))
599 userid, user_group.users_group_name))
574
600
575
601
576 @jsonrpc_method()
602 @jsonrpc_method()
577 def revoke_user_permission_from_user_group(
603 def revoke_user_permission_from_user_group(
578 request, apiuser, usergroupid, userid):
604 request, apiuser, usergroupid, userid):
579 """
605 """
580 Revoke a users permissions in a user group.
606 Revoke a users permissions in a user group.
581
607
582 :param apiuser: This is filled automatically from the |authtoken|.
608 :param apiuser: This is filled automatically from the |authtoken|.
583 :type apiuser: AuthUser
609 :type apiuser: AuthUser
584 :param usergroupid: Set the user group from which to revoke the user
610 :param usergroupid: Set the user group from which to revoke the user
585 permissions.
611 permissions.
586 :type: usergroupid: str or int
612 :type: usergroupid: str or int
587 :param userid: Set the userid of the user whose permissions will be
613 :param userid: Set the userid of the user whose permissions will be
588 revoked.
614 revoked.
589 :type userid: str
615 :type userid: str
590
616
591 Example output:
617 Example output:
592
618
593 .. code-block:: bash
619 .. code-block:: bash
594
620
595 id : <id_given_in_input>
621 id : <id_given_in_input>
596 result : {
622 result : {
597 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
623 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
598 "success": true
624 "success": true
599 }
625 }
600 error : null
626 error : null
601 """
627 """
602
628
603 user_group = get_user_group_or_error(usergroupid)
629 user_group = get_user_group_or_error(usergroupid)
604
630
605 if not has_superadmin_permission(apiuser):
631 if not has_superadmin_permission(apiuser):
606 # check if we have admin permission for this user group !
632 # check if we have admin permission for this user group !
607 _perms = ('usergroup.admin',)
633 _perms = ('usergroup.admin',)
608 if not HasUserGroupPermissionAnyApi(*_perms)(
634 if not HasUserGroupPermissionAnyApi(*_perms)(
609 user=apiuser, user_group_name=user_group.users_group_name):
635 user=apiuser, user_group_name=user_group.users_group_name):
610 raise JSONRPCError(
636 raise JSONRPCError(
611 'user group `%s` does not exist' % (usergroupid,))
637 'user group `%s` does not exist' % (usergroupid,))
612
638
613 user = get_user_or_error(userid)
639 user = get_user_or_error(userid)
614
640
615 try:
641 try:
616 UserGroupModel().revoke_user_permission(
642 UserGroupModel().revoke_user_permission(
617 user_group=user_group, user=user)
643 user_group=user_group, user=user)
618 Session().commit()
644 Session().commit()
619 return {
645 return {
620 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
646 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
621 user.username, user_group.users_group_name
647 user.username, user_group.users_group_name
622 ),
648 ),
623 'success': True
649 'success': True
624 }
650 }
625 except Exception:
651 except Exception:
626 log.exception("Error occurred during editing permissions "
652 log.exception("Error occurred during editing permissions "
627 "for user in user group")
653 "for user in user group")
628 raise JSONRPCError(
654 raise JSONRPCError(
629 'failed to edit permission for user: `%s` in user group: `%s`'
655 'failed to edit permission for user: `%s` in user group: `%s`'
630 % (userid, user_group.users_group_name))
656 % (userid, user_group.users_group_name))
631
657
632
658
633 @jsonrpc_method()
659 @jsonrpc_method()
634 def grant_user_group_permission_to_user_group(
660 def grant_user_group_permission_to_user_group(
635 request, apiuser, usergroupid, sourceusergroupid, perm):
661 request, apiuser, usergroupid, sourceusergroupid, perm):
636 """
662 """
637 Give one user group permissions to another user group.
663 Give one user group permissions to another user group.
638
664
639 :param apiuser: This is filled automatically from the |authtoken|.
665 :param apiuser: This is filled automatically from the |authtoken|.
640 :type apiuser: AuthUser
666 :type apiuser: AuthUser
641 :param usergroupid: Set the user group on which to edit permissions.
667 :param usergroupid: Set the user group on which to edit permissions.
642 :type usergroupid: str or int
668 :type usergroupid: str or int
643 :param sourceusergroupid: Set the source user group to which
669 :param sourceusergroupid: Set the source user group to which
644 access/permissions will be granted.
670 access/permissions will be granted.
645 :type sourceusergroupid: str or int
671 :type sourceusergroupid: str or int
646 :param perm: (usergroup.(none|read|write|admin))
672 :param perm: (usergroup.(none|read|write|admin))
647 :type perm: str
673 :type perm: str
648
674
649 Example output:
675 Example output:
650
676
651 .. code-block:: bash
677 .. code-block:: bash
652
678
653 id : <id_given_in_input>
679 id : <id_given_in_input>
654 result : {
680 result : {
655 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
681 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
656 "success": true
682 "success": true
657 }
683 }
658 error : null
684 error : null
659 """
685 """
660
686
661 user_group = get_user_group_or_error(sourceusergroupid)
687 user_group = get_user_group_or_error(sourceusergroupid)
662 target_user_group = get_user_group_or_error(usergroupid)
688 target_user_group = get_user_group_or_error(usergroupid)
663 perm = get_perm_or_error(perm, prefix='usergroup.')
689 perm = get_perm_or_error(perm, prefix='usergroup.')
664
690
665 if not has_superadmin_permission(apiuser):
691 if not has_superadmin_permission(apiuser):
666 # check if we have admin permission for this user group !
692 # check if we have admin permission for this user group !
667 _perms = ('usergroup.admin',)
693 _perms = ('usergroup.admin',)
668 if not HasUserGroupPermissionAnyApi(*_perms)(
694 if not HasUserGroupPermissionAnyApi(*_perms)(
669 user=apiuser,
695 user=apiuser,
670 user_group_name=target_user_group.users_group_name):
696 user_group_name=target_user_group.users_group_name):
671 raise JSONRPCError(
697 raise JSONRPCError(
672 'to user group `%s` does not exist' % (usergroupid,))
698 'to user group `%s` does not exist' % (usergroupid,))
673
699
674 # check if we have at least read permission for source user group !
700 # check if we have at least read permission for source user group !
675 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
701 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
676 if not HasUserGroupPermissionAnyApi(*_perms)(
702 if not HasUserGroupPermissionAnyApi(*_perms)(
677 user=apiuser, user_group_name=user_group.users_group_name):
703 user=apiuser, user_group_name=user_group.users_group_name):
678 raise JSONRPCError(
704 raise JSONRPCError(
679 'user group `%s` does not exist' % (sourceusergroupid,))
705 'user group `%s` does not exist' % (sourceusergroupid,))
680
706
681 try:
707 try:
682 UserGroupModel().grant_user_group_permission(
708 UserGroupModel().grant_user_group_permission(
683 target_user_group=target_user_group,
709 target_user_group=target_user_group,
684 user_group=user_group, perm=perm)
710 user_group=user_group, perm=perm)
685 Session().commit()
711 Session().commit()
686
712
687 return {
713 return {
688 'msg': 'Granted perm: `%s` for user group: `%s` '
714 'msg': 'Granted perm: `%s` for user group: `%s` '
689 'in user group: `%s`' % (
715 'in user group: `%s`' % (
690 perm.permission_name, user_group.users_group_name,
716 perm.permission_name, user_group.users_group_name,
691 target_user_group.users_group_name
717 target_user_group.users_group_name
692 ),
718 ),
693 'success': True
719 'success': True
694 }
720 }
695 except Exception:
721 except Exception:
696 log.exception("Error occurred during editing permissions "
722 log.exception("Error occurred during editing permissions "
697 "for user group in user group")
723 "for user group in user group")
698 raise JSONRPCError(
724 raise JSONRPCError(
699 'failed to edit permission for user group: `%s` in '
725 'failed to edit permission for user group: `%s` in '
700 'user group: `%s`' % (
726 'user group: `%s`' % (
701 sourceusergroupid, target_user_group.users_group_name
727 sourceusergroupid, target_user_group.users_group_name
702 )
728 )
703 )
729 )
704
730
705
731
706 @jsonrpc_method()
732 @jsonrpc_method()
707 def revoke_user_group_permission_from_user_group(
733 def revoke_user_group_permission_from_user_group(
708 request, apiuser, usergroupid, sourceusergroupid):
734 request, apiuser, usergroupid, sourceusergroupid):
709 """
735 """
710 Revoke the permissions that one user group has to another.
736 Revoke the permissions that one user group has to another.
711
737
712 :param apiuser: This is filled automatically from the |authtoken|.
738 :param apiuser: This is filled automatically from the |authtoken|.
713 :type apiuser: AuthUser
739 :type apiuser: AuthUser
714 :param usergroupid: Set the user group on which to edit permissions.
740 :param usergroupid: Set the user group on which to edit permissions.
715 :type usergroupid: str or int
741 :type usergroupid: str or int
716 :param sourceusergroupid: Set the user group from which permissions
742 :param sourceusergroupid: Set the user group from which permissions
717 are revoked.
743 are revoked.
718 :type sourceusergroupid: str or int
744 :type sourceusergroupid: str or int
719
745
720 Example output:
746 Example output:
721
747
722 .. code-block:: bash
748 .. code-block:: bash
723
749
724 id : <id_given_in_input>
750 id : <id_given_in_input>
725 result : {
751 result : {
726 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
752 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
727 "success": true
753 "success": true
728 }
754 }
729 error : null
755 error : null
730 """
756 """
731
757
732 user_group = get_user_group_or_error(sourceusergroupid)
758 user_group = get_user_group_or_error(sourceusergroupid)
733 target_user_group = get_user_group_or_error(usergroupid)
759 target_user_group = get_user_group_or_error(usergroupid)
734
760
735 if not has_superadmin_permission(apiuser):
761 if not has_superadmin_permission(apiuser):
736 # check if we have admin permission for this user group !
762 # check if we have admin permission for this user group !
737 _perms = ('usergroup.admin',)
763 _perms = ('usergroup.admin',)
738 if not HasUserGroupPermissionAnyApi(*_perms)(
764 if not HasUserGroupPermissionAnyApi(*_perms)(
739 user=apiuser,
765 user=apiuser,
740 user_group_name=target_user_group.users_group_name):
766 user_group_name=target_user_group.users_group_name):
741 raise JSONRPCError(
767 raise JSONRPCError(
742 'to user group `%s` does not exist' % (usergroupid,))
768 'to user group `%s` does not exist' % (usergroupid,))
743
769
744 # check if we have at least read permission
770 # check if we have at least read permission
745 # for the source user group !
771 # for the source user group !
746 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
772 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
747 if not HasUserGroupPermissionAnyApi(*_perms)(
773 if not HasUserGroupPermissionAnyApi(*_perms)(
748 user=apiuser, user_group_name=user_group.users_group_name):
774 user=apiuser, user_group_name=user_group.users_group_name):
749 raise JSONRPCError(
775 raise JSONRPCError(
750 'user group `%s` does not exist' % (sourceusergroupid,))
776 'user group `%s` does not exist' % (sourceusergroupid,))
751
777
752 try:
778 try:
753 UserGroupModel().revoke_user_group_permission(
779 UserGroupModel().revoke_user_group_permission(
754 target_user_group=target_user_group, user_group=user_group)
780 target_user_group=target_user_group, user_group=user_group)
755 Session().commit()
781 Session().commit()
756
782
757 return {
783 return {
758 'msg': 'Revoked perm for user group: '
784 'msg': 'Revoked perm for user group: '
759 '`%s` in user group: `%s`' % (
785 '`%s` in user group: `%s`' % (
760 user_group.users_group_name,
786 user_group.users_group_name,
761 target_user_group.users_group_name
787 target_user_group.users_group_name
762 ),
788 ),
763 'success': True
789 'success': True
764 }
790 }
765 except Exception:
791 except Exception:
766 log.exception("Error occurred during editing permissions "
792 log.exception("Error occurred during editing permissions "
767 "for user group in user group")
793 "for user group in user group")
768 raise JSONRPCError(
794 raise JSONRPCError(
769 'failed to edit permission for user group: '
795 'failed to edit permission for user group: '
770 '`%s` in user group: `%s`' % (
796 '`%s` in user group: `%s`' % (
771 sourceusergroupid, target_user_group.users_group_name
797 sourceusergroupid, target_user_group.users_group_name
772 )
798 )
773 )
799 )
@@ -1,509 +1,505 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from sqlalchemy.sql.functions import coalesce
27 from sqlalchemy.sql.functions import coalesce
28
28
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30
30
31 from rhodecode.lib import audit_logger
31 from rhodecode.lib import audit_logger
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
34 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import PartialRenderer
36 from rhodecode.lib.utils import PartialRenderer
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.lib.utils2 import safe_int, safe_unicode
38 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.user_group import UserGroupModel
40 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
41 from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class AdminUsersView(BaseAppView, DataGridAppView):
47 class AdminUsersView(BaseAppView, DataGridAppView):
48 ALLOW_SCOPED_TOKENS = False
48 ALLOW_SCOPED_TOKENS = False
49 """
49 """
50 This view has alternative version inside EE, if modified please take a look
50 This view has alternative version inside EE, if modified please take a look
51 in there as well.
51 in there as well.
52 """
52 """
53
53
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 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
56 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
57 self._register_global_c(c)
57 self._register_global_c(c)
58 return c
58 return c
59
59
60 def _redirect_for_default_user(self, username):
60 def _redirect_for_default_user(self, username):
61 _ = self.request.translate
61 _ = self.request.translate
62 if username == User.DEFAULT_USER:
62 if username == User.DEFAULT_USER:
63 h.flash(_("You can't edit this user"), category='warning')
63 h.flash(_("You can't edit this user"), category='warning')
64 # TODO(marcink): redirect to 'users' admin panel once this
64 # TODO(marcink): redirect to 'users' admin panel once this
65 # is a pyramid view
65 # is a pyramid view
66 raise HTTPFound('/')
66 raise HTTPFound('/')
67
67
68 @HasPermissionAllDecorator('hg.admin')
68 @HasPermissionAllDecorator('hg.admin')
69 @view_config(
69 @view_config(
70 route_name='users', request_method='GET',
70 route_name='users', request_method='GET',
71 renderer='rhodecode:templates/admin/users/users.mako')
71 renderer='rhodecode:templates/admin/users/users.mako')
72 def users_list(self):
72 def users_list(self):
73 c = self.load_default_context()
73 c = self.load_default_context()
74 return self._get_template_context(c)
74 return self._get_template_context(c)
75
75
76 @HasPermissionAllDecorator('hg.admin')
76 @HasPermissionAllDecorator('hg.admin')
77 @view_config(
77 @view_config(
78 # renderer defined below
78 # renderer defined below
79 route_name='users_data', request_method='GET',
79 route_name='users_data', request_method='GET',
80 renderer='json_ext', xhr=True)
80 renderer='json_ext', xhr=True)
81 def users_list_data(self):
81 def users_list_data(self):
82 draw, start, limit = self._extract_chunk(self.request)
82 draw, start, limit = self._extract_chunk(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(self.request)
84
84
85 _render = PartialRenderer('data_table/_dt_elements.mako')
85 _render = PartialRenderer('data_table/_dt_elements.mako')
86
86
87 def user_actions(user_id, username):
87 def user_actions(user_id, username):
88 return _render("user_actions", user_id, username)
88 return _render("user_actions", user_id, username)
89
89
90 users_data_total_count = User.query()\
90 users_data_total_count = User.query()\
91 .filter(User.username != User.DEFAULT_USER) \
91 .filter(User.username != User.DEFAULT_USER) \
92 .count()
92 .count()
93
93
94 # json generate
94 # json generate
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
95 base_q = User.query().filter(User.username != User.DEFAULT_USER)
96
96
97 if search_q:
97 if search_q:
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
98 like_expression = u'%{}%'.format(safe_unicode(search_q))
99 base_q = base_q.filter(or_(
99 base_q = base_q.filter(or_(
100 User.username.ilike(like_expression),
100 User.username.ilike(like_expression),
101 User._email.ilike(like_expression),
101 User._email.ilike(like_expression),
102 User.name.ilike(like_expression),
102 User.name.ilike(like_expression),
103 User.lastname.ilike(like_expression),
103 User.lastname.ilike(like_expression),
104 ))
104 ))
105
105
106 users_data_total_filtered_count = base_q.count()
106 users_data_total_filtered_count = base_q.count()
107
107
108 sort_col = getattr(User, order_by, None)
108 sort_col = getattr(User, order_by, None)
109 if sort_col:
109 if sort_col:
110 if order_dir == 'asc':
110 if order_dir == 'asc':
111 # handle null values properly to order by NULL last
111 # handle null values properly to order by NULL last
112 if order_by in ['last_activity']:
112 if order_by in ['last_activity']:
113 sort_col = coalesce(sort_col, datetime.date.max)
113 sort_col = coalesce(sort_col, datetime.date.max)
114 sort_col = sort_col.asc()
114 sort_col = sort_col.asc()
115 else:
115 else:
116 # handle null values properly to order by NULL last
116 # handle null values properly to order by NULL last
117 if order_by in ['last_activity']:
117 if order_by in ['last_activity']:
118 sort_col = coalesce(sort_col, datetime.date.min)
118 sort_col = coalesce(sort_col, datetime.date.min)
119 sort_col = sort_col.desc()
119 sort_col = sort_col.desc()
120
120
121 base_q = base_q.order_by(sort_col)
121 base_q = base_q.order_by(sort_col)
122 base_q = base_q.offset(start).limit(limit)
122 base_q = base_q.offset(start).limit(limit)
123
123
124 users_list = base_q.all()
124 users_list = base_q.all()
125
125
126 users_data = []
126 users_data = []
127 for user in users_list:
127 for user in users_list:
128 users_data.append({
128 users_data.append({
129 "username": h.gravatar_with_user(user.username),
129 "username": h.gravatar_with_user(user.username),
130 "email": user.email,
130 "email": user.email,
131 "first_name": user.first_name,
131 "first_name": user.first_name,
132 "last_name": user.last_name,
132 "last_name": user.last_name,
133 "last_login": h.format_date(user.last_login),
133 "last_login": h.format_date(user.last_login),
134 "last_activity": h.format_date(user.last_activity),
134 "last_activity": h.format_date(user.last_activity),
135 "active": h.bool2icon(user.active),
135 "active": h.bool2icon(user.active),
136 "active_raw": user.active,
136 "active_raw": user.active,
137 "admin": h.bool2icon(user.admin),
137 "admin": h.bool2icon(user.admin),
138 "extern_type": user.extern_type,
138 "extern_type": user.extern_type,
139 "extern_name": user.extern_name,
139 "extern_name": user.extern_name,
140 "action": user_actions(user.user_id, user.username),
140 "action": user_actions(user.user_id, user.username),
141 })
141 })
142
142
143 data = ({
143 data = ({
144 'draw': draw,
144 'draw': draw,
145 'data': users_data,
145 'data': users_data,
146 'recordsTotal': users_data_total_count,
146 'recordsTotal': users_data_total_count,
147 'recordsFiltered': users_data_total_filtered_count,
147 'recordsFiltered': users_data_total_filtered_count,
148 })
148 })
149
149
150 return data
150 return data
151
151
152 @LoginRequired()
152 @LoginRequired()
153 @HasPermissionAllDecorator('hg.admin')
153 @HasPermissionAllDecorator('hg.admin')
154 @view_config(
154 @view_config(
155 route_name='edit_user_auth_tokens', request_method='GET',
155 route_name='edit_user_auth_tokens', request_method='GET',
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
156 renderer='rhodecode:templates/admin/users/user_edit.mako')
157 def auth_tokens(self):
157 def auth_tokens(self):
158 _ = self.request.translate
158 _ = self.request.translate
159 c = self.load_default_context()
159 c = self.load_default_context()
160
160
161 user_id = self.request.matchdict.get('user_id')
161 user_id = self.request.matchdict.get('user_id')
162 c.user = User.get_or_404(user_id, pyramid_exc=True)
162 c.user = User.get_or_404(user_id, pyramid_exc=True)
163 self._redirect_for_default_user(c.user.username)
163 self._redirect_for_default_user(c.user.username)
164
164
165 c.active = 'auth_tokens'
165 c.active = 'auth_tokens'
166
166
167 c.lifetime_values = [
167 c.lifetime_values = [
168 (str(-1), _('forever')),
168 (str(-1), _('forever')),
169 (str(5), _('5 minutes')),
169 (str(5), _('5 minutes')),
170 (str(60), _('1 hour')),
170 (str(60), _('1 hour')),
171 (str(60 * 24), _('1 day')),
171 (str(60 * 24), _('1 day')),
172 (str(60 * 24 * 30), _('1 month')),
172 (str(60 * 24 * 30), _('1 month')),
173 ]
173 ]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
174 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
175 c.role_values = [
175 c.role_values = [
176 (x, AuthTokenModel.cls._get_role_name(x))
176 (x, AuthTokenModel.cls._get_role_name(x))
177 for x in AuthTokenModel.cls.ROLES]
177 for x in AuthTokenModel.cls.ROLES]
178 c.role_options = [(c.role_values, _("Role"))]
178 c.role_options = [(c.role_values, _("Role"))]
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
179 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
180 c.user.user_id, show_expired=True)
180 c.user.user_id, show_expired=True)
181 return self._get_template_context(c)
181 return self._get_template_context(c)
182
182
183 def maybe_attach_token_scope(self, token):
183 def maybe_attach_token_scope(self, token):
184 # implemented in EE edition
184 # implemented in EE edition
185 pass
185 pass
186
186
187 @LoginRequired()
187 @LoginRequired()
188 @HasPermissionAllDecorator('hg.admin')
188 @HasPermissionAllDecorator('hg.admin')
189 @CSRFRequired()
189 @CSRFRequired()
190 @view_config(
190 @view_config(
191 route_name='edit_user_auth_tokens_add', request_method='POST')
191 route_name='edit_user_auth_tokens_add', request_method='POST')
192 def auth_tokens_add(self):
192 def auth_tokens_add(self):
193 _ = self.request.translate
193 _ = self.request.translate
194 c = self.load_default_context()
194 c = self.load_default_context()
195
195
196 user_id = self.request.matchdict.get('user_id')
196 user_id = self.request.matchdict.get('user_id')
197 c.user = User.get_or_404(user_id, pyramid_exc=True)
197 c.user = User.get_or_404(user_id, pyramid_exc=True)
198
198
199 self._redirect_for_default_user(c.user.username)
199 self._redirect_for_default_user(c.user.username)
200
200
201 user_data = c.user.get_api_data()
201 user_data = c.user.get_api_data()
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
202 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
203 description = self.request.POST.get('description')
203 description = self.request.POST.get('description')
204 role = self.request.POST.get('role')
204 role = self.request.POST.get('role')
205
205
206 token = AuthTokenModel().create(
206 token = AuthTokenModel().create(
207 c.user.user_id, description, lifetime, role)
207 c.user.user_id, description, lifetime, role)
208 token_data = token.get_api_data()
208 token_data = token.get_api_data()
209
209
210 self.maybe_attach_token_scope(token)
210 self.maybe_attach_token_scope(token)
211 audit_logger.store_web(
211 audit_logger.store_web(
212 action='user.edit.token.add',
212 'user.edit.token.add', action_data={
213 action_data={'data': {'token': token_data, 'user': user_data}},
213 'data': {'token': token_data, 'user': user_data}},
214 user=self._rhodecode_user, )
214 user=self._rhodecode_user, )
215 Session().commit()
215 Session().commit()
216
216
217 h.flash(_("Auth token successfully created"), category='success')
217 h.flash(_("Auth token successfully created"), category='success')
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
218 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
219
219
220 @LoginRequired()
220 @LoginRequired()
221 @HasPermissionAllDecorator('hg.admin')
221 @HasPermissionAllDecorator('hg.admin')
222 @CSRFRequired()
222 @CSRFRequired()
223 @view_config(
223 @view_config(
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
224 route_name='edit_user_auth_tokens_delete', request_method='POST')
225 def auth_tokens_delete(self):
225 def auth_tokens_delete(self):
226 _ = self.request.translate
226 _ = self.request.translate
227 c = self.load_default_context()
227 c = self.load_default_context()
228
228
229 user_id = self.request.matchdict.get('user_id')
229 user_id = self.request.matchdict.get('user_id')
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
230 c.user = User.get_or_404(user_id, pyramid_exc=True)
231 self._redirect_for_default_user(c.user.username)
231 self._redirect_for_default_user(c.user.username)
232 user_data = c.user.get_api_data()
232 user_data = c.user.get_api_data()
233
233
234 del_auth_token = self.request.POST.get('del_auth_token')
234 del_auth_token = self.request.POST.get('del_auth_token')
235
235
236 if del_auth_token:
236 if del_auth_token:
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
237 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
238 token_data = token.get_api_data()
238 token_data = token.get_api_data()
239
239
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
240 AuthTokenModel().delete(del_auth_token, c.user.user_id)
241 audit_logger.store_web(
241 audit_logger.store_web(
242 action='user.edit.token.delete',
242 'user.edit.token.delete', action_data={
243 action_data={'data': {'token': token_data, 'user': user_data}},
243 'data': {'token': token_data, 'user': user_data}},
244 user=self._rhodecode_user,)
244 user=self._rhodecode_user,)
245 Session().commit()
245 Session().commit()
246 h.flash(_("Auth token successfully deleted"), category='success')
246 h.flash(_("Auth token successfully deleted"), category='success')
247
247
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
248 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
249
249
250 @LoginRequired()
250 @LoginRequired()
251 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
252 @view_config(
252 @view_config(
253 route_name='edit_user_emails', request_method='GET',
253 route_name='edit_user_emails', request_method='GET',
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
254 renderer='rhodecode:templates/admin/users/user_edit.mako')
255 def emails(self):
255 def emails(self):
256 _ = self.request.translate
256 _ = self.request.translate
257 c = self.load_default_context()
257 c = self.load_default_context()
258
258
259 user_id = self.request.matchdict.get('user_id')
259 user_id = self.request.matchdict.get('user_id')
260 c.user = User.get_or_404(user_id, pyramid_exc=True)
260 c.user = User.get_or_404(user_id, pyramid_exc=True)
261 self._redirect_for_default_user(c.user.username)
261 self._redirect_for_default_user(c.user.username)
262
262
263 c.active = 'emails'
263 c.active = 'emails'
264 c.user_email_map = UserEmailMap.query() \
264 c.user_email_map = UserEmailMap.query() \
265 .filter(UserEmailMap.user == c.user).all()
265 .filter(UserEmailMap.user == c.user).all()
266
266
267 return self._get_template_context(c)
267 return self._get_template_context(c)
268
268
269 @LoginRequired()
269 @LoginRequired()
270 @HasPermissionAllDecorator('hg.admin')
270 @HasPermissionAllDecorator('hg.admin')
271 @CSRFRequired()
271 @CSRFRequired()
272 @view_config(
272 @view_config(
273 route_name='edit_user_emails_add', request_method='POST')
273 route_name='edit_user_emails_add', request_method='POST')
274 def emails_add(self):
274 def emails_add(self):
275 _ = self.request.translate
275 _ = self.request.translate
276 c = self.load_default_context()
276 c = self.load_default_context()
277
277
278 user_id = self.request.matchdict.get('user_id')
278 user_id = self.request.matchdict.get('user_id')
279 c.user = User.get_or_404(user_id, pyramid_exc=True)
279 c.user = User.get_or_404(user_id, pyramid_exc=True)
280 self._redirect_for_default_user(c.user.username)
280 self._redirect_for_default_user(c.user.username)
281
281
282 email = self.request.POST.get('new_email')
282 email = self.request.POST.get('new_email')
283 user_data = c.user.get_api_data()
283 user_data = c.user.get_api_data()
284 try:
284 try:
285 UserModel().add_extra_email(c.user.user_id, email)
285 UserModel().add_extra_email(c.user.user_id, email)
286 audit_logger.store_web(
286 audit_logger.store_web(
287 'user.edit.email.add',
287 'user.edit.email.add', action_data={'email': email, 'user': user_data},
288 action_data={'email': email, 'user': user_data},
289 user=self._rhodecode_user)
288 user=self._rhodecode_user)
290 Session().commit()
289 Session().commit()
291 h.flash(_("Added new email address `%s` for user account") % email,
290 h.flash(_("Added new email address `%s` for user account") % email,
292 category='success')
291 category='success')
293 except formencode.Invalid as error:
292 except formencode.Invalid as error:
294 h.flash(h.escape(error.error_dict['email']), category='error')
293 h.flash(h.escape(error.error_dict['email']), category='error')
295 except Exception:
294 except Exception:
296 log.exception("Exception during email saving")
295 log.exception("Exception during email saving")
297 h.flash(_('An error occurred during email saving'),
296 h.flash(_('An error occurred during email saving'),
298 category='error')
297 category='error')
299 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
298 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
300
299
301 @LoginRequired()
300 @LoginRequired()
302 @HasPermissionAllDecorator('hg.admin')
301 @HasPermissionAllDecorator('hg.admin')
303 @CSRFRequired()
302 @CSRFRequired()
304 @view_config(
303 @view_config(
305 route_name='edit_user_emails_delete', request_method='POST')
304 route_name='edit_user_emails_delete', request_method='POST')
306 def emails_delete(self):
305 def emails_delete(self):
307 _ = self.request.translate
306 _ = self.request.translate
308 c = self.load_default_context()
307 c = self.load_default_context()
309
308
310 user_id = self.request.matchdict.get('user_id')
309 user_id = self.request.matchdict.get('user_id')
311 c.user = User.get_or_404(user_id, pyramid_exc=True)
310 c.user = User.get_or_404(user_id, pyramid_exc=True)
312 self._redirect_for_default_user(c.user.username)
311 self._redirect_for_default_user(c.user.username)
313
312
314 email_id = self.request.POST.get('del_email_id')
313 email_id = self.request.POST.get('del_email_id')
315 user_model = UserModel()
314 user_model = UserModel()
316
315
317 email = UserEmailMap.query().get(email_id).email
316 email = UserEmailMap.query().get(email_id).email
318 user_data = c.user.get_api_data()
317 user_data = c.user.get_api_data()
319 user_model.delete_extra_email(c.user.user_id, email_id)
318 user_model.delete_extra_email(c.user.user_id, email_id)
320 audit_logger.store_web(
319 audit_logger.store_web(
321 'user.edit.email.delete',
320 'user.edit.email.delete', action_data={'email': email, 'user': user_data},
322 action_data={'email': email, 'user': user_data},
323 user=self._rhodecode_user)
321 user=self._rhodecode_user)
324 Session().commit()
322 Session().commit()
325 h.flash(_("Removed email address from user account"),
323 h.flash(_("Removed email address from user account"),
326 category='success')
324 category='success')
327 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
325 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
328
326
329 @LoginRequired()
327 @LoginRequired()
330 @HasPermissionAllDecorator('hg.admin')
328 @HasPermissionAllDecorator('hg.admin')
331 @view_config(
329 @view_config(
332 route_name='edit_user_ips', request_method='GET',
330 route_name='edit_user_ips', request_method='GET',
333 renderer='rhodecode:templates/admin/users/user_edit.mako')
331 renderer='rhodecode:templates/admin/users/user_edit.mako')
334 def ips(self):
332 def ips(self):
335 _ = self.request.translate
333 _ = self.request.translate
336 c = self.load_default_context()
334 c = self.load_default_context()
337
335
338 user_id = self.request.matchdict.get('user_id')
336 user_id = self.request.matchdict.get('user_id')
339 c.user = User.get_or_404(user_id, pyramid_exc=True)
337 c.user = User.get_or_404(user_id, pyramid_exc=True)
340 self._redirect_for_default_user(c.user.username)
338 self._redirect_for_default_user(c.user.username)
341
339
342 c.active = 'ips'
340 c.active = 'ips'
343 c.user_ip_map = UserIpMap.query() \
341 c.user_ip_map = UserIpMap.query() \
344 .filter(UserIpMap.user == c.user).all()
342 .filter(UserIpMap.user == c.user).all()
345
343
346 c.inherit_default_ips = c.user.inherit_default_permissions
344 c.inherit_default_ips = c.user.inherit_default_permissions
347 c.default_user_ip_map = UserIpMap.query() \
345 c.default_user_ip_map = UserIpMap.query() \
348 .filter(UserIpMap.user == User.get_default_user()).all()
346 .filter(UserIpMap.user == User.get_default_user()).all()
349
347
350 return self._get_template_context(c)
348 return self._get_template_context(c)
351
349
352 @LoginRequired()
350 @LoginRequired()
353 @HasPermissionAllDecorator('hg.admin')
351 @HasPermissionAllDecorator('hg.admin')
354 @CSRFRequired()
352 @CSRFRequired()
355 @view_config(
353 @view_config(
356 route_name='edit_user_ips_add', request_method='POST')
354 route_name='edit_user_ips_add', request_method='POST')
357 def ips_add(self):
355 def ips_add(self):
358 _ = self.request.translate
356 _ = self.request.translate
359 c = self.load_default_context()
357 c = self.load_default_context()
360
358
361 user_id = self.request.matchdict.get('user_id')
359 user_id = self.request.matchdict.get('user_id')
362 c.user = User.get_or_404(user_id, pyramid_exc=True)
360 c.user = User.get_or_404(user_id, pyramid_exc=True)
363 # NOTE(marcink): this view is allowed for default users, as we can
361 # NOTE(marcink): this view is allowed for default users, as we can
364 # edit their IP white list
362 # edit their IP white list
365
363
366 user_model = UserModel()
364 user_model = UserModel()
367 desc = self.request.POST.get('description')
365 desc = self.request.POST.get('description')
368 try:
366 try:
369 ip_list = user_model.parse_ip_range(
367 ip_list = user_model.parse_ip_range(
370 self.request.POST.get('new_ip'))
368 self.request.POST.get('new_ip'))
371 except Exception as e:
369 except Exception as e:
372 ip_list = []
370 ip_list = []
373 log.exception("Exception during ip saving")
371 log.exception("Exception during ip saving")
374 h.flash(_('An error occurred during ip saving:%s' % (e,)),
372 h.flash(_('An error occurred during ip saving:%s' % (e,)),
375 category='error')
373 category='error')
376 added = []
374 added = []
377 user_data = c.user.get_api_data()
375 user_data = c.user.get_api_data()
378 for ip in ip_list:
376 for ip in ip_list:
379 try:
377 try:
380 user_model.add_extra_ip(c.user.user_id, ip, desc)
378 user_model.add_extra_ip(c.user.user_id, ip, desc)
381 audit_logger.store_web(
379 audit_logger.store_web(
382 'user.edit.ip.add',
380 'user.edit.ip.add', action_data={'ip': ip, 'user': user_data},
383 action_data={'ip': ip, 'user': user_data},
384 user=self._rhodecode_user)
381 user=self._rhodecode_user)
385 Session().commit()
382 Session().commit()
386 added.append(ip)
383 added.append(ip)
387 except formencode.Invalid as error:
384 except formencode.Invalid as error:
388 msg = error.error_dict['ip']
385 msg = error.error_dict['ip']
389 h.flash(msg, category='error')
386 h.flash(msg, category='error')
390 except Exception:
387 except Exception:
391 log.exception("Exception during ip saving")
388 log.exception("Exception during ip saving")
392 h.flash(_('An error occurred during ip saving'),
389 h.flash(_('An error occurred during ip saving'),
393 category='error')
390 category='error')
394 if added:
391 if added:
395 h.flash(
392 h.flash(
396 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
393 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
397 category='success')
394 category='success')
398 if 'default_user' in self.request.POST:
395 if 'default_user' in self.request.POST:
399 # case for editing global IP list we do it for 'DEFAULT' user
396 # case for editing global IP list we do it for 'DEFAULT' user
400 raise HTTPFound(h.route_path('admin_permissions_ips'))
397 raise HTTPFound(h.route_path('admin_permissions_ips'))
401 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
398 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
402
399
403 @LoginRequired()
400 @LoginRequired()
404 @HasPermissionAllDecorator('hg.admin')
401 @HasPermissionAllDecorator('hg.admin')
405 @CSRFRequired()
402 @CSRFRequired()
406 @view_config(
403 @view_config(
407 route_name='edit_user_ips_delete', request_method='POST')
404 route_name='edit_user_ips_delete', request_method='POST')
408 def ips_delete(self):
405 def ips_delete(self):
409 _ = self.request.translate
406 _ = self.request.translate
410 c = self.load_default_context()
407 c = self.load_default_context()
411
408
412 user_id = self.request.matchdict.get('user_id')
409 user_id = self.request.matchdict.get('user_id')
413 c.user = User.get_or_404(user_id, pyramid_exc=True)
410 c.user = User.get_or_404(user_id, pyramid_exc=True)
414 # NOTE(marcink): this view is allowed for default users, as we can
411 # NOTE(marcink): this view is allowed for default users, as we can
415 # edit their IP white list
412 # edit their IP white list
416
413
417 ip_id = self.request.POST.get('del_ip_id')
414 ip_id = self.request.POST.get('del_ip_id')
418 user_model = UserModel()
415 user_model = UserModel()
419 user_data = c.user.get_api_data()
416 user_data = c.user.get_api_data()
420 ip = UserIpMap.query().get(ip_id).ip_addr
417 ip = UserIpMap.query().get(ip_id).ip_addr
421 user_model.delete_extra_ip(c.user.user_id, ip_id)
418 user_model.delete_extra_ip(c.user.user_id, ip_id)
422 audit_logger.store_web(
419 audit_logger.store_web(
423 'user.edit.ip.delete',
420 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
424 action_data={'ip': ip, 'user': user_data},
425 user=self._rhodecode_user)
421 user=self._rhodecode_user)
426 Session().commit()
422 Session().commit()
427 h.flash(_("Removed ip address from user whitelist"), category='success')
423 h.flash(_("Removed ip address from user whitelist"), category='success')
428
424
429 if 'default_user' in self.request.POST:
425 if 'default_user' in self.request.POST:
430 # case for editing global IP list we do it for 'DEFAULT' user
426 # case for editing global IP list we do it for 'DEFAULT' user
431 raise HTTPFound(h.route_path('admin_permissions_ips'))
427 raise HTTPFound(h.route_path('admin_permissions_ips'))
432 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
428 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
433
429
434 @LoginRequired()
430 @LoginRequired()
435 @HasPermissionAllDecorator('hg.admin')
431 @HasPermissionAllDecorator('hg.admin')
436 @view_config(
432 @view_config(
437 route_name='edit_user_groups_management', request_method='GET',
433 route_name='edit_user_groups_management', request_method='GET',
438 renderer='rhodecode:templates/admin/users/user_edit.mako')
434 renderer='rhodecode:templates/admin/users/user_edit.mako')
439 def groups_management(self):
435 def groups_management(self):
440 c = self.load_default_context()
436 c = self.load_default_context()
441
437
442 user_id = self.request.matchdict.get('user_id')
438 user_id = self.request.matchdict.get('user_id')
443 c.user = User.get_or_404(user_id, pyramid_exc=True)
439 c.user = User.get_or_404(user_id, pyramid_exc=True)
444 c.data = c.user.group_member
440 c.data = c.user.group_member
445 self._redirect_for_default_user(c.user.username)
441 self._redirect_for_default_user(c.user.username)
446 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
442 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
447 for group in c.user.group_member]
443 for group in c.user.group_member]
448 c.groups = json.dumps(groups)
444 c.groups = json.dumps(groups)
449 c.active = 'groups'
445 c.active = 'groups'
450
446
451 return self._get_template_context(c)
447 return self._get_template_context(c)
452
448
453 @LoginRequired()
449 @LoginRequired()
454 @HasPermissionAllDecorator('hg.admin')
450 @HasPermissionAllDecorator('hg.admin')
455 @CSRFRequired()
451 @CSRFRequired()
456 @view_config(
452 @view_config(
457 route_name='edit_user_groups_management_updates', request_method='POST')
453 route_name='edit_user_groups_management_updates', request_method='POST')
458 def groups_management_updates(self):
454 def groups_management_updates(self):
459 _ = self.request.translate
455 _ = self.request.translate
460 c = self.load_default_context()
456 c = self.load_default_context()
461
457
462 user_id = self.request.matchdict.get('user_id')
458 user_id = self.request.matchdict.get('user_id')
463 c.user = User.get_or_404(user_id, pyramid_exc=True)
459 c.user = User.get_or_404(user_id, pyramid_exc=True)
464 self._redirect_for_default_user(c.user.username)
460 self._redirect_for_default_user(c.user.username)
465
461
466 users_groups = set(self.request.POST.getall('users_group_id'))
462 users_groups = set(self.request.POST.getall('users_group_id'))
467 users_groups_model = []
463 users_groups_model = []
468
464
469 for ugid in users_groups:
465 for ugid in users_groups:
470 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
466 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
471 user_group_model = UserGroupModel()
467 user_group_model = UserGroupModel()
472 user_group_model.change_groups(c.user, users_groups_model)
468 user_group_model.change_groups(c.user, users_groups_model)
473
469
474 Session().commit()
470 Session().commit()
475 c.active = 'user_groups_management'
471 c.active = 'user_groups_management'
476 h.flash(_("Groups successfully changed"), category='success')
472 h.flash(_("Groups successfully changed"), category='success')
477
473
478 return HTTPFound(h.route_path(
474 return HTTPFound(h.route_path(
479 'edit_user_groups_management', user_id=user_id))
475 'edit_user_groups_management', user_id=user_id))
480
476
481 @LoginRequired()
477 @LoginRequired()
482 @HasPermissionAllDecorator('hg.admin')
478 @HasPermissionAllDecorator('hg.admin')
483 @view_config(
479 @view_config(
484 route_name='edit_user_audit_logs', request_method='GET',
480 route_name='edit_user_audit_logs', request_method='GET',
485 renderer='rhodecode:templates/admin/users/user_edit.mako')
481 renderer='rhodecode:templates/admin/users/user_edit.mako')
486 def user_audit_logs(self):
482 def user_audit_logs(self):
487 _ = self.request.translate
483 _ = self.request.translate
488 c = self.load_default_context()
484 c = self.load_default_context()
489
485
490 user_id = self.request.matchdict.get('user_id')
486 user_id = self.request.matchdict.get('user_id')
491 c.user = User.get_or_404(user_id, pyramid_exc=True)
487 c.user = User.get_or_404(user_id, pyramid_exc=True)
492 self._redirect_for_default_user(c.user.username)
488 self._redirect_for_default_user(c.user.username)
493 c.active = 'audit'
489 c.active = 'audit'
494
490
495 p = safe_int(self.request.GET.get('page', 1), 1)
491 p = safe_int(self.request.GET.get('page', 1), 1)
496
492
497 filter_term = self.request.GET.get('filter')
493 filter_term = self.request.GET.get('filter')
498 user_log = UserModel().get_user_log(c.user, filter_term)
494 user_log = UserModel().get_user_log(c.user, filter_term)
499
495
500 def url_generator(**kw):
496 def url_generator(**kw):
501 if filter_term:
497 if filter_term:
502 kw['filter'] = filter_term
498 kw['filter'] = filter_term
503 return self.request.current_route_path(_query=kw)
499 return self.request.current_route_path(_query=kw)
504
500
505 c.audit_logs = h.Page(
501 c.audit_logs = h.Page(
506 user_log, page=p, items_per_page=10, url=url_generator)
502 user_log, page=p, items_per_page=10, url=url_generator)
507 c.filter_term = filter_term
503 c.filter_term = filter_term
508 return self._get_template_context(c)
504 return self._get_template_context(c)
509
505
@@ -1,426 +1,425 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import logging
25 import logging
26 import urlparse
26 import urlparse
27
27
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from recaptcha.client.captcha import submit
30 from recaptcha.client.captcha import submit
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
33 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.events import UserRegistered
34 from rhodecode.events import UserRegistered
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import audit_logger
36 from rhodecode.lib import audit_logger
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
38 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.exceptions import UserCreationError
40 from rhodecode.lib.exceptions import UserCreationError
41 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.model.db import User, UserApiKeys
42 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
43 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.auth_token import AuthTokenModel
45 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.translation import _
48 from rhodecode.translation import _
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53 CaptchaData = collections.namedtuple(
53 CaptchaData = collections.namedtuple(
54 'CaptchaData', 'active, private_key, public_key')
54 'CaptchaData', 'active, private_key, public_key')
55
55
56
56
57 def _store_user_in_session(session, username, remember=False):
57 def _store_user_in_session(session, username, remember=False):
58 user = User.get_by_username(username, case_insensitive=True)
58 user = User.get_by_username(username, case_insensitive=True)
59 auth_user = AuthUser(user.user_id)
59 auth_user = AuthUser(user.user_id)
60 auth_user.set_authenticated()
60 auth_user.set_authenticated()
61 cs = auth_user.get_cookie_store()
61 cs = auth_user.get_cookie_store()
62 session['rhodecode_user'] = cs
62 session['rhodecode_user'] = cs
63 user.update_lastlogin()
63 user.update_lastlogin()
64 Session().commit()
64 Session().commit()
65
65
66 # If they want to be remembered, update the cookie
66 # If they want to be remembered, update the cookie
67 if remember:
67 if remember:
68 _year = (datetime.datetime.now() +
68 _year = (datetime.datetime.now() +
69 datetime.timedelta(seconds=60 * 60 * 24 * 365))
69 datetime.timedelta(seconds=60 * 60 * 24 * 365))
70 session._set_cookie_expires(_year)
70 session._set_cookie_expires(_year)
71
71
72 session.save()
72 session.save()
73
73
74 safe_cs = cs.copy()
74 safe_cs = cs.copy()
75 safe_cs['password'] = '****'
75 safe_cs['password'] = '****'
76 log.info('user %s is now authenticated and stored in '
76 log.info('user %s is now authenticated and stored in '
77 'session, session attrs %s', username, safe_cs)
77 'session, session attrs %s', username, safe_cs)
78
78
79 # dumps session attrs back to cookie
79 # dumps session attrs back to cookie
80 session._update_cookie_out()
80 session._update_cookie_out()
81 # we set new cookie
81 # we set new cookie
82 headers = None
82 headers = None
83 if session.request['set_cookie']:
83 if session.request['set_cookie']:
84 # send set-cookie headers back to response to update cookie
84 # send set-cookie headers back to response to update cookie
85 headers = [('Set-Cookie', session.request['cookie_out'])]
85 headers = [('Set-Cookie', session.request['cookie_out'])]
86 return headers
86 return headers
87
87
88
88
89 def get_came_from(request):
89 def get_came_from(request):
90 came_from = safe_str(request.GET.get('came_from', ''))
90 came_from = safe_str(request.GET.get('came_from', ''))
91 parsed = urlparse.urlparse(came_from)
91 parsed = urlparse.urlparse(came_from)
92 allowed_schemes = ['http', 'https']
92 allowed_schemes = ['http', 'https']
93 default_came_from = h.route_path('home')
93 default_came_from = h.route_path('home')
94 if parsed.scheme and parsed.scheme not in allowed_schemes:
94 if parsed.scheme and parsed.scheme not in allowed_schemes:
95 log.error('Suspicious URL scheme detected %s for url %s' %
95 log.error('Suspicious URL scheme detected %s for url %s' %
96 (parsed.scheme, parsed))
96 (parsed.scheme, parsed))
97 came_from = default_came_from
97 came_from = default_came_from
98 elif parsed.netloc and request.host != parsed.netloc:
98 elif parsed.netloc and request.host != parsed.netloc:
99 log.error('Suspicious NETLOC detected %s for url %s server url '
99 log.error('Suspicious NETLOC detected %s for url %s server url '
100 'is: %s' % (parsed.netloc, parsed, request.host))
100 'is: %s' % (parsed.netloc, parsed, request.host))
101 came_from = default_came_from
101 came_from = default_came_from
102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
102 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
103 log.error('Header injection detected `%s` for url %s server url ' %
103 log.error('Header injection detected `%s` for url %s server url ' %
104 (parsed.path, parsed))
104 (parsed.path, parsed))
105 came_from = default_came_from
105 came_from = default_came_from
106
106
107 return came_from or default_came_from
107 return came_from or default_came_from
108
108
109
109
110 class LoginView(BaseAppView):
110 class LoginView(BaseAppView):
111
111
112 def load_default_context(self):
112 def load_default_context(self):
113 c = self._get_local_tmpl_context()
113 c = self._get_local_tmpl_context()
114 c.came_from = get_came_from(self.request)
114 c.came_from = get_came_from(self.request)
115 self._register_global_c(c)
115 self._register_global_c(c)
116 return c
116 return c
117
117
118 def _get_captcha_data(self):
118 def _get_captcha_data(self):
119 settings = SettingsModel().get_all_settings()
119 settings = SettingsModel().get_all_settings()
120 private_key = settings.get('rhodecode_captcha_private_key')
120 private_key = settings.get('rhodecode_captcha_private_key')
121 public_key = settings.get('rhodecode_captcha_public_key')
121 public_key = settings.get('rhodecode_captcha_public_key')
122 active = bool(private_key)
122 active = bool(private_key)
123 return CaptchaData(
123 return CaptchaData(
124 active=active, private_key=private_key, public_key=public_key)
124 active=active, private_key=private_key, public_key=public_key)
125
125
126 @view_config(
126 @view_config(
127 route_name='login', request_method='GET',
127 route_name='login', request_method='GET',
128 renderer='rhodecode:templates/login.mako')
128 renderer='rhodecode:templates/login.mako')
129 def login(self):
129 def login(self):
130 c = self.load_default_context()
130 c = self.load_default_context()
131 auth_user = self._rhodecode_user
131 auth_user = self._rhodecode_user
132
132
133 # redirect if already logged in
133 # redirect if already logged in
134 if (auth_user.is_authenticated and
134 if (auth_user.is_authenticated and
135 not auth_user.is_default and auth_user.ip_allowed):
135 not auth_user.is_default and auth_user.ip_allowed):
136 raise HTTPFound(c.came_from)
136 raise HTTPFound(c.came_from)
137
137
138 # check if we use headers plugin, and try to login using it.
138 # check if we use headers plugin, and try to login using it.
139 try:
139 try:
140 log.debug('Running PRE-AUTH for headers based authentication')
140 log.debug('Running PRE-AUTH for headers based authentication')
141 auth_info = authenticate(
141 auth_info = authenticate(
142 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
142 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
143 if auth_info:
143 if auth_info:
144 headers = _store_user_in_session(
144 headers = _store_user_in_session(
145 self.session, auth_info.get('username'))
145 self.session, auth_info.get('username'))
146 raise HTTPFound(c.came_from, headers=headers)
146 raise HTTPFound(c.came_from, headers=headers)
147 except UserCreationError as e:
147 except UserCreationError as e:
148 log.error(e)
148 log.error(e)
149 self.session.flash(e, queue='error')
149 self.session.flash(e, queue='error')
150
150
151 return self._get_template_context(c)
151 return self._get_template_context(c)
152
152
153 @view_config(
153 @view_config(
154 route_name='login', request_method='POST',
154 route_name='login', request_method='POST',
155 renderer='rhodecode:templates/login.mako')
155 renderer='rhodecode:templates/login.mako')
156 def login_post(self):
156 def login_post(self):
157 c = self.load_default_context()
157 c = self.load_default_context()
158
158
159 login_form = LoginForm()()
159 login_form = LoginForm()()
160
160
161 try:
161 try:
162 self.session.invalidate()
162 self.session.invalidate()
163 form_result = login_form.to_python(self.request.params)
163 form_result = login_form.to_python(self.request.params)
164 # form checks for username/password, now we're authenticated
164 # form checks for username/password, now we're authenticated
165 headers = _store_user_in_session(
165 headers = _store_user_in_session(
166 self.session,
166 self.session,
167 username=form_result['username'],
167 username=form_result['username'],
168 remember=form_result['remember'])
168 remember=form_result['remember'])
169 log.debug('Redirecting to "%s" after login.', c.came_from)
169 log.debug('Redirecting to "%s" after login.', c.came_from)
170
170
171 audit_user = audit_logger.UserWrap(
171 audit_user = audit_logger.UserWrap(
172 username=self.request.params.get('username'),
172 username=self.request.params.get('username'),
173 ip_addr=self.request.remote_addr)
173 ip_addr=self.request.remote_addr)
174 action_data = {'user_agent': self.request.user_agent}
174 action_data = {'user_agent': self.request.user_agent}
175 audit_logger.store_web(
175 audit_logger.store_web(
176 action='user.login.success', action_data=action_data,
176 'user.login.success', action_data=action_data,
177 user=audit_user, commit=True)
177 user=audit_user, commit=True)
178
178
179 raise HTTPFound(c.came_from, headers=headers)
179 raise HTTPFound(c.came_from, headers=headers)
180 except formencode.Invalid as errors:
180 except formencode.Invalid as errors:
181 defaults = errors.value
181 defaults = errors.value
182 # remove password from filling in form again
182 # remove password from filling in form again
183 defaults.pop('password', None)
183 defaults.pop('password', None)
184 render_ctx = self._get_template_context(c)
184 render_ctx = self._get_template_context(c)
185 render_ctx.update({
185 render_ctx.update({
186 'errors': errors.error_dict,
186 'errors': errors.error_dict,
187 'defaults': defaults,
187 'defaults': defaults,
188 })
188 })
189
189
190 audit_user = audit_logger.UserWrap(
190 audit_user = audit_logger.UserWrap(
191 username=self.request.params.get('username'),
191 username=self.request.params.get('username'),
192 ip_addr=self.request.remote_addr)
192 ip_addr=self.request.remote_addr)
193 action_data = {'user_agent': self.request.user_agent}
193 action_data = {'user_agent': self.request.user_agent}
194 audit_logger.store_web(
194 audit_logger.store_web(
195 action='user.login.failure', action_data=action_data,
195 'user.login.failure', action_data=action_data,
196 user=audit_user, commit=True)
196 user=audit_user, commit=True)
197 return render_ctx
197 return render_ctx
198
198
199 except UserCreationError as e:
199 except UserCreationError as e:
200 # headers auth or other auth functions that create users on
200 # headers auth or other auth functions that create users on
201 # the fly can throw this exception signaling that there's issue
201 # the fly can throw this exception signaling that there's issue
202 # with user creation, explanation should be provided in
202 # with user creation, explanation should be provided in
203 # Exception itself
203 # Exception itself
204 self.session.flash(e, queue='error')
204 self.session.flash(e, queue='error')
205 return self._get_template_context(c)
205 return self._get_template_context(c)
206
206
207 @CSRFRequired()
207 @CSRFRequired()
208 @view_config(route_name='logout', request_method='POST')
208 @view_config(route_name='logout', request_method='POST')
209 def logout(self):
209 def logout(self):
210 auth_user = self._rhodecode_user
210 auth_user = self._rhodecode_user
211 log.info('Deleting session for user: `%s`', auth_user)
211 log.info('Deleting session for user: `%s`', auth_user)
212
212
213 action_data = {'user_agent': self.request.user_agent}
213 action_data = {'user_agent': self.request.user_agent}
214 audit_logger.store_web(
214 audit_logger.store_web(
215 action='user.logout', action_data=action_data,
215 'user.logout', action_data=action_data,
216 user=auth_user, commit=True)
216 user=auth_user, commit=True)
217 self.session.delete()
217 self.session.delete()
218 return HTTPFound(h.route_path('home'))
218 return HTTPFound(h.route_path('home'))
219
219
220 @HasPermissionAnyDecorator(
220 @HasPermissionAnyDecorator(
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
221 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
222 @view_config(
222 @view_config(
223 route_name='register', request_method='GET',
223 route_name='register', request_method='GET',
224 renderer='rhodecode:templates/register.mako',)
224 renderer='rhodecode:templates/register.mako',)
225 def register(self, defaults=None, errors=None):
225 def register(self, defaults=None, errors=None):
226 c = self.load_default_context()
226 c = self.load_default_context()
227 defaults = defaults or {}
227 defaults = defaults or {}
228 errors = errors or {}
228 errors = errors or {}
229
229
230 settings = SettingsModel().get_all_settings()
230 settings = SettingsModel().get_all_settings()
231 register_message = settings.get('rhodecode_register_message') or ''
231 register_message = settings.get('rhodecode_register_message') or ''
232 captcha = self._get_captcha_data()
232 captcha = self._get_captcha_data()
233 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
233 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
234 .AuthUser.permissions['global']
234 .AuthUser.permissions['global']
235
235
236 render_ctx = self._get_template_context(c)
236 render_ctx = self._get_template_context(c)
237 render_ctx.update({
237 render_ctx.update({
238 'defaults': defaults,
238 'defaults': defaults,
239 'errors': errors,
239 'errors': errors,
240 'auto_active': auto_active,
240 'auto_active': auto_active,
241 'captcha_active': captcha.active,
241 'captcha_active': captcha.active,
242 'captcha_public_key': captcha.public_key,
242 'captcha_public_key': captcha.public_key,
243 'register_message': register_message,
243 'register_message': register_message,
244 })
244 })
245 return render_ctx
245 return render_ctx
246
246
247 @HasPermissionAnyDecorator(
247 @HasPermissionAnyDecorator(
248 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
248 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
249 @view_config(
249 @view_config(
250 route_name='register', request_method='POST',
250 route_name='register', request_method='POST',
251 renderer='rhodecode:templates/register.mako')
251 renderer='rhodecode:templates/register.mako')
252 def register_post(self):
252 def register_post(self):
253 captcha = self._get_captcha_data()
253 captcha = self._get_captcha_data()
254 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
254 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
255 .AuthUser.permissions['global']
255 .AuthUser.permissions['global']
256
256
257 register_form = RegisterForm()()
257 register_form = RegisterForm()()
258 try:
258 try:
259 form_result = register_form.to_python(self.request.params)
259 form_result = register_form.to_python(self.request.params)
260 form_result['active'] = auto_active
260 form_result['active'] = auto_active
261
261
262 if captcha.active:
262 if captcha.active:
263 response = submit(
263 response = submit(
264 self.request.params.get('recaptcha_challenge_field'),
264 self.request.params.get('recaptcha_challenge_field'),
265 self.request.params.get('recaptcha_response_field'),
265 self.request.params.get('recaptcha_response_field'),
266 private_key=captcha.private_key,
266 private_key=captcha.private_key,
267 remoteip=get_ip_addr(self.request.environ))
267 remoteip=get_ip_addr(self.request.environ))
268 if not response.is_valid:
268 if not response.is_valid:
269 _value = form_result
269 _value = form_result
270 _msg = _('Bad captcha')
270 _msg = _('Bad captcha')
271 error_dict = {'recaptcha_field': _msg}
271 error_dict = {'recaptcha_field': _msg}
272 raise formencode.Invalid(_msg, _value, None,
272 raise formencode.Invalid(_msg, _value, None,
273 error_dict=error_dict)
273 error_dict=error_dict)
274
274
275 new_user = UserModel().create_registration(form_result)
275 new_user = UserModel().create_registration(form_result)
276 event = UserRegistered(user=new_user, session=self.session)
276 event = UserRegistered(user=new_user, session=self.session)
277 self.request.registry.notify(event)
277 self.request.registry.notify(event)
278 self.session.flash(
278 self.session.flash(
279 _('You have successfully registered with RhodeCode'),
279 _('You have successfully registered with RhodeCode'),
280 queue='success')
280 queue='success')
281 Session().commit()
281 Session().commit()
282
282
283 redirect_ro = self.request.route_path('login')
283 redirect_ro = self.request.route_path('login')
284 raise HTTPFound(redirect_ro)
284 raise HTTPFound(redirect_ro)
285
285
286 except formencode.Invalid as errors:
286 except formencode.Invalid as errors:
287 errors.value.pop('password', None)
287 errors.value.pop('password', None)
288 errors.value.pop('password_confirmation', None)
288 errors.value.pop('password_confirmation', None)
289 return self.register(
289 return self.register(
290 defaults=errors.value, errors=errors.error_dict)
290 defaults=errors.value, errors=errors.error_dict)
291
291
292 except UserCreationError as e:
292 except UserCreationError as e:
293 # container auth or other auth functions that create users on
293 # container auth or other auth functions that create users on
294 # the fly can throw this exception signaling that there's issue
294 # the fly can throw this exception signaling that there's issue
295 # with user creation, explanation should be provided in
295 # with user creation, explanation should be provided in
296 # Exception itself
296 # Exception itself
297 self.session.flash(e, queue='error')
297 self.session.flash(e, queue='error')
298 return self.register()
298 return self.register()
299
299
300 @view_config(
300 @view_config(
301 route_name='reset_password', request_method=('GET', 'POST'),
301 route_name='reset_password', request_method=('GET', 'POST'),
302 renderer='rhodecode:templates/password_reset.mako')
302 renderer='rhodecode:templates/password_reset.mako')
303 def password_reset(self):
303 def password_reset(self):
304 captcha = self._get_captcha_data()
304 captcha = self._get_captcha_data()
305
305
306 render_ctx = {
306 render_ctx = {
307 'captcha_active': captcha.active,
307 'captcha_active': captcha.active,
308 'captcha_public_key': captcha.public_key,
308 'captcha_public_key': captcha.public_key,
309 'defaults': {},
309 'defaults': {},
310 'errors': {},
310 'errors': {},
311 }
311 }
312
312
313 # always send implicit message to prevent from discovery of
313 # always send implicit message to prevent from discovery of
314 # matching emails
314 # matching emails
315 msg = _('If such email exists, a password reset link was sent to it.')
315 msg = _('If such email exists, a password reset link was sent to it.')
316
316
317 if self.request.POST:
317 if self.request.POST:
318 if h.HasPermissionAny('hg.password_reset.disabled')():
318 if h.HasPermissionAny('hg.password_reset.disabled')():
319 _email = self.request.POST.get('email', '')
319 _email = self.request.POST.get('email', '')
320 log.error('Failed attempt to reset password for `%s`.', _email)
320 log.error('Failed attempt to reset password for `%s`.', _email)
321 self.session.flash(_('Password reset has been disabled.'),
321 self.session.flash(_('Password reset has been disabled.'),
322 queue='error')
322 queue='error')
323 return HTTPFound(self.request.route_path('reset_password'))
323 return HTTPFound(self.request.route_path('reset_password'))
324
324
325 password_reset_form = PasswordResetForm()()
325 password_reset_form = PasswordResetForm()()
326 try:
326 try:
327 form_result = password_reset_form.to_python(
327 form_result = password_reset_form.to_python(
328 self.request.params)
328 self.request.params)
329 user_email = form_result['email']
329 user_email = form_result['email']
330
330
331 if captcha.active:
331 if captcha.active:
332 response = submit(
332 response = submit(
333 self.request.params.get('recaptcha_challenge_field'),
333 self.request.params.get('recaptcha_challenge_field'),
334 self.request.params.get('recaptcha_response_field'),
334 self.request.params.get('recaptcha_response_field'),
335 private_key=captcha.private_key,
335 private_key=captcha.private_key,
336 remoteip=get_ip_addr(self.request.environ))
336 remoteip=get_ip_addr(self.request.environ))
337 if not response.is_valid:
337 if not response.is_valid:
338 _value = form_result
338 _value = form_result
339 _msg = _('Bad captcha')
339 _msg = _('Bad captcha')
340 error_dict = {'recaptcha_field': _msg}
340 error_dict = {'recaptcha_field': _msg}
341 raise formencode.Invalid(
341 raise formencode.Invalid(
342 _msg, _value, None, error_dict=error_dict)
342 _msg, _value, None, error_dict=error_dict)
343
343
344 # Generate reset URL and send mail.
344 # Generate reset URL and send mail.
345 user = User.get_by_email(user_email)
345 user = User.get_by_email(user_email)
346
346
347 # generate password reset token that expires in 10minutes
347 # generate password reset token that expires in 10minutes
348 desc = 'Generated token for password reset from {}'.format(
348 desc = 'Generated token for password reset from {}'.format(
349 datetime.datetime.now().isoformat())
349 datetime.datetime.now().isoformat())
350 reset_token = AuthTokenModel().create(
350 reset_token = AuthTokenModel().create(
351 user, lifetime=10,
351 user, lifetime=10,
352 description=desc,
352 description=desc,
353 role=UserApiKeys.ROLE_PASSWORD_RESET)
353 role=UserApiKeys.ROLE_PASSWORD_RESET)
354 Session().commit()
354 Session().commit()
355
355
356 log.debug('Successfully created password recovery token')
356 log.debug('Successfully created password recovery token')
357 password_reset_url = self.request.route_url(
357 password_reset_url = self.request.route_url(
358 'reset_password_confirmation',
358 'reset_password_confirmation',
359 _query={'key': reset_token.api_key})
359 _query={'key': reset_token.api_key})
360 UserModel().reset_password_link(
360 UserModel().reset_password_link(
361 form_result, password_reset_url)
361 form_result, password_reset_url)
362 # Display success message and redirect.
362 # Display success message and redirect.
363 self.session.flash(msg, queue='success')
363 self.session.flash(msg, queue='success')
364
364
365 action_data = {'email': user_email,
365 action_data = {'email': user_email,
366 'user_agent': self.request.user_agent}
366 'user_agent': self.request.user_agent}
367 audit_logger.store_web(
367 audit_logger.store_web(
368 action='user.password.reset_request',
368 'user.password.reset_request', action_data=action_data,
369 action_data=action_data,
370 user=self._rhodecode_user, commit=True)
369 user=self._rhodecode_user, commit=True)
371 return HTTPFound(self.request.route_path('reset_password'))
370 return HTTPFound(self.request.route_path('reset_password'))
372
371
373 except formencode.Invalid as errors:
372 except formencode.Invalid as errors:
374 render_ctx.update({
373 render_ctx.update({
375 'defaults': errors.value,
374 'defaults': errors.value,
376 'errors': errors.error_dict,
375 'errors': errors.error_dict,
377 })
376 })
378 if not self.request.params.get('email'):
377 if not self.request.params.get('email'):
379 # case of empty email, we want to report that
378 # case of empty email, we want to report that
380 return render_ctx
379 return render_ctx
381
380
382 if 'recaptcha_field' in errors.error_dict:
381 if 'recaptcha_field' in errors.error_dict:
383 # case of failed captcha
382 # case of failed captcha
384 return render_ctx
383 return render_ctx
385
384
386 log.debug('faking response on invalid password reset')
385 log.debug('faking response on invalid password reset')
387 # make this take 2s, to prevent brute forcing.
386 # make this take 2s, to prevent brute forcing.
388 time.sleep(2)
387 time.sleep(2)
389 self.session.flash(msg, queue='success')
388 self.session.flash(msg, queue='success')
390 return HTTPFound(self.request.route_path('reset_password'))
389 return HTTPFound(self.request.route_path('reset_password'))
391
390
392 return render_ctx
391 return render_ctx
393
392
394 @view_config(route_name='reset_password_confirmation',
393 @view_config(route_name='reset_password_confirmation',
395 request_method='GET')
394 request_method='GET')
396 def password_reset_confirmation(self):
395 def password_reset_confirmation(self):
397
396
398 if self.request.GET and self.request.GET.get('key'):
397 if self.request.GET and self.request.GET.get('key'):
399 # make this take 2s, to prevent brute forcing.
398 # make this take 2s, to prevent brute forcing.
400 time.sleep(2)
399 time.sleep(2)
401
400
402 token = AuthTokenModel().get_auth_token(
401 token = AuthTokenModel().get_auth_token(
403 self.request.GET.get('key'))
402 self.request.GET.get('key'))
404
403
405 # verify token is the correct role
404 # verify token is the correct role
406 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
405 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
407 log.debug('Got token with role:%s expected is %s',
406 log.debug('Got token with role:%s expected is %s',
408 getattr(token, 'role', 'EMPTY_TOKEN'),
407 getattr(token, 'role', 'EMPTY_TOKEN'),
409 UserApiKeys.ROLE_PASSWORD_RESET)
408 UserApiKeys.ROLE_PASSWORD_RESET)
410 self.session.flash(
409 self.session.flash(
411 _('Given reset token is invalid'), queue='error')
410 _('Given reset token is invalid'), queue='error')
412 return HTTPFound(self.request.route_path('reset_password'))
411 return HTTPFound(self.request.route_path('reset_password'))
413
412
414 try:
413 try:
415 owner = token.user
414 owner = token.user
416 data = {'email': owner.email, 'token': token.api_key}
415 data = {'email': owner.email, 'token': token.api_key}
417 UserModel().reset_password(data)
416 UserModel().reset_password(data)
418 self.session.flash(
417 self.session.flash(
419 _('Your password reset was successful, '
418 _('Your password reset was successful, '
420 'a new password has been sent to your email'),
419 'a new password has been sent to your email'),
421 queue='success')
420 queue='success')
422 except Exception as e:
421 except Exception as e:
423 log.error(e)
422 log.error(e)
424 return HTTPFound(self.request.route_path('reset_password'))
423 return HTTPFound(self.request.route_path('reset_password'))
425
424
426 return HTTPFound(self.request.route_path('login'))
425 return HTTPFound(self.request.route_path('login'))
@@ -1,399 +1,399 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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
23
24 import formencode
24 import formencode
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27
27
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode import forms
29 from rhodecode import forms
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.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
34 from rhodecode.lib.channelstream import channelstream_request, \
34 from rhodecode.lib.channelstream import channelstream_request, \
35 ChannelstreamException
35 ChannelstreamException
36 from rhodecode.lib.utils2 import safe_int, md5
36 from rhodecode.lib.utils2 import safe_int, md5
37 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.auth_token import AuthTokenModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
39 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload)
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.scm import RepoList
41 from rhodecode.model.scm import RepoList
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.validation_schema.schemas import user_schema
44 from rhodecode.model.validation_schema.schemas import user_schema
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class MyAccountView(BaseAppView):
49 class MyAccountView(BaseAppView):
50 ALLOW_SCOPED_TOKENS = False
50 ALLOW_SCOPED_TOKENS = False
51 """
51 """
52 This view has alternative version inside EE, if modified please take a look
52 This view has alternative version inside EE, if modified please take a look
53 in there as well.
53 in there as well.
54 """
54 """
55
55
56 def load_default_context(self):
56 def load_default_context(self):
57 c = self._get_local_tmpl_context()
57 c = self._get_local_tmpl_context()
58 c.user = c.auth_user.get_instance()
58 c.user = c.auth_user.get_instance()
59 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
59 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
60 self._register_global_c(c)
60 self._register_global_c(c)
61 return c
61 return c
62
62
63 @LoginRequired()
63 @LoginRequired()
64 @NotAnonymous()
64 @NotAnonymous()
65 @view_config(
65 @view_config(
66 route_name='my_account_profile', request_method='GET',
66 route_name='my_account_profile', request_method='GET',
67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 def my_account_profile(self):
68 def my_account_profile(self):
69 c = self.load_default_context()
69 c = self.load_default_context()
70 c.active = 'profile'
70 c.active = 'profile'
71 return self._get_template_context(c)
71 return self._get_template_context(c)
72
72
73 @LoginRequired()
73 @LoginRequired()
74 @NotAnonymous()
74 @NotAnonymous()
75 @view_config(
75 @view_config(
76 route_name='my_account_password', request_method='GET',
76 route_name='my_account_password', request_method='GET',
77 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 renderer='rhodecode:templates/admin/my_account/my_account.mako')
78 def my_account_password(self):
78 def my_account_password(self):
79 c = self.load_default_context()
79 c = self.load_default_context()
80 c.active = 'password'
80 c.active = 'password'
81 c.extern_type = c.user.extern_type
81 c.extern_type = c.user.extern_type
82
82
83 schema = user_schema.ChangePasswordSchema().bind(
83 schema = user_schema.ChangePasswordSchema().bind(
84 username=c.user.username)
84 username=c.user.username)
85
85
86 form = forms.Form(
86 form = forms.Form(
87 schema, buttons=(forms.buttons.save, forms.buttons.reset))
87 schema, buttons=(forms.buttons.save, forms.buttons.reset))
88
88
89 c.form = form
89 c.form = form
90 return self._get_template_context(c)
90 return self._get_template_context(c)
91
91
92 @LoginRequired()
92 @LoginRequired()
93 @NotAnonymous()
93 @NotAnonymous()
94 @CSRFRequired()
94 @CSRFRequired()
95 @view_config(
95 @view_config(
96 route_name='my_account_password', request_method='POST',
96 route_name='my_account_password', request_method='POST',
97 renderer='rhodecode:templates/admin/my_account/my_account.mako')
97 renderer='rhodecode:templates/admin/my_account/my_account.mako')
98 def my_account_password_update(self):
98 def my_account_password_update(self):
99 _ = self.request.translate
99 _ = self.request.translate
100 c = self.load_default_context()
100 c = self.load_default_context()
101 c.active = 'password'
101 c.active = 'password'
102 c.extern_type = c.user.extern_type
102 c.extern_type = c.user.extern_type
103
103
104 schema = user_schema.ChangePasswordSchema().bind(
104 schema = user_schema.ChangePasswordSchema().bind(
105 username=c.user.username)
105 username=c.user.username)
106
106
107 form = forms.Form(
107 form = forms.Form(
108 schema, buttons=(forms.buttons.save, forms.buttons.reset))
108 schema, buttons=(forms.buttons.save, forms.buttons.reset))
109
109
110 if c.extern_type != 'rhodecode':
110 if c.extern_type != 'rhodecode':
111 raise HTTPFound(self.request.route_path('my_account_password'))
111 raise HTTPFound(self.request.route_path('my_account_password'))
112
112
113 controls = self.request.POST.items()
113 controls = self.request.POST.items()
114 try:
114 try:
115 valid_data = form.validate(controls)
115 valid_data = form.validate(controls)
116 UserModel().update_user(c.user.user_id, **valid_data)
116 UserModel().update_user(c.user.user_id, **valid_data)
117 c.user.update_userdata(force_password_change=False)
117 c.user.update_userdata(force_password_change=False)
118 Session().commit()
118 Session().commit()
119 except forms.ValidationFailure as e:
119 except forms.ValidationFailure as e:
120 c.form = e
120 c.form = e
121 return self._get_template_context(c)
121 return self._get_template_context(c)
122
122
123 except Exception:
123 except Exception:
124 log.exception("Exception updating password")
124 log.exception("Exception updating password")
125 h.flash(_('Error occurred during update of user password'),
125 h.flash(_('Error occurred during update of user password'),
126 category='error')
126 category='error')
127 else:
127 else:
128 instance = c.auth_user.get_instance()
128 instance = c.auth_user.get_instance()
129 self.session.setdefault('rhodecode_user', {}).update(
129 self.session.setdefault('rhodecode_user', {}).update(
130 {'password': md5(instance.password)})
130 {'password': md5(instance.password)})
131 self.session.save()
131 self.session.save()
132 h.flash(_("Successfully updated password"), category='success')
132 h.flash(_("Successfully updated password"), category='success')
133
133
134 raise HTTPFound(self.request.route_path('my_account_password'))
134 raise HTTPFound(self.request.route_path('my_account_password'))
135
135
136 @LoginRequired()
136 @LoginRequired()
137 @NotAnonymous()
137 @NotAnonymous()
138 @view_config(
138 @view_config(
139 route_name='my_account_auth_tokens', request_method='GET',
139 route_name='my_account_auth_tokens', request_method='GET',
140 renderer='rhodecode:templates/admin/my_account/my_account.mako')
140 renderer='rhodecode:templates/admin/my_account/my_account.mako')
141 def my_account_auth_tokens(self):
141 def my_account_auth_tokens(self):
142 _ = self.request.translate
142 _ = self.request.translate
143
143
144 c = self.load_default_context()
144 c = self.load_default_context()
145 c.active = 'auth_tokens'
145 c.active = 'auth_tokens'
146
146
147 c.lifetime_values = [
147 c.lifetime_values = [
148 (str(-1), _('forever')),
148 (str(-1), _('forever')),
149 (str(5), _('5 minutes')),
149 (str(5), _('5 minutes')),
150 (str(60), _('1 hour')),
150 (str(60), _('1 hour')),
151 (str(60 * 24), _('1 day')),
151 (str(60 * 24), _('1 day')),
152 (str(60 * 24 * 30), _('1 month')),
152 (str(60 * 24 * 30), _('1 month')),
153 ]
153 ]
154 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
154 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
155 c.role_values = [
155 c.role_values = [
156 (x, AuthTokenModel.cls._get_role_name(x))
156 (x, AuthTokenModel.cls._get_role_name(x))
157 for x in AuthTokenModel.cls.ROLES]
157 for x in AuthTokenModel.cls.ROLES]
158 c.role_options = [(c.role_values, _("Role"))]
158 c.role_options = [(c.role_values, _("Role"))]
159 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
159 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
160 c.user.user_id, show_expired=True)
160 c.user.user_id, show_expired=True)
161 return self._get_template_context(c)
161 return self._get_template_context(c)
162
162
163 def maybe_attach_token_scope(self, token):
163 def maybe_attach_token_scope(self, token):
164 # implemented in EE edition
164 # implemented in EE edition
165 pass
165 pass
166
166
167 @LoginRequired()
167 @LoginRequired()
168 @NotAnonymous()
168 @NotAnonymous()
169 @CSRFRequired()
169 @CSRFRequired()
170 @view_config(
170 @view_config(
171 route_name='my_account_auth_tokens_add', request_method='POST',)
171 route_name='my_account_auth_tokens_add', request_method='POST',)
172 def my_account_auth_tokens_add(self):
172 def my_account_auth_tokens_add(self):
173 _ = self.request.translate
173 _ = self.request.translate
174 c = self.load_default_context()
174 c = self.load_default_context()
175
175
176 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
176 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
177 description = self.request.POST.get('description')
177 description = self.request.POST.get('description')
178 role = self.request.POST.get('role')
178 role = self.request.POST.get('role')
179
179
180 token = AuthTokenModel().create(
180 token = AuthTokenModel().create(
181 c.user.user_id, description, lifetime, role)
181 c.user.user_id, description, lifetime, role)
182 token_data = token.get_api_data()
182 token_data = token.get_api_data()
183
183
184 self.maybe_attach_token_scope(token)
184 self.maybe_attach_token_scope(token)
185 audit_logger.store_web(
185 audit_logger.store_web(
186 action='user.edit.token.add',
186 'user.edit.token.add', action_data={
187 action_data={'data': {'token': token_data, 'user': 'self'}},
187 'data': {'token': token_data, 'user': 'self'}},
188 user=self._rhodecode_user, )
188 user=self._rhodecode_user, )
189 Session().commit()
189 Session().commit()
190
190
191 h.flash(_("Auth token successfully created"), category='success')
191 h.flash(_("Auth token successfully created"), category='success')
192 return HTTPFound(h.route_path('my_account_auth_tokens'))
192 return HTTPFound(h.route_path('my_account_auth_tokens'))
193
193
194 @LoginRequired()
194 @LoginRequired()
195 @NotAnonymous()
195 @NotAnonymous()
196 @CSRFRequired()
196 @CSRFRequired()
197 @view_config(
197 @view_config(
198 route_name='my_account_auth_tokens_delete', request_method='POST')
198 route_name='my_account_auth_tokens_delete', request_method='POST')
199 def my_account_auth_tokens_delete(self):
199 def my_account_auth_tokens_delete(self):
200 _ = self.request.translate
200 _ = self.request.translate
201 c = self.load_default_context()
201 c = self.load_default_context()
202
202
203 del_auth_token = self.request.POST.get('del_auth_token')
203 del_auth_token = self.request.POST.get('del_auth_token')
204
204
205 if del_auth_token:
205 if del_auth_token:
206 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
206 token = UserApiKeys.get_or_404(del_auth_token, pyramid_exc=True)
207 token_data = token.get_api_data()
207 token_data = token.get_api_data()
208
208
209 AuthTokenModel().delete(del_auth_token, c.user.user_id)
209 AuthTokenModel().delete(del_auth_token, c.user.user_id)
210 audit_logger.store_web(
210 audit_logger.store_web(
211 action='user.edit.token.delete',
211 'user.edit.token.delete', action_data={
212 action_data={'data': {'token': token_data, 'user': 'self'}},
212 'data': {'token': token_data, 'user': 'self'}},
213 user=self._rhodecode_user,)
213 user=self._rhodecode_user,)
214 Session().commit()
214 Session().commit()
215 h.flash(_("Auth token successfully deleted"), category='success')
215 h.flash(_("Auth token successfully deleted"), category='success')
216
216
217 return HTTPFound(h.route_path('my_account_auth_tokens'))
217 return HTTPFound(h.route_path('my_account_auth_tokens'))
218
218
219 @LoginRequired()
219 @LoginRequired()
220 @NotAnonymous()
220 @NotAnonymous()
221 @view_config(
221 @view_config(
222 route_name='my_account_emails', request_method='GET',
222 route_name='my_account_emails', request_method='GET',
223 renderer='rhodecode:templates/admin/my_account/my_account.mako')
223 renderer='rhodecode:templates/admin/my_account/my_account.mako')
224 def my_account_emails(self):
224 def my_account_emails(self):
225 _ = self.request.translate
225 _ = self.request.translate
226
226
227 c = self.load_default_context()
227 c = self.load_default_context()
228 c.active = 'emails'
228 c.active = 'emails'
229
229
230 c.user_email_map = UserEmailMap.query()\
230 c.user_email_map = UserEmailMap.query()\
231 .filter(UserEmailMap.user == c.user).all()
231 .filter(UserEmailMap.user == c.user).all()
232 return self._get_template_context(c)
232 return self._get_template_context(c)
233
233
234 @LoginRequired()
234 @LoginRequired()
235 @NotAnonymous()
235 @NotAnonymous()
236 @CSRFRequired()
236 @CSRFRequired()
237 @view_config(
237 @view_config(
238 route_name='my_account_emails_add', request_method='POST')
238 route_name='my_account_emails_add', request_method='POST')
239 def my_account_emails_add(self):
239 def my_account_emails_add(self):
240 _ = self.request.translate
240 _ = self.request.translate
241 c = self.load_default_context()
241 c = self.load_default_context()
242
242
243 email = self.request.POST.get('new_email')
243 email = self.request.POST.get('new_email')
244
244
245 try:
245 try:
246 UserModel().add_extra_email(c.user.user_id, email)
246 UserModel().add_extra_email(c.user.user_id, email)
247 audit_logger.store_web(
247 audit_logger.store_web(
248 action='user.edit.email.add',
248 'user.edit.email.add', action_data={
249 action_data={'data': {'email': email, 'user': 'self'}},
249 'data': {'email': email, 'user': 'self'}},
250 user=self._rhodecode_user,)
250 user=self._rhodecode_user,)
251
251
252 Session().commit()
252 Session().commit()
253 h.flash(_("Added new email address `%s` for user account") % email,
253 h.flash(_("Added new email address `%s` for user account") % email,
254 category='success')
254 category='success')
255 except formencode.Invalid as error:
255 except formencode.Invalid as error:
256 h.flash(h.escape(error.error_dict['email']), category='error')
256 h.flash(h.escape(error.error_dict['email']), category='error')
257 except Exception:
257 except Exception:
258 log.exception("Exception in my_account_emails")
258 log.exception("Exception in my_account_emails")
259 h.flash(_('An error occurred during email saving'),
259 h.flash(_('An error occurred during email saving'),
260 category='error')
260 category='error')
261 return HTTPFound(h.route_path('my_account_emails'))
261 return HTTPFound(h.route_path('my_account_emails'))
262
262
263 @LoginRequired()
263 @LoginRequired()
264 @NotAnonymous()
264 @NotAnonymous()
265 @CSRFRequired()
265 @CSRFRequired()
266 @view_config(
266 @view_config(
267 route_name='my_account_emails_delete', request_method='POST')
267 route_name='my_account_emails_delete', request_method='POST')
268 def my_account_emails_delete(self):
268 def my_account_emails_delete(self):
269 _ = self.request.translate
269 _ = self.request.translate
270 c = self.load_default_context()
270 c = self.load_default_context()
271
271
272 del_email_id = self.request.POST.get('del_email_id')
272 del_email_id = self.request.POST.get('del_email_id')
273 if del_email_id:
273 if del_email_id:
274 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
274 email = UserEmailMap.get_or_404(del_email_id, pyramid_exc=True).email
275 UserModel().delete_extra_email(c.user.user_id, del_email_id)
275 UserModel().delete_extra_email(c.user.user_id, del_email_id)
276 audit_logger.store_web(
276 audit_logger.store_web(
277 action='user.edit.email.delete',
277 'user.edit.email.delete', action_data={
278 action_data={'data': {'email': email, 'user': 'self'}},
278 'data': {'email': email, 'user': 'self'}},
279 user=self._rhodecode_user,)
279 user=self._rhodecode_user,)
280 Session().commit()
280 Session().commit()
281 h.flash(_("Email successfully deleted"),
281 h.flash(_("Email successfully deleted"),
282 category='success')
282 category='success')
283 return HTTPFound(h.route_path('my_account_emails'))
283 return HTTPFound(h.route_path('my_account_emails'))
284
284
285 @LoginRequired()
285 @LoginRequired()
286 @NotAnonymous()
286 @NotAnonymous()
287 @CSRFRequired()
287 @CSRFRequired()
288 @view_config(
288 @view_config(
289 route_name='my_account_notifications_test_channelstream',
289 route_name='my_account_notifications_test_channelstream',
290 request_method='POST', renderer='json_ext')
290 request_method='POST', renderer='json_ext')
291 def my_account_notifications_test_channelstream(self):
291 def my_account_notifications_test_channelstream(self):
292 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
292 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
293 self._rhodecode_user.username, datetime.datetime.now())
293 self._rhodecode_user.username, datetime.datetime.now())
294 payload = {
294 payload = {
295 # 'channel': 'broadcast',
295 # 'channel': 'broadcast',
296 'type': 'message',
296 'type': 'message',
297 'timestamp': datetime.datetime.utcnow(),
297 'timestamp': datetime.datetime.utcnow(),
298 'user': 'system',
298 'user': 'system',
299 'pm_users': [self._rhodecode_user.username],
299 'pm_users': [self._rhodecode_user.username],
300 'message': {
300 'message': {
301 'message': message,
301 'message': message,
302 'level': 'info',
302 'level': 'info',
303 'topic': '/notifications'
303 'topic': '/notifications'
304 }
304 }
305 }
305 }
306
306
307 registry = self.request.registry
307 registry = self.request.registry
308 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
308 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
309 channelstream_config = rhodecode_plugins.get('channelstream', {})
309 channelstream_config = rhodecode_plugins.get('channelstream', {})
310
310
311 try:
311 try:
312 channelstream_request(channelstream_config, [payload], '/message')
312 channelstream_request(channelstream_config, [payload], '/message')
313 except ChannelstreamException as e:
313 except ChannelstreamException as e:
314 log.exception('Failed to send channelstream data')
314 log.exception('Failed to send channelstream data')
315 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
315 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
316 return {"response": 'Channelstream data sent. '
316 return {"response": 'Channelstream data sent. '
317 'You should see a new live message now.'}
317 'You should see a new live message now.'}
318
318
319 def _load_my_repos_data(self, watched=False):
319 def _load_my_repos_data(self, watched=False):
320 if watched:
320 if watched:
321 admin = False
321 admin = False
322 follows_repos = Session().query(UserFollowing)\
322 follows_repos = Session().query(UserFollowing)\
323 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
323 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
324 .options(joinedload(UserFollowing.follows_repository))\
324 .options(joinedload(UserFollowing.follows_repository))\
325 .all()
325 .all()
326 repo_list = [x.follows_repository for x in follows_repos]
326 repo_list = [x.follows_repository for x in follows_repos]
327 else:
327 else:
328 admin = True
328 admin = True
329 repo_list = Repository.get_all_repos(
329 repo_list = Repository.get_all_repos(
330 user_id=self._rhodecode_user.user_id)
330 user_id=self._rhodecode_user.user_id)
331 repo_list = RepoList(repo_list, perm_set=[
331 repo_list = RepoList(repo_list, perm_set=[
332 'repository.read', 'repository.write', 'repository.admin'])
332 'repository.read', 'repository.write', 'repository.admin'])
333
333
334 repos_data = RepoModel().get_repos_as_dict(
334 repos_data = RepoModel().get_repos_as_dict(
335 repo_list=repo_list, admin=admin)
335 repo_list=repo_list, admin=admin)
336 # json used to render the grid
336 # json used to render the grid
337 return json.dumps(repos_data)
337 return json.dumps(repos_data)
338
338
339 @LoginRequired()
339 @LoginRequired()
340 @NotAnonymous()
340 @NotAnonymous()
341 @view_config(
341 @view_config(
342 route_name='my_account_repos', request_method='GET',
342 route_name='my_account_repos', request_method='GET',
343 renderer='rhodecode:templates/admin/my_account/my_account.mako')
343 renderer='rhodecode:templates/admin/my_account/my_account.mako')
344 def my_account_repos(self):
344 def my_account_repos(self):
345 c = self.load_default_context()
345 c = self.load_default_context()
346 c.active = 'repos'
346 c.active = 'repos'
347
347
348 # json used to render the grid
348 # json used to render the grid
349 c.data = self._load_my_repos_data()
349 c.data = self._load_my_repos_data()
350 return self._get_template_context(c)
350 return self._get_template_context(c)
351
351
352 @LoginRequired()
352 @LoginRequired()
353 @NotAnonymous()
353 @NotAnonymous()
354 @view_config(
354 @view_config(
355 route_name='my_account_watched', request_method='GET',
355 route_name='my_account_watched', request_method='GET',
356 renderer='rhodecode:templates/admin/my_account/my_account.mako')
356 renderer='rhodecode:templates/admin/my_account/my_account.mako')
357 def my_account_watched(self):
357 def my_account_watched(self):
358 c = self.load_default_context()
358 c = self.load_default_context()
359 c.active = 'watched'
359 c.active = 'watched'
360
360
361 # json used to render the grid
361 # json used to render the grid
362 c.data = self._load_my_repos_data(watched=True)
362 c.data = self._load_my_repos_data(watched=True)
363 return self._get_template_context(c)
363 return self._get_template_context(c)
364
364
365 @LoginRequired()
365 @LoginRequired()
366 @NotAnonymous()
366 @NotAnonymous()
367 @view_config(
367 @view_config(
368 route_name='my_account_perms', request_method='GET',
368 route_name='my_account_perms', request_method='GET',
369 renderer='rhodecode:templates/admin/my_account/my_account.mako')
369 renderer='rhodecode:templates/admin/my_account/my_account.mako')
370 def my_account_perms(self):
370 def my_account_perms(self):
371 c = self.load_default_context()
371 c = self.load_default_context()
372 c.active = 'perms'
372 c.active = 'perms'
373
373
374 c.perm_user = c.auth_user
374 c.perm_user = c.auth_user
375 return self._get_template_context(c)
375 return self._get_template_context(c)
376
376
377 @LoginRequired()
377 @LoginRequired()
378 @NotAnonymous()
378 @NotAnonymous()
379 @view_config(
379 @view_config(
380 route_name='my_account_notifications', request_method='GET',
380 route_name='my_account_notifications', request_method='GET',
381 renderer='rhodecode:templates/admin/my_account/my_account.mako')
381 renderer='rhodecode:templates/admin/my_account/my_account.mako')
382 def my_notifications(self):
382 def my_notifications(self):
383 c = self.load_default_context()
383 c = self.load_default_context()
384 c.active = 'notifications'
384 c.active = 'notifications'
385
385
386 return self._get_template_context(c)
386 return self._get_template_context(c)
387
387
388 @LoginRequired()
388 @LoginRequired()
389 @NotAnonymous()
389 @NotAnonymous()
390 @CSRFRequired()
390 @CSRFRequired()
391 @view_config(
391 @view_config(
392 route_name='my_account_notifications_toggle_visibility',
392 route_name='my_account_notifications_toggle_visibility',
393 request_method='POST', renderer='json_ext')
393 request_method='POST', renderer='json_ext')
394 def my_notifications_toggle_visibility(self):
394 def my_notifications_toggle_visibility(self):
395 user = self._rhodecode_db_user
395 user = self._rhodecode_db_user
396 new_status = not user.user_data.get('notification_status', True)
396 new_status = not user.user_data.get('notification_status', True)
397 user.update_userdata(notification_status=new_status)
397 user.update_userdata(notification_status=new_status)
398 Session().commit()
398 Session().commit()
399 return user.user_data['notification_status'] No newline at end of file
399 return user.user_data['notification_status']
@@ -1,227 +1,226 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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.apps._base import RepoAppView
26 from rhodecode.apps._base import RepoAppView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
30 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 from rhodecode.lib.exceptions import AttachedForksError
31 from rhodecode.lib.exceptions import AttachedForksError
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.vcs import RepositoryError
33 from rhodecode.lib.vcs import RepositoryError
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
34 from rhodecode.model.db import Session, UserFollowing, User, Repository
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class RepoSettingsView(RepoAppView):
41 class RepoSettingsView(RepoAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45
45
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
46 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
47 c.repo_info = self.db_repo
47 c.repo_info = self.db_repo
48
48
49 self._register_global_c(c)
49 self._register_global_c(c)
50 return c
50 return c
51
51
52 @LoginRequired()
52 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.admin')
53 @HasRepoPermissionAnyDecorator('repository.admin')
54 @view_config(
54 @view_config(
55 route_name='edit_repo_advanced', request_method='GET',
55 route_name='edit_repo_advanced', request_method='GET',
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
56 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
57 def edit_advanced(self):
57 def edit_advanced(self):
58 c = self.load_default_context()
58 c = self.load_default_context()
59 c.active = 'advanced'
59 c.active = 'advanced'
60
60
61 c.default_user_id = User.get_default_user().user_id
61 c.default_user_id = User.get_default_user().user_id
62 c.in_public_journal = UserFollowing.query() \
62 c.in_public_journal = UserFollowing.query() \
63 .filter(UserFollowing.user_id == c.default_user_id) \
63 .filter(UserFollowing.user_id == c.default_user_id) \
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
64 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
65
65
66 c.has_origin_repo_read_perm = False
66 c.has_origin_repo_read_perm = False
67 if self.db_repo.fork:
67 if self.db_repo.fork:
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
68 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
69 'repository.write', 'repository.read', 'repository.admin')(
69 'repository.write', 'repository.read', 'repository.admin')(
70 self.db_repo.fork.repo_name, 'repo set as fork page')
70 self.db_repo.fork.repo_name, 'repo set as fork page')
71
71
72 return self._get_template_context(c)
72 return self._get_template_context(c)
73
73
74 @LoginRequired()
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator('repository.admin')
75 @HasRepoPermissionAnyDecorator('repository.admin')
76 @CSRFRequired()
76 @CSRFRequired()
77 @view_config(
77 @view_config(
78 route_name='edit_repo_advanced_delete', request_method='POST',
78 route_name='edit_repo_advanced_delete', request_method='POST',
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
79 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
80 def edit_advanced_delete(self):
80 def edit_advanced_delete(self):
81 """
81 """
82 Deletes the repository, or shows warnings if deletion is not possible
82 Deletes the repository, or shows warnings if deletion is not possible
83 because of attached forks or other errors.
83 because of attached forks or other errors.
84 """
84 """
85 _ = self.request.translate
85 _ = self.request.translate
86 handle_forks = self.request.POST.get('forks', None)
86 handle_forks = self.request.POST.get('forks', None)
87
87
88 try:
88 try:
89 _forks = self.db_repo.forks.count()
89 _forks = self.db_repo.forks.count()
90 if _forks and handle_forks:
90 if _forks and handle_forks:
91 if handle_forks == 'detach_forks':
91 if handle_forks == 'detach_forks':
92 handle_forks = 'detach'
92 handle_forks = 'detach'
93 h.flash(_('Detached %s forks') % _forks, category='success')
93 h.flash(_('Detached %s forks') % _forks, category='success')
94 elif handle_forks == 'delete_forks':
94 elif handle_forks == 'delete_forks':
95 handle_forks = 'delete'
95 handle_forks = 'delete'
96 h.flash(_('Deleted %s forks') % _forks, category='success')
96 h.flash(_('Deleted %s forks') % _forks, category='success')
97
97
98 repo_data = self.db_repo.get_api_data()
98 old_data = self.db_repo.get_api_data()
99 RepoModel().delete(self.db_repo, forks=handle_forks)
99 RepoModel().delete(self.db_repo, forks=handle_forks)
100
100
101 repo = audit_logger.RepoWrap(repo_id=None,
101 repo = audit_logger.RepoWrap(repo_id=None,
102 repo_name=self.db_repo.repo_name)
102 repo_name=self.db_repo.repo_name)
103 audit_logger.store_web(
103 audit_logger.store_web(
104 action='repo.delete',
104 'repo.delete', action_data={'old_data': old_data},
105 action_data={'data': repo_data},
106 user=self._rhodecode_user, repo=repo)
105 user=self._rhodecode_user, repo=repo)
107
106
108 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
107 ScmModel().mark_for_invalidation(self.db_repo_name, delete=True)
109 h.flash(
108 h.flash(
110 _('Deleted repository `%s`') % self.db_repo_name,
109 _('Deleted repository `%s`') % self.db_repo_name,
111 category='success')
110 category='success')
112 Session().commit()
111 Session().commit()
113 except AttachedForksError:
112 except AttachedForksError:
114 repo_advanced_url = h.route_path(
113 repo_advanced_url = h.route_path(
115 'edit_repo_advanced', repo_name=self.db_repo_name,
114 'edit_repo_advanced', repo_name=self.db_repo_name,
116 _anchor='advanced-delete')
115 _anchor='advanced-delete')
117 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
116 delete_anchor = h.link_to(_('detach or delete'), repo_advanced_url)
118 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
117 h.flash(_('Cannot delete `{repo}` it still contains attached forks. '
119 'Try using {delete_or_detach} option.')
118 'Try using {delete_or_detach} option.')
120 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
119 .format(repo=self.db_repo_name, delete_or_detach=delete_anchor),
121 category='warning')
120 category='warning')
122
121
123 # redirect to advanced for forks handle action ?
122 # redirect to advanced for forks handle action ?
124 raise HTTPFound(repo_advanced_url)
123 raise HTTPFound(repo_advanced_url)
125
124
126 except Exception:
125 except Exception:
127 log.exception("Exception during deletion of repository")
126 log.exception("Exception during deletion of repository")
128 h.flash(_('An error occurred during deletion of `%s`')
127 h.flash(_('An error occurred during deletion of `%s`')
129 % self.db_repo_name, category='error')
128 % self.db_repo_name, category='error')
130 # redirect to advanced for more deletion options
129 # redirect to advanced for more deletion options
131 raise HTTPFound(
130 raise HTTPFound(
132 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
131 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name),
133 _anchor='advanced-delete')
132 _anchor='advanced-delete')
134
133
135 raise HTTPFound(h.route_path('home'))
134 raise HTTPFound(h.route_path('home'))
136
135
137 @LoginRequired()
136 @LoginRequired()
138 @HasRepoPermissionAnyDecorator('repository.admin')
137 @HasRepoPermissionAnyDecorator('repository.admin')
139 @CSRFRequired()
138 @CSRFRequired()
140 @view_config(
139 @view_config(
141 route_name='edit_repo_advanced_journal', request_method='POST',
140 route_name='edit_repo_advanced_journal', request_method='POST',
142 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
141 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
143 def edit_advanced_journal(self):
142 def edit_advanced_journal(self):
144 """
143 """
145 Set's this repository to be visible in public journal,
144 Set's this repository to be visible in public journal,
146 in other words making default user to follow this repo
145 in other words making default user to follow this repo
147 """
146 """
148 _ = self.request.translate
147 _ = self.request.translate
149
148
150 try:
149 try:
151 user_id = User.get_default_user().user_id
150 user_id = User.get_default_user().user_id
152 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
151 ScmModel().toggle_following_repo(self.db_repo.repo_id, user_id)
153 h.flash(_('Updated repository visibility in public journal'),
152 h.flash(_('Updated repository visibility in public journal'),
154 category='success')
153 category='success')
155 Session().commit()
154 Session().commit()
156 except Exception:
155 except Exception:
157 h.flash(_('An error occurred during setting this '
156 h.flash(_('An error occurred during setting this '
158 'repository in public journal'),
157 'repository in public journal'),
159 category='error')
158 category='error')
160
159
161 raise HTTPFound(
160 raise HTTPFound(
162 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
161 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
163
162
164 @LoginRequired()
163 @LoginRequired()
165 @HasRepoPermissionAnyDecorator('repository.admin')
164 @HasRepoPermissionAnyDecorator('repository.admin')
166 @CSRFRequired()
165 @CSRFRequired()
167 @view_config(
166 @view_config(
168 route_name='edit_repo_advanced_fork', request_method='POST',
167 route_name='edit_repo_advanced_fork', request_method='POST',
169 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
168 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
170 def edit_advanced_fork(self):
169 def edit_advanced_fork(self):
171 """
170 """
172 Mark given repository as a fork of another
171 Mark given repository as a fork of another
173 """
172 """
174 _ = self.request.translate
173 _ = self.request.translate
175
174
176 new_fork_id = self.request.POST.get('id_fork_of')
175 new_fork_id = self.request.POST.get('id_fork_of')
177 try:
176 try:
178
177
179 if new_fork_id and not new_fork_id.isdigit():
178 if new_fork_id and not new_fork_id.isdigit():
180 log.error('Given fork id %s is not an INT', new_fork_id)
179 log.error('Given fork id %s is not an INT', new_fork_id)
181
180
182 fork_id = safe_int(new_fork_id)
181 fork_id = safe_int(new_fork_id)
183 repo = ScmModel().mark_as_fork(
182 repo = ScmModel().mark_as_fork(
184 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
183 self.db_repo_name, fork_id, self._rhodecode_user.user_id)
185 fork = repo.fork.repo_name if repo.fork else _('Nothing')
184 fork = repo.fork.repo_name if repo.fork else _('Nothing')
186 Session().commit()
185 Session().commit()
187 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
186 h.flash(_('Marked repo %s as fork of %s') % (self.db_repo_name, fork),
188 category='success')
187 category='success')
189 except RepositoryError as e:
188 except RepositoryError as e:
190 log.exception("Repository Error occurred")
189 log.exception("Repository Error occurred")
191 h.flash(str(e), category='error')
190 h.flash(str(e), category='error')
192 except Exception as e:
191 except Exception as e:
193 log.exception("Exception while editing fork")
192 log.exception("Exception while editing fork")
194 h.flash(_('An error occurred during this operation'),
193 h.flash(_('An error occurred during this operation'),
195 category='error')
194 category='error')
196
195
197 raise HTTPFound(
196 raise HTTPFound(
198 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
197 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
199
198
200 @LoginRequired()
199 @LoginRequired()
201 @HasRepoPermissionAnyDecorator('repository.admin')
200 @HasRepoPermissionAnyDecorator('repository.admin')
202 @CSRFRequired()
201 @CSRFRequired()
203 @view_config(
202 @view_config(
204 route_name='edit_repo_advanced_locking', request_method='POST',
203 route_name='edit_repo_advanced_locking', request_method='POST',
205 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
204 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
206 def edit_advanced_locking(self):
205 def edit_advanced_locking(self):
207 """
206 """
208 Toggle locking of repository
207 Toggle locking of repository
209 """
208 """
210 _ = self.request.translate
209 _ = self.request.translate
211 set_lock = self.request.POST.get('set_lock')
210 set_lock = self.request.POST.get('set_lock')
212 set_unlock = self.request.POST.get('set_unlock')
211 set_unlock = self.request.POST.get('set_unlock')
213
212
214 try:
213 try:
215 if set_lock:
214 if set_lock:
216 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
215 Repository.lock(self.db_repo, self._rhodecode_user.user_id,
217 lock_reason=Repository.LOCK_WEB)
216 lock_reason=Repository.LOCK_WEB)
218 h.flash(_('Locked repository'), category='success')
217 h.flash(_('Locked repository'), category='success')
219 elif set_unlock:
218 elif set_unlock:
220 Repository.unlock(self.db_repo)
219 Repository.unlock(self.db_repo)
221 h.flash(_('Unlocked repository'), category='success')
220 h.flash(_('Unlocked repository'), category='success')
222 except Exception as e:
221 except Exception as e:
223 log.exception("Exception during unlocking")
222 log.exception("Exception during unlocking")
224 h.flash(_('An error occurred during unlocking'), category='error')
223 h.flash(_('An error occurred during unlocking'), category='error')
225
224
226 raise HTTPFound(
225 raise HTTPFound(
227 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
226 h.route_path('edit_repo_advanced', repo_name=self.db_repo_name))
@@ -1,118 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 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 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib import audit_logger
25 from rhodecode.lib import audit_logger
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
28 NotAnonymous, CSRFRequired)
28 NotAnonymous, CSRFRequired)
29 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import json
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class StripView(RepoAppView):
34 class StripView(RepoAppView):
35 def load_default_context(self):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
37
37
38 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
39 c.repo_info = self.db_repo
39 c.repo_info = self.db_repo
40
40
41 self._register_global_c(c)
41 self._register_global_c(c)
42 return c
42 return c
43
43
44 @LoginRequired()
44 @LoginRequired()
45 @HasRepoPermissionAnyDecorator('repository.admin')
45 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
46 @view_config(
47 route_name='strip', request_method='GET',
47 route_name='strip', request_method='GET',
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 def strip(self):
49 def strip(self):
50 c = self.load_default_context()
50 c = self.load_default_context()
51 c.active = 'strip'
51 c.active = 'strip'
52 c.strip_limit = 10
52 c.strip_limit = 10
53
53
54 return self._get_template_context(c)
54 return self._get_template_context(c)
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.admin')
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @CSRFRequired()
58 @CSRFRequired()
59 @view_config(
59 @view_config(
60 route_name='strip_check', request_method='POST',
60 route_name='strip_check', request_method='POST',
61 renderer='json', xhr=True)
61 renderer='json', xhr=True)
62 def strip_check(self):
62 def strip_check(self):
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 data = {}
64 data = {}
65 rp = self.request.POST
65 rp = self.request.POST
66 for i in range(1, 11):
66 for i in range(1, 11):
67 chset = 'changeset_id-%d' % (i,)
67 chset = 'changeset_id-%d' % (i,)
68 check = rp.get(chset)
68 check = rp.get(chset)
69
69
70 if check:
70 if check:
71 data[i] = self.db_repo.get_changeset(rp[chset])
71 data[i] = self.db_repo.get_changeset(rp[chset])
72 if isinstance(data[i], EmptyCommit):
72 if isinstance(data[i], EmptyCommit):
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
73 data[i] = {'rev': None, 'commit': h.escape(rp[chset])}
74 else:
74 else:
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
75 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch,
76 'author': data[i].author,
76 'author': data[i].author,
77 'comment': data[i].message}
77 'comment': data[i].message}
78 else:
78 else:
79 break
79 break
80 return data
80 return data
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @HasRepoPermissionAnyDecorator('repository.admin')
83 @HasRepoPermissionAnyDecorator('repository.admin')
84 @CSRFRequired()
84 @CSRFRequired()
85 @view_config(
85 @view_config(
86 route_name='strip_execute', request_method='POST',
86 route_name='strip_execute', request_method='POST',
87 renderer='json', xhr=True)
87 renderer='json', xhr=True)
88 def strip_execute(self):
88 def strip_execute(self):
89 from rhodecode.model.scm import ScmModel
89 from rhodecode.model.scm import ScmModel
90
90
91 c = self.load_default_context()
91 c = self.load_default_context()
92 user = self._rhodecode_user
92 user = self._rhodecode_user
93 rp = self.request.POST
93 rp = self.request.POST
94 data = {}
94 data = {}
95 for idx in rp:
95 for idx in rp:
96 commit = json.loads(rp[idx])
96 commit = json.loads(rp[idx])
97 # If someone put two times the same branch
97 # If someone put two times the same branch
98 if commit['branch'] in data.keys():
98 if commit['branch'] in data.keys():
99 continue
99 continue
100 try:
100 try:
101 ScmModel().strip(
101 ScmModel().strip(
102 repo=c.repo_info,
102 repo=c.repo_info,
103 commit_id=commit['rev'], branch=commit['branch'])
103 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (
104 log.info('Stripped commit %s from repo `%s` by %s' % (
105 commit['rev'], c.repo_info.repo_name, user))
105 commit['rev'], c.repo_info.repo_name, user))
106 data[commit['rev']] = True
106 data[commit['rev']] = True
107
107
108 audit_logger.store_web(
108 audit_logger.store_web(
109 action='repo.commit.strip',
109 'repo.commit.strip', action_data={'commit_id': commit['rev']},
110 action_data={'commit_id': commit['rev']},
110 repo=self.db_repo, user=self._rhodecode_user, commit=True)
111 repo=self.db_repo,
112 user=self._rhodecode_user, commit=True)
113
111
114 except Exception as e:
112 except Exception as e:
115 data[commit['rev']] = False
113 data[commit['rev']] = False
116 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
114 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (
117 commit['rev'], self.db_repo_name, user, e.message))
115 commit['rev'], self.db_repo_name, user, e.message))
118 return data
116 return data
@@ -1,404 +1,405 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 """
22 """
23 Repository groups controller for RhodeCode
23 Repository groups controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import formencode
27 import formencode
28
28
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _, ungettext
33 from pylons.i18n.translation import _, ungettext
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, NotAnonymous, HasPermissionAll,
40 LoginRequired, NotAnonymous, HasPermissionAll,
41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
41 HasRepoGroupPermissionAll, HasRepoGroupPermissionAnyDecorator)
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.utils2 import safe_int
43 from rhodecode.lib.utils2 import safe_int
44 from rhodecode.model.db import RepoGroup, User
44 from rhodecode.model.db import RepoGroup, User
45 from rhodecode.model.scm import RepoGroupList
45 from rhodecode.model.scm import RepoGroupList
46 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.repo_group import RepoGroupModel
47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
47 from rhodecode.model.forms import RepoGroupForm, RepoGroupPermsForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class RepoGroupsController(BaseController):
54 class RepoGroupsController(BaseController):
55 """REST Controller styled on the Atom Publishing Protocol"""
55 """REST Controller styled on the Atom Publishing Protocol"""
56
56
57 @LoginRequired()
57 @LoginRequired()
58 def __before__(self):
58 def __before__(self):
59 super(RepoGroupsController, self).__before__()
59 super(RepoGroupsController, self).__before__()
60
60
61 def __load_defaults(self, allow_empty_group=False, repo_group=None):
61 def __load_defaults(self, allow_empty_group=False, repo_group=None):
62 if self._can_create_repo_group():
62 if self._can_create_repo_group():
63 # we're global admin, we're ok and we can create TOP level groups
63 # we're global admin, we're ok and we can create TOP level groups
64 allow_empty_group = True
64 allow_empty_group = True
65
65
66 # override the choices for this form, we need to filter choices
66 # override the choices for this form, we need to filter choices
67 # and display only those we have ADMIN right
67 # and display only those we have ADMIN right
68 groups_with_admin_rights = RepoGroupList(
68 groups_with_admin_rights = RepoGroupList(
69 RepoGroup.query().all(),
69 RepoGroup.query().all(),
70 perm_set=['group.admin'])
70 perm_set=['group.admin'])
71 c.repo_groups = RepoGroup.groups_choices(
71 c.repo_groups = RepoGroup.groups_choices(
72 groups=groups_with_admin_rights,
72 groups=groups_with_admin_rights,
73 show_empty_group=allow_empty_group)
73 show_empty_group=allow_empty_group)
74
74
75 if repo_group:
75 if repo_group:
76 # exclude filtered ids
76 # exclude filtered ids
77 exclude_group_ids = [repo_group.group_id]
77 exclude_group_ids = [repo_group.group_id]
78 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
78 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
79 c.repo_groups)
79 c.repo_groups)
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 parent_group = repo_group.parent_group
81 parent_group = repo_group.parent_group
82
82
83 add_parent_group = (parent_group and (
83 add_parent_group = (parent_group and (
84 unicode(parent_group.group_id) not in c.repo_groups_choices))
84 unicode(parent_group.group_id) not in c.repo_groups_choices))
85 if add_parent_group:
85 if add_parent_group:
86 c.repo_groups_choices.append(unicode(parent_group.group_id))
86 c.repo_groups_choices.append(unicode(parent_group.group_id))
87 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
87 c.repo_groups.append(RepoGroup._generate_choice(parent_group))
88
88
89 def __load_data(self, group_id):
89 def __load_data(self, group_id):
90 """
90 """
91 Load defaults settings for edit, and update
91 Load defaults settings for edit, and update
92
92
93 :param group_id:
93 :param group_id:
94 """
94 """
95 repo_group = RepoGroup.get_or_404(group_id)
95 repo_group = RepoGroup.get_or_404(group_id)
96 data = repo_group.get_dict()
96 data = repo_group.get_dict()
97 data['group_name'] = repo_group.name
97 data['group_name'] = repo_group.name
98
98
99 # fill owner
99 # fill owner
100 if repo_group.user:
100 if repo_group.user:
101 data.update({'user': repo_group.user.username})
101 data.update({'user': repo_group.user.username})
102 else:
102 else:
103 replacement_user = User.get_first_super_admin().username
103 replacement_user = User.get_first_super_admin().username
104 data.update({'user': replacement_user})
104 data.update({'user': replacement_user})
105
105
106 # fill repository group users
106 # fill repository group users
107 for p in repo_group.repo_group_to_perm:
107 for p in repo_group.repo_group_to_perm:
108 data.update({
108 data.update({
109 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
109 'u_perm_%s' % p.user.user_id: p.permission.permission_name})
110
110
111 # fill repository group user groups
111 # fill repository group user groups
112 for p in repo_group.users_group_to_perm:
112 for p in repo_group.users_group_to_perm:
113 data.update({
113 data.update({
114 'g_perm_%s' % p.users_group.users_group_id:
114 'g_perm_%s' % p.users_group.users_group_id:
115 p.permission.permission_name})
115 p.permission.permission_name})
116 # html and form expects -1 as empty parent group
116 # html and form expects -1 as empty parent group
117 data['group_parent_id'] = data['group_parent_id'] or -1
117 data['group_parent_id'] = data['group_parent_id'] or -1
118 return data
118 return data
119
119
120 def _revoke_perms_on_yourself(self, form_result):
120 def _revoke_perms_on_yourself(self, form_result):
121 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
121 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
122 form_result['perm_updates'])
122 form_result['perm_updates'])
123 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
123 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
124 form_result['perm_additions'])
124 form_result['perm_additions'])
125 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
125 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
126 form_result['perm_deletions'])
126 form_result['perm_deletions'])
127 admin_perm = 'group.admin'
127 admin_perm = 'group.admin'
128 if _updates and _updates[0][1] != admin_perm or \
128 if _updates and _updates[0][1] != admin_perm or \
129 _additions and _additions[0][1] != admin_perm or \
129 _additions and _additions[0][1] != admin_perm or \
130 _deletions and _deletions[0][1] != admin_perm:
130 _deletions and _deletions[0][1] != admin_perm:
131 return True
131 return True
132 return False
132 return False
133
133
134 def _can_create_repo_group(self, parent_group_id=None):
134 def _can_create_repo_group(self, parent_group_id=None):
135 is_admin = HasPermissionAll('hg.admin')('group create controller')
135 is_admin = HasPermissionAll('hg.admin')('group create controller')
136 create_repo_group = HasPermissionAll(
136 create_repo_group = HasPermissionAll(
137 'hg.repogroup.create.true')('group create controller')
137 'hg.repogroup.create.true')('group create controller')
138 if is_admin or (create_repo_group and not parent_group_id):
138 if is_admin or (create_repo_group and not parent_group_id):
139 # we're global admin, or we have global repo group create
139 # we're global admin, or we have global repo group create
140 # permission
140 # permission
141 # we're ok and we can create TOP level groups
141 # we're ok and we can create TOP level groups
142 return True
142 return True
143 elif parent_group_id:
143 elif parent_group_id:
144 # we check the permission if we can write to parent group
144 # we check the permission if we can write to parent group
145 group = RepoGroup.get(parent_group_id)
145 group = RepoGroup.get(parent_group_id)
146 group_name = group.group_name if group else None
146 group_name = group.group_name if group else None
147 if HasRepoGroupPermissionAll('group.admin')(
147 if HasRepoGroupPermissionAll('group.admin')(
148 group_name, 'check if user is an admin of group'):
148 group_name, 'check if user is an admin of group'):
149 # we're an admin of passed in group, we're ok.
149 # we're an admin of passed in group, we're ok.
150 return True
150 return True
151 else:
151 else:
152 return False
152 return False
153 return False
153 return False
154
154
155 @NotAnonymous()
155 @NotAnonymous()
156 def index(self):
156 def index(self):
157 repo_group_list = RepoGroup.get_all_repo_groups()
157 repo_group_list = RepoGroup.get_all_repo_groups()
158 _perms = ['group.admin']
158 _perms = ['group.admin']
159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
159 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
160 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
160 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
161 repo_group_list=repo_group_list_acl, admin=True)
161 repo_group_list=repo_group_list_acl, admin=True)
162 c.data = json.dumps(repo_group_data)
162 c.data = json.dumps(repo_group_data)
163 return render('admin/repo_groups/repo_groups.mako')
163 return render('admin/repo_groups/repo_groups.mako')
164
164
165 # perm checks inside
165 # perm checks inside
166 @NotAnonymous()
166 @NotAnonymous()
167 @auth.CSRFRequired()
167 @auth.CSRFRequired()
168 def create(self):
168 def create(self):
169
169
170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
170 parent_group_id = safe_int(request.POST.get('group_parent_id'))
171 can_create = self._can_create_repo_group(parent_group_id)
171 can_create = self._can_create_repo_group(parent_group_id)
172
172
173 self.__load_defaults()
173 self.__load_defaults()
174 # permissions for can create group based on parent_id are checked
174 # permissions for can create group based on parent_id are checked
175 # here in the Form
175 # here in the Form
176 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
176 available_groups = map(lambda k: unicode(k[0]), c.repo_groups)
177 repo_group_form = RepoGroupForm(available_groups=available_groups,
177 repo_group_form = RepoGroupForm(available_groups=available_groups,
178 can_create_in_root=can_create)()
178 can_create_in_root=can_create)()
179 try:
179 try:
180 owner = c.rhodecode_user
180 owner = c.rhodecode_user
181 form_result = repo_group_form.to_python(dict(request.POST))
181 form_result = repo_group_form.to_python(dict(request.POST))
182 repo_group = RepoGroupModel().create(
182 repo_group = RepoGroupModel().create(
183 group_name=form_result['group_name_full'],
183 group_name=form_result['group_name_full'],
184 group_description=form_result['group_description'],
184 group_description=form_result['group_description'],
185 owner=owner.user_id,
185 owner=owner.user_id,
186 copy_permissions=form_result['group_copy_permissions']
186 copy_permissions=form_result['group_copy_permissions']
187 )
187 )
188 Session().commit()
188 Session().flush()
189 repo_group_data = repo_group.get_api_data()
190 _new_group_name = form_result['group_name_full']
191
189
190 repo_group_data = repo_group.get_api_data()
192 audit_logger.store_web(
191 audit_logger.store_web(
193 action='repo_group.create',
192 'repo_group.create', action_data={'data': repo_group_data},
194 action_data={'data': repo_group_data},
193 user=c.rhodecode_user)
195 user=c.rhodecode_user, commit=True)
194
195 Session().commit()
196
197 _new_group_name = form_result['group_name_full']
196
198
197 repo_group_url = h.link_to(
199 repo_group_url = h.link_to(
198 _new_group_name,
200 _new_group_name,
199 h.route_path('repo_group_home', repo_group_name=_new_group_name))
201 h.route_path('repo_group_home', repo_group_name=_new_group_name))
200 h.flash(h.literal(_('Created repository group %s')
202 h.flash(h.literal(_('Created repository group %s')
201 % repo_group_url), category='success')
203 % repo_group_url), category='success')
202
204
203 except formencode.Invalid as errors:
205 except formencode.Invalid as errors:
204 return htmlfill.render(
206 return htmlfill.render(
205 render('admin/repo_groups/repo_group_add.mako'),
207 render('admin/repo_groups/repo_group_add.mako'),
206 defaults=errors.value,
208 defaults=errors.value,
207 errors=errors.error_dict or {},
209 errors=errors.error_dict or {},
208 prefix_error=False,
210 prefix_error=False,
209 encoding="UTF-8",
211 encoding="UTF-8",
210 force_defaults=False)
212 force_defaults=False)
211 except Exception:
213 except Exception:
212 log.exception("Exception during creation of repository group")
214 log.exception("Exception during creation of repository group")
213 h.flash(_('Error occurred during creation of repository group %s')
215 h.flash(_('Error occurred during creation of repository group %s')
214 % request.POST.get('group_name'), category='error')
216 % request.POST.get('group_name'), category='error')
215
217
216 # TODO: maybe we should get back to the main view, not the admin one
218 # TODO: maybe we should get back to the main view, not the admin one
217 return redirect(url('repo_groups', parent_group=parent_group_id))
219 return redirect(url('repo_groups', parent_group=parent_group_id))
218
220
219 # perm checks inside
221 # perm checks inside
220 @NotAnonymous()
222 @NotAnonymous()
221 def new(self):
223 def new(self):
222 # perm check for admin, create_group perm or admin of parent_group
224 # perm check for admin, create_group perm or admin of parent_group
223 parent_group_id = safe_int(request.GET.get('parent_group'))
225 parent_group_id = safe_int(request.GET.get('parent_group'))
224 if not self._can_create_repo_group(parent_group_id):
226 if not self._can_create_repo_group(parent_group_id):
225 return abort(403)
227 return abort(403)
226
228
227 self.__load_defaults()
229 self.__load_defaults()
228 return render('admin/repo_groups/repo_group_add.mako')
230 return render('admin/repo_groups/repo_group_add.mako')
229
231
230 @HasRepoGroupPermissionAnyDecorator('group.admin')
232 @HasRepoGroupPermissionAnyDecorator('group.admin')
231 @auth.CSRFRequired()
233 @auth.CSRFRequired()
232 def update(self, group_name):
234 def update(self, group_name):
233
235
234 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
236 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
235 can_create_in_root = self._can_create_repo_group()
237 can_create_in_root = self._can_create_repo_group()
236 show_root_location = can_create_in_root
238 show_root_location = can_create_in_root
237 if not c.repo_group.parent_group:
239 if not c.repo_group.parent_group:
238 # this group don't have a parrent so we should show empty value
240 # this group don't have a parrent so we should show empty value
239 show_root_location = True
241 show_root_location = True
240 self.__load_defaults(allow_empty_group=show_root_location,
242 self.__load_defaults(allow_empty_group=show_root_location,
241 repo_group=c.repo_group)
243 repo_group=c.repo_group)
242
244
243 repo_group_form = RepoGroupForm(
245 repo_group_form = RepoGroupForm(
244 edit=True, old_data=c.repo_group.get_dict(),
246 edit=True, old_data=c.repo_group.get_dict(),
245 available_groups=c.repo_groups_choices,
247 available_groups=c.repo_groups_choices,
246 can_create_in_root=can_create_in_root, allow_disabled=True)()
248 can_create_in_root=can_create_in_root, allow_disabled=True)()
247
249
248 old_values = c.repo_group.get_api_data()
250 old_values = c.repo_group.get_api_data()
249 try:
251 try:
250 form_result = repo_group_form.to_python(dict(request.POST))
252 form_result = repo_group_form.to_python(dict(request.POST))
251 gr_name = form_result['group_name']
253 gr_name = form_result['group_name']
252 new_gr = RepoGroupModel().update(group_name, form_result)
254 new_gr = RepoGroupModel().update(group_name, form_result)
253
255
254 audit_logger.store_web(
256 audit_logger.store_web(
255 'repo_group.edit', action_data={'old_data': old_values},
257 'repo_group.edit', action_data={'old_data': old_values},
256 user=c.rhodecode_user)
258 user=c.rhodecode_user)
257
259
258 Session().commit()
260 Session().commit()
259 h.flash(_('Updated repository group %s') % (gr_name,),
261 h.flash(_('Updated repository group %s') % (gr_name,),
260 category='success')
262 category='success')
261 # we now have new name !
263 # we now have new name !
262 group_name = new_gr.group_name
264 group_name = new_gr.group_name
263 except formencode.Invalid as errors:
265 except formencode.Invalid as errors:
264 c.active = 'settings'
266 c.active = 'settings'
265 return htmlfill.render(
267 return htmlfill.render(
266 render('admin/repo_groups/repo_group_edit.mako'),
268 render('admin/repo_groups/repo_group_edit.mako'),
267 defaults=errors.value,
269 defaults=errors.value,
268 errors=errors.error_dict or {},
270 errors=errors.error_dict or {},
269 prefix_error=False,
271 prefix_error=False,
270 encoding="UTF-8",
272 encoding="UTF-8",
271 force_defaults=False)
273 force_defaults=False)
272 except Exception:
274 except Exception:
273 log.exception("Exception during update or repository group")
275 log.exception("Exception during update or repository group")
274 h.flash(_('Error occurred during update of repository group %s')
276 h.flash(_('Error occurred during update of repository group %s')
275 % request.POST.get('group_name'), category='error')
277 % request.POST.get('group_name'), category='error')
276
278
277 return redirect(url('edit_repo_group', group_name=group_name))
279 return redirect(url('edit_repo_group', group_name=group_name))
278
280
279 @HasRepoGroupPermissionAnyDecorator('group.admin')
281 @HasRepoGroupPermissionAnyDecorator('group.admin')
280 @auth.CSRFRequired()
282 @auth.CSRFRequired()
281 def delete(self, group_name):
283 def delete(self, group_name):
282 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
284 gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
283 repos = gr.repositories.all()
285 repos = gr.repositories.all()
284 if repos:
286 if repos:
285 msg = ungettext(
287 msg = ungettext(
286 'This group contains %(num)d repository and cannot be deleted',
288 'This group contains %(num)d repository and cannot be deleted',
287 'This group contains %(num)d repositories and cannot be'
289 'This group contains %(num)d repositories and cannot be'
288 ' deleted',
290 ' deleted',
289 len(repos)) % {'num': len(repos)}
291 len(repos)) % {'num': len(repos)}
290 h.flash(msg, category='warning')
292 h.flash(msg, category='warning')
291 return redirect(url('repo_groups'))
293 return redirect(url('repo_groups'))
292
294
293 children = gr.children.all()
295 children = gr.children.all()
294 if children:
296 if children:
295 msg = ungettext(
297 msg = ungettext(
296 'This group contains %(num)d subgroup and cannot be deleted',
298 'This group contains %(num)d subgroup and cannot be deleted',
297 'This group contains %(num)d subgroups and cannot be deleted',
299 'This group contains %(num)d subgroups and cannot be deleted',
298 len(children)) % {'num': len(children)}
300 len(children)) % {'num': len(children)}
299 h.flash(msg, category='warning')
301 h.flash(msg, category='warning')
300 return redirect(url('repo_groups'))
302 return redirect(url('repo_groups'))
301
303
302 try:
304 try:
303 old_values = gr.get_api_data()
305 old_values = gr.get_api_data()
304 RepoGroupModel().delete(group_name)
306 RepoGroupModel().delete(group_name)
305
307
306 audit_logger.store_web(
308 audit_logger.store_web(
307 'repo_group.delete',
309 'repo_group.delete', action_data={'old_data': old_values},
308 action_data={'old_data': old_values},
309 user=c.rhodecode_user)
310 user=c.rhodecode_user)
310
311
311 Session().commit()
312 Session().commit()
312 h.flash(_('Removed repository group %s') % group_name,
313 h.flash(_('Removed repository group %s') % group_name,
313 category='success')
314 category='success')
314 except Exception:
315 except Exception:
315 log.exception("Exception during deletion of repository group")
316 log.exception("Exception during deletion of repository group")
316 h.flash(_('Error occurred during deletion of repository group %s')
317 h.flash(_('Error occurred during deletion of repository group %s')
317 % group_name, category='error')
318 % group_name, category='error')
318
319
319 return redirect(url('repo_groups'))
320 return redirect(url('repo_groups'))
320
321
321 @HasRepoGroupPermissionAnyDecorator('group.admin')
322 @HasRepoGroupPermissionAnyDecorator('group.admin')
322 def edit(self, group_name):
323 def edit(self, group_name):
323
324
324 c.active = 'settings'
325 c.active = 'settings'
325
326
326 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
327 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
327 # we can only allow moving empty group if it's already a top-level
328 # we can only allow moving empty group if it's already a top-level
328 # group, ie has no parents, or we're admin
329 # group, ie has no parents, or we're admin
329 can_create_in_root = self._can_create_repo_group()
330 can_create_in_root = self._can_create_repo_group()
330 show_root_location = can_create_in_root
331 show_root_location = can_create_in_root
331 if not c.repo_group.parent_group:
332 if not c.repo_group.parent_group:
332 # this group don't have a parrent so we should show empty value
333 # this group don't have a parrent so we should show empty value
333 show_root_location = True
334 show_root_location = True
334 self.__load_defaults(allow_empty_group=show_root_location,
335 self.__load_defaults(allow_empty_group=show_root_location,
335 repo_group=c.repo_group)
336 repo_group=c.repo_group)
336 defaults = self.__load_data(c.repo_group.group_id)
337 defaults = self.__load_data(c.repo_group.group_id)
337
338
338 return htmlfill.render(
339 return htmlfill.render(
339 render('admin/repo_groups/repo_group_edit.mako'),
340 render('admin/repo_groups/repo_group_edit.mako'),
340 defaults=defaults,
341 defaults=defaults,
341 encoding="UTF-8",
342 encoding="UTF-8",
342 force_defaults=False
343 force_defaults=False
343 )
344 )
344
345
345 @HasRepoGroupPermissionAnyDecorator('group.admin')
346 @HasRepoGroupPermissionAnyDecorator('group.admin')
346 def edit_repo_group_advanced(self, group_name):
347 def edit_repo_group_advanced(self, group_name):
347 c.active = 'advanced'
348 c.active = 'advanced'
348 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
349 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
349
350
350 return render('admin/repo_groups/repo_group_edit.mako')
351 return render('admin/repo_groups/repo_group_edit.mako')
351
352
352 @HasRepoGroupPermissionAnyDecorator('group.admin')
353 @HasRepoGroupPermissionAnyDecorator('group.admin')
353 def edit_repo_group_perms(self, group_name):
354 def edit_repo_group_perms(self, group_name):
354 c.active = 'perms'
355 c.active = 'perms'
355 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
356 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
356 self.__load_defaults()
357 self.__load_defaults()
357 defaults = self.__load_data(c.repo_group.group_id)
358 defaults = self.__load_data(c.repo_group.group_id)
358
359
359 return htmlfill.render(
360 return htmlfill.render(
360 render('admin/repo_groups/repo_group_edit.mako'),
361 render('admin/repo_groups/repo_group_edit.mako'),
361 defaults=defaults,
362 defaults=defaults,
362 encoding="UTF-8",
363 encoding="UTF-8",
363 force_defaults=False
364 force_defaults=False
364 )
365 )
365
366
366 @HasRepoGroupPermissionAnyDecorator('group.admin')
367 @HasRepoGroupPermissionAnyDecorator('group.admin')
367 @auth.CSRFRequired()
368 @auth.CSRFRequired()
368 def update_perms(self, group_name):
369 def update_perms(self, group_name):
369 """
370 """
370 Update permissions for given repository group
371 Update permissions for given repository group
371 """
372 """
372
373
373 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
374 c.repo_group = RepoGroupModel()._get_repo_group(group_name)
374 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
375 valid_recursive_choices = ['none', 'repos', 'groups', 'all']
375 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
376 form = RepoGroupPermsForm(valid_recursive_choices)().to_python(
376 request.POST)
377 request.POST)
377
378
378 if not c.rhodecode_user.is_admin:
379 if not c.rhodecode_user.is_admin:
379 if self._revoke_perms_on_yourself(form):
380 if self._revoke_perms_on_yourself(form):
380 msg = _('Cannot change permission for yourself as admin')
381 msg = _('Cannot change permission for yourself as admin')
381 h.flash(msg, category='warning')
382 h.flash(msg, category='warning')
382 return redirect(
383 return redirect(
383 url('edit_repo_group_perms', group_name=group_name))
384 url('edit_repo_group_perms', group_name=group_name))
384
385
385 # iterate over all members(if in recursive mode) of this groups and
386 # iterate over all members(if in recursive mode) of this groups and
386 # set the permissions !
387 # set the permissions !
387 # this can be potentially heavy operation
388 # this can be potentially heavy operation
388 changes = RepoGroupModel().update_permissions(
389 changes = RepoGroupModel().update_permissions(
389 c.repo_group,
390 c.repo_group,
390 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
391 form['perm_additions'], form['perm_updates'], form['perm_deletions'],
391 form['recursive'])
392 form['recursive'])
392
393
393 action_data = {
394 action_data = {
394 'added': changes['added'],
395 'added': changes['added'],
395 'updated': changes['updated'],
396 'updated': changes['updated'],
396 'deleted': changes['deleted'],
397 'deleted': changes['deleted'],
397 }
398 }
398 audit_logger.store_web(
399 audit_logger.store_web(
399 'repo_group.edit.permissions', action_data=action_data,
400 'repo_group.edit.permissions', action_data=action_data,
400 user=c.rhodecode_user)
401 user=c.rhodecode_user)
401
402
402 Session().commit()
403 Session().commit()
403 h.flash(_('Repository Group permissions updated'), category='success')
404 h.flash(_('Repository Group permissions updated'), category='success')
404 return redirect(url('edit_repo_group_perms', group_name=group_name))
405 return redirect(url('edit_repo_group_perms', group_name=group_name))
@@ -1,510 +1,513 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 import peppercorn
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
40 from rhodecode.lib.exceptions import UserGroupAssignedException,\
41 RepoGroupAssignmentError
41 RepoGroupAssignmentError
42 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils import jsonify
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
43 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
45 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
46 HasPermissionAnyDecorator, XHRRequired)
46 HasPermissionAnyDecorator, XHRRequired)
47 from rhodecode.lib.base import BaseController, render
47 from rhodecode.lib.base import BaseController, render
48 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.scm import UserGroupList
49 from rhodecode.model.scm import UserGroupList
50 from rhodecode.model.user_group import UserGroupModel
50 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
52 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
53 from rhodecode.model.forms import (
53 from rhodecode.model.forms import (
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
54 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
55 UserPermissionsForm)
55 UserPermissionsForm)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class UserGroupsController(BaseController):
62 class UserGroupsController(BaseController):
63 """REST Controller styled on the Atom Publishing Protocol"""
63 """REST Controller styled on the Atom Publishing Protocol"""
64
64
65 @LoginRequired()
65 @LoginRequired()
66 def __before__(self):
66 def __before__(self):
67 super(UserGroupsController, self).__before__()
67 super(UserGroupsController, self).__before__()
68 c.available_permissions = config['available_permissions']
68 c.available_permissions = config['available_permissions']
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
69 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
70
70
71 def __load_data(self, user_group_id):
71 def __load_data(self, user_group_id):
72 c.group_members_obj = [x.user for x in c.user_group.members]
72 c.group_members_obj = [x.user for x in c.user_group.members]
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
73 c.group_members_obj.sort(key=lambda u: u.username.lower())
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
75
75
76 def __load_defaults(self, user_group_id):
76 def __load_defaults(self, user_group_id):
77 """
77 """
78 Load defaults settings for edit, and update
78 Load defaults settings for edit, and update
79
79
80 :param user_group_id:
80 :param user_group_id:
81 """
81 """
82 user_group = UserGroup.get_or_404(user_group_id)
82 user_group = UserGroup.get_or_404(user_group_id)
83 data = user_group.get_dict()
83 data = user_group.get_dict()
84 # fill owner
84 # fill owner
85 if user_group.user:
85 if user_group.user:
86 data.update({'user': user_group.user.username})
86 data.update({'user': user_group.user.username})
87 else:
87 else:
88 replacement_user = User.get_first_super_admin().username
88 replacement_user = User.get_first_super_admin().username
89 data.update({'user': replacement_user})
89 data.update({'user': replacement_user})
90 return data
90 return data
91
91
92 def _revoke_perms_on_yourself(self, form_result):
92 def _revoke_perms_on_yourself(self, form_result):
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
93 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 form_result['perm_updates'])
94 form_result['perm_updates'])
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
95 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 form_result['perm_additions'])
96 form_result['perm_additions'])
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
98 form_result['perm_deletions'])
98 form_result['perm_deletions'])
99 admin_perm = 'usergroup.admin'
99 admin_perm = 'usergroup.admin'
100 if _updates and _updates[0][1] != admin_perm or \
100 if _updates and _updates[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
101 _additions and _additions[0][1] != admin_perm or \
102 _deletions and _deletions[0][1] != admin_perm:
102 _deletions and _deletions[0][1] != admin_perm:
103 return True
103 return True
104 return False
104 return False
105
105
106 # permission check inside
106 # permission check inside
107 @NotAnonymous()
107 @NotAnonymous()
108 def index(self):
108 def index(self):
109
109
110 from rhodecode.lib.utils import PartialRenderer
110 from rhodecode.lib.utils import PartialRenderer
111 _render = PartialRenderer('data_table/_dt_elements.mako')
111 _render = PartialRenderer('data_table/_dt_elements.mako')
112
112
113 def user_group_name(user_group_id, user_group_name):
113 def user_group_name(user_group_id, user_group_name):
114 return _render("user_group_name", user_group_id, user_group_name)
114 return _render("user_group_name", user_group_id, user_group_name)
115
115
116 def user_group_actions(user_group_id, user_group_name):
116 def user_group_actions(user_group_id, user_group_name):
117 return _render("user_group_actions", user_group_id, user_group_name)
117 return _render("user_group_actions", user_group_id, user_group_name)
118
118
119 # json generate
119 # json generate
120 group_iter = UserGroupList(UserGroup.query().all(),
120 group_iter = UserGroupList(UserGroup.query().all(),
121 perm_set=['usergroup.admin'])
121 perm_set=['usergroup.admin'])
122
122
123 user_groups_data = []
123 user_groups_data = []
124 for user_gr in group_iter:
124 for user_gr in group_iter:
125 user_groups_data.append({
125 user_groups_data.append({
126 "group_name": user_group_name(
126 "group_name": user_group_name(
127 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
127 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
128 "group_name_raw": user_gr.users_group_name,
128 "group_name_raw": user_gr.users_group_name,
129 "desc": h.escape(user_gr.user_group_description),
129 "desc": h.escape(user_gr.user_group_description),
130 "members": len(user_gr.members),
130 "members": len(user_gr.members),
131 "sync": user_gr.group_data.get('extern_type'),
131 "sync": user_gr.group_data.get('extern_type'),
132 "active": h.bool2icon(user_gr.users_group_active),
132 "active": h.bool2icon(user_gr.users_group_active),
133 "owner": h.escape(h.link_to_user(user_gr.user.username)),
133 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 "action": user_group_actions(
134 "action": user_group_actions(
135 user_gr.users_group_id, user_gr.users_group_name)
135 user_gr.users_group_id, user_gr.users_group_name)
136 })
136 })
137
137
138 c.data = json.dumps(user_groups_data)
138 c.data = json.dumps(user_groups_data)
139 return render('admin/user_groups/user_groups.mako')
139 return render('admin/user_groups/user_groups.mako')
140
140
141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
142 @auth.CSRFRequired()
142 @auth.CSRFRequired()
143 def create(self):
143 def create(self):
144
144
145 users_group_form = UserGroupForm()()
145 users_group_form = UserGroupForm()()
146 try:
146 try:
147 form_result = users_group_form.to_python(dict(request.POST))
147 form_result = users_group_form.to_python(dict(request.POST))
148 user_group = UserGroupModel().create(
148 user_group = UserGroupModel().create(
149 name=form_result['users_group_name'],
149 name=form_result['users_group_name'],
150 description=form_result['user_group_description'],
150 description=form_result['user_group_description'],
151 owner=c.rhodecode_user.user_id,
151 owner=c.rhodecode_user.user_id,
152 active=form_result['users_group_active'])
152 active=form_result['users_group_active'])
153 Session().flush()
153 Session().flush()
154 creation_data = user_group.get_api_data()
154 creation_data = user_group.get_api_data()
155 user_group_name = form_result['users_group_name']
155 user_group_name = form_result['users_group_name']
156
156
157 audit_logger.store_web(
157 audit_logger.store_web(
158 'user_group.create', action_data={'data': creation_data},
158 'user_group.create', action_data={'data': creation_data},
159 user=c.rhodecode_user)
159 user=c.rhodecode_user)
160
160
161 user_group_link = h.link_to(
161 user_group_link = h.link_to(
162 h.escape(user_group_name),
162 h.escape(user_group_name),
163 url('edit_users_group', user_group_id=user_group.users_group_id))
163 url('edit_users_group', user_group_id=user_group.users_group_id))
164 h.flash(h.literal(_('Created user group %(user_group_link)s')
164 h.flash(h.literal(_('Created user group %(user_group_link)s')
165 % {'user_group_link': user_group_link}),
165 % {'user_group_link': user_group_link}),
166 category='success')
166 category='success')
167 Session().commit()
167 Session().commit()
168 except formencode.Invalid as errors:
168 except formencode.Invalid as errors:
169 return htmlfill.render(
169 return htmlfill.render(
170 render('admin/user_groups/user_group_add.mako'),
170 render('admin/user_groups/user_group_add.mako'),
171 defaults=errors.value,
171 defaults=errors.value,
172 errors=errors.error_dict or {},
172 errors=errors.error_dict or {},
173 prefix_error=False,
173 prefix_error=False,
174 encoding="UTF-8",
174 encoding="UTF-8",
175 force_defaults=False)
175 force_defaults=False)
176 except Exception:
176 except Exception:
177 log.exception("Exception creating user group")
177 log.exception("Exception creating user group")
178 h.flash(_('Error occurred during creation of user group %s') \
178 h.flash(_('Error occurred during creation of user group %s') \
179 % request.POST.get('users_group_name'), category='error')
179 % request.POST.get('users_group_name'), category='error')
180
180
181 return redirect(
181 return redirect(
182 url('edit_users_group', user_group_id=user_group.users_group_id))
182 url('edit_users_group', user_group_id=user_group.users_group_id))
183
183
184 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
184 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
185 def new(self):
185 def new(self):
186 """GET /user_groups/new: Form to create a new item"""
186 """GET /user_groups/new: Form to create a new item"""
187 # url('new_users_group')
187 # url('new_users_group')
188 return render('admin/user_groups/user_group_add.mako')
188 return render('admin/user_groups/user_group_add.mako')
189
189
190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
191 @auth.CSRFRequired()
191 @auth.CSRFRequired()
192 def update(self, user_group_id):
192 def update(self, user_group_id):
193
193
194 user_group_id = safe_int(user_group_id)
194 user_group_id = safe_int(user_group_id)
195 c.user_group = UserGroup.get_or_404(user_group_id)
195 c.user_group = UserGroup.get_or_404(user_group_id)
196 c.active = 'settings'
196 c.active = 'settings'
197 self.__load_data(user_group_id)
197 self.__load_data(user_group_id)
198
198
199 users_group_form = UserGroupForm(
199 users_group_form = UserGroupForm(
200 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
200 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
201
201
202 old_values = c.user_group.get_api_data()
202 old_values = c.user_group.get_api_data()
203 try:
203 try:
204 form_result = users_group_form.to_python(request.POST)
204 form_result = users_group_form.to_python(request.POST)
205 pstruct = peppercorn.parse(request.POST.items())
205 pstruct = peppercorn.parse(request.POST.items())
206 form_result['users_group_members'] = pstruct['user_group_members']
206 form_result['users_group_members'] = pstruct['user_group_members']
207
207
208 UserGroupModel().update(c.user_group, form_result)
208 user_group, added_members, removed_members = \
209 UserGroupModel().update(c.user_group, form_result)
209 updated_user_group = form_result['users_group_name']
210 updated_user_group = form_result['users_group_name']
210
211
211 audit_logger.store_web(
212 audit_logger.store_web(
212 'user_group.edit', action_data={'old_data': old_values},
213 'user_group.edit', action_data={'old_data': old_values},
213 user=c.rhodecode_user)
214 user=c.rhodecode_user)
214
215
216 # TODO(marcink): use added/removed to set user_group.edit.member.add
217
215 h.flash(_('Updated user group %s') % updated_user_group,
218 h.flash(_('Updated user group %s') % updated_user_group,
216 category='success')
219 category='success')
217 Session().commit()
220 Session().commit()
218 except formencode.Invalid as errors:
221 except formencode.Invalid as errors:
219 defaults = errors.value
222 defaults = errors.value
220 e = errors.error_dict or {}
223 e = errors.error_dict or {}
221
224
222 return htmlfill.render(
225 return htmlfill.render(
223 render('admin/user_groups/user_group_edit.mako'),
226 render('admin/user_groups/user_group_edit.mako'),
224 defaults=defaults,
227 defaults=defaults,
225 errors=e,
228 errors=e,
226 prefix_error=False,
229 prefix_error=False,
227 encoding="UTF-8",
230 encoding="UTF-8",
228 force_defaults=False)
231 force_defaults=False)
229 except Exception:
232 except Exception:
230 log.exception("Exception during update of user group")
233 log.exception("Exception during update of user group")
231 h.flash(_('Error occurred during update of user group %s')
234 h.flash(_('Error occurred during update of user group %s')
232 % request.POST.get('users_group_name'), category='error')
235 % request.POST.get('users_group_name'), category='error')
233
236
234 return redirect(url('edit_users_group', user_group_id=user_group_id))
237 return redirect(url('edit_users_group', user_group_id=user_group_id))
235
238
236 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
239 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
237 @auth.CSRFRequired()
240 @auth.CSRFRequired()
238 def delete(self, user_group_id):
241 def delete(self, user_group_id):
239 user_group_id = safe_int(user_group_id)
242 user_group_id = safe_int(user_group_id)
240 c.user_group = UserGroup.get_or_404(user_group_id)
243 c.user_group = UserGroup.get_or_404(user_group_id)
241 force = str2bool(request.POST.get('force'))
244 force = str2bool(request.POST.get('force'))
242
245
243 old_values = c.user_group.get_api_data()
246 old_values = c.user_group.get_api_data()
244 try:
247 try:
245 UserGroupModel().delete(c.user_group, force=force)
248 UserGroupModel().delete(c.user_group, force=force)
246 audit_logger.store_web(
249 audit_logger.store_web(
247 'user.delete', action_data={'old_data': old_values},
250 'user.delete', action_data={'old_data': old_values},
248 user=c.rhodecode_user)
251 user=c.rhodecode_user)
249 Session().commit()
252 Session().commit()
250 h.flash(_('Successfully deleted user group'), category='success')
253 h.flash(_('Successfully deleted user group'), category='success')
251 except UserGroupAssignedException as e:
254 except UserGroupAssignedException as e:
252 h.flash(str(e), category='error')
255 h.flash(str(e), category='error')
253 except Exception:
256 except Exception:
254 log.exception("Exception during deletion of user group")
257 log.exception("Exception during deletion of user group")
255 h.flash(_('An error occurred during deletion of user group'),
258 h.flash(_('An error occurred during deletion of user group'),
256 category='error')
259 category='error')
257 return redirect(url('users_groups'))
260 return redirect(url('users_groups'))
258
261
259 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
262 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
260 def edit(self, user_group_id):
263 def edit(self, user_group_id):
261 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
264 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
262 # url('edit_users_group', user_group_id=ID)
265 # url('edit_users_group', user_group_id=ID)
263
266
264 user_group_id = safe_int(user_group_id)
267 user_group_id = safe_int(user_group_id)
265 c.user_group = UserGroup.get_or_404(user_group_id)
268 c.user_group = UserGroup.get_or_404(user_group_id)
266 c.active = 'settings'
269 c.active = 'settings'
267 self.__load_data(user_group_id)
270 self.__load_data(user_group_id)
268
271
269 defaults = self.__load_defaults(user_group_id)
272 defaults = self.__load_defaults(user_group_id)
270
273
271 return htmlfill.render(
274 return htmlfill.render(
272 render('admin/user_groups/user_group_edit.mako'),
275 render('admin/user_groups/user_group_edit.mako'),
273 defaults=defaults,
276 defaults=defaults,
274 encoding="UTF-8",
277 encoding="UTF-8",
275 force_defaults=False
278 force_defaults=False
276 )
279 )
277
280
278 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
281 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
279 def edit_perms(self, user_group_id):
282 def edit_perms(self, user_group_id):
280 user_group_id = safe_int(user_group_id)
283 user_group_id = safe_int(user_group_id)
281 c.user_group = UserGroup.get_or_404(user_group_id)
284 c.user_group = UserGroup.get_or_404(user_group_id)
282 c.active = 'perms'
285 c.active = 'perms'
283
286
284 defaults = {}
287 defaults = {}
285 # fill user group users
288 # fill user group users
286 for p in c.user_group.user_user_group_to_perm:
289 for p in c.user_group.user_user_group_to_perm:
287 defaults.update({'u_perm_%s' % p.user.user_id:
290 defaults.update({'u_perm_%s' % p.user.user_id:
288 p.permission.permission_name})
291 p.permission.permission_name})
289
292
290 for p in c.user_group.user_group_user_group_to_perm:
293 for p in c.user_group.user_group_user_group_to_perm:
291 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
294 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
292 p.permission.permission_name})
295 p.permission.permission_name})
293
296
294 return htmlfill.render(
297 return htmlfill.render(
295 render('admin/user_groups/user_group_edit.mako'),
298 render('admin/user_groups/user_group_edit.mako'),
296 defaults=defaults,
299 defaults=defaults,
297 encoding="UTF-8",
300 encoding="UTF-8",
298 force_defaults=False
301 force_defaults=False
299 )
302 )
300
303
301 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
304 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
302 @auth.CSRFRequired()
305 @auth.CSRFRequired()
303 def update_perms(self, user_group_id):
306 def update_perms(self, user_group_id):
304 """
307 """
305 grant permission for given usergroup
308 grant permission for given usergroup
306
309
307 :param user_group_id:
310 :param user_group_id:
308 """
311 """
309 user_group_id = safe_int(user_group_id)
312 user_group_id = safe_int(user_group_id)
310 c.user_group = UserGroup.get_or_404(user_group_id)
313 c.user_group = UserGroup.get_or_404(user_group_id)
311 form = UserGroupPermsForm()().to_python(request.POST)
314 form = UserGroupPermsForm()().to_python(request.POST)
312
315
313 if not c.rhodecode_user.is_admin:
316 if not c.rhodecode_user.is_admin:
314 if self._revoke_perms_on_yourself(form):
317 if self._revoke_perms_on_yourself(form):
315 msg = _('Cannot change permission for yourself as admin')
318 msg = _('Cannot change permission for yourself as admin')
316 h.flash(msg, category='warning')
319 h.flash(msg, category='warning')
317 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
320 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
318
321
319 try:
322 try:
320 UserGroupModel().update_permissions(user_group_id,
323 UserGroupModel().update_permissions(user_group_id,
321 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
324 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
322 except RepoGroupAssignmentError:
325 except RepoGroupAssignmentError:
323 h.flash(_('Target group cannot be the same'), category='error')
326 h.flash(_('Target group cannot be the same'), category='error')
324 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
325
328
326 # TODO(marcink): implement global permissions
329 # TODO(marcink): implement global permissions
327 # audit_log.store_web('user_group.edit.permissions')
330 # audit_log.store_web('user_group.edit.permissions')
328 Session().commit()
331 Session().commit()
329 h.flash(_('User Group permissions updated'), category='success')
332 h.flash(_('User Group permissions updated'), category='success')
330 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
333 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
331
334
332 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
335 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
333 def edit_perms_summary(self, user_group_id):
336 def edit_perms_summary(self, user_group_id):
334 user_group_id = safe_int(user_group_id)
337 user_group_id = safe_int(user_group_id)
335 c.user_group = UserGroup.get_or_404(user_group_id)
338 c.user_group = UserGroup.get_or_404(user_group_id)
336 c.active = 'perms_summary'
339 c.active = 'perms_summary'
337 permissions = {
340 permissions = {
338 'repositories': {},
341 'repositories': {},
339 'repositories_groups': {},
342 'repositories_groups': {},
340 }
343 }
341 ugroup_repo_perms = UserGroupRepoToPerm.query()\
344 ugroup_repo_perms = UserGroupRepoToPerm.query()\
342 .options(joinedload(UserGroupRepoToPerm.permission))\
345 .options(joinedload(UserGroupRepoToPerm.permission))\
343 .options(joinedload(UserGroupRepoToPerm.repository))\
346 .options(joinedload(UserGroupRepoToPerm.repository))\
344 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
347 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
345 .all()
348 .all()
346
349
347 for gr in ugroup_repo_perms:
350 for gr in ugroup_repo_perms:
348 permissions['repositories'][gr.repository.repo_name] \
351 permissions['repositories'][gr.repository.repo_name] \
349 = gr.permission.permission_name
352 = gr.permission.permission_name
350
353
351 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
354 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
352 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
355 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
353 .options(joinedload(UserGroupRepoGroupToPerm.group))\
356 .options(joinedload(UserGroupRepoGroupToPerm.group))\
354 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
357 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
355 .all()
358 .all()
356
359
357 for gr in ugroup_group_perms:
360 for gr in ugroup_group_perms:
358 permissions['repositories_groups'][gr.group.group_name] \
361 permissions['repositories_groups'][gr.group.group_name] \
359 = gr.permission.permission_name
362 = gr.permission.permission_name
360 c.permissions = permissions
363 c.permissions = permissions
361 return render('admin/user_groups/user_group_edit.mako')
364 return render('admin/user_groups/user_group_edit.mako')
362
365
363 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
366 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
364 def edit_global_perms(self, user_group_id):
367 def edit_global_perms(self, user_group_id):
365 user_group_id = safe_int(user_group_id)
368 user_group_id = safe_int(user_group_id)
366 c.user_group = UserGroup.get_or_404(user_group_id)
369 c.user_group = UserGroup.get_or_404(user_group_id)
367 c.active = 'global_perms'
370 c.active = 'global_perms'
368
371
369 c.default_user = User.get_default_user()
372 c.default_user = User.get_default_user()
370 defaults = c.user_group.get_dict()
373 defaults = c.user_group.get_dict()
371 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
374 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
372 defaults.update(c.user_group.get_default_perms())
375 defaults.update(c.user_group.get_default_perms())
373
376
374 return htmlfill.render(
377 return htmlfill.render(
375 render('admin/user_groups/user_group_edit.mako'),
378 render('admin/user_groups/user_group_edit.mako'),
376 defaults=defaults,
379 defaults=defaults,
377 encoding="UTF-8",
380 encoding="UTF-8",
378 force_defaults=False
381 force_defaults=False
379 )
382 )
380
383
381 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
384 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
382 @auth.CSRFRequired()
385 @auth.CSRFRequired()
383 def update_global_perms(self, user_group_id):
386 def update_global_perms(self, user_group_id):
384 user_group_id = safe_int(user_group_id)
387 user_group_id = safe_int(user_group_id)
385 user_group = UserGroup.get_or_404(user_group_id)
388 user_group = UserGroup.get_or_404(user_group_id)
386 c.active = 'global_perms'
389 c.active = 'global_perms'
387
390
388 try:
391 try:
389 # first stage that verifies the checkbox
392 # first stage that verifies the checkbox
390 _form = UserIndividualPermissionsForm()
393 _form = UserIndividualPermissionsForm()
391 form_result = _form.to_python(dict(request.POST))
394 form_result = _form.to_python(dict(request.POST))
392 inherit_perms = form_result['inherit_default_permissions']
395 inherit_perms = form_result['inherit_default_permissions']
393 user_group.inherit_default_permissions = inherit_perms
396 user_group.inherit_default_permissions = inherit_perms
394 Session().add(user_group)
397 Session().add(user_group)
395
398
396 if not inherit_perms:
399 if not inherit_perms:
397 # only update the individual ones if we un check the flag
400 # only update the individual ones if we un check the flag
398 _form = UserPermissionsForm(
401 _form = UserPermissionsForm(
399 [x[0] for x in c.repo_create_choices],
402 [x[0] for x in c.repo_create_choices],
400 [x[0] for x in c.repo_create_on_write_choices],
403 [x[0] for x in c.repo_create_on_write_choices],
401 [x[0] for x in c.repo_group_create_choices],
404 [x[0] for x in c.repo_group_create_choices],
402 [x[0] for x in c.user_group_create_choices],
405 [x[0] for x in c.user_group_create_choices],
403 [x[0] for x in c.fork_choices],
406 [x[0] for x in c.fork_choices],
404 [x[0] for x in c.inherit_default_permission_choices])()
407 [x[0] for x in c.inherit_default_permission_choices])()
405
408
406 form_result = _form.to_python(dict(request.POST))
409 form_result = _form.to_python(dict(request.POST))
407 form_result.update({'perm_user_group_id': user_group.users_group_id})
410 form_result.update({'perm_user_group_id': user_group.users_group_id})
408
411
409 PermissionModel().update_user_group_permissions(form_result)
412 PermissionModel().update_user_group_permissions(form_result)
410
413
411 Session().commit()
414 Session().commit()
412 h.flash(_('User Group global permissions updated successfully'),
415 h.flash(_('User Group global permissions updated successfully'),
413 category='success')
416 category='success')
414
417
415 except formencode.Invalid as errors:
418 except formencode.Invalid as errors:
416 defaults = errors.value
419 defaults = errors.value
417 c.user_group = user_group
420 c.user_group = user_group
418 return htmlfill.render(
421 return htmlfill.render(
419 render('admin/user_groups/user_group_edit.mako'),
422 render('admin/user_groups/user_group_edit.mako'),
420 defaults=defaults,
423 defaults=defaults,
421 errors=errors.error_dict or {},
424 errors=errors.error_dict or {},
422 prefix_error=False,
425 prefix_error=False,
423 encoding="UTF-8",
426 encoding="UTF-8",
424 force_defaults=False)
427 force_defaults=False)
425 except Exception:
428 except Exception:
426 log.exception("Exception during permissions saving")
429 log.exception("Exception during permissions saving")
427 h.flash(_('An error occurred during permissions saving'),
430 h.flash(_('An error occurred during permissions saving'),
428 category='error')
431 category='error')
429
432
430 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
433 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
431
434
432 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
435 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
433 def edit_advanced(self, user_group_id):
436 def edit_advanced(self, user_group_id):
434 user_group_id = safe_int(user_group_id)
437 user_group_id = safe_int(user_group_id)
435 c.user_group = UserGroup.get_or_404(user_group_id)
438 c.user_group = UserGroup.get_or_404(user_group_id)
436 c.active = 'advanced'
439 c.active = 'advanced'
437 c.group_members_obj = sorted(
440 c.group_members_obj = sorted(
438 (x.user for x in c.user_group.members),
441 (x.user for x in c.user_group.members),
439 key=lambda u: u.username.lower())
442 key=lambda u: u.username.lower())
440
443
441 c.group_to_repos = sorted(
444 c.group_to_repos = sorted(
442 (x.repository for x in c.user_group.users_group_repo_to_perm),
445 (x.repository for x in c.user_group.users_group_repo_to_perm),
443 key=lambda u: u.repo_name.lower())
446 key=lambda u: u.repo_name.lower())
444
447
445 c.group_to_repo_groups = sorted(
448 c.group_to_repo_groups = sorted(
446 (x.group for x in c.user_group.users_group_repo_group_to_perm),
449 (x.group for x in c.user_group.users_group_repo_group_to_perm),
447 key=lambda u: u.group_name.lower())
450 key=lambda u: u.group_name.lower())
448
451
449 return render('admin/user_groups/user_group_edit.mako')
452 return render('admin/user_groups/user_group_edit.mako')
450
453
451 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
454 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
452 def edit_advanced_set_synchronization(self, user_group_id):
455 def edit_advanced_set_synchronization(self, user_group_id):
453 user_group_id = safe_int(user_group_id)
456 user_group_id = safe_int(user_group_id)
454 user_group = UserGroup.get_or_404(user_group_id)
457 user_group = UserGroup.get_or_404(user_group_id)
455
458
456 existing = user_group.group_data.get('extern_type')
459 existing = user_group.group_data.get('extern_type')
457
460
458 if existing:
461 if existing:
459 new_state = user_group.group_data
462 new_state = user_group.group_data
460 new_state['extern_type'] = None
463 new_state['extern_type'] = None
461 else:
464 else:
462 new_state = user_group.group_data
465 new_state = user_group.group_data
463 new_state['extern_type'] = 'manual'
466 new_state['extern_type'] = 'manual'
464 new_state['extern_type_set_by'] = c.rhodecode_user.username
467 new_state['extern_type_set_by'] = c.rhodecode_user.username
465
468
466 try:
469 try:
467 user_group.group_data = new_state
470 user_group.group_data = new_state
468 Session().add(user_group)
471 Session().add(user_group)
469 Session().commit()
472 Session().commit()
470
473
471 h.flash(_('User Group synchronization updated successfully'),
474 h.flash(_('User Group synchronization updated successfully'),
472 category='success')
475 category='success')
473 except Exception:
476 except Exception:
474 log.exception("Exception during sync settings saving")
477 log.exception("Exception during sync settings saving")
475 h.flash(_('An error occurred during synchronization update'),
478 h.flash(_('An error occurred during synchronization update'),
476 category='error')
479 category='error')
477
480
478 return redirect(
481 return redirect(
479 url('edit_user_group_advanced', user_group_id=user_group_id))
482 url('edit_user_group_advanced', user_group_id=user_group_id))
480
483
481 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
484 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
482 @XHRRequired()
485 @XHRRequired()
483 @jsonify
486 @jsonify
484 def user_group_members(self, user_group_id):
487 def user_group_members(self, user_group_id):
485 """
488 """
486 Return members of given user group
489 Return members of given user group
487 """
490 """
488 user_group_id = safe_int(user_group_id)
491 user_group_id = safe_int(user_group_id)
489 user_group = UserGroup.get_or_404(user_group_id)
492 user_group = UserGroup.get_or_404(user_group_id)
490 group_members_obj = sorted((x.user for x in user_group.members),
493 group_members_obj = sorted((x.user for x in user_group.members),
491 key=lambda u: u.username.lower())
494 key=lambda u: u.username.lower())
492
495
493 group_members = [
496 group_members = [
494 {
497 {
495 'id': user.user_id,
498 'id': user.user_id,
496 'first_name': user.first_name,
499 'first_name': user.first_name,
497 'last_name': user.last_name,
500 'last_name': user.last_name,
498 'username': user.username,
501 'username': user.username,
499 'icon_link': h.gravatar_url(user.email, 30),
502 'icon_link': h.gravatar_url(user.email, 30),
500 'value_display': h.person(user.email),
503 'value_display': h.person(user.email),
501 'value': user.username,
504 'value': user.username,
502 'value_type': 'user',
505 'value_type': 'user',
503 'active': user.active,
506 'active': user.active,
504 }
507 }
505 for user in group_members_obj
508 for user in group_members_obj
506 ]
509 ]
507
510
508 return {
511 return {
509 'members': group_members
512 'members': group_members
510 }
513 }
@@ -1,1110 +1,1110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Files controller for RhodeCode Enterprise
22 Files controller for RhodeCode Enterprise
23 """
23 """
24
24
25 import itertools
25 import itertools
26 import logging
26 import logging
27 import os
27 import os
28 import shutil
28 import shutil
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from webob.exc import HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPNotFound, HTTPBadRequest
35
35
36 from rhodecode.controllers.utils import parse_path_ref
36 from rhodecode.controllers.utils import parse_path_ref
37 from rhodecode.lib import diffs, helpers as h, caches
37 from rhodecode.lib import diffs, helpers as h, caches
38 from rhodecode.lib import audit_logger
38 from rhodecode.lib import audit_logger
39 from rhodecode.lib.codeblocks import (
39 from rhodecode.lib.codeblocks import (
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
40 filenode_as_lines_tokens, filenode_as_annotated_lines_tokens)
41 from rhodecode.lib.utils import jsonify
41 from rhodecode.lib.utils import jsonify
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 convert_line_endings, detect_mode, safe_str, str2bool)
43 convert_line_endings, detect_mode, safe_str, str2bool)
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
45 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired, XHRRequired)
46 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.base import BaseRepoController, render
47 from rhodecode.lib.vcs import path as vcspath
47 from rhodecode.lib.vcs import path as vcspath
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
48 from rhodecode.lib.vcs.backends.base import EmptyCommit
49 from rhodecode.lib.vcs.conf import settings
49 from rhodecode.lib.vcs.conf import settings
50 from rhodecode.lib.vcs.exceptions import (
50 from rhodecode.lib.vcs.exceptions import (
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
51 RepositoryError, CommitDoesNotExistError, EmptyRepositoryError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
52 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,
53 NodeDoesNotExistError, CommitError, NodeError)
53 NodeDoesNotExistError, CommitError, NodeError)
54 from rhodecode.lib.vcs.nodes import FileNode
54 from rhodecode.lib.vcs.nodes import FileNode
55
55
56 from rhodecode.model.repo import RepoModel
56 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.db import Repository
58 from rhodecode.model.db import Repository
59
59
60 from rhodecode.controllers.changeset import (
60 from rhodecode.controllers.changeset import (
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
61 _ignorews_url, _context_url, get_line_ctx, get_ignore_ws)
62 from rhodecode.lib.exceptions import NonRelativePathError
62 from rhodecode.lib.exceptions import NonRelativePathError
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 class FilesController(BaseRepoController):
67 class FilesController(BaseRepoController):
68
68
69 def __before__(self):
69 def __before__(self):
70 super(FilesController, self).__before__()
70 super(FilesController, self).__before__()
71 c.cut_off_limit = self.cut_off_limit_file
71 c.cut_off_limit = self.cut_off_limit_file
72
72
73 def _get_default_encoding(self):
73 def _get_default_encoding(self):
74 enc_list = getattr(c, 'default_encodings', [])
74 enc_list = getattr(c, 'default_encodings', [])
75 return enc_list[0] if enc_list else 'UTF-8'
75 return enc_list[0] if enc_list else 'UTF-8'
76
76
77 def __get_commit_or_redirect(self, commit_id, repo_name,
77 def __get_commit_or_redirect(self, commit_id, repo_name,
78 redirect_after=True):
78 redirect_after=True):
79 """
79 """
80 This is a safe way to get commit. If an error occurs it redirects to
80 This is a safe way to get commit. If an error occurs it redirects to
81 tip with proper message
81 tip with proper message
82
82
83 :param commit_id: id of commit to fetch
83 :param commit_id: id of commit to fetch
84 :param repo_name: repo name to redirect after
84 :param repo_name: repo name to redirect after
85 :param redirect_after: toggle redirection
85 :param redirect_after: toggle redirection
86 """
86 """
87 try:
87 try:
88 return c.rhodecode_repo.get_commit(commit_id)
88 return c.rhodecode_repo.get_commit(commit_id)
89 except EmptyRepositoryError:
89 except EmptyRepositoryError:
90 if not redirect_after:
90 if not redirect_after:
91 return None
91 return None
92 url_ = url('files_add_home',
92 url_ = url('files_add_home',
93 repo_name=c.repo_name,
93 repo_name=c.repo_name,
94 revision=0, f_path='', anchor='edit')
94 revision=0, f_path='', anchor='edit')
95 if h.HasRepoPermissionAny(
95 if h.HasRepoPermissionAny(
96 'repository.write', 'repository.admin')(c.repo_name):
96 'repository.write', 'repository.admin')(c.repo_name):
97 add_new = h.link_to(
97 add_new = h.link_to(
98 _('Click here to add a new file.'),
98 _('Click here to add a new file.'),
99 url_, class_="alert-link")
99 url_, class_="alert-link")
100 else:
100 else:
101 add_new = ""
101 add_new = ""
102 h.flash(h.literal(
102 h.flash(h.literal(
103 _('There are no files yet. %s') % add_new), category='warning')
103 _('There are no files yet. %s') % add_new), category='warning')
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
104 redirect(h.route_path('repo_summary', repo_name=repo_name))
105 except (CommitDoesNotExistError, LookupError):
105 except (CommitDoesNotExistError, LookupError):
106 msg = _('No such commit exists for this repository')
106 msg = _('No such commit exists for this repository')
107 h.flash(msg, category='error')
107 h.flash(msg, category='error')
108 raise HTTPNotFound()
108 raise HTTPNotFound()
109 except RepositoryError as e:
109 except RepositoryError as e:
110 h.flash(safe_str(e), category='error')
110 h.flash(safe_str(e), category='error')
111 raise HTTPNotFound()
111 raise HTTPNotFound()
112
112
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
113 def __get_filenode_or_redirect(self, repo_name, commit, path):
114 """
114 """
115 Returns file_node, if error occurs or given path is directory,
115 Returns file_node, if error occurs or given path is directory,
116 it'll redirect to top level path
116 it'll redirect to top level path
117
117
118 :param repo_name: repo_name
118 :param repo_name: repo_name
119 :param commit: given commit
119 :param commit: given commit
120 :param path: path to lookup
120 :param path: path to lookup
121 """
121 """
122 try:
122 try:
123 file_node = commit.get_node(path)
123 file_node = commit.get_node(path)
124 if file_node.is_dir():
124 if file_node.is_dir():
125 raise RepositoryError('The given path is a directory')
125 raise RepositoryError('The given path is a directory')
126 except CommitDoesNotExistError:
126 except CommitDoesNotExistError:
127 log.exception('No such commit exists for this repository')
127 log.exception('No such commit exists for this repository')
128 h.flash(_('No such commit exists for this repository'), category='error')
128 h.flash(_('No such commit exists for this repository'), category='error')
129 raise HTTPNotFound()
129 raise HTTPNotFound()
130 except RepositoryError as e:
130 except RepositoryError as e:
131 h.flash(safe_str(e), category='error')
131 h.flash(safe_str(e), category='error')
132 raise HTTPNotFound()
132 raise HTTPNotFound()
133
133
134 return file_node
134 return file_node
135
135
136 def __get_tree_cache_manager(self, repo_name, namespace_type):
136 def __get_tree_cache_manager(self, repo_name, namespace_type):
137 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
137 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
138 return caches.get_cache_manager('repo_cache_long', _namespace)
138 return caches.get_cache_manager('repo_cache_long', _namespace)
139
139
140 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
140 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
141 full_load=False, force=False):
141 full_load=False, force=False):
142 def _cached_tree():
142 def _cached_tree():
143 log.debug('Generating cached file tree for %s, %s, %s',
143 log.debug('Generating cached file tree for %s, %s, %s',
144 repo_name, commit_id, f_path)
144 repo_name, commit_id, f_path)
145 c.full_load = full_load
145 c.full_load = full_load
146 return render('files/files_browser_tree.mako')
146 return render('files/files_browser_tree.mako')
147
147
148 cache_manager = self.__get_tree_cache_manager(
148 cache_manager = self.__get_tree_cache_manager(
149 repo_name, caches.FILE_TREE)
149 repo_name, caches.FILE_TREE)
150
150
151 cache_key = caches.compute_key_from_params(
151 cache_key = caches.compute_key_from_params(
152 repo_name, commit_id, f_path)
152 repo_name, commit_id, f_path)
153
153
154 if force:
154 if force:
155 # we want to force recompute of caches
155 # we want to force recompute of caches
156 cache_manager.remove_value(cache_key)
156 cache_manager.remove_value(cache_key)
157
157
158 return cache_manager.get(cache_key, createfunc=_cached_tree)
158 return cache_manager.get(cache_key, createfunc=_cached_tree)
159
159
160 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
160 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
161 def _cached_nodes():
161 def _cached_nodes():
162 log.debug('Generating cached nodelist for %s, %s, %s',
162 log.debug('Generating cached nodelist for %s, %s, %s',
163 repo_name, commit_id, f_path)
163 repo_name, commit_id, f_path)
164 _d, _f = ScmModel().get_nodes(
164 _d, _f = ScmModel().get_nodes(
165 repo_name, commit_id, f_path, flat=False)
165 repo_name, commit_id, f_path, flat=False)
166 return _d + _f
166 return _d + _f
167
167
168 cache_manager = self.__get_tree_cache_manager(
168 cache_manager = self.__get_tree_cache_manager(
169 repo_name, caches.FILE_SEARCH_TREE_META)
169 repo_name, caches.FILE_SEARCH_TREE_META)
170
170
171 cache_key = caches.compute_key_from_params(
171 cache_key = caches.compute_key_from_params(
172 repo_name, commit_id, f_path)
172 repo_name, commit_id, f_path)
173 return cache_manager.get(cache_key, createfunc=_cached_nodes)
173 return cache_manager.get(cache_key, createfunc=_cached_nodes)
174
174
175 @LoginRequired()
175 @LoginRequired()
176 @HasRepoPermissionAnyDecorator(
176 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
177 'repository.read', 'repository.write', 'repository.admin')
178 def index(
178 def index(
179 self, repo_name, revision, f_path, annotate=False, rendered=False):
179 self, repo_name, revision, f_path, annotate=False, rendered=False):
180 commit_id = revision
180 commit_id = revision
181
181
182 # redirect to given commit_id from form if given
182 # redirect to given commit_id from form if given
183 get_commit_id = request.GET.get('at_rev', None)
183 get_commit_id = request.GET.get('at_rev', None)
184 if get_commit_id:
184 if get_commit_id:
185 self.__get_commit_or_redirect(get_commit_id, repo_name)
185 self.__get_commit_or_redirect(get_commit_id, repo_name)
186
186
187 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
187 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
188 c.branch = request.GET.get('branch', None)
188 c.branch = request.GET.get('branch', None)
189 c.f_path = f_path
189 c.f_path = f_path
190 c.annotate = annotate
190 c.annotate = annotate
191 # default is false, but .rst/.md files later are autorendered, we can
191 # default is false, but .rst/.md files later are autorendered, we can
192 # overwrite autorendering by setting this GET flag
192 # overwrite autorendering by setting this GET flag
193 c.renderer = rendered or not request.GET.get('no-render', False)
193 c.renderer = rendered or not request.GET.get('no-render', False)
194
194
195 # prev link
195 # prev link
196 try:
196 try:
197 prev_commit = c.commit.prev(c.branch)
197 prev_commit = c.commit.prev(c.branch)
198 c.prev_commit = prev_commit
198 c.prev_commit = prev_commit
199 c.url_prev = url('files_home', repo_name=c.repo_name,
199 c.url_prev = url('files_home', repo_name=c.repo_name,
200 revision=prev_commit.raw_id, f_path=f_path)
200 revision=prev_commit.raw_id, f_path=f_path)
201 if c.branch:
201 if c.branch:
202 c.url_prev += '?branch=%s' % c.branch
202 c.url_prev += '?branch=%s' % c.branch
203 except (CommitDoesNotExistError, VCSError):
203 except (CommitDoesNotExistError, VCSError):
204 c.url_prev = '#'
204 c.url_prev = '#'
205 c.prev_commit = EmptyCommit()
205 c.prev_commit = EmptyCommit()
206
206
207 # next link
207 # next link
208 try:
208 try:
209 next_commit = c.commit.next(c.branch)
209 next_commit = c.commit.next(c.branch)
210 c.next_commit = next_commit
210 c.next_commit = next_commit
211 c.url_next = url('files_home', repo_name=c.repo_name,
211 c.url_next = url('files_home', repo_name=c.repo_name,
212 revision=next_commit.raw_id, f_path=f_path)
212 revision=next_commit.raw_id, f_path=f_path)
213 if c.branch:
213 if c.branch:
214 c.url_next += '?branch=%s' % c.branch
214 c.url_next += '?branch=%s' % c.branch
215 except (CommitDoesNotExistError, VCSError):
215 except (CommitDoesNotExistError, VCSError):
216 c.url_next = '#'
216 c.url_next = '#'
217 c.next_commit = EmptyCommit()
217 c.next_commit = EmptyCommit()
218
218
219 # files or dirs
219 # files or dirs
220 try:
220 try:
221 c.file = c.commit.get_node(f_path)
221 c.file = c.commit.get_node(f_path)
222 c.file_author = True
222 c.file_author = True
223 c.file_tree = ''
223 c.file_tree = ''
224 if c.file.is_file():
224 if c.file.is_file():
225 c.lf_node = c.file.get_largefile_node()
225 c.lf_node = c.file.get_largefile_node()
226
226
227 c.file_source_page = 'true'
227 c.file_source_page = 'true'
228 c.file_last_commit = c.file.last_commit
228 c.file_last_commit = c.file.last_commit
229 if c.file.size < self.cut_off_limit_file:
229 if c.file.size < self.cut_off_limit_file:
230 if c.annotate: # annotation has precedence over renderer
230 if c.annotate: # annotation has precedence over renderer
231 c.annotated_lines = filenode_as_annotated_lines_tokens(
231 c.annotated_lines = filenode_as_annotated_lines_tokens(
232 c.file
232 c.file
233 )
233 )
234 else:
234 else:
235 c.renderer = (
235 c.renderer = (
236 c.renderer and h.renderer_from_filename(c.file.path)
236 c.renderer and h.renderer_from_filename(c.file.path)
237 )
237 )
238 if not c.renderer:
238 if not c.renderer:
239 c.lines = filenode_as_lines_tokens(c.file)
239 c.lines = filenode_as_lines_tokens(c.file)
240
240
241 c.on_branch_head = self._is_valid_head(
241 c.on_branch_head = self._is_valid_head(
242 commit_id, c.rhodecode_repo)
242 commit_id, c.rhodecode_repo)
243
243
244 branch = c.commit.branch if (
244 branch = c.commit.branch if (
245 c.commit.branch and '/' not in c.commit.branch) else None
245 c.commit.branch and '/' not in c.commit.branch) else None
246 c.branch_or_raw_id = branch or c.commit.raw_id
246 c.branch_or_raw_id = branch or c.commit.raw_id
247 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
247 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
248
248
249 author = c.file_last_commit.author
249 author = c.file_last_commit.author
250 c.authors = [(h.email(author),
250 c.authors = [(h.email(author),
251 h.person(author, 'username_or_name_or_email'))]
251 h.person(author, 'username_or_name_or_email'))]
252 else:
252 else:
253 c.file_source_page = 'false'
253 c.file_source_page = 'false'
254 c.authors = []
254 c.authors = []
255 c.file_tree = self._get_tree_at_commit(
255 c.file_tree = self._get_tree_at_commit(
256 repo_name, c.commit.raw_id, f_path)
256 repo_name, c.commit.raw_id, f_path)
257
257
258 except RepositoryError as e:
258 except RepositoryError as e:
259 h.flash(safe_str(e), category='error')
259 h.flash(safe_str(e), category='error')
260 raise HTTPNotFound()
260 raise HTTPNotFound()
261
261
262 if request.environ.get('HTTP_X_PJAX'):
262 if request.environ.get('HTTP_X_PJAX'):
263 return render('files/files_pjax.mako')
263 return render('files/files_pjax.mako')
264
264
265 return render('files/files.mako')
265 return render('files/files.mako')
266
266
267 @LoginRequired()
267 @LoginRequired()
268 @HasRepoPermissionAnyDecorator(
268 @HasRepoPermissionAnyDecorator(
269 'repository.read', 'repository.write', 'repository.admin')
269 'repository.read', 'repository.write', 'repository.admin')
270 def annotate_previous(self, repo_name, revision, f_path):
270 def annotate_previous(self, repo_name, revision, f_path):
271
271
272 commit_id = revision
272 commit_id = revision
273 commit = self.__get_commit_or_redirect(commit_id, repo_name)
273 commit = self.__get_commit_or_redirect(commit_id, repo_name)
274 prev_commit_id = commit.raw_id
274 prev_commit_id = commit.raw_id
275
275
276 f_path = f_path
276 f_path = f_path
277 is_file = False
277 is_file = False
278 try:
278 try:
279 _file = commit.get_node(f_path)
279 _file = commit.get_node(f_path)
280 is_file = _file.is_file()
280 is_file = _file.is_file()
281 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
281 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
282 pass
282 pass
283
283
284 if is_file:
284 if is_file:
285 history = commit.get_file_history(f_path)
285 history = commit.get_file_history(f_path)
286 prev_commit_id = history[1].raw_id \
286 prev_commit_id = history[1].raw_id \
287 if len(history) > 1 else prev_commit_id
287 if len(history) > 1 else prev_commit_id
288
288
289 return redirect(h.url(
289 return redirect(h.url(
290 'files_annotate_home', repo_name=repo_name,
290 'files_annotate_home', repo_name=repo_name,
291 revision=prev_commit_id, f_path=f_path))
291 revision=prev_commit_id, f_path=f_path))
292
292
293 @LoginRequired()
293 @LoginRequired()
294 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
294 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
295 'repository.admin')
295 'repository.admin')
296 @jsonify
296 @jsonify
297 def history(self, repo_name, revision, f_path):
297 def history(self, repo_name, revision, f_path):
298 commit = self.__get_commit_or_redirect(revision, repo_name)
298 commit = self.__get_commit_or_redirect(revision, repo_name)
299 f_path = f_path
299 f_path = f_path
300 _file = commit.get_node(f_path)
300 _file = commit.get_node(f_path)
301 if _file.is_file():
301 if _file.is_file():
302 file_history, _hist = self._get_node_history(commit, f_path)
302 file_history, _hist = self._get_node_history(commit, f_path)
303
303
304 res = []
304 res = []
305 for obj in file_history:
305 for obj in file_history:
306 res.append({
306 res.append({
307 'text': obj[1],
307 'text': obj[1],
308 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
308 'children': [{'id': o[0], 'text': o[1]} for o in obj[0]]
309 })
309 })
310
310
311 data = {
311 data = {
312 'more': False,
312 'more': False,
313 'results': res
313 'results': res
314 }
314 }
315 return data
315 return data
316
316
317 @LoginRequired()
317 @LoginRequired()
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
318 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
319 'repository.admin')
319 'repository.admin')
320 def authors(self, repo_name, revision, f_path):
320 def authors(self, repo_name, revision, f_path):
321 commit = self.__get_commit_or_redirect(revision, repo_name)
321 commit = self.__get_commit_or_redirect(revision, repo_name)
322 file_node = commit.get_node(f_path)
322 file_node = commit.get_node(f_path)
323 if file_node.is_file():
323 if file_node.is_file():
324 c.file_last_commit = file_node.last_commit
324 c.file_last_commit = file_node.last_commit
325 if request.GET.get('annotate') == '1':
325 if request.GET.get('annotate') == '1':
326 # use _hist from annotation if annotation mode is on
326 # use _hist from annotation if annotation mode is on
327 commit_ids = set(x[1] for x in file_node.annotate)
327 commit_ids = set(x[1] for x in file_node.annotate)
328 _hist = (
328 _hist = (
329 c.rhodecode_repo.get_commit(commit_id)
329 c.rhodecode_repo.get_commit(commit_id)
330 for commit_id in commit_ids)
330 for commit_id in commit_ids)
331 else:
331 else:
332 _f_history, _hist = self._get_node_history(commit, f_path)
332 _f_history, _hist = self._get_node_history(commit, f_path)
333 c.file_author = False
333 c.file_author = False
334 c.authors = []
334 c.authors = []
335 for author in set(commit.author for commit in _hist):
335 for author in set(commit.author for commit in _hist):
336 c.authors.append((
336 c.authors.append((
337 h.email(author),
337 h.email(author),
338 h.person(author, 'username_or_name_or_email')))
338 h.person(author, 'username_or_name_or_email')))
339 return render('files/file_authors_box.mako')
339 return render('files/file_authors_box.mako')
340
340
341 @LoginRequired()
341 @LoginRequired()
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
342 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
343 'repository.admin')
343 'repository.admin')
344 def rawfile(self, repo_name, revision, f_path):
344 def rawfile(self, repo_name, revision, f_path):
345 """
345 """
346 Action for download as raw
346 Action for download as raw
347 """
347 """
348 commit = self.__get_commit_or_redirect(revision, repo_name)
348 commit = self.__get_commit_or_redirect(revision, repo_name)
349 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
349 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
350
350
351 if request.GET.get('lf'):
351 if request.GET.get('lf'):
352 # only if lf get flag is passed, we download this file
352 # only if lf get flag is passed, we download this file
353 # as LFS/Largefile
353 # as LFS/Largefile
354 lf_node = file_node.get_largefile_node()
354 lf_node = file_node.get_largefile_node()
355 if lf_node:
355 if lf_node:
356 # overwrite our pointer with the REAL large-file
356 # overwrite our pointer with the REAL large-file
357 file_node = lf_node
357 file_node = lf_node
358
358
359 response.content_disposition = 'attachment; filename=%s' % \
359 response.content_disposition = 'attachment; filename=%s' % \
360 safe_str(f_path.split(Repository.NAME_SEP)[-1])
360 safe_str(f_path.split(Repository.NAME_SEP)[-1])
361
361
362 response.content_type = file_node.mimetype
362 response.content_type = file_node.mimetype
363 charset = self._get_default_encoding()
363 charset = self._get_default_encoding()
364 if charset:
364 if charset:
365 response.charset = charset
365 response.charset = charset
366
366
367 return file_node.content
367 return file_node.content
368
368
369 @LoginRequired()
369 @LoginRequired()
370 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
370 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
371 'repository.admin')
371 'repository.admin')
372 def raw(self, repo_name, revision, f_path):
372 def raw(self, repo_name, revision, f_path):
373 """
373 """
374 Action for show as raw, some mimetypes are "rendered",
374 Action for show as raw, some mimetypes are "rendered",
375 those include images, icons.
375 those include images, icons.
376 """
376 """
377 commit = self.__get_commit_or_redirect(revision, repo_name)
377 commit = self.__get_commit_or_redirect(revision, repo_name)
378 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
378 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
379
379
380 raw_mimetype_mapping = {
380 raw_mimetype_mapping = {
381 # map original mimetype to a mimetype used for "show as raw"
381 # map original mimetype to a mimetype used for "show as raw"
382 # you can also provide a content-disposition to override the
382 # you can also provide a content-disposition to override the
383 # default "attachment" disposition.
383 # default "attachment" disposition.
384 # orig_type: (new_type, new_dispo)
384 # orig_type: (new_type, new_dispo)
385
385
386 # show images inline:
386 # show images inline:
387 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
387 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
388 # for example render an SVG with javascript inside or even render
388 # for example render an SVG with javascript inside or even render
389 # HTML.
389 # HTML.
390 'image/x-icon': ('image/x-icon', 'inline'),
390 'image/x-icon': ('image/x-icon', 'inline'),
391 'image/png': ('image/png', 'inline'),
391 'image/png': ('image/png', 'inline'),
392 'image/gif': ('image/gif', 'inline'),
392 'image/gif': ('image/gif', 'inline'),
393 'image/jpeg': ('image/jpeg', 'inline'),
393 'image/jpeg': ('image/jpeg', 'inline'),
394 'application/pdf': ('application/pdf', 'inline'),
394 'application/pdf': ('application/pdf', 'inline'),
395 }
395 }
396
396
397 mimetype = file_node.mimetype
397 mimetype = file_node.mimetype
398 try:
398 try:
399 mimetype, dispo = raw_mimetype_mapping[mimetype]
399 mimetype, dispo = raw_mimetype_mapping[mimetype]
400 except KeyError:
400 except KeyError:
401 # we don't know anything special about this, handle it safely
401 # we don't know anything special about this, handle it safely
402 if file_node.is_binary:
402 if file_node.is_binary:
403 # do same as download raw for binary files
403 # do same as download raw for binary files
404 mimetype, dispo = 'application/octet-stream', 'attachment'
404 mimetype, dispo = 'application/octet-stream', 'attachment'
405 else:
405 else:
406 # do not just use the original mimetype, but force text/plain,
406 # do not just use the original mimetype, but force text/plain,
407 # otherwise it would serve text/html and that might be unsafe.
407 # otherwise it would serve text/html and that might be unsafe.
408 # Note: underlying vcs library fakes text/plain mimetype if the
408 # Note: underlying vcs library fakes text/plain mimetype if the
409 # mimetype can not be determined and it thinks it is not
409 # mimetype can not be determined and it thinks it is not
410 # binary.This might lead to erroneous text display in some
410 # binary.This might lead to erroneous text display in some
411 # cases, but helps in other cases, like with text files
411 # cases, but helps in other cases, like with text files
412 # without extension.
412 # without extension.
413 mimetype, dispo = 'text/plain', 'inline'
413 mimetype, dispo = 'text/plain', 'inline'
414
414
415 if dispo == 'attachment':
415 if dispo == 'attachment':
416 dispo = 'attachment; filename=%s' % safe_str(
416 dispo = 'attachment; filename=%s' % safe_str(
417 f_path.split(os.sep)[-1])
417 f_path.split(os.sep)[-1])
418
418
419 response.content_disposition = dispo
419 response.content_disposition = dispo
420 response.content_type = mimetype
420 response.content_type = mimetype
421 charset = self._get_default_encoding()
421 charset = self._get_default_encoding()
422 if charset:
422 if charset:
423 response.charset = charset
423 response.charset = charset
424 return file_node.content
424 return file_node.content
425
425
426 @CSRFRequired()
426 @CSRFRequired()
427 @LoginRequired()
427 @LoginRequired()
428 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
428 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
429 def delete(self, repo_name, revision, f_path):
429 def delete(self, repo_name, revision, f_path):
430 commit_id = revision
430 commit_id = revision
431
431
432 repo = c.rhodecode_db_repo
432 repo = c.rhodecode_db_repo
433 if repo.enable_locking and repo.locked[0]:
433 if repo.enable_locking and repo.locked[0]:
434 h.flash(_('This repository has been locked by %s on %s')
434 h.flash(_('This repository has been locked by %s on %s')
435 % (h.person_by_id(repo.locked[0]),
435 % (h.person_by_id(repo.locked[0]),
436 h.format_date(h.time_to_datetime(repo.locked[1]))),
436 h.format_date(h.time_to_datetime(repo.locked[1]))),
437 'warning')
437 'warning')
438 return redirect(h.url('files_home',
438 return redirect(h.url('files_home',
439 repo_name=repo_name, revision='tip'))
439 repo_name=repo_name, revision='tip'))
440
440
441 if not self._is_valid_head(commit_id, repo.scm_instance()):
441 if not self._is_valid_head(commit_id, repo.scm_instance()):
442 h.flash(_('You can only delete files with revision '
442 h.flash(_('You can only delete files with revision '
443 'being a valid branch '), category='warning')
443 'being a valid branch '), category='warning')
444 return redirect(h.url('files_home',
444 return redirect(h.url('files_home',
445 repo_name=repo_name, revision='tip',
445 repo_name=repo_name, revision='tip',
446 f_path=f_path))
446 f_path=f_path))
447
447
448 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
448 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
449 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
449 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
450
450
451 c.default_message = _(
451 c.default_message = _(
452 'Deleted file {} via RhodeCode Enterprise').format(f_path)
452 'Deleted file {} via RhodeCode Enterprise').format(f_path)
453 c.f_path = f_path
453 c.f_path = f_path
454 node_path = f_path
454 node_path = f_path
455 author = c.rhodecode_user.full_contact
455 author = c.rhodecode_user.full_contact
456 message = request.POST.get('message') or c.default_message
456 message = request.POST.get('message') or c.default_message
457 try:
457 try:
458 nodes = {
458 nodes = {
459 node_path: {
459 node_path: {
460 'content': ''
460 'content': ''
461 }
461 }
462 }
462 }
463 self.scm_model.delete_nodes(
463 self.scm_model.delete_nodes(
464 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
464 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
465 message=message,
465 message=message,
466 nodes=nodes,
466 nodes=nodes,
467 parent_commit=c.commit,
467 parent_commit=c.commit,
468 author=author,
468 author=author,
469 )
469 )
470
470
471 h.flash(
471 h.flash(
472 _('Successfully deleted file `{}`').format(
472 _('Successfully deleted file `{}`').format(
473 h.escape(f_path)), category='success')
473 h.escape(f_path)), category='success')
474 except Exception:
474 except Exception:
475 msg = _('Error occurred during commit')
475 msg = _('Error occurred during commit')
476 log.exception(msg)
476 log.exception(msg)
477 h.flash(msg, category='error')
477 h.flash(msg, category='error')
478 return redirect(url('changeset_home',
478 return redirect(url('changeset_home',
479 repo_name=c.repo_name, revision='tip'))
479 repo_name=c.repo_name, revision='tip'))
480
480
481 @LoginRequired()
481 @LoginRequired()
482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
482 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
483 def delete_home(self, repo_name, revision, f_path):
483 def delete_home(self, repo_name, revision, f_path):
484 commit_id = revision
484 commit_id = revision
485
485
486 repo = c.rhodecode_db_repo
486 repo = c.rhodecode_db_repo
487 if repo.enable_locking and repo.locked[0]:
487 if repo.enable_locking and repo.locked[0]:
488 h.flash(_('This repository has been locked by %s on %s')
488 h.flash(_('This repository has been locked by %s on %s')
489 % (h.person_by_id(repo.locked[0]),
489 % (h.person_by_id(repo.locked[0]),
490 h.format_date(h.time_to_datetime(repo.locked[1]))),
490 h.format_date(h.time_to_datetime(repo.locked[1]))),
491 'warning')
491 'warning')
492 return redirect(h.url('files_home',
492 return redirect(h.url('files_home',
493 repo_name=repo_name, revision='tip'))
493 repo_name=repo_name, revision='tip'))
494
494
495 if not self._is_valid_head(commit_id, repo.scm_instance()):
495 if not self._is_valid_head(commit_id, repo.scm_instance()):
496 h.flash(_('You can only delete files with revision '
496 h.flash(_('You can only delete files with revision '
497 'being a valid branch '), category='warning')
497 'being a valid branch '), category='warning')
498 return redirect(h.url('files_home',
498 return redirect(h.url('files_home',
499 repo_name=repo_name, revision='tip',
499 repo_name=repo_name, revision='tip',
500 f_path=f_path))
500 f_path=f_path))
501
501
502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
502 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
503 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
504
504
505 c.default_message = _(
505 c.default_message = _(
506 'Deleted file {} via RhodeCode Enterprise').format(f_path)
506 'Deleted file {} via RhodeCode Enterprise').format(f_path)
507 c.f_path = f_path
507 c.f_path = f_path
508
508
509 return render('files/files_delete.mako')
509 return render('files/files_delete.mako')
510
510
511 @CSRFRequired()
511 @CSRFRequired()
512 @LoginRequired()
512 @LoginRequired()
513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
514 def edit(self, repo_name, revision, f_path):
514 def edit(self, repo_name, revision, f_path):
515 commit_id = revision
515 commit_id = revision
516
516
517 repo = c.rhodecode_db_repo
517 repo = c.rhodecode_db_repo
518 if repo.enable_locking and repo.locked[0]:
518 if repo.enable_locking and repo.locked[0]:
519 h.flash(_('This repository has been locked by %s on %s')
519 h.flash(_('This repository has been locked by %s on %s')
520 % (h.person_by_id(repo.locked[0]),
520 % (h.person_by_id(repo.locked[0]),
521 h.format_date(h.time_to_datetime(repo.locked[1]))),
521 h.format_date(h.time_to_datetime(repo.locked[1]))),
522 'warning')
522 'warning')
523 return redirect(h.url('files_home',
523 return redirect(h.url('files_home',
524 repo_name=repo_name, revision='tip'))
524 repo_name=repo_name, revision='tip'))
525
525
526 if not self._is_valid_head(commit_id, repo.scm_instance()):
526 if not self._is_valid_head(commit_id, repo.scm_instance()):
527 h.flash(_('You can only edit files with revision '
527 h.flash(_('You can only edit files with revision '
528 'being a valid branch '), category='warning')
528 'being a valid branch '), category='warning')
529 return redirect(h.url('files_home',
529 return redirect(h.url('files_home',
530 repo_name=repo_name, revision='tip',
530 repo_name=repo_name, revision='tip',
531 f_path=f_path))
531 f_path=f_path))
532
532
533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
533 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
534 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
535
535
536 if c.file.is_binary:
536 if c.file.is_binary:
537 return redirect(url('files_home', repo_name=c.repo_name,
537 return redirect(url('files_home', repo_name=c.repo_name,
538 revision=c.commit.raw_id, f_path=f_path))
538 revision=c.commit.raw_id, f_path=f_path))
539 c.default_message = _(
539 c.default_message = _(
540 'Edited file {} via RhodeCode Enterprise').format(f_path)
540 'Edited file {} via RhodeCode Enterprise').format(f_path)
541 c.f_path = f_path
541 c.f_path = f_path
542 old_content = c.file.content
542 old_content = c.file.content
543 sl = old_content.splitlines(1)
543 sl = old_content.splitlines(1)
544 first_line = sl[0] if sl else ''
544 first_line = sl[0] if sl else ''
545
545
546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
546 # modes: 0 - Unix, 1 - Mac, 2 - DOS
547 mode = detect_mode(first_line, 0)
547 mode = detect_mode(first_line, 0)
548 content = convert_line_endings(request.POST.get('content', ''), mode)
548 content = convert_line_endings(request.POST.get('content', ''), mode)
549
549
550 message = request.POST.get('message') or c.default_message
550 message = request.POST.get('message') or c.default_message
551 org_f_path = c.file.unicode_path
551 org_f_path = c.file.unicode_path
552 filename = request.POST['filename']
552 filename = request.POST['filename']
553 org_filename = c.file.name
553 org_filename = c.file.name
554
554
555 if content == old_content and filename == org_filename:
555 if content == old_content and filename == org_filename:
556 h.flash(_('No changes'), category='warning')
556 h.flash(_('No changes'), category='warning')
557 return redirect(url('changeset_home', repo_name=c.repo_name,
557 return redirect(url('changeset_home', repo_name=c.repo_name,
558 revision='tip'))
558 revision='tip'))
559 try:
559 try:
560 mapping = {
560 mapping = {
561 org_f_path: {
561 org_f_path: {
562 'org_filename': org_f_path,
562 'org_filename': org_f_path,
563 'filename': os.path.join(c.file.dir_path, filename),
563 'filename': os.path.join(c.file.dir_path, filename),
564 'content': content,
564 'content': content,
565 'lexer': '',
565 'lexer': '',
566 'op': 'mod',
566 'op': 'mod',
567 }
567 }
568 }
568 }
569
569
570 ScmModel().update_nodes(
570 ScmModel().update_nodes(
571 user=c.rhodecode_user.user_id,
571 user=c.rhodecode_user.user_id,
572 repo=c.rhodecode_db_repo,
572 repo=c.rhodecode_db_repo,
573 message=message,
573 message=message,
574 nodes=mapping,
574 nodes=mapping,
575 parent_commit=c.commit,
575 parent_commit=c.commit,
576 )
576 )
577
577
578 h.flash(
578 h.flash(
579 _('Successfully committed changes to file `{}`').format(
579 _('Successfully committed changes to file `{}`').format(
580 h.escape(f_path)), category='success')
580 h.escape(f_path)), category='success')
581 except Exception:
581 except Exception:
582 log.exception('Error occurred during commit')
582 log.exception('Error occurred during commit')
583 h.flash(_('Error occurred during commit'), category='error')
583 h.flash(_('Error occurred during commit'), category='error')
584 return redirect(url('changeset_home',
584 return redirect(url('changeset_home',
585 repo_name=c.repo_name, revision='tip'))
585 repo_name=c.repo_name, revision='tip'))
586
586
587 @LoginRequired()
587 @LoginRequired()
588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
588 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
589 def edit_home(self, repo_name, revision, f_path):
589 def edit_home(self, repo_name, revision, f_path):
590 commit_id = revision
590 commit_id = revision
591
591
592 repo = c.rhodecode_db_repo
592 repo = c.rhodecode_db_repo
593 if repo.enable_locking and repo.locked[0]:
593 if repo.enable_locking and repo.locked[0]:
594 h.flash(_('This repository has been locked by %s on %s')
594 h.flash(_('This repository has been locked by %s on %s')
595 % (h.person_by_id(repo.locked[0]),
595 % (h.person_by_id(repo.locked[0]),
596 h.format_date(h.time_to_datetime(repo.locked[1]))),
596 h.format_date(h.time_to_datetime(repo.locked[1]))),
597 'warning')
597 'warning')
598 return redirect(h.url('files_home',
598 return redirect(h.url('files_home',
599 repo_name=repo_name, revision='tip'))
599 repo_name=repo_name, revision='tip'))
600
600
601 if not self._is_valid_head(commit_id, repo.scm_instance()):
601 if not self._is_valid_head(commit_id, repo.scm_instance()):
602 h.flash(_('You can only edit files with revision '
602 h.flash(_('You can only edit files with revision '
603 'being a valid branch '), category='warning')
603 'being a valid branch '), category='warning')
604 return redirect(h.url('files_home',
604 return redirect(h.url('files_home',
605 repo_name=repo_name, revision='tip',
605 repo_name=repo_name, revision='tip',
606 f_path=f_path))
606 f_path=f_path))
607
607
608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
608 c.commit = self.__get_commit_or_redirect(commit_id, repo_name)
609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
609 c.file = self.__get_filenode_or_redirect(repo_name, c.commit, f_path)
610
610
611 if c.file.is_binary:
611 if c.file.is_binary:
612 return redirect(url('files_home', repo_name=c.repo_name,
612 return redirect(url('files_home', repo_name=c.repo_name,
613 revision=c.commit.raw_id, f_path=f_path))
613 revision=c.commit.raw_id, f_path=f_path))
614 c.default_message = _(
614 c.default_message = _(
615 'Edited file {} via RhodeCode Enterprise').format(f_path)
615 'Edited file {} via RhodeCode Enterprise').format(f_path)
616 c.f_path = f_path
616 c.f_path = f_path
617
617
618 return render('files/files_edit.mako')
618 return render('files/files_edit.mako')
619
619
620 def _is_valid_head(self, commit_id, repo):
620 def _is_valid_head(self, commit_id, repo):
621 # check if commit is a branch identifier- basically we cannot
621 # check if commit is a branch identifier- basically we cannot
622 # create multiple heads via file editing
622 # create multiple heads via file editing
623 valid_heads = repo.branches.keys() + repo.branches.values()
623 valid_heads = repo.branches.keys() + repo.branches.values()
624
624
625 if h.is_svn(repo) and not repo.is_empty():
625 if h.is_svn(repo) and not repo.is_empty():
626 # Note: Subversion only has one head, we add it here in case there
626 # Note: Subversion only has one head, we add it here in case there
627 # is no branch matched.
627 # is no branch matched.
628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
628 valid_heads.append(repo.get_commit(commit_idx=-1).raw_id)
629
629
630 # check if commit is a branch name or branch hash
630 # check if commit is a branch name or branch hash
631 return commit_id in valid_heads
631 return commit_id in valid_heads
632
632
633 @CSRFRequired()
633 @CSRFRequired()
634 @LoginRequired()
634 @LoginRequired()
635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
635 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
636 def add(self, repo_name, revision, f_path):
636 def add(self, repo_name, revision, f_path):
637 repo = Repository.get_by_repo_name(repo_name)
637 repo = Repository.get_by_repo_name(repo_name)
638 if repo.enable_locking and repo.locked[0]:
638 if repo.enable_locking and repo.locked[0]:
639 h.flash(_('This repository has been locked by %s on %s')
639 h.flash(_('This repository has been locked by %s on %s')
640 % (h.person_by_id(repo.locked[0]),
640 % (h.person_by_id(repo.locked[0]),
641 h.format_date(h.time_to_datetime(repo.locked[1]))),
641 h.format_date(h.time_to_datetime(repo.locked[1]))),
642 'warning')
642 'warning')
643 return redirect(h.url('files_home',
643 return redirect(h.url('files_home',
644 repo_name=repo_name, revision='tip'))
644 repo_name=repo_name, revision='tip'))
645
645
646 r_post = request.POST
646 r_post = request.POST
647
647
648 c.commit = self.__get_commit_or_redirect(
648 c.commit = self.__get_commit_or_redirect(
649 revision, repo_name, redirect_after=False)
649 revision, repo_name, redirect_after=False)
650 if c.commit is None:
650 if c.commit is None:
651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
651 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
652 c.default_message = (_('Added file via RhodeCode Enterprise'))
652 c.default_message = (_('Added file via RhodeCode Enterprise'))
653 c.f_path = f_path
653 c.f_path = f_path
654 unix_mode = 0
654 unix_mode = 0
655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
655 content = convert_line_endings(r_post.get('content', ''), unix_mode)
656
656
657 message = r_post.get('message') or c.default_message
657 message = r_post.get('message') or c.default_message
658 filename = r_post.get('filename')
658 filename = r_post.get('filename')
659 location = r_post.get('location', '') # dir location
659 location = r_post.get('location', '') # dir location
660 file_obj = r_post.get('upload_file', None)
660 file_obj = r_post.get('upload_file', None)
661
661
662 if file_obj is not None and hasattr(file_obj, 'filename'):
662 if file_obj is not None and hasattr(file_obj, 'filename'):
663 filename = r_post.get('filename_upload')
663 filename = r_post.get('filename_upload')
664 content = file_obj.file
664 content = file_obj.file
665
665
666 if hasattr(content, 'file'):
666 if hasattr(content, 'file'):
667 # non posix systems store real file under file attr
667 # non posix systems store real file under file attr
668 content = content.file
668 content = content.file
669
669
670 # If there's no commit, redirect to repo summary
670 # If there's no commit, redirect to repo summary
671 if type(c.commit) is EmptyCommit:
671 if type(c.commit) is EmptyCommit:
672 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
672 redirect_url = h.route_path('repo_summary', repo_name=c.repo_name)
673 else:
673 else:
674 redirect_url = url("changeset_home", repo_name=c.repo_name,
674 redirect_url = url("changeset_home", repo_name=c.repo_name,
675 revision='tip')
675 revision='tip')
676
676
677 if not filename:
677 if not filename:
678 h.flash(_('No filename'), category='warning')
678 h.flash(_('No filename'), category='warning')
679 return redirect(redirect_url)
679 return redirect(redirect_url)
680
680
681 # extract the location from filename,
681 # extract the location from filename,
682 # allows using foo/bar.txt syntax to create subdirectories
682 # allows using foo/bar.txt syntax to create subdirectories
683 subdir_loc = filename.rsplit('/', 1)
683 subdir_loc = filename.rsplit('/', 1)
684 if len(subdir_loc) == 2:
684 if len(subdir_loc) == 2:
685 location = os.path.join(location, subdir_loc[0])
685 location = os.path.join(location, subdir_loc[0])
686
686
687 # strip all crap out of file, just leave the basename
687 # strip all crap out of file, just leave the basename
688 filename = os.path.basename(filename)
688 filename = os.path.basename(filename)
689 node_path = os.path.join(location, filename)
689 node_path = os.path.join(location, filename)
690 author = c.rhodecode_user.full_contact
690 author = c.rhodecode_user.full_contact
691
691
692 try:
692 try:
693 nodes = {
693 nodes = {
694 node_path: {
694 node_path: {
695 'content': content
695 'content': content
696 }
696 }
697 }
697 }
698 self.scm_model.create_nodes(
698 self.scm_model.create_nodes(
699 user=c.rhodecode_user.user_id,
699 user=c.rhodecode_user.user_id,
700 repo=c.rhodecode_db_repo,
700 repo=c.rhodecode_db_repo,
701 message=message,
701 message=message,
702 nodes=nodes,
702 nodes=nodes,
703 parent_commit=c.commit,
703 parent_commit=c.commit,
704 author=author,
704 author=author,
705 )
705 )
706
706
707 h.flash(
707 h.flash(
708 _('Successfully committed new file `{}`').format(
708 _('Successfully committed new file `{}`').format(
709 h.escape(node_path)), category='success')
709 h.escape(node_path)), category='success')
710 except NonRelativePathError as e:
710 except NonRelativePathError as e:
711 h.flash(_(
711 h.flash(_(
712 'The location specified must be a relative path and must not '
712 'The location specified must be a relative path and must not '
713 'contain .. in the path'), category='warning')
713 'contain .. in the path'), category='warning')
714 return redirect(url('changeset_home', repo_name=c.repo_name,
714 return redirect(url('changeset_home', repo_name=c.repo_name,
715 revision='tip'))
715 revision='tip'))
716 except (NodeError, NodeAlreadyExistsError) as e:
716 except (NodeError, NodeAlreadyExistsError) as e:
717 h.flash(_(h.escape(e)), category='error')
717 h.flash(_(h.escape(e)), category='error')
718 except Exception:
718 except Exception:
719 log.exception('Error occurred during commit')
719 log.exception('Error occurred during commit')
720 h.flash(_('Error occurred during commit'), category='error')
720 h.flash(_('Error occurred during commit'), category='error')
721 return redirect(url('changeset_home',
721 return redirect(url('changeset_home',
722 repo_name=c.repo_name, revision='tip'))
722 repo_name=c.repo_name, revision='tip'))
723
723
724 @LoginRequired()
724 @LoginRequired()
725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
725 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
726 def add_home(self, repo_name, revision, f_path):
726 def add_home(self, repo_name, revision, f_path):
727
727
728 repo = Repository.get_by_repo_name(repo_name)
728 repo = Repository.get_by_repo_name(repo_name)
729 if repo.enable_locking and repo.locked[0]:
729 if repo.enable_locking and repo.locked[0]:
730 h.flash(_('This repository has been locked by %s on %s')
730 h.flash(_('This repository has been locked by %s on %s')
731 % (h.person_by_id(repo.locked[0]),
731 % (h.person_by_id(repo.locked[0]),
732 h.format_date(h.time_to_datetime(repo.locked[1]))),
732 h.format_date(h.time_to_datetime(repo.locked[1]))),
733 'warning')
733 'warning')
734 return redirect(h.url('files_home',
734 return redirect(h.url('files_home',
735 repo_name=repo_name, revision='tip'))
735 repo_name=repo_name, revision='tip'))
736
736
737 c.commit = self.__get_commit_or_redirect(
737 c.commit = self.__get_commit_or_redirect(
738 revision, repo_name, redirect_after=False)
738 revision, repo_name, redirect_after=False)
739 if c.commit is None:
739 if c.commit is None:
740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
740 c.commit = EmptyCommit(alias=c.rhodecode_repo.alias)
741 c.default_message = (_('Added file via RhodeCode Enterprise'))
741 c.default_message = (_('Added file via RhodeCode Enterprise'))
742 c.f_path = f_path
742 c.f_path = f_path
743
743
744 return render('files/files_add.mako')
744 return render('files/files_add.mako')
745
745
746 @LoginRequired()
746 @LoginRequired()
747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
747 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
748 'repository.admin')
748 'repository.admin')
749 def archivefile(self, repo_name, fname):
749 def archivefile(self, repo_name, fname):
750 fileformat = None
750 fileformat = None
751 commit_id = None
751 commit_id = None
752 ext = None
752 ext = None
753 subrepos = request.GET.get('subrepos') == 'true'
753 subrepos = request.GET.get('subrepos') == 'true'
754
754
755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
755 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
756 archive_spec = fname.split(ext_data[1])
756 archive_spec = fname.split(ext_data[1])
757 if len(archive_spec) == 2 and archive_spec[1] == '':
757 if len(archive_spec) == 2 and archive_spec[1] == '':
758 fileformat = a_type or ext_data[1]
758 fileformat = a_type or ext_data[1]
759 commit_id = archive_spec[0]
759 commit_id = archive_spec[0]
760 ext = ext_data[1]
760 ext = ext_data[1]
761
761
762 dbrepo = RepoModel().get_by_repo_name(repo_name)
762 dbrepo = RepoModel().get_by_repo_name(repo_name)
763 if not dbrepo.enable_downloads:
763 if not dbrepo.enable_downloads:
764 return _('Downloads disabled')
764 return _('Downloads disabled')
765
765
766 try:
766 try:
767 commit = c.rhodecode_repo.get_commit(commit_id)
767 commit = c.rhodecode_repo.get_commit(commit_id)
768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
768 content_type = settings.ARCHIVE_SPECS[fileformat][0]
769 except CommitDoesNotExistError:
769 except CommitDoesNotExistError:
770 return _('Unknown revision %s') % commit_id
770 return _('Unknown revision %s') % commit_id
771 except EmptyRepositoryError:
771 except EmptyRepositoryError:
772 return _('Empty repository')
772 return _('Empty repository')
773 except KeyError:
773 except KeyError:
774 return _('Unknown archive type')
774 return _('Unknown archive type')
775
775
776 # archive cache
776 # archive cache
777 from rhodecode import CONFIG
777 from rhodecode import CONFIG
778
778
779 archive_name = '%s-%s%s%s' % (
779 archive_name = '%s-%s%s%s' % (
780 safe_str(repo_name.replace('/', '_')),
780 safe_str(repo_name.replace('/', '_')),
781 '-sub' if subrepos else '',
781 '-sub' if subrepos else '',
782 safe_str(commit.short_id), ext)
782 safe_str(commit.short_id), ext)
783
783
784 use_cached_archive = False
784 use_cached_archive = False
785 archive_cache_enabled = CONFIG.get(
785 archive_cache_enabled = CONFIG.get(
786 'archive_cache_dir') and not request.GET.get('no_cache')
786 'archive_cache_dir') and not request.GET.get('no_cache')
787
787
788 if archive_cache_enabled:
788 if archive_cache_enabled:
789 # check if we it's ok to write
789 # check if we it's ok to write
790 if not os.path.isdir(CONFIG['archive_cache_dir']):
790 if not os.path.isdir(CONFIG['archive_cache_dir']):
791 os.makedirs(CONFIG['archive_cache_dir'])
791 os.makedirs(CONFIG['archive_cache_dir'])
792 cached_archive_path = os.path.join(
792 cached_archive_path = os.path.join(
793 CONFIG['archive_cache_dir'], archive_name)
793 CONFIG['archive_cache_dir'], archive_name)
794 if os.path.isfile(cached_archive_path):
794 if os.path.isfile(cached_archive_path):
795 log.debug('Found cached archive in %s', cached_archive_path)
795 log.debug('Found cached archive in %s', cached_archive_path)
796 fd, archive = None, cached_archive_path
796 fd, archive = None, cached_archive_path
797 use_cached_archive = True
797 use_cached_archive = True
798 else:
798 else:
799 log.debug('Archive %s is not yet cached', archive_name)
799 log.debug('Archive %s is not yet cached', archive_name)
800
800
801 if not use_cached_archive:
801 if not use_cached_archive:
802 # generate new archive
802 # generate new archive
803 fd, archive = tempfile.mkstemp()
803 fd, archive = tempfile.mkstemp()
804 log.debug('Creating new temp archive in %s', archive)
804 log.debug('Creating new temp archive in %s', archive)
805 try:
805 try:
806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
806 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos)
807 except ImproperArchiveTypeError:
807 except ImproperArchiveTypeError:
808 return _('Unknown archive type')
808 return _('Unknown archive type')
809 if archive_cache_enabled:
809 if archive_cache_enabled:
810 # if we generated the archive and we have cache enabled
810 # if we generated the archive and we have cache enabled
811 # let's use this for future
811 # let's use this for future
812 log.debug('Storing new archive in %s', cached_archive_path)
812 log.debug('Storing new archive in %s', cached_archive_path)
813 shutil.move(archive, cached_archive_path)
813 shutil.move(archive, cached_archive_path)
814 archive = cached_archive_path
814 archive = cached_archive_path
815
815
816 # store download action
816 # store download action
817 audit_logger.store_web(
817 audit_logger.store_web(
818 action='repo.archive.download',
818 'repo.archive.download', action_data={
819 action_data={'user_agent': request.user_agent,
819 'user_agent': request.user_agent,
820 'archive_name': archive_name,
820 'archive_name': archive_name,
821 'archive_spec': fname,
821 'archive_spec': fname,
822 'archive_cached': use_cached_archive},
822 'archive_cached': use_cached_archive},
823 user=c.rhodecode_user,
823 user=c.rhodecode_user,
824 repo=dbrepo,
824 repo=dbrepo,
825 commit=True
825 commit=True
826 )
826 )
827
827
828 response.content_disposition = str(
828 response.content_disposition = str(
829 'attachment; filename=%s' % archive_name)
829 'attachment; filename=%s' % archive_name)
830 response.content_type = str(content_type)
830 response.content_type = str(content_type)
831
831
832 def get_chunked_archive(archive):
832 def get_chunked_archive(archive):
833 with open(archive, 'rb') as stream:
833 with open(archive, 'rb') as stream:
834 while True:
834 while True:
835 data = stream.read(16 * 1024)
835 data = stream.read(16 * 1024)
836 if not data:
836 if not data:
837 if fd: # fd means we used temporary file
837 if fd: # fd means we used temporary file
838 os.close(fd)
838 os.close(fd)
839 if not archive_cache_enabled:
839 if not archive_cache_enabled:
840 log.debug('Destroying temp archive %s', archive)
840 log.debug('Destroying temp archive %s', archive)
841 os.remove(archive)
841 os.remove(archive)
842 break
842 break
843 yield data
843 yield data
844
844
845 return get_chunked_archive(archive)
845 return get_chunked_archive(archive)
846
846
847 @LoginRequired()
847 @LoginRequired()
848 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
848 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
849 'repository.admin')
849 'repository.admin')
850 def diff(self, repo_name, f_path):
850 def diff(self, repo_name, f_path):
851
851
852 c.action = request.GET.get('diff')
852 c.action = request.GET.get('diff')
853 diff1 = request.GET.get('diff1', '')
853 diff1 = request.GET.get('diff1', '')
854 diff2 = request.GET.get('diff2', '')
854 diff2 = request.GET.get('diff2', '')
855
855
856 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
856 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
857
857
858 ignore_whitespace = str2bool(request.GET.get('ignorews'))
858 ignore_whitespace = str2bool(request.GET.get('ignorews'))
859 line_context = request.GET.get('context', 3)
859 line_context = request.GET.get('context', 3)
860
860
861 if not any((diff1, diff2)):
861 if not any((diff1, diff2)):
862 h.flash(
862 h.flash(
863 'Need query parameter "diff1" or "diff2" to generate a diff.',
863 'Need query parameter "diff1" or "diff2" to generate a diff.',
864 category='error')
864 category='error')
865 raise HTTPBadRequest()
865 raise HTTPBadRequest()
866
866
867 if c.action not in ['download', 'raw']:
867 if c.action not in ['download', 'raw']:
868 # redirect to new view if we render diff
868 # redirect to new view if we render diff
869 return redirect(
869 return redirect(
870 url('compare_url', repo_name=repo_name,
870 url('compare_url', repo_name=repo_name,
871 source_ref_type='rev',
871 source_ref_type='rev',
872 source_ref=diff1,
872 source_ref=diff1,
873 target_repo=c.repo_name,
873 target_repo=c.repo_name,
874 target_ref_type='rev',
874 target_ref_type='rev',
875 target_ref=diff2,
875 target_ref=diff2,
876 f_path=f_path))
876 f_path=f_path))
877
877
878 try:
878 try:
879 node1 = self._get_file_node(diff1, path1)
879 node1 = self._get_file_node(diff1, path1)
880 node2 = self._get_file_node(diff2, f_path)
880 node2 = self._get_file_node(diff2, f_path)
881 except (RepositoryError, NodeError):
881 except (RepositoryError, NodeError):
882 log.exception("Exception while trying to get node from repository")
882 log.exception("Exception while trying to get node from repository")
883 return redirect(url(
883 return redirect(url(
884 'files_home', repo_name=c.repo_name, f_path=f_path))
884 'files_home', repo_name=c.repo_name, f_path=f_path))
885
885
886 if all(isinstance(node.commit, EmptyCommit)
886 if all(isinstance(node.commit, EmptyCommit)
887 for node in (node1, node2)):
887 for node in (node1, node2)):
888 raise HTTPNotFound
888 raise HTTPNotFound
889
889
890 c.commit_1 = node1.commit
890 c.commit_1 = node1.commit
891 c.commit_2 = node2.commit
891 c.commit_2 = node2.commit
892
892
893 if c.action == 'download':
893 if c.action == 'download':
894 _diff = diffs.get_gitdiff(node1, node2,
894 _diff = diffs.get_gitdiff(node1, node2,
895 ignore_whitespace=ignore_whitespace,
895 ignore_whitespace=ignore_whitespace,
896 context=line_context)
896 context=line_context)
897 diff = diffs.DiffProcessor(_diff, format='gitdiff')
897 diff = diffs.DiffProcessor(_diff, format='gitdiff')
898
898
899 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
899 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
900 response.content_type = 'text/plain'
900 response.content_type = 'text/plain'
901 response.content_disposition = (
901 response.content_disposition = (
902 'attachment; filename=%s' % (diff_name,)
902 'attachment; filename=%s' % (diff_name,)
903 )
903 )
904 charset = self._get_default_encoding()
904 charset = self._get_default_encoding()
905 if charset:
905 if charset:
906 response.charset = charset
906 response.charset = charset
907 return diff.as_raw()
907 return diff.as_raw()
908
908
909 elif c.action == 'raw':
909 elif c.action == 'raw':
910 _diff = diffs.get_gitdiff(node1, node2,
910 _diff = diffs.get_gitdiff(node1, node2,
911 ignore_whitespace=ignore_whitespace,
911 ignore_whitespace=ignore_whitespace,
912 context=line_context)
912 context=line_context)
913 diff = diffs.DiffProcessor(_diff, format='gitdiff')
913 diff = diffs.DiffProcessor(_diff, format='gitdiff')
914 response.content_type = 'text/plain'
914 response.content_type = 'text/plain'
915 charset = self._get_default_encoding()
915 charset = self._get_default_encoding()
916 if charset:
916 if charset:
917 response.charset = charset
917 response.charset = charset
918 return diff.as_raw()
918 return diff.as_raw()
919
919
920 else:
920 else:
921 return redirect(
921 return redirect(
922 url('compare_url', repo_name=repo_name,
922 url('compare_url', repo_name=repo_name,
923 source_ref_type='rev',
923 source_ref_type='rev',
924 source_ref=diff1,
924 source_ref=diff1,
925 target_repo=c.repo_name,
925 target_repo=c.repo_name,
926 target_ref_type='rev',
926 target_ref_type='rev',
927 target_ref=diff2,
927 target_ref=diff2,
928 f_path=f_path))
928 f_path=f_path))
929
929
930 @LoginRequired()
930 @LoginRequired()
931 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
931 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
932 'repository.admin')
932 'repository.admin')
933 def diff_2way(self, repo_name, f_path):
933 def diff_2way(self, repo_name, f_path):
934 """
934 """
935 Kept only to make OLD links work
935 Kept only to make OLD links work
936 """
936 """
937 diff1 = request.GET.get('diff1', '')
937 diff1 = request.GET.get('diff1', '')
938 diff2 = request.GET.get('diff2', '')
938 diff2 = request.GET.get('diff2', '')
939
939
940 if not any((diff1, diff2)):
940 if not any((diff1, diff2)):
941 h.flash(
941 h.flash(
942 'Need query parameter "diff1" or "diff2" to generate a diff.',
942 'Need query parameter "diff1" or "diff2" to generate a diff.',
943 category='error')
943 category='error')
944 raise HTTPBadRequest()
944 raise HTTPBadRequest()
945
945
946 return redirect(
946 return redirect(
947 url('compare_url', repo_name=repo_name,
947 url('compare_url', repo_name=repo_name,
948 source_ref_type='rev',
948 source_ref_type='rev',
949 source_ref=diff1,
949 source_ref=diff1,
950 target_repo=c.repo_name,
950 target_repo=c.repo_name,
951 target_ref_type='rev',
951 target_ref_type='rev',
952 target_ref=diff2,
952 target_ref=diff2,
953 f_path=f_path,
953 f_path=f_path,
954 diffmode='sideside'))
954 diffmode='sideside'))
955
955
956 def _get_file_node(self, commit_id, f_path):
956 def _get_file_node(self, commit_id, f_path):
957 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
957 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
958 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
958 commit = c.rhodecode_repo.get_commit(commit_id=commit_id)
959 try:
959 try:
960 node = commit.get_node(f_path)
960 node = commit.get_node(f_path)
961 if node.is_dir():
961 if node.is_dir():
962 raise NodeError('%s path is a %s not a file'
962 raise NodeError('%s path is a %s not a file'
963 % (node, type(node)))
963 % (node, type(node)))
964 except NodeDoesNotExistError:
964 except NodeDoesNotExistError:
965 commit = EmptyCommit(
965 commit = EmptyCommit(
966 commit_id=commit_id,
966 commit_id=commit_id,
967 idx=commit.idx,
967 idx=commit.idx,
968 repo=commit.repository,
968 repo=commit.repository,
969 alias=commit.repository.alias,
969 alias=commit.repository.alias,
970 message=commit.message,
970 message=commit.message,
971 author=commit.author,
971 author=commit.author,
972 date=commit.date)
972 date=commit.date)
973 node = FileNode(f_path, '', commit=commit)
973 node = FileNode(f_path, '', commit=commit)
974 else:
974 else:
975 commit = EmptyCommit(
975 commit = EmptyCommit(
976 repo=c.rhodecode_repo,
976 repo=c.rhodecode_repo,
977 alias=c.rhodecode_repo.alias)
977 alias=c.rhodecode_repo.alias)
978 node = FileNode(f_path, '', commit=commit)
978 node = FileNode(f_path, '', commit=commit)
979 return node
979 return node
980
980
981 def _get_node_history(self, commit, f_path, commits=None):
981 def _get_node_history(self, commit, f_path, commits=None):
982 """
982 """
983 get commit history for given node
983 get commit history for given node
984
984
985 :param commit: commit to calculate history
985 :param commit: commit to calculate history
986 :param f_path: path for node to calculate history for
986 :param f_path: path for node to calculate history for
987 :param commits: if passed don't calculate history and take
987 :param commits: if passed don't calculate history and take
988 commits defined in this list
988 commits defined in this list
989 """
989 """
990 # calculate history based on tip
990 # calculate history based on tip
991 tip = c.rhodecode_repo.get_commit()
991 tip = c.rhodecode_repo.get_commit()
992 if commits is None:
992 if commits is None:
993 pre_load = ["author", "branch"]
993 pre_load = ["author", "branch"]
994 try:
994 try:
995 commits = tip.get_file_history(f_path, pre_load=pre_load)
995 commits = tip.get_file_history(f_path, pre_load=pre_load)
996 except (NodeDoesNotExistError, CommitError):
996 except (NodeDoesNotExistError, CommitError):
997 # this node is not present at tip!
997 # this node is not present at tip!
998 commits = commit.get_file_history(f_path, pre_load=pre_load)
998 commits = commit.get_file_history(f_path, pre_load=pre_load)
999
999
1000 history = []
1000 history = []
1001 commits_group = ([], _("Changesets"))
1001 commits_group = ([], _("Changesets"))
1002 for commit in commits:
1002 for commit in commits:
1003 branch = ' (%s)' % commit.branch if commit.branch else ''
1003 branch = ' (%s)' % commit.branch if commit.branch else ''
1004 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1004 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
1005 commits_group[0].append((commit.raw_id, n_desc,))
1005 commits_group[0].append((commit.raw_id, n_desc,))
1006 history.append(commits_group)
1006 history.append(commits_group)
1007
1007
1008 symbolic_reference = self._symbolic_reference
1008 symbolic_reference = self._symbolic_reference
1009
1009
1010 if c.rhodecode_repo.alias == 'svn':
1010 if c.rhodecode_repo.alias == 'svn':
1011 adjusted_f_path = self._adjust_file_path_for_svn(
1011 adjusted_f_path = self._adjust_file_path_for_svn(
1012 f_path, c.rhodecode_repo)
1012 f_path, c.rhodecode_repo)
1013 if adjusted_f_path != f_path:
1013 if adjusted_f_path != f_path:
1014 log.debug(
1014 log.debug(
1015 'Recognized svn tag or branch in file "%s", using svn '
1015 'Recognized svn tag or branch in file "%s", using svn '
1016 'specific symbolic references', f_path)
1016 'specific symbolic references', f_path)
1017 f_path = adjusted_f_path
1017 f_path = adjusted_f_path
1018 symbolic_reference = self._symbolic_reference_svn
1018 symbolic_reference = self._symbolic_reference_svn
1019
1019
1020 branches = self._create_references(
1020 branches = self._create_references(
1021 c.rhodecode_repo.branches, symbolic_reference, f_path)
1021 c.rhodecode_repo.branches, symbolic_reference, f_path)
1022 branches_group = (branches, _("Branches"))
1022 branches_group = (branches, _("Branches"))
1023
1023
1024 tags = self._create_references(
1024 tags = self._create_references(
1025 c.rhodecode_repo.tags, symbolic_reference, f_path)
1025 c.rhodecode_repo.tags, symbolic_reference, f_path)
1026 tags_group = (tags, _("Tags"))
1026 tags_group = (tags, _("Tags"))
1027
1027
1028 history.append(branches_group)
1028 history.append(branches_group)
1029 history.append(tags_group)
1029 history.append(tags_group)
1030
1030
1031 return history, commits
1031 return history, commits
1032
1032
1033 def _adjust_file_path_for_svn(self, f_path, repo):
1033 def _adjust_file_path_for_svn(self, f_path, repo):
1034 """
1034 """
1035 Computes the relative path of `f_path`.
1035 Computes the relative path of `f_path`.
1036
1036
1037 This is mainly based on prefix matching of the recognized tags and
1037 This is mainly based on prefix matching of the recognized tags and
1038 branches in the underlying repository.
1038 branches in the underlying repository.
1039 """
1039 """
1040 tags_and_branches = itertools.chain(
1040 tags_and_branches = itertools.chain(
1041 repo.branches.iterkeys(),
1041 repo.branches.iterkeys(),
1042 repo.tags.iterkeys())
1042 repo.tags.iterkeys())
1043 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1043 tags_and_branches = sorted(tags_and_branches, key=len, reverse=True)
1044
1044
1045 for name in tags_and_branches:
1045 for name in tags_and_branches:
1046 if f_path.startswith(name + '/'):
1046 if f_path.startswith(name + '/'):
1047 f_path = vcspath.relpath(f_path, name)
1047 f_path = vcspath.relpath(f_path, name)
1048 break
1048 break
1049 return f_path
1049 return f_path
1050
1050
1051 def _create_references(
1051 def _create_references(
1052 self, branches_or_tags, symbolic_reference, f_path):
1052 self, branches_or_tags, symbolic_reference, f_path):
1053 items = []
1053 items = []
1054 for name, commit_id in branches_or_tags.items():
1054 for name, commit_id in branches_or_tags.items():
1055 sym_ref = symbolic_reference(commit_id, name, f_path)
1055 sym_ref = symbolic_reference(commit_id, name, f_path)
1056 items.append((sym_ref, name))
1056 items.append((sym_ref, name))
1057 return items
1057 return items
1058
1058
1059 def _symbolic_reference(self, commit_id, name, f_path):
1059 def _symbolic_reference(self, commit_id, name, f_path):
1060 return commit_id
1060 return commit_id
1061
1061
1062 def _symbolic_reference_svn(self, commit_id, name, f_path):
1062 def _symbolic_reference_svn(self, commit_id, name, f_path):
1063 new_f_path = vcspath.join(name, f_path)
1063 new_f_path = vcspath.join(name, f_path)
1064 return u'%s@%s' % (new_f_path, commit_id)
1064 return u'%s@%s' % (new_f_path, commit_id)
1065
1065
1066 @LoginRequired()
1066 @LoginRequired()
1067 @XHRRequired()
1067 @XHRRequired()
1068 @HasRepoPermissionAnyDecorator(
1068 @HasRepoPermissionAnyDecorator(
1069 'repository.read', 'repository.write', 'repository.admin')
1069 'repository.read', 'repository.write', 'repository.admin')
1070 @jsonify
1070 @jsonify
1071 def nodelist(self, repo_name, revision, f_path):
1071 def nodelist(self, repo_name, revision, f_path):
1072 commit = self.__get_commit_or_redirect(revision, repo_name)
1072 commit = self.__get_commit_or_redirect(revision, repo_name)
1073
1073
1074 metadata = self._get_nodelist_at_commit(
1074 metadata = self._get_nodelist_at_commit(
1075 repo_name, commit.raw_id, f_path)
1075 repo_name, commit.raw_id, f_path)
1076 return {'nodes': metadata}
1076 return {'nodes': metadata}
1077
1077
1078 @LoginRequired()
1078 @LoginRequired()
1079 @XHRRequired()
1079 @XHRRequired()
1080 @HasRepoPermissionAnyDecorator(
1080 @HasRepoPermissionAnyDecorator(
1081 'repository.read', 'repository.write', 'repository.admin')
1081 'repository.read', 'repository.write', 'repository.admin')
1082 def nodetree_full(self, repo_name, commit_id, f_path):
1082 def nodetree_full(self, repo_name, commit_id, f_path):
1083 """
1083 """
1084 Returns rendered html of file tree that contains commit date,
1084 Returns rendered html of file tree that contains commit date,
1085 author, revision for the specified combination of
1085 author, revision for the specified combination of
1086 repo, commit_id and file path
1086 repo, commit_id and file path
1087
1087
1088 :param repo_name: name of the repository
1088 :param repo_name: name of the repository
1089 :param commit_id: commit_id of file tree
1089 :param commit_id: commit_id of file tree
1090 :param f_path: file path of the requested directory
1090 :param f_path: file path of the requested directory
1091 """
1091 """
1092
1092
1093 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1093 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1094 try:
1094 try:
1095 dir_node = commit.get_node(f_path)
1095 dir_node = commit.get_node(f_path)
1096 except RepositoryError as e:
1096 except RepositoryError as e:
1097 return 'error {}'.format(safe_str(e))
1097 return 'error {}'.format(safe_str(e))
1098
1098
1099 if dir_node.is_file():
1099 if dir_node.is_file():
1100 return ''
1100 return ''
1101
1101
1102 c.file = dir_node
1102 c.file = dir_node
1103 c.commit = commit
1103 c.commit = commit
1104
1104
1105 # using force=True here, make a little trick. We flush the cache and
1105 # using force=True here, make a little trick. We flush the cache and
1106 # compute it using the same key as without full_load, so the fully
1106 # compute it using the same key as without full_load, so the fully
1107 # loaded cached tree is now returned instead of partial
1107 # loaded cached tree is now returned instead of partial
1108 return self._get_tree_at_commit(
1108 return self._get_tree_at_commit(
1109 repo_name, commit.raw_id, dir_node.path, full_load=True,
1109 repo_name, commit.raw_id, dir_node.path, full_load=True,
1110 force=True)
1110 force=True)
@@ -1,257 +1,255 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 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
23
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25 from rhodecode.model.db import User, UserLog, Repository
25 from rhodecode.model.db import User, UserLog, Repository
26
26
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30 # action as key, and expected action_data as value
30 # action as key, and expected action_data as value
31 ACTIONS_V1 = {
31 ACTIONS_V1 = {
32 'user.login.success': {'user_agent': ''},
32 'user.login.success': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
33 'user.login.failure': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
34 'user.logout': {'user_agent': ''},
35 'user.password.reset_request': {},
35 'user.password.reset_request': {},
36 'user.push': {'user_agent': '', 'commit_ids': []},
36 'user.push': {'user_agent': '', 'commit_ids': []},
37 'user.pull': {'user_agent': ''},
37 'user.pull': {'user_agent': ''},
38
38
39 'user.create': {'data': {}},
39 'user.create': {'data': {}},
40 'user.delete': {'old_data': {}},
40 'user.delete': {'old_data': {}},
41 'user.edit': {'old_data': {}},
41 'user.edit': {'old_data': {}},
42 'user.edit.permissions': {},
42 'user.edit.permissions': {},
43 'user.edit.ip.add': {'ip': {}, 'user': {}},
43 'user.edit.ip.add': {'ip': {}, 'user': {}},
44 'user.edit.ip.delete': {'ip': {}, 'user': {}},
44 'user.edit.ip.delete': {'ip': {}, 'user': {}},
45 'user.edit.token.add': {'token': {}, 'user': {}},
45 'user.edit.token.add': {'token': {}, 'user': {}},
46 'user.edit.token.delete': {'token': {}, 'user': {}},
46 'user.edit.token.delete': {'token': {}, 'user': {}},
47 'user.edit.email.add': {'email': ''},
47 'user.edit.email.add': {'email': ''},
48 'user.edit.email.delete': {'email': ''},
48 'user.edit.email.delete': {'email': ''},
49 'user.edit.password_reset.enabled': {},
49 'user.edit.password_reset.enabled': {},
50 'user.edit.password_reset.disabled': {},
50 'user.edit.password_reset.disabled': {},
51
51
52 'user_group.create': {'data': {}},
52 'user_group.create': {'data': {}},
53 'user_group.delete': {'old_data': {}},
53 'user_group.delete': {'old_data': {}},
54 'user_group.edit': {'old_data': {}},
54 'user_group.edit': {'old_data': {}},
55 'user_group.edit.permissions': {},
55 'user_group.edit.permissions': {},
56 'user_group.edit.member.add': {},
56 'user_group.edit.member.add': {'user': {}},
57 'user_group.edit.member.delete': {},
57 'user_group.edit.member.delete': {'user': {}},
58
58
59 'repo.create': {'data': {}},
59 'repo.create': {'data': {}},
60 'repo.fork': {'data': {}},
60 'repo.fork': {'data': {}},
61 'repo.edit': {'old_data': {}},
61 'repo.edit': {'old_data': {}},
62 'repo.edit.permissions': {},
62 'repo.edit.permissions': {},
63 'repo.delete': {'old_data': {}},
63 'repo.delete': {'old_data': {}},
64 'repo.commit.strip': {'commit_id': ''},
64 'repo.commit.strip': {'commit_id': ''},
65 'repo.archive.download': {'user_agent': '', 'archive_name': '',
65 'repo.archive.download': {'user_agent': '', 'archive_name': '',
66 'archive_spec': '', 'archive_cached': ''},
66 'archive_spec': '', 'archive_cached': ''},
67 'repo.pull_request.create': '',
67 'repo.pull_request.create': '',
68 'repo.pull_request.edit': '',
68 'repo.pull_request.edit': '',
69 'repo.pull_request.delete': '',
69 'repo.pull_request.delete': '',
70 'repo.pull_request.close': '',
70 'repo.pull_request.close': '',
71 'repo.pull_request.merge': '',
71 'repo.pull_request.merge': '',
72 'repo.pull_request.vote': '',
72 'repo.pull_request.vote': '',
73 'repo.pull_request.comment.create': '',
73 'repo.pull_request.comment.create': '',
74 'repo.pull_request.comment.delete': '',
74 'repo.pull_request.comment.delete': '',
75
75
76 'repo.pull_request.reviewer.add': '',
76 'repo.pull_request.reviewer.add': '',
77 'repo.pull_request.reviewer.delete': '',
77 'repo.pull_request.reviewer.delete': '',
78
78
79 'repo.commit.comment.create': '',
79 'repo.commit.comment.create': {'data': {}},
80 'repo.commit.comment.delete': '',
80 'repo.commit.comment.delete': {'data': {}},
81 'repo.commit.vote': '',
81 'repo.commit.vote': '',
82
82
83 'repo_group.create': {'data': {}},
83 'repo_group.create': {'data': {}},
84 'repo_group.edit': {'old_data': {}},
84 'repo_group.edit': {'old_data': {}},
85 'repo_group.edit.permissions': {},
85 'repo_group.edit.permissions': {},
86 'repo_group.delete': {'old_data': {}},
86 'repo_group.delete': {'old_data': {}},
87 }
87 }
88 ACTIONS = ACTIONS_V1
88 ACTIONS = ACTIONS_V1
89
89
90 SOURCE_WEB = 'source_web'
90 SOURCE_WEB = 'source_web'
91 SOURCE_API = 'source_api'
91 SOURCE_API = 'source_api'
92
92
93
93
94 class UserWrap(object):
94 class UserWrap(object):
95 """
95 """
96 Fake object used to imitate AuthUser
96 Fake object used to imitate AuthUser
97 """
97 """
98
98
99 def __init__(self, user_id=None, username=None, ip_addr=None):
99 def __init__(self, user_id=None, username=None, ip_addr=None):
100 self.user_id = user_id
100 self.user_id = user_id
101 self.username = username
101 self.username = username
102 self.ip_addr = ip_addr
102 self.ip_addr = ip_addr
103
103
104
104
105 class RepoWrap(object):
105 class RepoWrap(object):
106 """
106 """
107 Fake object used to imitate RepoObject that audit logger requires
107 Fake object used to imitate RepoObject that audit logger requires
108 """
108 """
109
109
110 def __init__(self, repo_id=None, repo_name=None):
110 def __init__(self, repo_id=None, repo_name=None):
111 self.repo_id = repo_id
111 self.repo_id = repo_id
112 self.repo_name = repo_name
112 self.repo_name = repo_name
113
113
114
114
115 def _store_log(action_name, action_data, user_id, username, user_data,
115 def _store_log(action_name, action_data, user_id, username, user_data,
116 ip_address, repository_id, repository_name):
116 ip_address, repository_id, repository_name):
117 user_log = UserLog()
117 user_log = UserLog()
118 user_log.version = UserLog.VERSION_2
118 user_log.version = UserLog.VERSION_2
119
119
120 user_log.action = action_name
120 user_log.action = action_name
121 user_log.action_data = action_data
121 user_log.action_data = action_data
122
122
123 user_log.user_ip = ip_address
123 user_log.user_ip = ip_address
124
124
125 user_log.user_id = user_id
125 user_log.user_id = user_id
126 user_log.username = username
126 user_log.username = username
127 user_log.user_data = user_data
127 user_log.user_data = user_data
128
128
129 user_log.repository_id = repository_id
129 user_log.repository_id = repository_id
130 user_log.repository_name = repository_name
130 user_log.repository_name = repository_name
131
131
132 user_log.action_date = datetime.datetime.now()
132 user_log.action_date = datetime.datetime.now()
133
133
134 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
134 log.info('AUDIT: Logging action: `%s` by user:id:%s[%s] ip:%s',
135 action_name, user_id, username, ip_address)
135 action_name, user_id, username, ip_address)
136
136
137 return user_log
137 return user_log
138
138
139
139
140 def store_web(*args, **kwargs):
140 def store_web(*args, **kwargs):
141 if 'action_data' not in kwargs:
141 if 'action_data' not in kwargs:
142 kwargs['action_data'] = {}
142 kwargs['action_data'] = {}
143 kwargs['action_data'].update({
143 kwargs['action_data'].update({
144 'source': SOURCE_WEB
144 'source': SOURCE_WEB
145 })
145 })
146 return store(*args, **kwargs)
146 return store(*args, **kwargs)
147
147
148
148
149 def store_api(*args, **kwargs):
149 def store_api(*args, **kwargs):
150 if 'action_data' not in kwargs:
150 if 'action_data' not in kwargs:
151 kwargs['action_data'] = {}
151 kwargs['action_data'] = {}
152 kwargs['action_data'].update({
152 kwargs['action_data'].update({
153 'source': SOURCE_API
153 'source': SOURCE_API
154 })
154 })
155 return store(*args, **kwargs)
155 return store(*args, **kwargs)
156
156
157
157
158 def store(action, user, action_data=None, user_data=None, ip_addr=None,
158 def store(action, user, action_data=None, user_data=None, ip_addr=None,
159 repo=None, sa_session=None, commit=False):
159 repo=None, sa_session=None, commit=False):
160 """
160 """
161 Audit logger for various actions made by users, typically this
161 Audit logger for various actions made by users, typically this
162 results in a call such::
162 results in a call such::
163
163
164 from rhodecode.lib import audit_logger
164 from rhodecode.lib import audit_logger
165
165
166 audit_logger.store(
166 audit_logger.store(
167 action='repo.edit', user=self._rhodecode_user)
167 'repo.edit', user=self._rhodecode_user)
168 audit_logger.store(
168 audit_logger.store(
169 action='repo.delete', action_data={'data': repo_data},
169 'repo.delete', action_data={'data': repo_data},
170 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
170 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'))
171
171
172 # repo action
172 # repo action
173 audit_logger.store(
173 audit_logger.store(
174 action='repo.delete',
174 'repo.delete',
175 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
175 user=audit_logger.UserWrap(username='itried-login', ip_addr='8.8.8.8'),
176 repo=audit_logger.RepoWrap(repo_name='some-repo'))
176 repo=audit_logger.RepoWrap(repo_name='some-repo'))
177
177
178 # repo action, when we know and have the repository object already
178 # repo action, when we know and have the repository object already
179 audit_logger.store(
179 audit_logger.store(
180 action='repo.delete',
180 'repo.delete', action_data={'source': audit_logger.SOURCE_WEB, },
181 action_data={'source': audit_logger.SOURCE_WEB, },
182 user=self._rhodecode_user,
181 user=self._rhodecode_user,
183 repo=repo_object)
182 repo=repo_object)
184
183
185 # alternative wrapper to the above
184 # alternative wrapper to the above
186 audit_logger.store_web(
185 audit_logger.store_web(
187 action='repo.delete',
186 'repo.delete', action_data={},
188 action_data={},
189 user=self._rhodecode_user,
187 user=self._rhodecode_user,
190 repo=repo_object)
188 repo=repo_object)
191
189
192 # without an user ?
190 # without an user ?
193 audit_logger.store(
191 audit_logger.store(
194 action='user.login.failure',
192 'user.login.failure',
195 user=audit_logger.UserWrap(
193 user=audit_logger.UserWrap(
196 username=self.request.params.get('username'),
194 username=self.request.params.get('username'),
197 ip_addr=self.request.remote_addr))
195 ip_addr=self.request.remote_addr))
198
196
199 """
197 """
200 from rhodecode.lib.utils2 import safe_unicode
198 from rhodecode.lib.utils2 import safe_unicode
201 from rhodecode.lib.auth import AuthUser
199 from rhodecode.lib.auth import AuthUser
202
200
203 action_spec = ACTIONS.get(action, None)
201 action_spec = ACTIONS.get(action, None)
204 if action_spec is None:
202 if action_spec is None:
205 raise ValueError('Action `{}` is not supported'.format(action))
203 raise ValueError('Action `{}` is not supported'.format(action))
206
204
207 if not sa_session:
205 if not sa_session:
208 sa_session = meta.Session()
206 sa_session = meta.Session()
209
207
210 try:
208 try:
211 username = getattr(user, 'username', None)
209 username = getattr(user, 'username', None)
212 if not username:
210 if not username:
213 pass
211 pass
214
212
215 user_id = getattr(user, 'user_id', None)
213 user_id = getattr(user, 'user_id', None)
216 if not user_id:
214 if not user_id:
217 # maybe we have username ? Try to figure user_id from username
215 # maybe we have username ? Try to figure user_id from username
218 if username:
216 if username:
219 user_id = getattr(
217 user_id = getattr(
220 User.get_by_username(username), 'user_id', None)
218 User.get_by_username(username), 'user_id', None)
221
219
222 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
220 ip_addr = ip_addr or getattr(user, 'ip_addr', None)
223 if not ip_addr:
221 if not ip_addr:
224 pass
222 pass
225
223
226 if not user_data:
224 if not user_data:
227 # try to get this from the auth user
225 # try to get this from the auth user
228 if isinstance(user, AuthUser):
226 if isinstance(user, AuthUser):
229 user_data = {
227 user_data = {
230 'username': user.username,
228 'username': user.username,
231 'email': user.email,
229 'email': user.email,
232 }
230 }
233
231
234 repository_name = getattr(repo, 'repo_name', None)
232 repository_name = getattr(repo, 'repo_name', None)
235 repository_id = getattr(repo, 'repo_id', None)
233 repository_id = getattr(repo, 'repo_id', None)
236 if not repository_id:
234 if not repository_id:
237 # maybe we have repo_name ? Try to figure repo_id from repo_name
235 # maybe we have repo_name ? Try to figure repo_id from repo_name
238 if repository_name:
236 if repository_name:
239 repository_id = getattr(
237 repository_id = getattr(
240 Repository.get_by_repo_name(repository_name), 'repo_id', None)
238 Repository.get_by_repo_name(repository_name), 'repo_id', None)
241
239
242 user_log = _store_log(
240 user_log = _store_log(
243 action_name=safe_unicode(action),
241 action_name=safe_unicode(action),
244 action_data=action_data or {},
242 action_data=action_data or {},
245 user_id=user_id,
243 user_id=user_id,
246 username=username,
244 username=username,
247 user_data=user_data or {},
245 user_data=user_data or {},
248 ip_address=safe_unicode(ip_addr),
246 ip_address=safe_unicode(ip_addr),
249 repository_id=repository_id,
247 repository_id=repository_id,
250 repository_name=repository_name
248 repository_name=repository_name
251 )
249 )
252 sa_session.add(user_log)
250 sa_session.add(user_log)
253 if commit:
251 if commit:
254 sa_session.commit()
252 sa_session.commit()
255
253
256 except Exception:
254 except Exception:
257 log.exception('AUDIT: failed to store audit log')
255 log.exception('AUDIT: failed to store audit log')
@@ -1,299 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 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 RhodeCode task modules, containing all task that suppose to be run
22 RhodeCode task modules, containing all task that suppose to be run
23 by celery daemon
23 by celery daemon
24 """
24 """
25
25
26
26
27 import os
27 import os
28 import logging
28 import logging
29
29
30 from celery.task import task
30 from celery.task import task
31 from pylons import config
31 from pylons import config
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.celerylib import (
35 from rhodecode.lib.celerylib import (
36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
37 get_session, vcsconnection, RhodecodeCeleryTask)
37 get_session, vcsconnection, RhodecodeCeleryTask)
38 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.hooks_base import log_create_repository
39 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
40 from rhodecode.lib.utils import add_cache
40 from rhodecode.lib.utils import add_cache
41 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.lib.utils2 import safe_int, str2bool
42 from rhodecode.model.db import Repository, User
42 from rhodecode.model.db import Repository, User
43
43
44
44
45 add_cache(config) # pragma: no cover
45 add_cache(config) # pragma: no cover
46
46
47
47
48 def get_logger(cls):
48 def get_logger(cls):
49 if rhodecode.CELERY_ENABLED:
49 if rhodecode.CELERY_ENABLED:
50 try:
50 try:
51 log = cls.get_logger()
51 log = cls.get_logger()
52 except Exception:
52 except Exception:
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54 else:
54 else:
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 return log
57 return log
58
58
59
59
60 @task(ignore_result=True, base=RhodecodeCeleryTask)
60 @task(ignore_result=True, base=RhodecodeCeleryTask)
61 @dbsession
61 @dbsession
62 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 def send_email(recipients, subject, body='', html_body='', email_config=None):
63 """
63 """
64 Sends an email with defined parameters from the .ini files.
64 Sends an email with defined parameters from the .ini files.
65
65
66 :param recipients: list of recipients, it this is empty the defined email
66 :param recipients: list of recipients, it this is empty the defined email
67 address from field 'email_to' is used instead
67 address from field 'email_to' is used instead
68 :param subject: subject of the mail
68 :param subject: subject of the mail
69 :param body: body of the mail
69 :param body: body of the mail
70 :param html_body: html version of body
70 :param html_body: html version of body
71 """
71 """
72 log = get_logger(send_email)
72 log = get_logger(send_email)
73
73
74 email_config = email_config or rhodecode.CONFIG
74 email_config = email_config or rhodecode.CONFIG
75 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
76 if not recipients:
76 if not recipients:
77 # if recipients are not defined we send to email_config + all admins
77 # if recipients are not defined we send to email_config + all admins
78 admins = [
78 admins = [
79 u.email for u in User.query().filter(User.admin == True).all()]
79 u.email for u in User.query().filter(User.admin == True).all()]
80 recipients = [email_config.get('email_to')] + admins
80 recipients = [email_config.get('email_to')] + admins
81
81
82 mail_server = email_config.get('smtp_server') or None
82 mail_server = email_config.get('smtp_server') or None
83 if mail_server is None:
83 if mail_server is None:
84 log.error("SMTP server information missing. Sending email failed. "
84 log.error("SMTP server information missing. Sending email failed. "
85 "Make sure that `smtp_server` variable is configured "
85 "Make sure that `smtp_server` variable is configured "
86 "inside the .ini file")
86 "inside the .ini file")
87 return False
87 return False
88
88
89 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 mail_from = email_config.get('app_email_from', 'RhodeCode')
90 user = email_config.get('smtp_username')
90 user = email_config.get('smtp_username')
91 passwd = email_config.get('smtp_password')
91 passwd = email_config.get('smtp_password')
92 mail_port = email_config.get('smtp_port')
92 mail_port = email_config.get('smtp_port')
93 tls = str2bool(email_config.get('smtp_use_tls'))
93 tls = str2bool(email_config.get('smtp_use_tls'))
94 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 ssl = str2bool(email_config.get('smtp_use_ssl'))
95 debug = str2bool(email_config.get('debug'))
95 debug = str2bool(email_config.get('debug'))
96 smtp_auth = email_config.get('smtp_auth')
96 smtp_auth = email_config.get('smtp_auth')
97
97
98 try:
98 try:
99 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
100 mail_port, ssl, tls, debug=debug)
100 mail_port, ssl, tls, debug=debug)
101 m.send(recipients, subject, body, html_body)
101 m.send(recipients, subject, body, html_body)
102 except Exception:
102 except Exception:
103 log.exception('Mail sending failed')
103 log.exception('Mail sending failed')
104 return False
104 return False
105 return True
105 return True
106
106
107
107
108 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 @task(ignore_result=True, base=RhodecodeCeleryTask)
109 @dbsession
109 @dbsession
110 @vcsconnection
110 @vcsconnection
111 def create_repo(form_data, cur_user):
111 def create_repo(form_data, cur_user):
112 from rhodecode.model.repo import RepoModel
112 from rhodecode.model.repo import RepoModel
113 from rhodecode.model.user import UserModel
113 from rhodecode.model.user import UserModel
114 from rhodecode.model.settings import SettingsModel
114 from rhodecode.model.settings import SettingsModel
115
115
116 log = get_logger(create_repo)
116 log = get_logger(create_repo)
117 DBS = get_session()
117 DBS = get_session()
118
118
119 cur_user = UserModel(DBS)._get_user(cur_user)
119 cur_user = UserModel(DBS)._get_user(cur_user)
120 owner = cur_user
120 owner = cur_user
121
121
122 repo_name = form_data['repo_name']
122 repo_name = form_data['repo_name']
123 repo_name_full = form_data['repo_name_full']
123 repo_name_full = form_data['repo_name_full']
124 repo_type = form_data['repo_type']
124 repo_type = form_data['repo_type']
125 description = form_data['repo_description']
125 description = form_data['repo_description']
126 private = form_data['repo_private']
126 private = form_data['repo_private']
127 clone_uri = form_data.get('clone_uri')
127 clone_uri = form_data.get('clone_uri')
128 repo_group = safe_int(form_data['repo_group'])
128 repo_group = safe_int(form_data['repo_group'])
129 landing_rev = form_data['repo_landing_rev']
129 landing_rev = form_data['repo_landing_rev']
130 copy_fork_permissions = form_data.get('copy_permissions')
130 copy_fork_permissions = form_data.get('copy_permissions')
131 copy_group_permissions = form_data.get('repo_copy_permissions')
131 copy_group_permissions = form_data.get('repo_copy_permissions')
132 fork_of = form_data.get('fork_parent_id')
132 fork_of = form_data.get('fork_parent_id')
133 state = form_data.get('repo_state', Repository.STATE_PENDING)
133 state = form_data.get('repo_state', Repository.STATE_PENDING)
134
134
135 # repo creation defaults, private and repo_type are filled in form
135 # repo creation defaults, private and repo_type are filled in form
136 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
137 enable_statistics = form_data.get(
137 enable_statistics = form_data.get(
138 'enable_statistics', defs.get('repo_enable_statistics'))
138 'enable_statistics', defs.get('repo_enable_statistics'))
139 enable_locking = form_data.get(
139 enable_locking = form_data.get(
140 'enable_locking', defs.get('repo_enable_locking'))
140 'enable_locking', defs.get('repo_enable_locking'))
141 enable_downloads = form_data.get(
141 enable_downloads = form_data.get(
142 'enable_downloads', defs.get('repo_enable_downloads'))
142 'enable_downloads', defs.get('repo_enable_downloads'))
143
143
144 try:
144 try:
145 repo = RepoModel(DBS)._create_repo(
145 repo = RepoModel(DBS)._create_repo(
146 repo_name=repo_name_full,
146 repo_name=repo_name_full,
147 repo_type=repo_type,
147 repo_type=repo_type,
148 description=description,
148 description=description,
149 owner=owner,
149 owner=owner,
150 private=private,
150 private=private,
151 clone_uri=clone_uri,
151 clone_uri=clone_uri,
152 repo_group=repo_group,
152 repo_group=repo_group,
153 landing_rev=landing_rev,
153 landing_rev=landing_rev,
154 fork_of=fork_of,
154 fork_of=fork_of,
155 copy_fork_permissions=copy_fork_permissions,
155 copy_fork_permissions=copy_fork_permissions,
156 copy_group_permissions=copy_group_permissions,
156 copy_group_permissions=copy_group_permissions,
157 enable_statistics=enable_statistics,
157 enable_statistics=enable_statistics,
158 enable_locking=enable_locking,
158 enable_locking=enable_locking,
159 enable_downloads=enable_downloads,
159 enable_downloads=enable_downloads,
160 state=state
160 state=state
161 )
161 )
162 DBS.commit()
162 DBS.commit()
163
163
164 # now create this repo on Filesystem
164 # now create this repo on Filesystem
165 RepoModel(DBS)._create_filesystem_repo(
165 RepoModel(DBS)._create_filesystem_repo(
166 repo_name=repo_name,
166 repo_name=repo_name,
167 repo_type=repo_type,
167 repo_type=repo_type,
168 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
168 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
169 clone_uri=clone_uri,
169 clone_uri=clone_uri,
170 )
170 )
171 repo = Repository.get_by_repo_name(repo_name_full)
171 repo = Repository.get_by_repo_name(repo_name_full)
172 log_create_repository(created_by=owner.username, **repo.get_dict())
172 log_create_repository(created_by=owner.username, **repo.get_dict())
173
173
174 # update repo commit caches initially
174 # update repo commit caches initially
175 repo.update_commit_cache()
175 repo.update_commit_cache()
176
176
177 # set new created state
177 # set new created state
178 repo.set_state(Repository.STATE_CREATED)
178 repo.set_state(Repository.STATE_CREATED)
179 repo_id = repo.repo_id
179 repo_id = repo.repo_id
180 repo_data = repo.get_api_data()
180 repo_data = repo.get_api_data()
181
181
182 audit_logger.store_web(
182 audit_logger.store(
183 action='repo.create',
183 'repo.create', action_data={'data': repo_data},
184 action_data={'data': repo_data},
185 user=cur_user,
184 user=cur_user,
186 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
185 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
187
186
188 DBS.commit()
187 DBS.commit()
189 except Exception:
188 except Exception:
190 log.warning('Exception occurred when creating repository, '
189 log.warning('Exception occurred when creating repository, '
191 'doing cleanup...', exc_info=True)
190 'doing cleanup...', exc_info=True)
192 # rollback things manually !
191 # rollback things manually !
193 repo = Repository.get_by_repo_name(repo_name_full)
192 repo = Repository.get_by_repo_name(repo_name_full)
194 if repo:
193 if repo:
195 Repository.delete(repo.repo_id)
194 Repository.delete(repo.repo_id)
196 DBS.commit()
195 DBS.commit()
197 RepoModel(DBS)._delete_filesystem_repo(repo)
196 RepoModel(DBS)._delete_filesystem_repo(repo)
198 raise
197 raise
199
198
200 # it's an odd fix to make celery fail task when exception occurs
199 # it's an odd fix to make celery fail task when exception occurs
201 def on_failure(self, *args, **kwargs):
200 def on_failure(self, *args, **kwargs):
202 pass
201 pass
203
202
204 return True
203 return True
205
204
206
205
207 @task(ignore_result=True, base=RhodecodeCeleryTask)
206 @task(ignore_result=True, base=RhodecodeCeleryTask)
208 @dbsession
207 @dbsession
209 @vcsconnection
208 @vcsconnection
210 def create_repo_fork(form_data, cur_user):
209 def create_repo_fork(form_data, cur_user):
211 """
210 """
212 Creates a fork of repository using internal VCS methods
211 Creates a fork of repository using internal VCS methods
213
212
214 :param form_data:
213 :param form_data:
215 :param cur_user:
214 :param cur_user:
216 """
215 """
217 from rhodecode.model.repo import RepoModel
216 from rhodecode.model.repo import RepoModel
218 from rhodecode.model.user import UserModel
217 from rhodecode.model.user import UserModel
219
218
220 log = get_logger(create_repo_fork)
219 log = get_logger(create_repo_fork)
221 DBS = get_session()
220 DBS = get_session()
222
221
223 cur_user = UserModel(DBS)._get_user(cur_user)
222 cur_user = UserModel(DBS)._get_user(cur_user)
224 owner = cur_user
223 owner = cur_user
225
224
226 repo_name = form_data['repo_name'] # fork in this case
225 repo_name = form_data['repo_name'] # fork in this case
227 repo_name_full = form_data['repo_name_full']
226 repo_name_full = form_data['repo_name_full']
228 repo_type = form_data['repo_type']
227 repo_type = form_data['repo_type']
229 description = form_data['description']
228 description = form_data['description']
230 private = form_data['private']
229 private = form_data['private']
231 clone_uri = form_data.get('clone_uri')
230 clone_uri = form_data.get('clone_uri')
232 repo_group = safe_int(form_data['repo_group'])
231 repo_group = safe_int(form_data['repo_group'])
233 landing_rev = form_data['landing_rev']
232 landing_rev = form_data['landing_rev']
234 copy_fork_permissions = form_data.get('copy_permissions')
233 copy_fork_permissions = form_data.get('copy_permissions')
235 fork_id = safe_int(form_data.get('fork_parent_id'))
234 fork_id = safe_int(form_data.get('fork_parent_id'))
236
235
237 try:
236 try:
238 fork_of = RepoModel(DBS)._get_repo(fork_id)
237 fork_of = RepoModel(DBS)._get_repo(fork_id)
239 RepoModel(DBS)._create_repo(
238 RepoModel(DBS)._create_repo(
240 repo_name=repo_name_full,
239 repo_name=repo_name_full,
241 repo_type=repo_type,
240 repo_type=repo_type,
242 description=description,
241 description=description,
243 owner=owner,
242 owner=owner,
244 private=private,
243 private=private,
245 clone_uri=clone_uri,
244 clone_uri=clone_uri,
246 repo_group=repo_group,
245 repo_group=repo_group,
247 landing_rev=landing_rev,
246 landing_rev=landing_rev,
248 fork_of=fork_of,
247 fork_of=fork_of,
249 copy_fork_permissions=copy_fork_permissions
248 copy_fork_permissions=copy_fork_permissions
250 )
249 )
251
250
252 DBS.commit()
251 DBS.commit()
253
252
254 base_path = Repository.base_path()
253 base_path = Repository.base_path()
255 source_repo_path = os.path.join(base_path, fork_of.repo_name)
254 source_repo_path = os.path.join(base_path, fork_of.repo_name)
256
255
257 # now create this repo on Filesystem
256 # now create this repo on Filesystem
258 RepoModel(DBS)._create_filesystem_repo(
257 RepoModel(DBS)._create_filesystem_repo(
259 repo_name=repo_name,
258 repo_name=repo_name,
260 repo_type=repo_type,
259 repo_type=repo_type,
261 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
260 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
262 clone_uri=source_repo_path,
261 clone_uri=source_repo_path,
263 )
262 )
264 repo = Repository.get_by_repo_name(repo_name_full)
263 repo = Repository.get_by_repo_name(repo_name_full)
265 log_create_repository(created_by=owner.username, **repo.get_dict())
264 log_create_repository(created_by=owner.username, **repo.get_dict())
266
265
267 # update repo commit caches initially
266 # update repo commit caches initially
268 config = repo._config
267 config = repo._config
269 config.set('extensions', 'largefiles', '')
268 config.set('extensions', 'largefiles', '')
270 repo.update_commit_cache(config=config)
269 repo.update_commit_cache(config=config)
271
270
272 # set new created state
271 # set new created state
273 repo.set_state(Repository.STATE_CREATED)
272 repo.set_state(Repository.STATE_CREATED)
274
273
275 repo_id = repo.repo_id
274 repo_id = repo.repo_id
276 repo_data = repo.get_api_data()
275 repo_data = repo.get_api_data()
277 audit_logger.store_web(
276 audit_logger.store(
278 action='repo.fork',
277 'repo.fork', action_data={'data': repo_data},
279 action_data={'data': repo_data},
280 user=cur_user,
278 user=cur_user,
281 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
279 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
282
280
283 DBS.commit()
281 DBS.commit()
284 except Exception as e:
282 except Exception as e:
285 log.warning('Exception %s occurred when forking repository, '
283 log.warning('Exception %s occurred when forking repository, '
286 'doing cleanup...', e)
284 'doing cleanup...', e)
287 # rollback things manually !
285 # rollback things manually !
288 repo = Repository.get_by_repo_name(repo_name_full)
286 repo = Repository.get_by_repo_name(repo_name_full)
289 if repo:
287 if repo:
290 Repository.delete(repo.repo_id)
288 Repository.delete(repo.repo_id)
291 DBS.commit()
289 DBS.commit()
292 RepoModel(DBS)._delete_filesystem_repo(repo)
290 RepoModel(DBS)._delete_filesystem_repo(repo)
293 raise
291 raise
294
292
295 # it's an odd fix to make celery fail task when exception occurs
293 # it's an odd fix to make celery fail task when exception occurs
296 def on_failure(self, *args, **kwargs):
294 def on_failure(self, *args, **kwargs):
297 pass
295 pass
298
296
299 return True
297 return True
@@ -1,425 +1,425 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 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 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import collections
27 import collections
28 import logging
28 import logging
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
36 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42
42
43
43
44 def is_shadow_repo(extras):
44 def is_shadow_repo(extras):
45 """
45 """
46 Returns ``True`` if this is an action executed against a shadow repository.
46 Returns ``True`` if this is an action executed against a shadow repository.
47 """
47 """
48 return extras['is_shadow_repo']
48 return extras['is_shadow_repo']
49
49
50
50
51 def _get_scm_size(alias, root_path):
51 def _get_scm_size(alias, root_path):
52
52
53 if not alias.startswith('.'):
53 if not alias.startswith('.'):
54 alias += '.'
54 alias += '.'
55
55
56 size_scm, size_root = 0, 0
56 size_scm, size_root = 0, 0
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
58 if path.find(alias) != -1:
58 if path.find(alias) != -1:
59 for f in files:
59 for f in files:
60 try:
60 try:
61 size_scm += os.path.getsize(os.path.join(path, f))
61 size_scm += os.path.getsize(os.path.join(path, f))
62 except OSError:
62 except OSError:
63 pass
63 pass
64 else:
64 else:
65 for f in files:
65 for f in files:
66 try:
66 try:
67 size_root += os.path.getsize(os.path.join(path, f))
67 size_root += os.path.getsize(os.path.join(path, f))
68 except OSError:
68 except OSError:
69 pass
69 pass
70
70
71 size_scm_f = h.format_byte_size_binary(size_scm)
71 size_scm_f = h.format_byte_size_binary(size_scm)
72 size_root_f = h.format_byte_size_binary(size_root)
72 size_root_f = h.format_byte_size_binary(size_root)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
74
74
75 return size_scm_f, size_root_f, size_total_f
75 return size_scm_f, size_root_f, size_total_f
76
76
77
77
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
79 def repo_size(extras):
79 def repo_size(extras):
80 """Present size of repository after push."""
80 """Present size of repository after push."""
81 repo = Repository.get_by_repo_name(extras.repository)
81 repo = Repository.get_by_repo_name(extras.repository)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
84 repo.repo_full_path)
84 repo.repo_full_path)
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
87 return HookResponse(0, msg)
87 return HookResponse(0, msg)
88
88
89
89
90 def pre_push(extras):
90 def pre_push(extras):
91 """
91 """
92 Hook executed before pushing code.
92 Hook executed before pushing code.
93
93
94 It bans pushing when the repository is locked.
94 It bans pushing when the repository is locked.
95 """
95 """
96
96
97 usr = User.get_by_username(extras.username)
97 usr = User.get_by_username(extras.username)
98 output = ''
98 output = ''
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
100 locked_by = User.get(extras.locked_by[0]).username
100 locked_by = User.get(extras.locked_by[0]).username
101 reason = extras.locked_by[2]
101 reason = extras.locked_by[2]
102 # this exception is interpreted in git/hg middlewares and based
102 # this exception is interpreted in git/hg middlewares and based
103 # on that proper return code is server to client
103 # on that proper return code is server to client
104 _http_ret = HTTPLockedRC(
104 _http_ret = HTTPLockedRC(
105 _locked_by_explanation(extras.repository, locked_by, reason))
105 _locked_by_explanation(extras.repository, locked_by, reason))
106 if str(_http_ret.code).startswith('2'):
106 if str(_http_ret.code).startswith('2'):
107 # 2xx Codes don't raise exceptions
107 # 2xx Codes don't raise exceptions
108 output = _http_ret.title
108 output = _http_ret.title
109 else:
109 else:
110 raise _http_ret
110 raise _http_ret
111
111
112 # Propagate to external components. This is done after checking the
112 # Propagate to external components. This is done after checking the
113 # lock, for consistent behavior.
113 # lock, for consistent behavior.
114 if not is_shadow_repo(extras):
114 if not is_shadow_repo(extras):
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
116 events.trigger(events.RepoPrePushEvent(
116 events.trigger(events.RepoPrePushEvent(
117 repo_name=extras.repository, extras=extras))
117 repo_name=extras.repository, extras=extras))
118
118
119 return HookResponse(0, output)
119 return HookResponse(0, output)
120
120
121
121
122 def pre_pull(extras):
122 def pre_pull(extras):
123 """
123 """
124 Hook executed before pulling the code.
124 Hook executed before pulling the code.
125
125
126 It bans pulling when the repository is locked.
126 It bans pulling when the repository is locked.
127 """
127 """
128
128
129 output = ''
129 output = ''
130 if extras.locked_by[0]:
130 if extras.locked_by[0]:
131 locked_by = User.get(extras.locked_by[0]).username
131 locked_by = User.get(extras.locked_by[0]).username
132 reason = extras.locked_by[2]
132 reason = extras.locked_by[2]
133 # this exception is interpreted in git/hg middlewares and based
133 # this exception is interpreted in git/hg middlewares and based
134 # on that proper return code is server to client
134 # on that proper return code is server to client
135 _http_ret = HTTPLockedRC(
135 _http_ret = HTTPLockedRC(
136 _locked_by_explanation(extras.repository, locked_by, reason))
136 _locked_by_explanation(extras.repository, locked_by, reason))
137 if str(_http_ret.code).startswith('2'):
137 if str(_http_ret.code).startswith('2'):
138 # 2xx Codes don't raise exceptions
138 # 2xx Codes don't raise exceptions
139 output = _http_ret.title
139 output = _http_ret.title
140 else:
140 else:
141 raise _http_ret
141 raise _http_ret
142
142
143 # Propagate to external components. This is done after checking the
143 # Propagate to external components. This is done after checking the
144 # lock, for consistent behavior.
144 # lock, for consistent behavior.
145 if not is_shadow_repo(extras):
145 if not is_shadow_repo(extras):
146 pre_pull_extension(**extras)
146 pre_pull_extension(**extras)
147 events.trigger(events.RepoPrePullEvent(
147 events.trigger(events.RepoPrePullEvent(
148 repo_name=extras.repository, extras=extras))
148 repo_name=extras.repository, extras=extras))
149
149
150 return HookResponse(0, output)
150 return HookResponse(0, output)
151
151
152
152
153 def post_pull(extras):
153 def post_pull(extras):
154 """Hook executed after client pulls the code."""
154 """Hook executed after client pulls the code."""
155
155
156 audit_user = audit_logger.UserWrap(
156 audit_user = audit_logger.UserWrap(
157 username=extras.username,
157 username=extras.username,
158 ip_addr=extras.ip)
158 ip_addr=extras.ip)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
160 audit_logger.store(
160 audit_logger.store(
161 action='user.pull', action_data={
161 'user.pull', action_data={
162 'user_agent': extras.user_agent},
162 'user_agent': extras.user_agent},
163 user=audit_user, repo=repo, commit=True)
163 user=audit_user, repo=repo, commit=True)
164
164
165 # Propagate to external components.
165 # Propagate to external components.
166 if not is_shadow_repo(extras):
166 if not is_shadow_repo(extras):
167 post_pull_extension(**extras)
167 post_pull_extension(**extras)
168 events.trigger(events.RepoPullEvent(
168 events.trigger(events.RepoPullEvent(
169 repo_name=extras.repository, extras=extras))
169 repo_name=extras.repository, extras=extras))
170
170
171 output = ''
171 output = ''
172 # make lock is a tri state False, True, None. We only make lock on True
172 # make lock is a tri state False, True, None. We only make lock on True
173 if extras.make_lock is True and not is_shadow_repo(extras):
173 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
174 user = User.get_by_username(extras.username)
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
176 user.user_id,
176 user.user_id,
177 lock_reason=Repository.LOCK_PULL)
177 lock_reason=Repository.LOCK_PULL)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
179 output += msg
179 output += msg
180
180
181 if extras.locked_by[0]:
181 if extras.locked_by[0]:
182 locked_by = User.get(extras.locked_by[0]).username
182 locked_by = User.get(extras.locked_by[0]).username
183 reason = extras.locked_by[2]
183 reason = extras.locked_by[2]
184 _http_ret = HTTPLockedRC(
184 _http_ret = HTTPLockedRC(
185 _locked_by_explanation(extras.repository, locked_by, reason))
185 _locked_by_explanation(extras.repository, locked_by, reason))
186 if str(_http_ret.code).startswith('2'):
186 if str(_http_ret.code).startswith('2'):
187 # 2xx Codes don't raise exceptions
187 # 2xx Codes don't raise exceptions
188 output += _http_ret.title
188 output += _http_ret.title
189
189
190 return HookResponse(0, output)
190 return HookResponse(0, output)
191
191
192
192
193 def post_push(extras):
193 def post_push(extras):
194 """Hook executed after user pushes to the repository."""
194 """Hook executed after user pushes to the repository."""
195 commit_ids = extras.commit_ids
195 commit_ids = extras.commit_ids
196
196
197 # log the push call
197 # log the push call
198 audit_user = audit_logger.UserWrap(
198 audit_user = audit_logger.UserWrap(
199 username=extras.username, ip_addr=extras.ip)
199 username=extras.username, ip_addr=extras.ip)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
201 audit_logger.store(
201 audit_logger.store(
202 action='user.push', action_data={
202 'user.push', action_data={
203 'user_agent': extras.user_agent,
203 'user_agent': extras.user_agent,
204 'commit_ids': commit_ids[:10000]},
204 'commit_ids': commit_ids[:10000]},
205 user=audit_user, repo=repo, commit=True)
205 user=audit_user, repo=repo, commit=True)
206
206
207 # Propagate to external components.
207 # Propagate to external components.
208 if not is_shadow_repo(extras):
208 if not is_shadow_repo(extras):
209 post_push_extension(
209 post_push_extension(
210 repo_store_path=Repository.base_path(),
210 repo_store_path=Repository.base_path(),
211 pushed_revs=commit_ids,
211 pushed_revs=commit_ids,
212 **extras)
212 **extras)
213 events.trigger(events.RepoPushEvent(
213 events.trigger(events.RepoPushEvent(
214 repo_name=extras.repository,
214 repo_name=extras.repository,
215 pushed_commit_ids=commit_ids,
215 pushed_commit_ids=commit_ids,
216 extras=extras))
216 extras=extras))
217
217
218 output = ''
218 output = ''
219 # make lock is a tri state False, True, None. We only release lock on False
219 # make lock is a tri state False, True, None. We only release lock on False
220 if extras.make_lock is False and not is_shadow_repo(extras):
220 if extras.make_lock is False and not is_shadow_repo(extras):
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
222 msg = 'Released lock on repo `%s`\n' % extras.repository
222 msg = 'Released lock on repo `%s`\n' % extras.repository
223 output += msg
223 output += msg
224
224
225 if extras.locked_by[0]:
225 if extras.locked_by[0]:
226 locked_by = User.get(extras.locked_by[0]).username
226 locked_by = User.get(extras.locked_by[0]).username
227 reason = extras.locked_by[2]
227 reason = extras.locked_by[2]
228 _http_ret = HTTPLockedRC(
228 _http_ret = HTTPLockedRC(
229 _locked_by_explanation(extras.repository, locked_by, reason))
229 _locked_by_explanation(extras.repository, locked_by, reason))
230 # TODO: johbo: if not?
230 # TODO: johbo: if not?
231 if str(_http_ret.code).startswith('2'):
231 if str(_http_ret.code).startswith('2'):
232 # 2xx Codes don't raise exceptions
232 # 2xx Codes don't raise exceptions
233 output += _http_ret.title
233 output += _http_ret.title
234
234
235 if extras.new_refs:
235 if extras.new_refs:
236 tmpl = \
236 tmpl = \
237 extras.server_url + '/' + \
237 extras.server_url + '/' + \
238 extras.repository + \
238 extras.repository + \
239 "/pull-request/new?{ref_type}={ref_name}"
239 "/pull-request/new?{ref_type}={ref_name}"
240 for branch_name in extras.new_refs['branches']:
240 for branch_name in extras.new_refs['branches']:
241 output += 'RhodeCode: open pull request link: {}\n'.format(
241 output += 'RhodeCode: open pull request link: {}\n'.format(
242 tmpl.format(ref_type='branch', ref_name=branch_name))
242 tmpl.format(ref_type='branch', ref_name=branch_name))
243
243
244 for book_name in extras.new_refs['bookmarks']:
244 for book_name in extras.new_refs['bookmarks']:
245 output += 'RhodeCode: open pull request link: {}\n'.format(
245 output += 'RhodeCode: open pull request link: {}\n'.format(
246 tmpl.format(ref_type='bookmark', ref_name=book_name))
246 tmpl.format(ref_type='bookmark', ref_name=book_name))
247
247
248 output += 'RhodeCode: push completed\n'
248 output += 'RhodeCode: push completed\n'
249 return HookResponse(0, output)
249 return HookResponse(0, output)
250
250
251
251
252 def _locked_by_explanation(repo_name, user_name, reason):
252 def _locked_by_explanation(repo_name, user_name, reason):
253 message = (
253 message = (
254 'Repository `%s` locked by user `%s`. Reason:`%s`'
254 'Repository `%s` locked by user `%s`. Reason:`%s`'
255 % (repo_name, user_name, reason))
255 % (repo_name, user_name, reason))
256 return message
256 return message
257
257
258
258
259 def check_allowed_create_user(user_dict, created_by, **kwargs):
259 def check_allowed_create_user(user_dict, created_by, **kwargs):
260 # pre create hooks
260 # pre create hooks
261 if pre_create_user.is_active():
261 if pre_create_user.is_active():
262 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
262 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
263 if not allowed:
263 if not allowed:
264 raise UserCreationError(reason)
264 raise UserCreationError(reason)
265
265
266
266
267 class ExtensionCallback(object):
267 class ExtensionCallback(object):
268 """
268 """
269 Forwards a given call to rcextensions, sanitizes keyword arguments.
269 Forwards a given call to rcextensions, sanitizes keyword arguments.
270
270
271 Does check if there is an extension active for that hook. If it is
271 Does check if there is an extension active for that hook. If it is
272 there, it will forward all `kwargs_keys` keyword arguments to the
272 there, it will forward all `kwargs_keys` keyword arguments to the
273 extension callback.
273 extension callback.
274 """
274 """
275
275
276 def __init__(self, hook_name, kwargs_keys):
276 def __init__(self, hook_name, kwargs_keys):
277 self._hook_name = hook_name
277 self._hook_name = hook_name
278 self._kwargs_keys = set(kwargs_keys)
278 self._kwargs_keys = set(kwargs_keys)
279
279
280 def __call__(self, *args, **kwargs):
280 def __call__(self, *args, **kwargs):
281 log.debug('Calling extension callback for %s', self._hook_name)
281 log.debug('Calling extension callback for %s', self._hook_name)
282
282
283 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
283 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
284 # backward compat for removed api_key for old hooks. THis was it works
284 # backward compat for removed api_key for old hooks. THis was it works
285 # with older rcextensions that require api_key present
285 # with older rcextensions that require api_key present
286 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
286 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
287 kwargs_to_pass['api_key'] = '_DEPRECATED_'
287 kwargs_to_pass['api_key'] = '_DEPRECATED_'
288
288
289 callback = self._get_callback()
289 callback = self._get_callback()
290 if callback:
290 if callback:
291 return callback(**kwargs_to_pass)
291 return callback(**kwargs_to_pass)
292 else:
292 else:
293 log.debug('extensions callback not found skipping...')
293 log.debug('extensions callback not found skipping...')
294
294
295 def is_active(self):
295 def is_active(self):
296 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
296 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
297
297
298 def _get_callback(self):
298 def _get_callback(self):
299 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
299 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
300
300
301
301
302 pre_pull_extension = ExtensionCallback(
302 pre_pull_extension = ExtensionCallback(
303 hook_name='PRE_PULL_HOOK',
303 hook_name='PRE_PULL_HOOK',
304 kwargs_keys=(
304 kwargs_keys=(
305 'server_url', 'config', 'scm', 'username', 'ip', 'action',
305 'server_url', 'config', 'scm', 'username', 'ip', 'action',
306 'repository'))
306 'repository'))
307
307
308
308
309 post_pull_extension = ExtensionCallback(
309 post_pull_extension = ExtensionCallback(
310 hook_name='PULL_HOOK',
310 hook_name='PULL_HOOK',
311 kwargs_keys=(
311 kwargs_keys=(
312 'server_url', 'config', 'scm', 'username', 'ip', 'action',
312 'server_url', 'config', 'scm', 'username', 'ip', 'action',
313 'repository'))
313 'repository'))
314
314
315
315
316 pre_push_extension = ExtensionCallback(
316 pre_push_extension = ExtensionCallback(
317 hook_name='PRE_PUSH_HOOK',
317 hook_name='PRE_PUSH_HOOK',
318 kwargs_keys=(
318 kwargs_keys=(
319 'server_url', 'config', 'scm', 'username', 'ip', 'action',
319 'server_url', 'config', 'scm', 'username', 'ip', 'action',
320 'repository', 'repo_store_path', 'commit_ids'))
320 'repository', 'repo_store_path', 'commit_ids'))
321
321
322
322
323 post_push_extension = ExtensionCallback(
323 post_push_extension = ExtensionCallback(
324 hook_name='PUSH_HOOK',
324 hook_name='PUSH_HOOK',
325 kwargs_keys=(
325 kwargs_keys=(
326 'server_url', 'config', 'scm', 'username', 'ip', 'action',
326 'server_url', 'config', 'scm', 'username', 'ip', 'action',
327 'repository', 'repo_store_path', 'pushed_revs'))
327 'repository', 'repo_store_path', 'pushed_revs'))
328
328
329
329
330 pre_create_user = ExtensionCallback(
330 pre_create_user = ExtensionCallback(
331 hook_name='PRE_CREATE_USER_HOOK',
331 hook_name='PRE_CREATE_USER_HOOK',
332 kwargs_keys=(
332 kwargs_keys=(
333 'username', 'password', 'email', 'firstname', 'lastname', 'active',
333 'username', 'password', 'email', 'firstname', 'lastname', 'active',
334 'admin', 'created_by'))
334 'admin', 'created_by'))
335
335
336
336
337 log_create_pull_request = ExtensionCallback(
337 log_create_pull_request = ExtensionCallback(
338 hook_name='CREATE_PULL_REQUEST',
338 hook_name='CREATE_PULL_REQUEST',
339 kwargs_keys=(
339 kwargs_keys=(
340 'server_url', 'config', 'scm', 'username', 'ip', 'action',
340 'server_url', 'config', 'scm', 'username', 'ip', 'action',
341 'repository', 'pull_request_id', 'url', 'title', 'description',
341 'repository', 'pull_request_id', 'url', 'title', 'description',
342 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
342 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
343 'mergeable', 'source', 'target', 'author', 'reviewers'))
343 'mergeable', 'source', 'target', 'author', 'reviewers'))
344
344
345
345
346 log_merge_pull_request = ExtensionCallback(
346 log_merge_pull_request = ExtensionCallback(
347 hook_name='MERGE_PULL_REQUEST',
347 hook_name='MERGE_PULL_REQUEST',
348 kwargs_keys=(
348 kwargs_keys=(
349 'server_url', 'config', 'scm', 'username', 'ip', 'action',
349 'server_url', 'config', 'scm', 'username', 'ip', 'action',
350 'repository', 'pull_request_id', 'url', 'title', 'description',
350 'repository', 'pull_request_id', 'url', 'title', 'description',
351 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
351 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
352 'mergeable', 'source', 'target', 'author', 'reviewers'))
352 'mergeable', 'source', 'target', 'author', 'reviewers'))
353
353
354
354
355 log_close_pull_request = ExtensionCallback(
355 log_close_pull_request = ExtensionCallback(
356 hook_name='CLOSE_PULL_REQUEST',
356 hook_name='CLOSE_PULL_REQUEST',
357 kwargs_keys=(
357 kwargs_keys=(
358 'server_url', 'config', 'scm', 'username', 'ip', 'action',
358 'server_url', 'config', 'scm', 'username', 'ip', 'action',
359 'repository', 'pull_request_id', 'url', 'title', 'description',
359 'repository', 'pull_request_id', 'url', 'title', 'description',
360 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
360 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
361 'mergeable', 'source', 'target', 'author', 'reviewers'))
361 'mergeable', 'source', 'target', 'author', 'reviewers'))
362
362
363
363
364 log_review_pull_request = ExtensionCallback(
364 log_review_pull_request = ExtensionCallback(
365 hook_name='REVIEW_PULL_REQUEST',
365 hook_name='REVIEW_PULL_REQUEST',
366 kwargs_keys=(
366 kwargs_keys=(
367 'server_url', 'config', 'scm', 'username', 'ip', 'action',
367 'server_url', 'config', 'scm', 'username', 'ip', 'action',
368 'repository', 'pull_request_id', 'url', 'title', 'description',
368 'repository', 'pull_request_id', 'url', 'title', 'description',
369 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
369 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
370 'mergeable', 'source', 'target', 'author', 'reviewers'))
370 'mergeable', 'source', 'target', 'author', 'reviewers'))
371
371
372
372
373 log_update_pull_request = ExtensionCallback(
373 log_update_pull_request = ExtensionCallback(
374 hook_name='UPDATE_PULL_REQUEST',
374 hook_name='UPDATE_PULL_REQUEST',
375 kwargs_keys=(
375 kwargs_keys=(
376 'server_url', 'config', 'scm', 'username', 'ip', 'action',
376 'server_url', 'config', 'scm', 'username', 'ip', 'action',
377 'repository', 'pull_request_id', 'url', 'title', 'description',
377 'repository', 'pull_request_id', 'url', 'title', 'description',
378 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
378 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
379 'mergeable', 'source', 'target', 'author', 'reviewers'))
379 'mergeable', 'source', 'target', 'author', 'reviewers'))
380
380
381
381
382 log_create_user = ExtensionCallback(
382 log_create_user = ExtensionCallback(
383 hook_name='CREATE_USER_HOOK',
383 hook_name='CREATE_USER_HOOK',
384 kwargs_keys=(
384 kwargs_keys=(
385 'username', 'full_name_or_username', 'full_contact', 'user_id',
385 'username', 'full_name_or_username', 'full_contact', 'user_id',
386 'name', 'firstname', 'short_contact', 'admin', 'lastname',
386 'name', 'firstname', 'short_contact', 'admin', 'lastname',
387 'ip_addresses', 'extern_type', 'extern_name',
387 'ip_addresses', 'extern_type', 'extern_name',
388 'email', 'api_keys', 'last_login',
388 'email', 'api_keys', 'last_login',
389 'full_name', 'active', 'password', 'emails',
389 'full_name', 'active', 'password', 'emails',
390 'inherit_default_permissions', 'created_by', 'created_on'))
390 'inherit_default_permissions', 'created_by', 'created_on'))
391
391
392
392
393 log_delete_user = ExtensionCallback(
393 log_delete_user = ExtensionCallback(
394 hook_name='DELETE_USER_HOOK',
394 hook_name='DELETE_USER_HOOK',
395 kwargs_keys=(
395 kwargs_keys=(
396 'username', 'full_name_or_username', 'full_contact', 'user_id',
396 'username', 'full_name_or_username', 'full_contact', 'user_id',
397 'name', 'firstname', 'short_contact', 'admin', 'lastname',
397 'name', 'firstname', 'short_contact', 'admin', 'lastname',
398 'ip_addresses',
398 'ip_addresses',
399 'email', 'last_login',
399 'email', 'last_login',
400 'full_name', 'active', 'password', 'emails',
400 'full_name', 'active', 'password', 'emails',
401 'inherit_default_permissions', 'deleted_by'))
401 'inherit_default_permissions', 'deleted_by'))
402
402
403
403
404 log_create_repository = ExtensionCallback(
404 log_create_repository = ExtensionCallback(
405 hook_name='CREATE_REPO_HOOK',
405 hook_name='CREATE_REPO_HOOK',
406 kwargs_keys=(
406 kwargs_keys=(
407 'repo_name', 'repo_type', 'description', 'private', 'created_on',
407 'repo_name', 'repo_type', 'description', 'private', 'created_on',
408 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
408 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
409 'clone_uri', 'fork_id', 'group_id', 'created_by'))
409 'clone_uri', 'fork_id', 'group_id', 'created_by'))
410
410
411
411
412 log_delete_repository = ExtensionCallback(
412 log_delete_repository = ExtensionCallback(
413 hook_name='DELETE_REPO_HOOK',
413 hook_name='DELETE_REPO_HOOK',
414 kwargs_keys=(
414 kwargs_keys=(
415 'repo_name', 'repo_type', 'description', 'private', 'created_on',
415 'repo_name', 'repo_type', 'description', 'private', 'created_on',
416 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
416 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
417 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
417 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
418
418
419
419
420 log_create_repository_group = ExtensionCallback(
420 log_create_repository_group = ExtensionCallback(
421 hook_name='CREATE_REPO_GROUP_HOOK',
421 hook_name='CREATE_REPO_GROUP_HOOK',
422 kwargs_keys=(
422 kwargs_keys=(
423 'group_name', 'group_parent_id', 'group_description',
423 'group_name', 'group_parent_id', 'group_description',
424 'group_id', 'user_id', 'created_by', 'created_on',
424 'group_id', 'user_id', 'created_by', 'created_on',
425 'enable_locking'))
425 'enable_locking'))
@@ -1,618 +1,623 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 """
22 """
23 user group model for RhodeCode
23 user group model for RhodeCode
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.lib.utils2 import safe_str, safe_unicode
30 from rhodecode.lib.utils2 import safe_str, safe_unicode
31 from rhodecode.lib.exceptions import (
31 from rhodecode.lib.exceptions import (
32 UserGroupAssignedException, RepoGroupAssignmentError)
32 UserGroupAssignedException, RepoGroupAssignmentError)
33 from rhodecode.lib.utils2 import (
33 from rhodecode.lib.utils2 import (
34 get_current_rhodecode_user, action_logger_generic)
34 get_current_rhodecode_user, action_logger_generic)
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.scm import UserGroupList
36 from rhodecode.model.scm import UserGroupList
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 true, func, User, UserGroupMember, UserGroup,
38 true, func, User, UserGroupMember, UserGroup,
39 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
39 UserGroupRepoToPerm, Permission, UserGroupToPerm, UserUserGroupToPerm,
40 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
40 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm)
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class UserGroupModel(BaseModel):
46 class UserGroupModel(BaseModel):
47
47
48 cls = UserGroup
48 cls = UserGroup
49
49
50 def _get_user_group(self, user_group):
50 def _get_user_group(self, user_group):
51 return self._get_instance(UserGroup, user_group,
51 return self._get_instance(UserGroup, user_group,
52 callback=UserGroup.get_by_group_name)
52 callback=UserGroup.get_by_group_name)
53
53
54 def _create_default_perms(self, user_group):
54 def _create_default_perms(self, user_group):
55 # create default permission
55 # create default permission
56 default_perm = 'usergroup.read'
56 default_perm = 'usergroup.read'
57 def_user = User.get_default_user()
57 def_user = User.get_default_user()
58 for p in def_user.user_perms:
58 for p in def_user.user_perms:
59 if p.permission.permission_name.startswith('usergroup.'):
59 if p.permission.permission_name.startswith('usergroup.'):
60 default_perm = p.permission.permission_name
60 default_perm = p.permission.permission_name
61 break
61 break
62
62
63 user_group_to_perm = UserUserGroupToPerm()
63 user_group_to_perm = UserUserGroupToPerm()
64 user_group_to_perm.permission = Permission.get_by_key(default_perm)
64 user_group_to_perm.permission = Permission.get_by_key(default_perm)
65
65
66 user_group_to_perm.user_group = user_group
66 user_group_to_perm.user_group = user_group
67 user_group_to_perm.user_id = def_user.user_id
67 user_group_to_perm.user_id = def_user.user_id
68 return user_group_to_perm
68 return user_group_to_perm
69
69
70 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
70 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
71 perm_deletions=None, check_perms=True, cur_user=None):
71 perm_deletions=None, check_perms=True, cur_user=None):
72 from rhodecode.lib.auth import HasUserGroupPermissionAny
72 from rhodecode.lib.auth import HasUserGroupPermissionAny
73 if not perm_additions:
73 if not perm_additions:
74 perm_additions = []
74 perm_additions = []
75 if not perm_updates:
75 if not perm_updates:
76 perm_updates = []
76 perm_updates = []
77 if not perm_deletions:
77 if not perm_deletions:
78 perm_deletions = []
78 perm_deletions = []
79
79
80 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
80 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
81
81
82 # update permissions
82 # update permissions
83 for member_id, perm, member_type in perm_updates:
83 for member_id, perm, member_type in perm_updates:
84 member_id = int(member_id)
84 member_id = int(member_id)
85 if member_type == 'user':
85 if member_type == 'user':
86 # this updates existing one
86 # this updates existing one
87 self.grant_user_permission(
87 self.grant_user_permission(
88 user_group=user_group, user=member_id, perm=perm
88 user_group=user_group, user=member_id, perm=perm
89 )
89 )
90 else:
90 else:
91 # check if we have permissions to alter this usergroup
91 # check if we have permissions to alter this usergroup
92 member_name = UserGroup.get(member_id).users_group_name
92 member_name = UserGroup.get(member_id).users_group_name
93 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
93 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
94 self.grant_user_group_permission(
94 self.grant_user_group_permission(
95 target_user_group=user_group, user_group=member_id, perm=perm
95 target_user_group=user_group, user_group=member_id, perm=perm
96 )
96 )
97
97
98 # set new permissions
98 # set new permissions
99 for member_id, perm, member_type in perm_additions:
99 for member_id, perm, member_type in perm_additions:
100 member_id = int(member_id)
100 member_id = int(member_id)
101 if member_type == 'user':
101 if member_type == 'user':
102 self.grant_user_permission(
102 self.grant_user_permission(
103 user_group=user_group, user=member_id, perm=perm
103 user_group=user_group, user=member_id, perm=perm
104 )
104 )
105 else:
105 else:
106 # check if we have permissions to alter this usergroup
106 # check if we have permissions to alter this usergroup
107 member_name = UserGroup.get(member_id).users_group_name
107 member_name = UserGroup.get(member_id).users_group_name
108 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
108 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
109 self.grant_user_group_permission(
109 self.grant_user_group_permission(
110 target_user_group=user_group, user_group=member_id, perm=perm
110 target_user_group=user_group, user_group=member_id, perm=perm
111 )
111 )
112
112
113 # delete permissions
113 # delete permissions
114 for member_id, perm, member_type in perm_deletions:
114 for member_id, perm, member_type in perm_deletions:
115 member_id = int(member_id)
115 member_id = int(member_id)
116 if member_type == 'user':
116 if member_type == 'user':
117 self.revoke_user_permission(user_group=user_group, user=member_id)
117 self.revoke_user_permission(user_group=user_group, user=member_id)
118 else:
118 else:
119 # check if we have permissions to alter this usergroup
119 # check if we have permissions to alter this usergroup
120 member_name = UserGroup.get(member_id).users_group_name
120 member_name = UserGroup.get(member_id).users_group_name
121 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
121 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
122 self.revoke_user_group_permission(
122 self.revoke_user_group_permission(
123 target_user_group=user_group, user_group=member_id
123 target_user_group=user_group, user_group=member_id
124 )
124 )
125
125
126 def get(self, user_group_id, cache=False):
126 def get(self, user_group_id, cache=False):
127 return UserGroup.get(user_group_id)
127 return UserGroup.get(user_group_id)
128
128
129 def get_group(self, user_group):
129 def get_group(self, user_group):
130 return self._get_user_group(user_group)
130 return self._get_user_group(user_group)
131
131
132 def get_by_name(self, name, cache=False, case_insensitive=False):
132 def get_by_name(self, name, cache=False, case_insensitive=False):
133 return UserGroup.get_by_group_name(name, cache, case_insensitive)
133 return UserGroup.get_by_group_name(name, cache, case_insensitive)
134
134
135 def create(self, name, description, owner, active=True, group_data=None):
135 def create(self, name, description, owner, active=True, group_data=None):
136 try:
136 try:
137 new_user_group = UserGroup()
137 new_user_group = UserGroup()
138 new_user_group.user = self._get_user(owner)
138 new_user_group.user = self._get_user(owner)
139 new_user_group.users_group_name = name
139 new_user_group.users_group_name = name
140 new_user_group.user_group_description = description
140 new_user_group.user_group_description = description
141 new_user_group.users_group_active = active
141 new_user_group.users_group_active = active
142 if group_data:
142 if group_data:
143 new_user_group.group_data = group_data
143 new_user_group.group_data = group_data
144 self.sa.add(new_user_group)
144 self.sa.add(new_user_group)
145 perm_obj = self._create_default_perms(new_user_group)
145 perm_obj = self._create_default_perms(new_user_group)
146 self.sa.add(perm_obj)
146 self.sa.add(perm_obj)
147
147
148 self.grant_user_permission(user_group=new_user_group,
148 self.grant_user_permission(user_group=new_user_group,
149 user=owner, perm='usergroup.admin')
149 user=owner, perm='usergroup.admin')
150
150
151 return new_user_group
151 return new_user_group
152 except Exception:
152 except Exception:
153 log.error(traceback.format_exc())
153 log.error(traceback.format_exc())
154 raise
154 raise
155
155
156 def _get_memberships_for_user_ids(self, user_group, user_id_list):
156 def _get_memberships_for_user_ids(self, user_group, user_id_list):
157 members = []
157 members = []
158 for user_id in user_id_list:
158 for user_id in user_id_list:
159 member = self._get_membership(user_group.users_group_id, user_id)
159 member = self._get_membership(user_group.users_group_id, user_id)
160 members.append(member)
160 members.append(member)
161 return members
161 return members
162
162
163 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
163 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
164 current_members = user_group.members or []
164 current_members = user_group.members or []
165 current_members_ids = [m.user.user_id for m in current_members]
165 current_members_ids = [m.user.user_id for m in current_members]
166
166
167 added_members = [
167 added_members = [
168 user_id for user_id in user_id_list
168 user_id for user_id in user_id_list
169 if user_id not in current_members_ids]
169 if user_id not in current_members_ids]
170 if user_id_list == []:
170 if user_id_list == []:
171 # all members were deleted
171 # all members were deleted
172 deleted_members = current_members_ids
172 deleted_members = current_members_ids
173 else:
173 else:
174 deleted_members = [
174 deleted_members = [
175 user_id for user_id in current_members_ids
175 user_id for user_id in current_members_ids
176 if user_id not in user_id_list]
176 if user_id not in user_id_list]
177
177
178 return (added_members, deleted_members)
178 return added_members, deleted_members
179
179
180 def _set_users_as_members(self, user_group, user_ids):
180 def _set_users_as_members(self, user_group, user_ids):
181 user_group.members = []
181 user_group.members = []
182 self.sa.flush()
182 self.sa.flush()
183 members = self._get_memberships_for_user_ids(
183 members = self._get_memberships_for_user_ids(
184 user_group, user_ids)
184 user_group, user_ids)
185 user_group.members = members
185 user_group.members = members
186 self.sa.add(user_group)
186 self.sa.add(user_group)
187
187
188 def _update_members_from_user_ids(self, user_group, user_ids):
188 def _update_members_from_user_ids(self, user_group, user_ids):
189 added, removed = self._get_added_and_removed_user_ids(
189 added, removed = self._get_added_and_removed_user_ids(
190 user_group, user_ids)
190 user_group, user_ids)
191 self._set_users_as_members(user_group, user_ids)
191 self._set_users_as_members(user_group, user_ids)
192 self._log_user_changes('added to', user_group, added)
192 self._log_user_changes('added to', user_group, added)
193 self._log_user_changes('removed from', user_group, removed)
193 self._log_user_changes('removed from', user_group, removed)
194 return added, removed
194
195
195 def _clean_members_data(self, members_data):
196 def _clean_members_data(self, members_data):
196 if not members_data:
197 if not members_data:
197 members_data = []
198 members_data = []
198
199
199 members = []
200 members = []
200 for user in members_data:
201 for user in members_data:
201 uid = int(user['member_user_id'])
202 uid = int(user['member_user_id'])
202 if uid not in members and user['type'] in ['new', 'existing']:
203 if uid not in members and user['type'] in ['new', 'existing']:
203 members.append(uid)
204 members.append(uid)
204 return members
205 return members
205
206
206 def update(self, user_group, form_data):
207 def update(self, user_group, form_data):
207 user_group = self._get_user_group(user_group)
208 user_group = self._get_user_group(user_group)
208 if 'users_group_name' in form_data:
209 if 'users_group_name' in form_data:
209 user_group.users_group_name = form_data['users_group_name']
210 user_group.users_group_name = form_data['users_group_name']
210 if 'users_group_active' in form_data:
211 if 'users_group_active' in form_data:
211 user_group.users_group_active = form_data['users_group_active']
212 user_group.users_group_active = form_data['users_group_active']
212 if 'user_group_description' in form_data:
213 if 'user_group_description' in form_data:
213 user_group.user_group_description = form_data[
214 user_group.user_group_description = form_data[
214 'user_group_description']
215 'user_group_description']
215
216
216 # handle owner change
217 # handle owner change
217 if 'user' in form_data:
218 if 'user' in form_data:
218 owner = form_data['user']
219 owner = form_data['user']
219 if isinstance(owner, basestring):
220 if isinstance(owner, basestring):
220 owner = User.get_by_username(form_data['user'])
221 owner = User.get_by_username(form_data['user'])
221
222
222 if not isinstance(owner, User):
223 if not isinstance(owner, User):
223 raise ValueError(
224 raise ValueError(
224 'invalid owner for user group: %s' % form_data['user'])
225 'invalid owner for user group: %s' % form_data['user'])
225
226
226 user_group.user = owner
227 user_group.user = owner
227
228
229 added_user_ids = []
230 removed_user_ids = []
228 if 'users_group_members' in form_data:
231 if 'users_group_members' in form_data:
229 members_id_list = self._clean_members_data(
232 members_id_list = self._clean_members_data(
230 form_data['users_group_members'])
233 form_data['users_group_members'])
231 self._update_members_from_user_ids(user_group, members_id_list)
234 added_user_ids, removed_user_ids = \
235 self._update_members_from_user_ids(user_group, members_id_list)
232
236
233 self.sa.add(user_group)
237 self.sa.add(user_group)
238 return user_group, added_user_ids, removed_user_ids
234
239
235 def delete(self, user_group, force=False):
240 def delete(self, user_group, force=False):
236 """
241 """
237 Deletes repository group, unless force flag is used
242 Deletes repository group, unless force flag is used
238 raises exception if there are members in that group, else deletes
243 raises exception if there are members in that group, else deletes
239 group and users
244 group and users
240
245
241 :param user_group:
246 :param user_group:
242 :param force:
247 :param force:
243 """
248 """
244 user_group = self._get_user_group(user_group)
249 user_group = self._get_user_group(user_group)
245 try:
250 try:
246 # check if this group is not assigned to repo
251 # check if this group is not assigned to repo
247 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
252 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
248 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
253 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
249 # check if this group is not assigned to repo
254 # check if this group is not assigned to repo
250 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
255 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
251 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
256 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
252
257
253 if (assigned_to_repo or assigned_to_repo_group) and not force:
258 if (assigned_to_repo or assigned_to_repo_group) and not force:
254 assigned = ','.join(map(safe_str,
259 assigned = ','.join(map(safe_str,
255 assigned_to_repo+assigned_to_repo_group))
260 assigned_to_repo+assigned_to_repo_group))
256
261
257 raise UserGroupAssignedException(
262 raise UserGroupAssignedException(
258 'UserGroup assigned to %s' % (assigned,))
263 'UserGroup assigned to %s' % (assigned,))
259 self.sa.delete(user_group)
264 self.sa.delete(user_group)
260 except Exception:
265 except Exception:
261 log.error(traceback.format_exc())
266 log.error(traceback.format_exc())
262 raise
267 raise
263
268
264 def _log_user_changes(self, action, user_group, user_or_users):
269 def _log_user_changes(self, action, user_group, user_or_users):
265 users = user_or_users
270 users = user_or_users
266 if not isinstance(users, (list, tuple)):
271 if not isinstance(users, (list, tuple)):
267 users = [users]
272 users = [users]
268 rhodecode_user = get_current_rhodecode_user()
273 rhodecode_user = get_current_rhodecode_user()
269 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
274 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
270 group_name = user_group.users_group_name
275 group_name = user_group.users_group_name
271
276
272 for user_or_user_id in users:
277 for user_or_user_id in users:
273 user = self._get_user(user_or_user_id)
278 user = self._get_user(user_or_user_id)
274 log_text = 'User {user} {action} {group}'.format(
279 log_text = 'User {user} {action} {group}'.format(
275 action=action, user=user.username, group=group_name)
280 action=action, user=user.username, group=group_name)
276 log.info('Logging action: {0} by {1} ip:{2}'.format(
281 log.info('Logging action: {0} by {1} ip:{2}'.format(
277 log_text, rhodecode_user, ipaddr))
282 log_text, rhodecode_user, ipaddr))
278
283
279 def _find_user_in_group(self, user, user_group):
284 def _find_user_in_group(self, user, user_group):
280 user_group_member = None
285 user_group_member = None
281 for m in user_group.members:
286 for m in user_group.members:
282 if m.user_id == user.user_id:
287 if m.user_id == user.user_id:
283 # Found this user's membership row
288 # Found this user's membership row
284 user_group_member = m
289 user_group_member = m
285 break
290 break
286
291
287 return user_group_member
292 return user_group_member
288
293
289 def _get_membership(self, user_group_id, user_id):
294 def _get_membership(self, user_group_id, user_id):
290 user_group_member = UserGroupMember(user_group_id, user_id)
295 user_group_member = UserGroupMember(user_group_id, user_id)
291 return user_group_member
296 return user_group_member
292
297
293 def add_user_to_group(self, user_group, user):
298 def add_user_to_group(self, user_group, user):
294 user_group = self._get_user_group(user_group)
299 user_group = self._get_user_group(user_group)
295 user = self._get_user(user)
300 user = self._get_user(user)
296 user_member = self._find_user_in_group(user, user_group)
301 user_member = self._find_user_in_group(user, user_group)
297 if user_member:
302 if user_member:
298 # user already in the group, skip
303 # user already in the group, skip
299 return True
304 return True
300
305
301 member = self._get_membership(
306 member = self._get_membership(
302 user_group.users_group_id, user.user_id)
307 user_group.users_group_id, user.user_id)
303 user_group.members.append(member)
308 user_group.members.append(member)
304
309
305 try:
310 try:
306 self.sa.add(member)
311 self.sa.add(member)
307 except Exception:
312 except Exception:
308 # what could go wrong here?
313 # what could go wrong here?
309 log.error(traceback.format_exc())
314 log.error(traceback.format_exc())
310 raise
315 raise
311
316
312 self._log_user_changes('added to', user_group, user)
317 self._log_user_changes('added to', user_group, user)
313 return member
318 return member
314
319
315 def remove_user_from_group(self, user_group, user):
320 def remove_user_from_group(self, user_group, user):
316 user_group = self._get_user_group(user_group)
321 user_group = self._get_user_group(user_group)
317 user = self._get_user(user)
322 user = self._get_user(user)
318 user_group_member = self._find_user_in_group(user, user_group)
323 user_group_member = self._find_user_in_group(user, user_group)
319
324
320 if not user_group_member:
325 if not user_group_member:
321 # User isn't in that group
326 # User isn't in that group
322 return False
327 return False
323
328
324 try:
329 try:
325 self.sa.delete(user_group_member)
330 self.sa.delete(user_group_member)
326 except Exception:
331 except Exception:
327 log.error(traceback.format_exc())
332 log.error(traceback.format_exc())
328 raise
333 raise
329
334
330 self._log_user_changes('removed from', user_group, user)
335 self._log_user_changes('removed from', user_group, user)
331 return True
336 return True
332
337
333 def has_perm(self, user_group, perm):
338 def has_perm(self, user_group, perm):
334 user_group = self._get_user_group(user_group)
339 user_group = self._get_user_group(user_group)
335 perm = self._get_perm(perm)
340 perm = self._get_perm(perm)
336
341
337 return UserGroupToPerm.query()\
342 return UserGroupToPerm.query()\
338 .filter(UserGroupToPerm.users_group == user_group)\
343 .filter(UserGroupToPerm.users_group == user_group)\
339 .filter(UserGroupToPerm.permission == perm).scalar() is not None
344 .filter(UserGroupToPerm.permission == perm).scalar() is not None
340
345
341 def grant_perm(self, user_group, perm):
346 def grant_perm(self, user_group, perm):
342 user_group = self._get_user_group(user_group)
347 user_group = self._get_user_group(user_group)
343 perm = self._get_perm(perm)
348 perm = self._get_perm(perm)
344
349
345 # if this permission is already granted skip it
350 # if this permission is already granted skip it
346 _perm = UserGroupToPerm.query()\
351 _perm = UserGroupToPerm.query()\
347 .filter(UserGroupToPerm.users_group == user_group)\
352 .filter(UserGroupToPerm.users_group == user_group)\
348 .filter(UserGroupToPerm.permission == perm)\
353 .filter(UserGroupToPerm.permission == perm)\
349 .scalar()
354 .scalar()
350 if _perm:
355 if _perm:
351 return
356 return
352
357
353 new = UserGroupToPerm()
358 new = UserGroupToPerm()
354 new.users_group = user_group
359 new.users_group = user_group
355 new.permission = perm
360 new.permission = perm
356 self.sa.add(new)
361 self.sa.add(new)
357 return new
362 return new
358
363
359 def revoke_perm(self, user_group, perm):
364 def revoke_perm(self, user_group, perm):
360 user_group = self._get_user_group(user_group)
365 user_group = self._get_user_group(user_group)
361 perm = self._get_perm(perm)
366 perm = self._get_perm(perm)
362
367
363 obj = UserGroupToPerm.query()\
368 obj = UserGroupToPerm.query()\
364 .filter(UserGroupToPerm.users_group == user_group)\
369 .filter(UserGroupToPerm.users_group == user_group)\
365 .filter(UserGroupToPerm.permission == perm).scalar()
370 .filter(UserGroupToPerm.permission == perm).scalar()
366 if obj:
371 if obj:
367 self.sa.delete(obj)
372 self.sa.delete(obj)
368
373
369 def grant_user_permission(self, user_group, user, perm):
374 def grant_user_permission(self, user_group, user, perm):
370 """
375 """
371 Grant permission for user on given user group, or update
376 Grant permission for user on given user group, or update
372 existing one if found
377 existing one if found
373
378
374 :param user_group: Instance of UserGroup, users_group_id,
379 :param user_group: Instance of UserGroup, users_group_id,
375 or users_group_name
380 or users_group_name
376 :param user: Instance of User, user_id or username
381 :param user: Instance of User, user_id or username
377 :param perm: Instance of Permission, or permission_name
382 :param perm: Instance of Permission, or permission_name
378 """
383 """
379
384
380 user_group = self._get_user_group(user_group)
385 user_group = self._get_user_group(user_group)
381 user = self._get_user(user)
386 user = self._get_user(user)
382 permission = self._get_perm(perm)
387 permission = self._get_perm(perm)
383
388
384 # check if we have that permission already
389 # check if we have that permission already
385 obj = self.sa.query(UserUserGroupToPerm)\
390 obj = self.sa.query(UserUserGroupToPerm)\
386 .filter(UserUserGroupToPerm.user == user)\
391 .filter(UserUserGroupToPerm.user == user)\
387 .filter(UserUserGroupToPerm.user_group == user_group)\
392 .filter(UserUserGroupToPerm.user_group == user_group)\
388 .scalar()
393 .scalar()
389 if obj is None:
394 if obj is None:
390 # create new !
395 # create new !
391 obj = UserUserGroupToPerm()
396 obj = UserUserGroupToPerm()
392 obj.user_group = user_group
397 obj.user_group = user_group
393 obj.user = user
398 obj.user = user
394 obj.permission = permission
399 obj.permission = permission
395 self.sa.add(obj)
400 self.sa.add(obj)
396 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
401 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
397 action_logger_generic(
402 action_logger_generic(
398 'granted permission: {} to user: {} on usergroup: {}'.format(
403 'granted permission: {} to user: {} on usergroup: {}'.format(
399 perm, user, user_group), namespace='security.usergroup')
404 perm, user, user_group), namespace='security.usergroup')
400
405
401 return obj
406 return obj
402
407
403 def revoke_user_permission(self, user_group, user):
408 def revoke_user_permission(self, user_group, user):
404 """
409 """
405 Revoke permission for user on given user group
410 Revoke permission for user on given user group
406
411
407 :param user_group: Instance of UserGroup, users_group_id,
412 :param user_group: Instance of UserGroup, users_group_id,
408 or users_group name
413 or users_group name
409 :param user: Instance of User, user_id or username
414 :param user: Instance of User, user_id or username
410 """
415 """
411
416
412 user_group = self._get_user_group(user_group)
417 user_group = self._get_user_group(user_group)
413 user = self._get_user(user)
418 user = self._get_user(user)
414
419
415 obj = self.sa.query(UserUserGroupToPerm)\
420 obj = self.sa.query(UserUserGroupToPerm)\
416 .filter(UserUserGroupToPerm.user == user)\
421 .filter(UserUserGroupToPerm.user == user)\
417 .filter(UserUserGroupToPerm.user_group == user_group)\
422 .filter(UserUserGroupToPerm.user_group == user_group)\
418 .scalar()
423 .scalar()
419 if obj:
424 if obj:
420 self.sa.delete(obj)
425 self.sa.delete(obj)
421 log.debug('Revoked perm on %s on %s', user_group, user)
426 log.debug('Revoked perm on %s on %s', user_group, user)
422 action_logger_generic(
427 action_logger_generic(
423 'revoked permission from user: {} on usergroup: {}'.format(
428 'revoked permission from user: {} on usergroup: {}'.format(
424 user, user_group), namespace='security.usergroup')
429 user, user_group), namespace='security.usergroup')
425
430
426 def grant_user_group_permission(self, target_user_group, user_group, perm):
431 def grant_user_group_permission(self, target_user_group, user_group, perm):
427 """
432 """
428 Grant user group permission for given target_user_group
433 Grant user group permission for given target_user_group
429
434
430 :param target_user_group:
435 :param target_user_group:
431 :param user_group:
436 :param user_group:
432 :param perm:
437 :param perm:
433 """
438 """
434 target_user_group = self._get_user_group(target_user_group)
439 target_user_group = self._get_user_group(target_user_group)
435 user_group = self._get_user_group(user_group)
440 user_group = self._get_user_group(user_group)
436 permission = self._get_perm(perm)
441 permission = self._get_perm(perm)
437 # forbid assigning same user group to itself
442 # forbid assigning same user group to itself
438 if target_user_group == user_group:
443 if target_user_group == user_group:
439 raise RepoGroupAssignmentError('target repo:%s cannot be '
444 raise RepoGroupAssignmentError('target repo:%s cannot be '
440 'assigned to itself' % target_user_group)
445 'assigned to itself' % target_user_group)
441
446
442 # check if we have that permission already
447 # check if we have that permission already
443 obj = self.sa.query(UserGroupUserGroupToPerm)\
448 obj = self.sa.query(UserGroupUserGroupToPerm)\
444 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
449 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
445 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
450 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
446 .scalar()
451 .scalar()
447 if obj is None:
452 if obj is None:
448 # create new !
453 # create new !
449 obj = UserGroupUserGroupToPerm()
454 obj = UserGroupUserGroupToPerm()
450 obj.user_group = user_group
455 obj.user_group = user_group
451 obj.target_user_group = target_user_group
456 obj.target_user_group = target_user_group
452 obj.permission = permission
457 obj.permission = permission
453 self.sa.add(obj)
458 self.sa.add(obj)
454 log.debug(
459 log.debug(
455 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
460 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
456 action_logger_generic(
461 action_logger_generic(
457 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
462 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
458 perm, user_group, target_user_group),
463 perm, user_group, target_user_group),
459 namespace='security.usergroup')
464 namespace='security.usergroup')
460
465
461 return obj
466 return obj
462
467
463 def revoke_user_group_permission(self, target_user_group, user_group):
468 def revoke_user_group_permission(self, target_user_group, user_group):
464 """
469 """
465 Revoke user group permission for given target_user_group
470 Revoke user group permission for given target_user_group
466
471
467 :param target_user_group:
472 :param target_user_group:
468 :param user_group:
473 :param user_group:
469 """
474 """
470 target_user_group = self._get_user_group(target_user_group)
475 target_user_group = self._get_user_group(target_user_group)
471 user_group = self._get_user_group(user_group)
476 user_group = self._get_user_group(user_group)
472
477
473 obj = self.sa.query(UserGroupUserGroupToPerm)\
478 obj = self.sa.query(UserGroupUserGroupToPerm)\
474 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
479 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
475 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
480 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
476 .scalar()
481 .scalar()
477 if obj:
482 if obj:
478 self.sa.delete(obj)
483 self.sa.delete(obj)
479 log.debug(
484 log.debug(
480 'Revoked perm on %s on %s', target_user_group, user_group)
485 'Revoked perm on %s on %s', target_user_group, user_group)
481 action_logger_generic(
486 action_logger_generic(
482 'revoked permission from usergroup: {} on usergroup: {}'.format(
487 'revoked permission from usergroup: {} on usergroup: {}'.format(
483 user_group, target_user_group),
488 user_group, target_user_group),
484 namespace='security.repogroup')
489 namespace='security.repogroup')
485
490
486 def enforce_groups(self, user, groups, extern_type=None):
491 def enforce_groups(self, user, groups, extern_type=None):
487 user = self._get_user(user)
492 user = self._get_user(user)
488 log.debug('Enforcing groups %s on user %s', groups, user)
493 log.debug('Enforcing groups %s on user %s', groups, user)
489 current_groups = user.group_member
494 current_groups = user.group_member
490 # find the external created groups
495 # find the external created groups
491 externals = [x.users_group for x in current_groups
496 externals = [x.users_group for x in current_groups
492 if 'extern_type' in x.users_group.group_data]
497 if 'extern_type' in x.users_group.group_data]
493
498
494 # calculate from what groups user should be removed
499 # calculate from what groups user should be removed
495 # externals that are not in groups
500 # externals that are not in groups
496 for gr in externals:
501 for gr in externals:
497 if gr.users_group_name not in groups:
502 if gr.users_group_name not in groups:
498 log.debug('Removing user %s from user group %s', user, gr)
503 log.debug('Removing user %s from user group %s', user, gr)
499 self.remove_user_from_group(gr, user)
504 self.remove_user_from_group(gr, user)
500
505
501 # now we calculate in which groups user should be == groups params
506 # now we calculate in which groups user should be == groups params
502 owner = User.get_first_super_admin().username
507 owner = User.get_first_super_admin().username
503 for gr in set(groups):
508 for gr in set(groups):
504 existing_group = UserGroup.get_by_group_name(gr)
509 existing_group = UserGroup.get_by_group_name(gr)
505 if not existing_group:
510 if not existing_group:
506 desc = 'Automatically created from plugin:%s' % extern_type
511 desc = 'Automatically created from plugin:%s' % extern_type
507 # we use first admin account to set the owner of the group
512 # we use first admin account to set the owner of the group
508 existing_group = UserGroupModel().create(
513 existing_group = UserGroupModel().create(
509 gr, desc, owner, group_data={'extern_type': extern_type})
514 gr, desc, owner, group_data={'extern_type': extern_type})
510
515
511 # we can only add users to special groups created via plugins
516 # we can only add users to special groups created via plugins
512 managed = 'extern_type' in existing_group.group_data
517 managed = 'extern_type' in existing_group.group_data
513 if managed:
518 if managed:
514 log.debug('Adding user %s to user group %s', user, gr)
519 log.debug('Adding user %s to user group %s', user, gr)
515 UserGroupModel().add_user_to_group(existing_group, user)
520 UserGroupModel().add_user_to_group(existing_group, user)
516 else:
521 else:
517 log.debug('Skipping addition to group %s since it is '
522 log.debug('Skipping addition to group %s since it is '
518 'not set to be automatically synchronized' % gr)
523 'not set to be automatically synchronized' % gr)
519
524
520 def change_groups(self, user, groups):
525 def change_groups(self, user, groups):
521 """
526 """
522 This method changes user group assignment
527 This method changes user group assignment
523 :param user: User
528 :param user: User
524 :param groups: array of UserGroupModel
529 :param groups: array of UserGroupModel
525 :return:
530 :return:
526 """
531 """
527 user = self._get_user(user)
532 user = self._get_user(user)
528 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
533 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
529 current_groups = user.group_member
534 current_groups = user.group_member
530 current_groups = [x.users_group for x in current_groups]
535 current_groups = [x.users_group for x in current_groups]
531
536
532 # calculate from what groups user should be removed/add
537 # calculate from what groups user should be removed/add
533 groups = set(groups)
538 groups = set(groups)
534 current_groups = set(current_groups)
539 current_groups = set(current_groups)
535
540
536 groups_to_remove = current_groups - groups
541 groups_to_remove = current_groups - groups
537 groups_to_add = groups - current_groups
542 groups_to_add = groups - current_groups
538
543
539 for gr in groups_to_remove:
544 for gr in groups_to_remove:
540 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
545 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
541 self.remove_user_from_group(gr.users_group_name, user.username)
546 self.remove_user_from_group(gr.users_group_name, user.username)
542 for gr in groups_to_add:
547 for gr in groups_to_add:
543 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
548 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
544 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
549 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
545
550
546 def _serialize_user_group(self, user_group):
551 def _serialize_user_group(self, user_group):
547 import rhodecode.lib.helpers as h
552 import rhodecode.lib.helpers as h
548 return {
553 return {
549 'id': user_group.users_group_id,
554 'id': user_group.users_group_id,
550 # TODO: marcink figure out a way to generate the url for the
555 # TODO: marcink figure out a way to generate the url for the
551 # icon
556 # icon
552 'icon_link': '',
557 'icon_link': '',
553 'value_display': 'Group: %s (%d members)' % (
558 'value_display': 'Group: %s (%d members)' % (
554 user_group.users_group_name, len(user_group.members),),
559 user_group.users_group_name, len(user_group.members),),
555 'value': user_group.users_group_name,
560 'value': user_group.users_group_name,
556 'description': user_group.user_group_description,
561 'description': user_group.user_group_description,
557 'owner': user_group.user.username,
562 'owner': user_group.user.username,
558
563
559 'owner_icon': h.gravatar_url(user_group.user.email, 30),
564 'owner_icon': h.gravatar_url(user_group.user.email, 30),
560 'value_display_owner': h.person(user_group.user.email),
565 'value_display_owner': h.person(user_group.user.email),
561
566
562 'value_type': 'user_group',
567 'value_type': 'user_group',
563 'active': user_group.users_group_active,
568 'active': user_group.users_group_active,
564 }
569 }
565
570
566 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
571 def get_user_groups(self, name_contains=None, limit=20, only_active=True,
567 expand_groups=False):
572 expand_groups=False):
568 query = self.sa.query(UserGroup)
573 query = self.sa.query(UserGroup)
569 if only_active:
574 if only_active:
570 query = query.filter(UserGroup.users_group_active == true())
575 query = query.filter(UserGroup.users_group_active == true())
571
576
572 if name_contains:
577 if name_contains:
573 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
578 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
574 query = query.filter(
579 query = query.filter(
575 UserGroup.users_group_name.ilike(ilike_expression))\
580 UserGroup.users_group_name.ilike(ilike_expression))\
576 .order_by(func.length(UserGroup.users_group_name))\
581 .order_by(func.length(UserGroup.users_group_name))\
577 .order_by(UserGroup.users_group_name)
582 .order_by(UserGroup.users_group_name)
578
583
579 query = query.limit(limit)
584 query = query.limit(limit)
580 user_groups = query.all()
585 user_groups = query.all()
581 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
586 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
582 user_groups = UserGroupList(user_groups, perm_set=perm_set)
587 user_groups = UserGroupList(user_groups, perm_set=perm_set)
583
588
584 # store same serialize method to extract data from User
589 # store same serialize method to extract data from User
585 from rhodecode.model.user import UserModel
590 from rhodecode.model.user import UserModel
586 serialize_user = UserModel()._serialize_user
591 serialize_user = UserModel()._serialize_user
587
592
588 _groups = []
593 _groups = []
589 for group in user_groups:
594 for group in user_groups:
590 entry = self._serialize_user_group(group)
595 entry = self._serialize_user_group(group)
591 if expand_groups:
596 if expand_groups:
592 expanded_members = []
597 expanded_members = []
593 for member in group.members:
598 for member in group.members:
594 expanded_members.append(serialize_user(member.user))
599 expanded_members.append(serialize_user(member.user))
595 entry['members'] = expanded_members
600 entry['members'] = expanded_members
596 _groups.append(entry)
601 _groups.append(entry)
597 return _groups
602 return _groups
598
603
599 @staticmethod
604 @staticmethod
600 def get_user_groups_as_dict(user_group):
605 def get_user_groups_as_dict(user_group):
601 import rhodecode.lib.helpers as h
606 import rhodecode.lib.helpers as h
602
607
603 data = {
608 data = {
604 'users_group_id': user_group.users_group_id,
609 'users_group_id': user_group.users_group_id,
605 'group_name': user_group.users_group_name,
610 'group_name': user_group.users_group_name,
606 'group_description': user_group.user_group_description,
611 'group_description': user_group.user_group_description,
607 'active': user_group.users_group_active,
612 'active': user_group.users_group_active,
608 "owner": user_group.user.username,
613 "owner": user_group.user.username,
609 'owner_icon': h.gravatar_url(user_group.user.email, 30),
614 'owner_icon': h.gravatar_url(user_group.user.email, 30),
610 "owner_data": {
615 "owner_data": {
611 'owner': user_group.user.username,
616 'owner': user_group.user.username,
612 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
617 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
613 }
618 }
614 return data
619 return data
615
620
616
621
617
622
618
623
General Comments 0
You need to be logged in to leave comments. Login now