##// END OF EJS Templates
api: added proper full permission flush on API calls when creating repos and repo groups....
super-admin -
r4697:63f392fc stable
parent child Browse files
Show More
@@ -1,759 +1,762 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import JSONRPCValidationError
24 from rhodecode.api import JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
33 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
34 from rhodecode.model.permission import PermissionModel
34 from rhodecode.model.permission import PermissionModel
35 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model.scm import RepoGroupList
37 from rhodecode.model import validation_schema
37 from rhodecode.model import validation_schema
38 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 from rhodecode.model.validation_schema.schemas import repo_group_schema
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 @jsonrpc_method()
44 @jsonrpc_method()
45 def get_repo_group(request, apiuser, repogroupid):
45 def get_repo_group(request, apiuser, repogroupid):
46 """
46 """
47 Return the specified |repo| group, along with permissions,
47 Return the specified |repo| group, along with permissions,
48 and repositories inside the group
48 and repositories inside the group
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repogroupid: Specify the name of ID of the repository group.
52 :param repogroupid: Specify the name of ID of the repository group.
53 :type repogroupid: str or int
53 :type repogroupid: str or int
54
54
55
55
56 Example output:
56 Example output:
57
57
58 .. code-block:: bash
58 .. code-block:: bash
59
59
60 {
60 {
61 "error": null,
61 "error": null,
62 "id": repo-group-id,
62 "id": repo-group-id,
63 "result": {
63 "result": {
64 "group_description": "repo group description",
64 "group_description": "repo group description",
65 "group_id": 14,
65 "group_id": 14,
66 "group_name": "group name",
66 "group_name": "group name",
67 "permissions": [
67 "permissions": [
68 {
68 {
69 "name": "super-admin-username",
69 "name": "super-admin-username",
70 "origin": "super-admin",
70 "origin": "super-admin",
71 "permission": "group.admin",
71 "permission": "group.admin",
72 "type": "user"
72 "type": "user"
73 },
73 },
74 {
74 {
75 "name": "owner-name",
75 "name": "owner-name",
76 "origin": "owner",
76 "origin": "owner",
77 "permission": "group.admin",
77 "permission": "group.admin",
78 "type": "user"
78 "type": "user"
79 },
79 },
80 {
80 {
81 "name": "user-group-name",
81 "name": "user-group-name",
82 "origin": "permission",
82 "origin": "permission",
83 "permission": "group.write",
83 "permission": "group.write",
84 "type": "user_group"
84 "type": "user_group"
85 }
85 }
86 ],
86 ],
87 "owner": "owner-name",
87 "owner": "owner-name",
88 "parent_group": null,
88 "parent_group": null,
89 "repositories": [ repo-list ]
89 "repositories": [ repo-list ]
90 }
90 }
91 }
91 }
92 """
92 """
93
93
94 repo_group = get_repo_group_or_error(repogroupid)
94 repo_group = get_repo_group_or_error(repogroupid)
95 if not has_superadmin_permission(apiuser):
95 if not has_superadmin_permission(apiuser):
96 # check if we have at least read permission for this repo group !
96 # check if we have at least read permission for this repo group !
97 _perms = ('group.admin', 'group.write', 'group.read',)
97 _perms = ('group.admin', 'group.write', 'group.read',)
98 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 if not HasRepoGroupPermissionAnyApi(*_perms)(
99 user=apiuser, group_name=repo_group.group_name):
99 user=apiuser, group_name=repo_group.group_name):
100 raise JSONRPCError(
100 raise JSONRPCError(
101 'repository group `%s` does not exist' % (repogroupid,))
101 'repository group `%s` does not exist' % (repogroupid,))
102
102
103 permissions = []
103 permissions = []
104 for _user in repo_group.permissions():
104 for _user in repo_group.permissions():
105 user_data = {
105 user_data = {
106 'name': _user.username,
106 'name': _user.username,
107 'permission': _user.permission,
107 'permission': _user.permission,
108 'origin': get_origin(_user),
108 'origin': get_origin(_user),
109 'type': "user",
109 'type': "user",
110 }
110 }
111 permissions.append(user_data)
111 permissions.append(user_data)
112
112
113 for _user_group in repo_group.permission_user_groups():
113 for _user_group in repo_group.permission_user_groups():
114 user_group_data = {
114 user_group_data = {
115 'name': _user_group.users_group_name,
115 'name': _user_group.users_group_name,
116 'permission': _user_group.permission,
116 'permission': _user_group.permission,
117 'origin': get_origin(_user_group),
117 'origin': get_origin(_user_group),
118 'type': "user_group",
118 'type': "user_group",
119 }
119 }
120 permissions.append(user_group_data)
120 permissions.append(user_group_data)
121
121
122 data = repo_group.get_api_data()
122 data = repo_group.get_api_data()
123 data["permissions"] = permissions
123 data["permissions"] = permissions
124 return data
124 return data
125
125
126
126
127 @jsonrpc_method()
127 @jsonrpc_method()
128 def get_repo_groups(request, apiuser):
128 def get_repo_groups(request, apiuser):
129 """
129 """
130 Returns all repository groups.
130 Returns all repository groups.
131
131
132 :param apiuser: This is filled automatically from the |authtoken|.
132 :param apiuser: This is filled automatically from the |authtoken|.
133 :type apiuser: AuthUser
133 :type apiuser: AuthUser
134 """
134 """
135
135
136 result = []
136 result = []
137 _perms = ('group.read', 'group.write', 'group.admin',)
137 _perms = ('group.read', 'group.write', 'group.admin',)
138 extras = {'user': apiuser}
138 extras = {'user': apiuser}
139 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
140 perm_set=_perms, extra_kwargs=extras):
140 perm_set=_perms, extra_kwargs=extras):
141 result.append(repo_group.get_api_data())
141 result.append(repo_group.get_api_data())
142 return result
142 return result
143
143
144
144
145 @jsonrpc_method()
145 @jsonrpc_method()
146 def create_repo_group(
146 def create_repo_group(
147 request, apiuser, group_name,
147 request, apiuser, group_name,
148 owner=Optional(OAttr('apiuser')),
148 owner=Optional(OAttr('apiuser')),
149 description=Optional(''),
149 description=Optional(''),
150 copy_permissions=Optional(False)):
150 copy_permissions=Optional(False)):
151 """
151 """
152 Creates a repository group.
152 Creates a repository group.
153
153
154 * If the repository group name contains "/", repository group will be
154 * If the repository group name contains "/", repository group will be
155 created inside a repository group or nested repository groups
155 created inside a repository group or nested repository groups
156
156
157 For example "foo/bar/group1" will create repository group called "group1"
157 For example "foo/bar/group1" will create repository group called "group1"
158 inside group "foo/bar". You have to have permissions to access and
158 inside group "foo/bar". You have to have permissions to access and
159 write to the last repository group ("bar" in this example)
159 write to the last repository group ("bar" in this example)
160
160
161 This command can only be run using an |authtoken| with at least
161 This command can only be run using an |authtoken| with at least
162 permissions to create repository groups, or admin permissions to
162 permissions to create repository groups, or admin permissions to
163 parent repository groups.
163 parent repository groups.
164
164
165 :param apiuser: This is filled automatically from the |authtoken|.
165 :param apiuser: This is filled automatically from the |authtoken|.
166 :type apiuser: AuthUser
166 :type apiuser: AuthUser
167 :param group_name: Set the repository group name.
167 :param group_name: Set the repository group name.
168 :type group_name: str
168 :type group_name: str
169 :param description: Set the |repo| group description.
169 :param description: Set the |repo| group description.
170 :type description: str
170 :type description: str
171 :param owner: Set the |repo| group owner.
171 :param owner: Set the |repo| group owner.
172 :type owner: str
172 :type owner: str
173 :param copy_permissions:
173 :param copy_permissions:
174 :type copy_permissions:
174 :type copy_permissions:
175
175
176 Example output:
176 Example output:
177
177
178 .. code-block:: bash
178 .. code-block:: bash
179
179
180 id : <id_given_in_input>
180 id : <id_given_in_input>
181 result : {
181 result : {
182 "msg": "Created new repo group `<repo_group_name>`"
182 "msg": "Created new repo group `<repo_group_name>`"
183 "repo_group": <repogroup_object>
183 "repo_group": <repogroup_object>
184 }
184 }
185 error : null
185 error : null
186
186
187
187
188 Example error output:
188 Example error output:
189
189
190 .. code-block:: bash
190 .. code-block:: bash
191
191
192 id : <id_given_in_input>
192 id : <id_given_in_input>
193 result : null
193 result : null
194 error : {
194 error : {
195 failed to create repo group `<repogroupid>`
195 failed to create repo group `<repogroupid>`
196 }
196 }
197
197
198 """
198 """
199
199
200 owner = validate_set_owner_permissions(apiuser, owner)
200 owner = validate_set_owner_permissions(apiuser, owner)
201
201
202 description = Optional.extract(description)
202 description = Optional.extract(description)
203 copy_permissions = Optional.extract(copy_permissions)
203 copy_permissions = Optional.extract(copy_permissions)
204
204
205 schema = repo_group_schema.RepoGroupSchema().bind(
205 schema = repo_group_schema.RepoGroupSchema().bind(
206 # user caller
206 # user caller
207 user=apiuser)
207 user=apiuser)
208
208
209 try:
209 try:
210 schema_data = schema.deserialize(dict(
210 schema_data = schema.deserialize(dict(
211 repo_group_name=group_name,
211 repo_group_name=group_name,
212 repo_group_owner=owner.username,
212 repo_group_owner=owner.username,
213 repo_group_description=description,
213 repo_group_description=description,
214 repo_group_copy_permissions=copy_permissions,
214 repo_group_copy_permissions=copy_permissions,
215 ))
215 ))
216 except validation_schema.Invalid as err:
216 except validation_schema.Invalid as err:
217 raise JSONRPCValidationError(colander_exc=err)
217 raise JSONRPCValidationError(colander_exc=err)
218
218
219 validated_group_name = schema_data['repo_group_name']
219 validated_group_name = schema_data['repo_group_name']
220
220
221 try:
221 try:
222 repo_group = RepoGroupModel().create(
222 repo_group = RepoGroupModel().create(
223 owner=owner,
223 owner=owner,
224 group_name=validated_group_name,
224 group_name=validated_group_name,
225 group_description=schema_data['repo_group_description'],
225 group_description=schema_data['repo_group_description'],
226 copy_permissions=schema_data['repo_group_copy_permissions'])
226 copy_permissions=schema_data['repo_group_copy_permissions'])
227 Session().flush()
227 Session().flush()
228
228
229 repo_group_data = repo_group.get_api_data()
229 repo_group_data = repo_group.get_api_data()
230 audit_logger.store_api(
230 audit_logger.store_api(
231 'repo_group.create', action_data={'data': repo_group_data},
231 'repo_group.create', action_data={'data': repo_group_data},
232 user=apiuser)
232 user=apiuser)
233
233
234 Session().commit()
234 Session().commit()
235
236 PermissionModel().trigger_permission_flush()
237
235 return {
238 return {
236 'msg': 'Created new repo group `%s`' % validated_group_name,
239 'msg': 'Created new repo group `%s`' % validated_group_name,
237 'repo_group': repo_group.get_api_data()
240 'repo_group': repo_group.get_api_data()
238 }
241 }
239 except Exception:
242 except Exception:
240 log.exception("Exception occurred while trying create repo group")
243 log.exception("Exception occurred while trying create repo group")
241 raise JSONRPCError(
244 raise JSONRPCError(
242 'failed to create repo group `%s`' % (validated_group_name,))
245 'failed to create repo group `%s`' % (validated_group_name,))
243
246
244
247
245 @jsonrpc_method()
248 @jsonrpc_method()
246 def update_repo_group(
249 def update_repo_group(
247 request, apiuser, repogroupid, group_name=Optional(''),
250 request, apiuser, repogroupid, group_name=Optional(''),
248 description=Optional(''), owner=Optional(OAttr('apiuser')),
251 description=Optional(''), owner=Optional(OAttr('apiuser')),
249 enable_locking=Optional(False)):
252 enable_locking=Optional(False)):
250 """
253 """
251 Updates repository group with the details given.
254 Updates repository group with the details given.
252
255
253 This command can only be run using an |authtoken| with admin
256 This command can only be run using an |authtoken| with admin
254 permissions.
257 permissions.
255
258
256 * If the group_name name contains "/", repository group will be updated
259 * If the group_name name contains "/", repository group will be updated
257 accordingly with a repository group or nested repository groups
260 accordingly with a repository group or nested repository groups
258
261
259 For example repogroupid=group-test group_name="foo/bar/group-test"
262 For example repogroupid=group-test group_name="foo/bar/group-test"
260 will update repository group called "group-test" and place it
263 will update repository group called "group-test" and place it
261 inside group "foo/bar".
264 inside group "foo/bar".
262 You have to have permissions to access and write to the last repository
265 You have to have permissions to access and write to the last repository
263 group ("bar" in this example)
266 group ("bar" in this example)
264
267
265 :param apiuser: This is filled automatically from the |authtoken|.
268 :param apiuser: This is filled automatically from the |authtoken|.
266 :type apiuser: AuthUser
269 :type apiuser: AuthUser
267 :param repogroupid: Set the ID of repository group.
270 :param repogroupid: Set the ID of repository group.
268 :type repogroupid: str or int
271 :type repogroupid: str or int
269 :param group_name: Set the name of the |repo| group.
272 :param group_name: Set the name of the |repo| group.
270 :type group_name: str
273 :type group_name: str
271 :param description: Set a description for the group.
274 :param description: Set a description for the group.
272 :type description: str
275 :type description: str
273 :param owner: Set the |repo| group owner.
276 :param owner: Set the |repo| group owner.
274 :type owner: str
277 :type owner: str
275 :param enable_locking: Enable |repo| locking. The default is false.
278 :param enable_locking: Enable |repo| locking. The default is false.
276 :type enable_locking: bool
279 :type enable_locking: bool
277 """
280 """
278
281
279 repo_group = get_repo_group_or_error(repogroupid)
282 repo_group = get_repo_group_or_error(repogroupid)
280
283
281 if not has_superadmin_permission(apiuser):
284 if not has_superadmin_permission(apiuser):
282 validate_repo_group_permissions(
285 validate_repo_group_permissions(
283 apiuser, repogroupid, repo_group, ('group.admin',))
286 apiuser, repogroupid, repo_group, ('group.admin',))
284
287
285 updates = dict(
288 updates = dict(
286 group_name=group_name
289 group_name=group_name
287 if not isinstance(group_name, Optional) else repo_group.group_name,
290 if not isinstance(group_name, Optional) else repo_group.group_name,
288
291
289 group_description=description
292 group_description=description
290 if not isinstance(description, Optional) else repo_group.group_description,
293 if not isinstance(description, Optional) else repo_group.group_description,
291
294
292 user=owner
295 user=owner
293 if not isinstance(owner, Optional) else repo_group.user.username,
296 if not isinstance(owner, Optional) else repo_group.user.username,
294
297
295 enable_locking=enable_locking
298 enable_locking=enable_locking
296 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
299 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
297 )
300 )
298
301
299 schema = repo_group_schema.RepoGroupSchema().bind(
302 schema = repo_group_schema.RepoGroupSchema().bind(
300 # user caller
303 # user caller
301 user=apiuser,
304 user=apiuser,
302 old_values=repo_group.get_api_data())
305 old_values=repo_group.get_api_data())
303
306
304 try:
307 try:
305 schema_data = schema.deserialize(dict(
308 schema_data = schema.deserialize(dict(
306 repo_group_name=updates['group_name'],
309 repo_group_name=updates['group_name'],
307 repo_group_owner=updates['user'],
310 repo_group_owner=updates['user'],
308 repo_group_description=updates['group_description'],
311 repo_group_description=updates['group_description'],
309 repo_group_enable_locking=updates['enable_locking'],
312 repo_group_enable_locking=updates['enable_locking'],
310 ))
313 ))
311 except validation_schema.Invalid as err:
314 except validation_schema.Invalid as err:
312 raise JSONRPCValidationError(colander_exc=err)
315 raise JSONRPCValidationError(colander_exc=err)
313
316
314 validated_updates = dict(
317 validated_updates = dict(
315 group_name=schema_data['repo_group']['repo_group_name_without_group'],
318 group_name=schema_data['repo_group']['repo_group_name_without_group'],
316 group_parent_id=schema_data['repo_group']['repo_group_id'],
319 group_parent_id=schema_data['repo_group']['repo_group_id'],
317 user=schema_data['repo_group_owner'],
320 user=schema_data['repo_group_owner'],
318 group_description=schema_data['repo_group_description'],
321 group_description=schema_data['repo_group_description'],
319 enable_locking=schema_data['repo_group_enable_locking'],
322 enable_locking=schema_data['repo_group_enable_locking'],
320 )
323 )
321
324
322 old_data = repo_group.get_api_data()
325 old_data = repo_group.get_api_data()
323 try:
326 try:
324 RepoGroupModel().update(repo_group, validated_updates)
327 RepoGroupModel().update(repo_group, validated_updates)
325 audit_logger.store_api(
328 audit_logger.store_api(
326 'repo_group.edit', action_data={'old_data': old_data},
329 'repo_group.edit', action_data={'old_data': old_data},
327 user=apiuser)
330 user=apiuser)
328
331
329 Session().commit()
332 Session().commit()
330 return {
333 return {
331 'msg': 'updated repository group ID:%s %s' % (
334 'msg': 'updated repository group ID:%s %s' % (
332 repo_group.group_id, repo_group.group_name),
335 repo_group.group_id, repo_group.group_name),
333 'repo_group': repo_group.get_api_data()
336 'repo_group': repo_group.get_api_data()
334 }
337 }
335 except Exception:
338 except Exception:
336 log.exception(
339 log.exception(
337 u"Exception occurred while trying update repo group %s",
340 u"Exception occurred while trying update repo group %s",
338 repogroupid)
341 repogroupid)
339 raise JSONRPCError('failed to update repository group `%s`'
342 raise JSONRPCError('failed to update repository group `%s`'
340 % (repogroupid,))
343 % (repogroupid,))
341
344
342
345
343 @jsonrpc_method()
346 @jsonrpc_method()
344 def delete_repo_group(request, apiuser, repogroupid):
347 def delete_repo_group(request, apiuser, repogroupid):
345 """
348 """
346 Deletes a |repo| group.
349 Deletes a |repo| group.
347
350
348 :param apiuser: This is filled automatically from the |authtoken|.
351 :param apiuser: This is filled automatically from the |authtoken|.
349 :type apiuser: AuthUser
352 :type apiuser: AuthUser
350 :param repogroupid: Set the name or ID of repository group to be
353 :param repogroupid: Set the name or ID of repository group to be
351 deleted.
354 deleted.
352 :type repogroupid: str or int
355 :type repogroupid: str or int
353
356
354 Example output:
357 Example output:
355
358
356 .. code-block:: bash
359 .. code-block:: bash
357
360
358 id : <id_given_in_input>
361 id : <id_given_in_input>
359 result : {
362 result : {
360 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
363 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
361 'repo_group': null
364 'repo_group': null
362 }
365 }
363 error : null
366 error : null
364
367
365 Example error output:
368 Example error output:
366
369
367 .. code-block:: bash
370 .. code-block:: bash
368
371
369 id : <id_given_in_input>
372 id : <id_given_in_input>
370 result : null
373 result : null
371 error : {
374 error : {
372 "failed to delete repo group ID:<repogroupid> <repogroupname>"
375 "failed to delete repo group ID:<repogroupid> <repogroupname>"
373 }
376 }
374
377
375 """
378 """
376
379
377 repo_group = get_repo_group_or_error(repogroupid)
380 repo_group = get_repo_group_or_error(repogroupid)
378 if not has_superadmin_permission(apiuser):
381 if not has_superadmin_permission(apiuser):
379 validate_repo_group_permissions(
382 validate_repo_group_permissions(
380 apiuser, repogroupid, repo_group, ('group.admin',))
383 apiuser, repogroupid, repo_group, ('group.admin',))
381
384
382 old_data = repo_group.get_api_data()
385 old_data = repo_group.get_api_data()
383 try:
386 try:
384 RepoGroupModel().delete(repo_group)
387 RepoGroupModel().delete(repo_group)
385 audit_logger.store_api(
388 audit_logger.store_api(
386 'repo_group.delete', action_data={'old_data': old_data},
389 'repo_group.delete', action_data={'old_data': old_data},
387 user=apiuser)
390 user=apiuser)
388 Session().commit()
391 Session().commit()
389 return {
392 return {
390 'msg': 'deleted repo group ID:%s %s' %
393 'msg': 'deleted repo group ID:%s %s' %
391 (repo_group.group_id, repo_group.group_name),
394 (repo_group.group_id, repo_group.group_name),
392 'repo_group': None
395 'repo_group': None
393 }
396 }
394 except Exception:
397 except Exception:
395 log.exception("Exception occurred while trying to delete repo group")
398 log.exception("Exception occurred while trying to delete repo group")
396 raise JSONRPCError('failed to delete repo group ID:%s %s' %
399 raise JSONRPCError('failed to delete repo group ID:%s %s' %
397 (repo_group.group_id, repo_group.group_name))
400 (repo_group.group_id, repo_group.group_name))
398
401
399
402
400 @jsonrpc_method()
403 @jsonrpc_method()
401 def grant_user_permission_to_repo_group(
404 def grant_user_permission_to_repo_group(
402 request, apiuser, repogroupid, userid, perm,
405 request, apiuser, repogroupid, userid, perm,
403 apply_to_children=Optional('none')):
406 apply_to_children=Optional('none')):
404 """
407 """
405 Grant permission for a user on the given repository group, or update
408 Grant permission for a user on the given repository group, or update
406 existing permissions if found.
409 existing permissions if found.
407
410
408 This command can only be run using an |authtoken| with admin
411 This command can only be run using an |authtoken| with admin
409 permissions.
412 permissions.
410
413
411 :param apiuser: This is filled automatically from the |authtoken|.
414 :param apiuser: This is filled automatically from the |authtoken|.
412 :type apiuser: AuthUser
415 :type apiuser: AuthUser
413 :param repogroupid: Set the name or ID of repository group.
416 :param repogroupid: Set the name or ID of repository group.
414 :type repogroupid: str or int
417 :type repogroupid: str or int
415 :param userid: Set the user name.
418 :param userid: Set the user name.
416 :type userid: str
419 :type userid: str
417 :param perm: (group.(none|read|write|admin))
420 :param perm: (group.(none|read|write|admin))
418 :type perm: str
421 :type perm: str
419 :param apply_to_children: 'none', 'repos', 'groups', 'all'
422 :param apply_to_children: 'none', 'repos', 'groups', 'all'
420 :type apply_to_children: str
423 :type apply_to_children: str
421
424
422 Example output:
425 Example output:
423
426
424 .. code-block:: bash
427 .. code-block:: bash
425
428
426 id : <id_given_in_input>
429 id : <id_given_in_input>
427 result: {
430 result: {
428 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
431 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
429 "success": true
432 "success": true
430 }
433 }
431 error: null
434 error: null
432
435
433 Example error output:
436 Example error output:
434
437
435 .. code-block:: bash
438 .. code-block:: bash
436
439
437 id : <id_given_in_input>
440 id : <id_given_in_input>
438 result : null
441 result : null
439 error : {
442 error : {
440 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
443 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
441 }
444 }
442
445
443 """
446 """
444
447
445 repo_group = get_repo_group_or_error(repogroupid)
448 repo_group = get_repo_group_or_error(repogroupid)
446
449
447 if not has_superadmin_permission(apiuser):
450 if not has_superadmin_permission(apiuser):
448 validate_repo_group_permissions(
451 validate_repo_group_permissions(
449 apiuser, repogroupid, repo_group, ('group.admin',))
452 apiuser, repogroupid, repo_group, ('group.admin',))
450
453
451 user = get_user_or_error(userid)
454 user = get_user_or_error(userid)
452 perm = get_perm_or_error(perm, prefix='group.')
455 perm = get_perm_or_error(perm, prefix='group.')
453 apply_to_children = Optional.extract(apply_to_children)
456 apply_to_children = Optional.extract(apply_to_children)
454
457
455 perm_additions = [[user.user_id, perm.permission_name, "user"]]
458 perm_additions = [[user.user_id, perm.permission_name, "user"]]
456 try:
459 try:
457 changes = RepoGroupModel().update_permissions(
460 changes = RepoGroupModel().update_permissions(
458 repo_group=repo_group, perm_additions=perm_additions,
461 repo_group=repo_group, perm_additions=perm_additions,
459 recursive=apply_to_children, cur_user=apiuser)
462 recursive=apply_to_children, cur_user=apiuser)
460
463
461 action_data = {
464 action_data = {
462 'added': changes['added'],
465 'added': changes['added'],
463 'updated': changes['updated'],
466 'updated': changes['updated'],
464 'deleted': changes['deleted'],
467 'deleted': changes['deleted'],
465 }
468 }
466 audit_logger.store_api(
469 audit_logger.store_api(
467 'repo_group.edit.permissions', action_data=action_data,
470 'repo_group.edit.permissions', action_data=action_data,
468 user=apiuser)
471 user=apiuser)
469 Session().commit()
472 Session().commit()
470 PermissionModel().flush_user_permission_caches(changes)
473 PermissionModel().flush_user_permission_caches(changes)
471
474
472 return {
475 return {
473 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
476 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
474 '`%s` in repo group: `%s`' % (
477 '`%s` in repo group: `%s`' % (
475 perm.permission_name, apply_to_children, user.username,
478 perm.permission_name, apply_to_children, user.username,
476 repo_group.name
479 repo_group.name
477 ),
480 ),
478 'success': True
481 'success': True
479 }
482 }
480 except Exception:
483 except Exception:
481 log.exception("Exception occurred while trying to grant "
484 log.exception("Exception occurred while trying to grant "
482 "user permissions to repo group")
485 "user permissions to repo group")
483 raise JSONRPCError(
486 raise JSONRPCError(
484 'failed to edit permission for user: '
487 'failed to edit permission for user: '
485 '`%s` in repo group: `%s`' % (userid, repo_group.name))
488 '`%s` in repo group: `%s`' % (userid, repo_group.name))
486
489
487
490
488 @jsonrpc_method()
491 @jsonrpc_method()
489 def revoke_user_permission_from_repo_group(
492 def revoke_user_permission_from_repo_group(
490 request, apiuser, repogroupid, userid,
493 request, apiuser, repogroupid, userid,
491 apply_to_children=Optional('none')):
494 apply_to_children=Optional('none')):
492 """
495 """
493 Revoke permission for a user in a given repository group.
496 Revoke permission for a user in a given repository group.
494
497
495 This command can only be run using an |authtoken| with admin
498 This command can only be run using an |authtoken| with admin
496 permissions on the |repo| group.
499 permissions on the |repo| group.
497
500
498 :param apiuser: This is filled automatically from the |authtoken|.
501 :param apiuser: This is filled automatically from the |authtoken|.
499 :type apiuser: AuthUser
502 :type apiuser: AuthUser
500 :param repogroupid: Set the name or ID of the repository group.
503 :param repogroupid: Set the name or ID of the repository group.
501 :type repogroupid: str or int
504 :type repogroupid: str or int
502 :param userid: Set the user name to revoke.
505 :param userid: Set the user name to revoke.
503 :type userid: str
506 :type userid: str
504 :param apply_to_children: 'none', 'repos', 'groups', 'all'
507 :param apply_to_children: 'none', 'repos', 'groups', 'all'
505 :type apply_to_children: str
508 :type apply_to_children: str
506
509
507 Example output:
510 Example output:
508
511
509 .. code-block:: bash
512 .. code-block:: bash
510
513
511 id : <id_given_in_input>
514 id : <id_given_in_input>
512 result: {
515 result: {
513 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
516 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
514 "success": true
517 "success": true
515 }
518 }
516 error: null
519 error: null
517
520
518 Example error output:
521 Example error output:
519
522
520 .. code-block:: bash
523 .. code-block:: bash
521
524
522 id : <id_given_in_input>
525 id : <id_given_in_input>
523 result : null
526 result : null
524 error : {
527 error : {
525 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
528 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
526 }
529 }
527
530
528 """
531 """
529
532
530 repo_group = get_repo_group_or_error(repogroupid)
533 repo_group = get_repo_group_or_error(repogroupid)
531
534
532 if not has_superadmin_permission(apiuser):
535 if not has_superadmin_permission(apiuser):
533 validate_repo_group_permissions(
536 validate_repo_group_permissions(
534 apiuser, repogroupid, repo_group, ('group.admin',))
537 apiuser, repogroupid, repo_group, ('group.admin',))
535
538
536 user = get_user_or_error(userid)
539 user = get_user_or_error(userid)
537 apply_to_children = Optional.extract(apply_to_children)
540 apply_to_children = Optional.extract(apply_to_children)
538
541
539 perm_deletions = [[user.user_id, None, "user"]]
542 perm_deletions = [[user.user_id, None, "user"]]
540 try:
543 try:
541 changes = RepoGroupModel().update_permissions(
544 changes = RepoGroupModel().update_permissions(
542 repo_group=repo_group, perm_deletions=perm_deletions,
545 repo_group=repo_group, perm_deletions=perm_deletions,
543 recursive=apply_to_children, cur_user=apiuser)
546 recursive=apply_to_children, cur_user=apiuser)
544
547
545 action_data = {
548 action_data = {
546 'added': changes['added'],
549 'added': changes['added'],
547 'updated': changes['updated'],
550 'updated': changes['updated'],
548 'deleted': changes['deleted'],
551 'deleted': changes['deleted'],
549 }
552 }
550 audit_logger.store_api(
553 audit_logger.store_api(
551 'repo_group.edit.permissions', action_data=action_data,
554 'repo_group.edit.permissions', action_data=action_data,
552 user=apiuser)
555 user=apiuser)
553 Session().commit()
556 Session().commit()
554 PermissionModel().flush_user_permission_caches(changes)
557 PermissionModel().flush_user_permission_caches(changes)
555
558
556 return {
559 return {
557 'msg': 'Revoked perm (recursive:%s) for user: '
560 'msg': 'Revoked perm (recursive:%s) for user: '
558 '`%s` in repo group: `%s`' % (
561 '`%s` in repo group: `%s`' % (
559 apply_to_children, user.username, repo_group.name
562 apply_to_children, user.username, repo_group.name
560 ),
563 ),
561 'success': True
564 'success': True
562 }
565 }
563 except Exception:
566 except Exception:
564 log.exception("Exception occurred while trying revoke user "
567 log.exception("Exception occurred while trying revoke user "
565 "permission from repo group")
568 "permission from repo group")
566 raise JSONRPCError(
569 raise JSONRPCError(
567 'failed to edit permission for user: '
570 'failed to edit permission for user: '
568 '`%s` in repo group: `%s`' % (userid, repo_group.name))
571 '`%s` in repo group: `%s`' % (userid, repo_group.name))
569
572
570
573
571 @jsonrpc_method()
574 @jsonrpc_method()
572 def grant_user_group_permission_to_repo_group(
575 def grant_user_group_permission_to_repo_group(
573 request, apiuser, repogroupid, usergroupid, perm,
576 request, apiuser, repogroupid, usergroupid, perm,
574 apply_to_children=Optional('none'), ):
577 apply_to_children=Optional('none'), ):
575 """
578 """
576 Grant permission for a user group on given repository group, or update
579 Grant permission for a user group on given repository group, or update
577 existing permissions if found.
580 existing permissions if found.
578
581
579 This command can only be run using an |authtoken| with admin
582 This command can only be run using an |authtoken| with admin
580 permissions on the |repo| group.
583 permissions on the |repo| group.
581
584
582 :param apiuser: This is filled automatically from the |authtoken|.
585 :param apiuser: This is filled automatically from the |authtoken|.
583 :type apiuser: AuthUser
586 :type apiuser: AuthUser
584 :param repogroupid: Set the name or id of repository group
587 :param repogroupid: Set the name or id of repository group
585 :type repogroupid: str or int
588 :type repogroupid: str or int
586 :param usergroupid: id of usergroup
589 :param usergroupid: id of usergroup
587 :type usergroupid: str or int
590 :type usergroupid: str or int
588 :param perm: (group.(none|read|write|admin))
591 :param perm: (group.(none|read|write|admin))
589 :type perm: str
592 :type perm: str
590 :param apply_to_children: 'none', 'repos', 'groups', 'all'
593 :param apply_to_children: 'none', 'repos', 'groups', 'all'
591 :type apply_to_children: str
594 :type apply_to_children: str
592
595
593 Example output:
596 Example output:
594
597
595 .. code-block:: bash
598 .. code-block:: bash
596
599
597 id : <id_given_in_input>
600 id : <id_given_in_input>
598 result : {
601 result : {
599 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
602 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
600 "success": true
603 "success": true
601
604
602 }
605 }
603 error : null
606 error : null
604
607
605 Example error output:
608 Example error output:
606
609
607 .. code-block:: bash
610 .. code-block:: bash
608
611
609 id : <id_given_in_input>
612 id : <id_given_in_input>
610 result : null
613 result : null
611 error : {
614 error : {
612 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
615 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
613 }
616 }
614
617
615 """
618 """
616
619
617 repo_group = get_repo_group_or_error(repogroupid)
620 repo_group = get_repo_group_or_error(repogroupid)
618 perm = get_perm_or_error(perm, prefix='group.')
621 perm = get_perm_or_error(perm, prefix='group.')
619 user_group = get_user_group_or_error(usergroupid)
622 user_group = get_user_group_or_error(usergroupid)
620 if not has_superadmin_permission(apiuser):
623 if not has_superadmin_permission(apiuser):
621 validate_repo_group_permissions(
624 validate_repo_group_permissions(
622 apiuser, repogroupid, repo_group, ('group.admin',))
625 apiuser, repogroupid, repo_group, ('group.admin',))
623
626
624 # check if we have at least read permission for this user group !
627 # check if we have at least read permission for this user group !
625 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
628 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
626 if not HasUserGroupPermissionAnyApi(*_perms)(
629 if not HasUserGroupPermissionAnyApi(*_perms)(
627 user=apiuser, user_group_name=user_group.users_group_name):
630 user=apiuser, user_group_name=user_group.users_group_name):
628 raise JSONRPCError(
631 raise JSONRPCError(
629 'user group `%s` does not exist' % (usergroupid,))
632 'user group `%s` does not exist' % (usergroupid,))
630
633
631 apply_to_children = Optional.extract(apply_to_children)
634 apply_to_children = Optional.extract(apply_to_children)
632
635
633 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
636 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
634 try:
637 try:
635 changes = RepoGroupModel().update_permissions(
638 changes = RepoGroupModel().update_permissions(
636 repo_group=repo_group, perm_additions=perm_additions,
639 repo_group=repo_group, perm_additions=perm_additions,
637 recursive=apply_to_children, cur_user=apiuser)
640 recursive=apply_to_children, cur_user=apiuser)
638
641
639 action_data = {
642 action_data = {
640 'added': changes['added'],
643 'added': changes['added'],
641 'updated': changes['updated'],
644 'updated': changes['updated'],
642 'deleted': changes['deleted'],
645 'deleted': changes['deleted'],
643 }
646 }
644 audit_logger.store_api(
647 audit_logger.store_api(
645 'repo_group.edit.permissions', action_data=action_data,
648 'repo_group.edit.permissions', action_data=action_data,
646 user=apiuser)
649 user=apiuser)
647 Session().commit()
650 Session().commit()
648 PermissionModel().flush_user_permission_caches(changes)
651 PermissionModel().flush_user_permission_caches(changes)
649
652
650 return {
653 return {
651 'msg': 'Granted perm: `%s` (recursive:%s) '
654 'msg': 'Granted perm: `%s` (recursive:%s) '
652 'for user group: `%s` in repo group: `%s`' % (
655 'for user group: `%s` in repo group: `%s`' % (
653 perm.permission_name, apply_to_children,
656 perm.permission_name, apply_to_children,
654 user_group.users_group_name, repo_group.name
657 user_group.users_group_name, repo_group.name
655 ),
658 ),
656 'success': True
659 'success': True
657 }
660 }
658 except Exception:
661 except Exception:
659 log.exception("Exception occurred while trying to grant user "
662 log.exception("Exception occurred while trying to grant user "
660 "group permissions to repo group")
663 "group permissions to repo group")
661 raise JSONRPCError(
664 raise JSONRPCError(
662 'failed to edit permission for user group: `%s` in '
665 'failed to edit permission for user group: `%s` in '
663 'repo group: `%s`' % (
666 'repo group: `%s`' % (
664 usergroupid, repo_group.name
667 usergroupid, repo_group.name
665 )
668 )
666 )
669 )
667
670
668
671
669 @jsonrpc_method()
672 @jsonrpc_method()
670 def revoke_user_group_permission_from_repo_group(
673 def revoke_user_group_permission_from_repo_group(
671 request, apiuser, repogroupid, usergroupid,
674 request, apiuser, repogroupid, usergroupid,
672 apply_to_children=Optional('none')):
675 apply_to_children=Optional('none')):
673 """
676 """
674 Revoke permission for user group on given repository.
677 Revoke permission for user group on given repository.
675
678
676 This command can only be run using an |authtoken| with admin
679 This command can only be run using an |authtoken| with admin
677 permissions on the |repo| group.
680 permissions on the |repo| group.
678
681
679 :param apiuser: This is filled automatically from the |authtoken|.
682 :param apiuser: This is filled automatically from the |authtoken|.
680 :type apiuser: AuthUser
683 :type apiuser: AuthUser
681 :param repogroupid: name or id of repository group
684 :param repogroupid: name or id of repository group
682 :type repogroupid: str or int
685 :type repogroupid: str or int
683 :param usergroupid:
686 :param usergroupid:
684 :param apply_to_children: 'none', 'repos', 'groups', 'all'
687 :param apply_to_children: 'none', 'repos', 'groups', 'all'
685 :type apply_to_children: str
688 :type apply_to_children: str
686
689
687 Example output:
690 Example output:
688
691
689 .. code-block:: bash
692 .. code-block:: bash
690
693
691 id : <id_given_in_input>
694 id : <id_given_in_input>
692 result: {
695 result: {
693 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
696 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
694 "success": true
697 "success": true
695 }
698 }
696 error: null
699 error: null
697
700
698 Example error output:
701 Example error output:
699
702
700 .. code-block:: bash
703 .. code-block:: bash
701
704
702 id : <id_given_in_input>
705 id : <id_given_in_input>
703 result : null
706 result : null
704 error : {
707 error : {
705 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
708 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
706 }
709 }
707
710
708
711
709 """
712 """
710
713
711 repo_group = get_repo_group_or_error(repogroupid)
714 repo_group = get_repo_group_or_error(repogroupid)
712 user_group = get_user_group_or_error(usergroupid)
715 user_group = get_user_group_or_error(usergroupid)
713 if not has_superadmin_permission(apiuser):
716 if not has_superadmin_permission(apiuser):
714 validate_repo_group_permissions(
717 validate_repo_group_permissions(
715 apiuser, repogroupid, repo_group, ('group.admin',))
718 apiuser, repogroupid, repo_group, ('group.admin',))
716
719
717 # check if we have at least read permission for this user group !
720 # check if we have at least read permission for this user group !
718 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
721 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
719 if not HasUserGroupPermissionAnyApi(*_perms)(
722 if not HasUserGroupPermissionAnyApi(*_perms)(
720 user=apiuser, user_group_name=user_group.users_group_name):
723 user=apiuser, user_group_name=user_group.users_group_name):
721 raise JSONRPCError(
724 raise JSONRPCError(
722 'user group `%s` does not exist' % (usergroupid,))
725 'user group `%s` does not exist' % (usergroupid,))
723
726
724 apply_to_children = Optional.extract(apply_to_children)
727 apply_to_children = Optional.extract(apply_to_children)
725
728
726 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
729 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
727 try:
730 try:
728 changes = RepoGroupModel().update_permissions(
731 changes = RepoGroupModel().update_permissions(
729 repo_group=repo_group, perm_deletions=perm_deletions,
732 repo_group=repo_group, perm_deletions=perm_deletions,
730 recursive=apply_to_children, cur_user=apiuser)
733 recursive=apply_to_children, cur_user=apiuser)
731
734
732 action_data = {
735 action_data = {
733 'added': changes['added'],
736 'added': changes['added'],
734 'updated': changes['updated'],
737 'updated': changes['updated'],
735 'deleted': changes['deleted'],
738 'deleted': changes['deleted'],
736 }
739 }
737 audit_logger.store_api(
740 audit_logger.store_api(
738 'repo_group.edit.permissions', action_data=action_data,
741 'repo_group.edit.permissions', action_data=action_data,
739 user=apiuser)
742 user=apiuser)
740 Session().commit()
743 Session().commit()
741 PermissionModel().flush_user_permission_caches(changes)
744 PermissionModel().flush_user_permission_caches(changes)
742
745
743 return {
746 return {
744 'msg': 'Revoked perm (recursive:%s) for user group: '
747 'msg': 'Revoked perm (recursive:%s) for user group: '
745 '`%s` in repo group: `%s`' % (
748 '`%s` in repo group: `%s`' % (
746 apply_to_children, user_group.users_group_name,
749 apply_to_children, user_group.users_group_name,
747 repo_group.name
750 repo_group.name
748 ),
751 ),
749 'success': True
752 'success': True
750 }
753 }
751 except Exception:
754 except Exception:
752 log.exception("Exception occurred while trying revoke user group "
755 log.exception("Exception occurred while trying revoke user group "
753 "permissions from repo group")
756 "permissions from repo group")
754 raise JSONRPCError(
757 raise JSONRPCError(
755 'failed to edit permission for user group: '
758 'failed to edit permission for user group: '
756 '`%s` in repo group: `%s`' % (
759 '`%s` in repo group: `%s`' % (
757 user_group.users_group_name, repo_group.name
760 user_group.users_group_name, repo_group.name
758 )
761 )
759 )
762 )
@@ -1,362 +1,356 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
20 import datetime
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28
28
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34
34
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
36 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
37 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h, audit_logger
38 from rhodecode.lib import helpers as h, audit_logger
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 from rhodecode.model.forms import RepoGroupForm
40 from rhodecode.model.forms import RepoGroupForm
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo_group import RepoGroupModel
42 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.scm import RepoGroupList
43 from rhodecode.model.scm import RepoGroupList
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 return c
55 return c
56
56
57 def _load_form_data(self, c):
57 def _load_form_data(self, c):
58 allow_empty_group = False
58 allow_empty_group = False
59
59
60 if self._can_create_repo_group():
60 if self._can_create_repo_group():
61 # we're global admin, we're ok and we can create TOP level groups
61 # we're global admin, we're ok and we can create TOP level groups
62 allow_empty_group = True
62 allow_empty_group = True
63
63
64 # override the choices for this form, we need to filter choices
64 # override the choices for this form, we need to filter choices
65 # and display only those we have ADMIN right
65 # and display only those we have ADMIN right
66 groups_with_admin_rights = RepoGroupList(
66 groups_with_admin_rights = RepoGroupList(
67 RepoGroup.query().all(),
67 RepoGroup.query().all(),
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 c.repo_groups = RepoGroup.groups_choices(
69 c.repo_groups = RepoGroup.groups_choices(
70 groups=groups_with_admin_rights,
70 groups=groups_with_admin_rights,
71 show_empty_group=allow_empty_group)
71 show_empty_group=allow_empty_group)
72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
72 c.personal_repo_group = self._rhodecode_user.personal_repo_group
73
73
74 def _can_create_repo_group(self, parent_group_id=None):
74 def _can_create_repo_group(self, parent_group_id=None):
75 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 is_admin = HasPermissionAny('hg.admin')('group create controller')
76 create_repo_group = HasPermissionAny(
76 create_repo_group = HasPermissionAny(
77 'hg.repogroup.create.true')('group create controller')
77 'hg.repogroup.create.true')('group create controller')
78 if is_admin or (create_repo_group and not parent_group_id):
78 if is_admin or (create_repo_group and not parent_group_id):
79 # we're global admin, or we have global repo group create
79 # we're global admin, or we have global repo group create
80 # permission
80 # permission
81 # we're ok and we can create TOP level groups
81 # we're ok and we can create TOP level groups
82 return True
82 return True
83 elif parent_group_id:
83 elif parent_group_id:
84 # we check the permission if we can write to parent group
84 # we check the permission if we can write to parent group
85 group = RepoGroup.get(parent_group_id)
85 group = RepoGroup.get(parent_group_id)
86 group_name = group.group_name if group else None
86 group_name = group.group_name if group else None
87 if HasRepoGroupPermissionAny('group.admin')(
87 if HasRepoGroupPermissionAny('group.admin')(
88 group_name, 'check if user is an admin of group'):
88 group_name, 'check if user is an admin of group'):
89 # we're an admin of passed in group, we're ok.
89 # we're an admin of passed in group, we're ok.
90 return True
90 return True
91 else:
91 else:
92 return False
92 return False
93 return False
93 return False
94
94
95 # permission check in data loading of
95 # permission check in data loading of
96 # `repo_group_list_data` via RepoGroupList
96 # `repo_group_list_data` via RepoGroupList
97 @LoginRequired()
97 @LoginRequired()
98 @NotAnonymous()
98 @NotAnonymous()
99 def repo_group_list(self):
99 def repo_group_list(self):
100 c = self.load_default_context()
100 c = self.load_default_context()
101 return self._get_template_context(c)
101 return self._get_template_context(c)
102
102
103 # permission check inside
103 # permission check inside
104 @LoginRequired()
104 @LoginRequired()
105 @NotAnonymous()
105 @NotAnonymous()
106 def repo_group_list_data(self):
106 def repo_group_list_data(self):
107 self.load_default_context()
107 self.load_default_context()
108 column_map = {
108 column_map = {
109 'name': 'group_name_hash',
109 'name': 'group_name_hash',
110 'desc': 'group_description',
110 'desc': 'group_description',
111 'last_change': 'updated_on',
111 'last_change': 'updated_on',
112 'top_level_repos': 'repos_total',
112 'top_level_repos': 'repos_total',
113 'owner': 'user_username',
113 'owner': 'user_username',
114 }
114 }
115 draw, start, limit = self._extract_chunk(self.request)
115 draw, start, limit = self._extract_chunk(self.request)
116 search_q, order_by, order_dir = self._extract_ordering(
116 search_q, order_by, order_dir = self._extract_ordering(
117 self.request, column_map=column_map)
117 self.request, column_map=column_map)
118
118
119 _render = self.request.get_partial_renderer(
119 _render = self.request.get_partial_renderer(
120 'rhodecode:templates/data_table/_dt_elements.mako')
120 'rhodecode:templates/data_table/_dt_elements.mako')
121 c = _render.get_call_context()
121 c = _render.get_call_context()
122
122
123 def quick_menu(repo_group_name):
123 def quick_menu(repo_group_name):
124 return _render('quick_repo_group_menu', repo_group_name)
124 return _render('quick_repo_group_menu', repo_group_name)
125
125
126 def repo_group_lnk(repo_group_name):
126 def repo_group_lnk(repo_group_name):
127 return _render('repo_group_name', repo_group_name)
127 return _render('repo_group_name', repo_group_name)
128
128
129 def last_change(last_change):
129 def last_change(last_change):
130 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
130 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
131 ts = time.time()
131 ts = time.time()
132 utc_offset = (datetime.datetime.fromtimestamp(ts)
132 utc_offset = (datetime.datetime.fromtimestamp(ts)
133 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
133 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
134 last_change = last_change + datetime.timedelta(seconds=utc_offset)
134 last_change = last_change + datetime.timedelta(seconds=utc_offset)
135 return _render("last_change", last_change)
135 return _render("last_change", last_change)
136
136
137 def desc(desc, personal):
137 def desc(desc, personal):
138 return _render(
138 return _render(
139 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
139 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
140
140
141 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
141 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
142 return _render(
142 return _render(
143 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
143 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
144
144
145 def user_profile(username):
145 def user_profile(username):
146 return _render('user_profile', username)
146 return _render('user_profile', username)
147
147
148 _perms = ['group.admin']
148 _perms = ['group.admin']
149 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
149 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
150
150
151 repo_groups_data_total_count = RepoGroup.query()\
151 repo_groups_data_total_count = RepoGroup.query()\
152 .filter(or_(
152 .filter(or_(
153 # generate multiple IN to fix limitation problems
153 # generate multiple IN to fix limitation problems
154 *in_filter_generator(RepoGroup.group_id, allowed_ids)
154 *in_filter_generator(RepoGroup.group_id, allowed_ids)
155 )) \
155 )) \
156 .count()
156 .count()
157
157
158 repo_groups_data_total_inactive_count = RepoGroup.query()\
158 repo_groups_data_total_inactive_count = RepoGroup.query()\
159 .filter(RepoGroup.group_id.in_(allowed_ids))\
159 .filter(RepoGroup.group_id.in_(allowed_ids))\
160 .count()
160 .count()
161
161
162 repo_count = count(Repository.repo_id)
162 repo_count = count(Repository.repo_id)
163 base_q = Session.query(
163 base_q = Session.query(
164 RepoGroup.group_name,
164 RepoGroup.group_name,
165 RepoGroup.group_name_hash,
165 RepoGroup.group_name_hash,
166 RepoGroup.group_description,
166 RepoGroup.group_description,
167 RepoGroup.group_id,
167 RepoGroup.group_id,
168 RepoGroup.personal,
168 RepoGroup.personal,
169 RepoGroup.updated_on,
169 RepoGroup.updated_on,
170 User,
170 User,
171 repo_count.label('repos_count')
171 repo_count.label('repos_count')
172 ) \
172 ) \
173 .filter(or_(
173 .filter(or_(
174 # generate multiple IN to fix limitation problems
174 # generate multiple IN to fix limitation problems
175 *in_filter_generator(RepoGroup.group_id, allowed_ids)
175 *in_filter_generator(RepoGroup.group_id, allowed_ids)
176 )) \
176 )) \
177 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
177 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
178 .join(User, User.user_id == RepoGroup.user_id) \
178 .join(User, User.user_id == RepoGroup.user_id) \
179 .group_by(RepoGroup, User)
179 .group_by(RepoGroup, User)
180
180
181 if search_q:
181 if search_q:
182 like_expression = u'%{}%'.format(safe_unicode(search_q))
182 like_expression = u'%{}%'.format(safe_unicode(search_q))
183 base_q = base_q.filter(or_(
183 base_q = base_q.filter(or_(
184 RepoGroup.group_name.ilike(like_expression),
184 RepoGroup.group_name.ilike(like_expression),
185 ))
185 ))
186
186
187 repo_groups_data_total_filtered_count = base_q.count()
187 repo_groups_data_total_filtered_count = base_q.count()
188 # the inactive isn't really used, but we still make it same as other data grids
188 # the inactive isn't really used, but we still make it same as other data grids
189 # which use inactive (users,user groups)
189 # which use inactive (users,user groups)
190 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
190 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
191
191
192 sort_defined = False
192 sort_defined = False
193 if order_by == 'group_name':
193 if order_by == 'group_name':
194 sort_col = func.lower(RepoGroup.group_name)
194 sort_col = func.lower(RepoGroup.group_name)
195 sort_defined = True
195 sort_defined = True
196 elif order_by == 'repos_total':
196 elif order_by == 'repos_total':
197 sort_col = repo_count
197 sort_col = repo_count
198 sort_defined = True
198 sort_defined = True
199 elif order_by == 'user_username':
199 elif order_by == 'user_username':
200 sort_col = User.username
200 sort_col = User.username
201 else:
201 else:
202 sort_col = getattr(RepoGroup, order_by, None)
202 sort_col = getattr(RepoGroup, order_by, None)
203
203
204 if sort_defined or sort_col:
204 if sort_defined or sort_col:
205 if order_dir == 'asc':
205 if order_dir == 'asc':
206 sort_col = sort_col.asc()
206 sort_col = sort_col.asc()
207 else:
207 else:
208 sort_col = sort_col.desc()
208 sort_col = sort_col.desc()
209
209
210 base_q = base_q.order_by(sort_col)
210 base_q = base_q.order_by(sort_col)
211 base_q = base_q.offset(start).limit(limit)
211 base_q = base_q.offset(start).limit(limit)
212
212
213 # authenticated access to user groups
213 # authenticated access to user groups
214 auth_repo_group_list = base_q.all()
214 auth_repo_group_list = base_q.all()
215
215
216 repo_groups_data = []
216 repo_groups_data = []
217 for repo_gr in auth_repo_group_list:
217 for repo_gr in auth_repo_group_list:
218 row = {
218 row = {
219 "menu": quick_menu(repo_gr.group_name),
219 "menu": quick_menu(repo_gr.group_name),
220 "name": repo_group_lnk(repo_gr.group_name),
220 "name": repo_group_lnk(repo_gr.group_name),
221
221
222 "last_change": last_change(repo_gr.updated_on),
222 "last_change": last_change(repo_gr.updated_on),
223
223
224 "last_changeset": "",
224 "last_changeset": "",
225 "last_changeset_raw": "",
225 "last_changeset_raw": "",
226
226
227 "desc": desc(repo_gr.group_description, repo_gr.personal),
227 "desc": desc(repo_gr.group_description, repo_gr.personal),
228 "owner": user_profile(repo_gr.User.username),
228 "owner": user_profile(repo_gr.User.username),
229 "top_level_repos": repo_gr.repos_count,
229 "top_level_repos": repo_gr.repos_count,
230 "action": repo_group_actions(
230 "action": repo_group_actions(
231 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
231 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
232
232
233 }
233 }
234
234
235 repo_groups_data.append(row)
235 repo_groups_data.append(row)
236
236
237 data = ({
237 data = ({
238 'draw': draw,
238 'draw': draw,
239 'data': repo_groups_data,
239 'data': repo_groups_data,
240 'recordsTotal': repo_groups_data_total_count,
240 'recordsTotal': repo_groups_data_total_count,
241 'recordsTotalInactive': repo_groups_data_total_inactive_count,
241 'recordsTotalInactive': repo_groups_data_total_inactive_count,
242 'recordsFiltered': repo_groups_data_total_filtered_count,
242 'recordsFiltered': repo_groups_data_total_filtered_count,
243 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
243 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
244 })
244 })
245
245
246 return data
246 return data
247
247
248 @LoginRequired()
248 @LoginRequired()
249 @NotAnonymous()
249 @NotAnonymous()
250 # perm checks inside
250 # perm checks inside
251 def repo_group_new(self):
251 def repo_group_new(self):
252 c = self.load_default_context()
252 c = self.load_default_context()
253
253
254 # perm check for admin, create_group perm or admin of parent_group
254 # perm check for admin, create_group perm or admin of parent_group
255 parent_group_id = safe_int(self.request.GET.get('parent_group'))
255 parent_group_id = safe_int(self.request.GET.get('parent_group'))
256 _gr = RepoGroup.get(parent_group_id)
256 _gr = RepoGroup.get(parent_group_id)
257 if not self._can_create_repo_group(parent_group_id):
257 if not self._can_create_repo_group(parent_group_id):
258 raise HTTPForbidden()
258 raise HTTPForbidden()
259
259
260 self._load_form_data(c)
260 self._load_form_data(c)
261
261
262 defaults = {} # Future proof for default of repo group
262 defaults = {} # Future proof for default of repo group
263
263
264 parent_group_choice = '-1'
264 parent_group_choice = '-1'
265 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
265 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
266 parent_group_choice = self._rhodecode_user.personal_repo_group
266 parent_group_choice = self._rhodecode_user.personal_repo_group
267
267
268 if parent_group_id and _gr:
268 if parent_group_id and _gr:
269 if parent_group_id in [x[0] for x in c.repo_groups]:
269 if parent_group_id in [x[0] for x in c.repo_groups]:
270 parent_group_choice = safe_unicode(parent_group_id)
270 parent_group_choice = safe_unicode(parent_group_id)
271
271
272 defaults.update({'group_parent_id': parent_group_choice})
272 defaults.update({'group_parent_id': parent_group_choice})
273
273
274 data = render(
274 data = render(
275 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
275 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
276 self._get_template_context(c), self.request)
276 self._get_template_context(c), self.request)
277
277
278 html = formencode.htmlfill.render(
278 html = formencode.htmlfill.render(
279 data,
279 data,
280 defaults=defaults,
280 defaults=defaults,
281 encoding="UTF-8",
281 encoding="UTF-8",
282 force_defaults=False
282 force_defaults=False
283 )
283 )
284 return Response(html)
284 return Response(html)
285
285
286 @LoginRequired()
286 @LoginRequired()
287 @NotAnonymous()
287 @NotAnonymous()
288 @CSRFRequired()
288 @CSRFRequired()
289 # perm checks inside
289 # perm checks inside
290 def repo_group_create(self):
290 def repo_group_create(self):
291 c = self.load_default_context()
291 c = self.load_default_context()
292 _ = self.request.translate
292 _ = self.request.translate
293
293
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
294 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
295 can_create = self._can_create_repo_group(parent_group_id)
295 can_create = self._can_create_repo_group(parent_group_id)
296
296
297 self._load_form_data(c)
297 self._load_form_data(c)
298 # permissions for can create group based on parent_id are checked
298 # permissions for can create group based on parent_id are checked
299 # here in the Form
299 # here in the Form
300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
300 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
301 repo_group_form = RepoGroupForm(
301 repo_group_form = RepoGroupForm(
302 self.request.translate, available_groups=available_groups,
302 self.request.translate, available_groups=available_groups,
303 can_create_in_root=can_create)()
303 can_create_in_root=can_create)()
304
304
305 repo_group_name = self.request.POST.get('group_name')
305 repo_group_name = self.request.POST.get('group_name')
306 try:
306 try:
307 owner = self._rhodecode_user
307 owner = self._rhodecode_user
308 form_result = repo_group_form.to_python(dict(self.request.POST))
308 form_result = repo_group_form.to_python(dict(self.request.POST))
309 copy_permissions = form_result.get('group_copy_permissions')
309 copy_permissions = form_result.get('group_copy_permissions')
310 repo_group = RepoGroupModel().create(
310 repo_group = RepoGroupModel().create(
311 group_name=form_result['group_name_full'],
311 group_name=form_result['group_name_full'],
312 group_description=form_result['group_description'],
312 group_description=form_result['group_description'],
313 owner=owner.user_id,
313 owner=owner.user_id,
314 copy_permissions=form_result['group_copy_permissions']
314 copy_permissions=form_result['group_copy_permissions']
315 )
315 )
316 Session().flush()
316 Session().flush()
317
317
318 repo_group_data = repo_group.get_api_data()
318 repo_group_data = repo_group.get_api_data()
319 audit_logger.store_web(
319 audit_logger.store_web(
320 'repo_group.create', action_data={'data': repo_group_data},
320 'repo_group.create', action_data={'data': repo_group_data},
321 user=self._rhodecode_user)
321 user=self._rhodecode_user)
322
322
323 Session().commit()
323 Session().commit()
324
324
325 _new_group_name = form_result['group_name_full']
325 _new_group_name = form_result['group_name_full']
326
326
327 repo_group_url = h.link_to(
327 repo_group_url = h.link_to(
328 _new_group_name,
328 _new_group_name,
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
329 h.route_path('repo_group_home', repo_group_name=_new_group_name))
330 h.flash(h.literal(_('Created repository group %s')
330 h.flash(h.literal(_('Created repository group %s')
331 % repo_group_url), category='success')
331 % repo_group_url), category='success')
332
332
333 except formencode.Invalid as errors:
333 except formencode.Invalid as errors:
334 data = render(
334 data = render(
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
335 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
336 self._get_template_context(c), self.request)
336 self._get_template_context(c), self.request)
337 html = formencode.htmlfill.render(
337 html = formencode.htmlfill.render(
338 data,
338 data,
339 defaults=errors.value,
339 defaults=errors.value,
340 errors=errors.error_dict or {},
340 errors=errors.error_dict or {},
341 prefix_error=False,
341 prefix_error=False,
342 encoding="UTF-8",
342 encoding="UTF-8",
343 force_defaults=False
343 force_defaults=False
344 )
344 )
345 return Response(html)
345 return Response(html)
346 except Exception:
346 except Exception:
347 log.exception("Exception during creation of repository group")
347 log.exception("Exception during creation of repository group")
348 h.flash(_('Error occurred during creation of repository group %s')
348 h.flash(_('Error occurred during creation of repository group %s')
349 % repo_group_name, category='error')
349 % repo_group_name, category='error')
350 raise HTTPFound(h.route_path('home'))
350 raise HTTPFound(h.route_path('home'))
351
351
352 affected_user_ids = [self._rhodecode_user.user_id]
352 PermissionModel().trigger_permission_flush()
353 if copy_permissions:
354 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
355 copy_perms = [perm['user_id'] for perm in user_group_perms]
356 # also include those newly created by copy
357 affected_user_ids.extend(copy_perms)
358 PermissionModel().trigger_permission_flush(affected_user_ids)
359
353
360 raise HTTPFound(
354 raise HTTPFound(
361 h.route_path('repo_group_home',
355 h.route_path('repo_group_home',
362 repo_group_name=form_result['group_name_full']))
356 repo_group_name=form_result['group_name_full']))
@@ -1,253 +1,249 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26
26
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.celerylib.utils import get_task_id
32 from rhodecode.lib.celerylib.utils import get_task_id
33
33
34 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
35 LoginRequired, CSRFRequired, NotAnonymous,
35 LoginRequired, CSRFRequired, NotAnonymous,
36 HasPermissionAny, HasRepoGroupPermissionAny)
36 HasPermissionAny, HasRepoGroupPermissionAny)
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils2 import safe_int, safe_unicode
39 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 from rhodecode.model.forms import RepoForm
40 from rhodecode.model.forms import RepoForm
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.db import (
45 from rhodecode.model.db import (
46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class AdminReposView(BaseAppView, DataGridAppView):
51 class AdminReposView(BaseAppView, DataGridAppView):
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55 return c
55 return c
56
56
57 def _load_form_data(self, c):
57 def _load_form_data(self, c):
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 perm_set=['group.write', 'group.admin'])
59 perm_set=['group.write', 'group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @NotAnonymous()
65 @NotAnonymous()
66 # perms check inside
66 # perms check inside
67 def repository_list(self):
67 def repository_list(self):
68 c = self.load_default_context()
68 c = self.load_default_context()
69 return self._get_template_context(c)
69 return self._get_template_context(c)
70
70
71 @LoginRequired()
71 @LoginRequired()
72 @NotAnonymous()
72 @NotAnonymous()
73 # perms check inside
73 # perms check inside
74 def repository_list_data(self):
74 def repository_list_data(self):
75 self.load_default_context()
75 self.load_default_context()
76 column_map = {
76 column_map = {
77 'name': 'repo_name',
77 'name': 'repo_name',
78 'desc': 'description',
78 'desc': 'description',
79 'last_change': 'updated_on',
79 'last_change': 'updated_on',
80 'owner': 'user_username',
80 'owner': 'user_username',
81 }
81 }
82 draw, start, limit = self._extract_chunk(self.request)
82 draw, start, limit = self._extract_chunk(self.request)
83 search_q, order_by, order_dir = self._extract_ordering(
83 search_q, order_by, order_dir = self._extract_ordering(
84 self.request, column_map=column_map)
84 self.request, column_map=column_map)
85
85
86 _perms = ['repository.admin']
86 _perms = ['repository.admin']
87 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
87 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
88
88
89 repos_data_total_count = Repository.query() \
89 repos_data_total_count = Repository.query() \
90 .filter(or_(
90 .filter(or_(
91 # generate multiple IN to fix limitation problems
91 # generate multiple IN to fix limitation problems
92 *in_filter_generator(Repository.repo_id, allowed_ids))
92 *in_filter_generator(Repository.repo_id, allowed_ids))
93 ) \
93 ) \
94 .count()
94 .count()
95
95
96 base_q = Session.query(
96 base_q = Session.query(
97 Repository.repo_id,
97 Repository.repo_id,
98 Repository.repo_name,
98 Repository.repo_name,
99 Repository.description,
99 Repository.description,
100 Repository.repo_type,
100 Repository.repo_type,
101 Repository.repo_state,
101 Repository.repo_state,
102 Repository.private,
102 Repository.private,
103 Repository.archived,
103 Repository.archived,
104 Repository.fork,
104 Repository.fork,
105 Repository.updated_on,
105 Repository.updated_on,
106 Repository._changeset_cache,
106 Repository._changeset_cache,
107 User,
107 User,
108 ) \
108 ) \
109 .filter(or_(
109 .filter(or_(
110 # generate multiple IN to fix limitation problems
110 # generate multiple IN to fix limitation problems
111 *in_filter_generator(Repository.repo_id, allowed_ids))
111 *in_filter_generator(Repository.repo_id, allowed_ids))
112 ) \
112 ) \
113 .join(User, User.user_id == Repository.user_id) \
113 .join(User, User.user_id == Repository.user_id) \
114 .group_by(Repository, User)
114 .group_by(Repository, User)
115
115
116 if search_q:
116 if search_q:
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 base_q = base_q.filter(or_(
118 base_q = base_q.filter(or_(
119 Repository.repo_name.ilike(like_expression),
119 Repository.repo_name.ilike(like_expression),
120 ))
120 ))
121
121
122 repos_data_total_filtered_count = base_q.count()
122 repos_data_total_filtered_count = base_q.count()
123
123
124 sort_defined = False
124 sort_defined = False
125 if order_by == 'repo_name':
125 if order_by == 'repo_name':
126 sort_col = func.lower(Repository.repo_name)
126 sort_col = func.lower(Repository.repo_name)
127 sort_defined = True
127 sort_defined = True
128 elif order_by == 'user_username':
128 elif order_by == 'user_username':
129 sort_col = User.username
129 sort_col = User.username
130 else:
130 else:
131 sort_col = getattr(Repository, order_by, None)
131 sort_col = getattr(Repository, order_by, None)
132
132
133 if sort_defined or sort_col:
133 if sort_defined or sort_col:
134 if order_dir == 'asc':
134 if order_dir == 'asc':
135 sort_col = sort_col.asc()
135 sort_col = sort_col.asc()
136 else:
136 else:
137 sort_col = sort_col.desc()
137 sort_col = sort_col.desc()
138
138
139 base_q = base_q.order_by(sort_col)
139 base_q = base_q.order_by(sort_col)
140 base_q = base_q.offset(start).limit(limit)
140 base_q = base_q.offset(start).limit(limit)
141
141
142 repos_list = base_q.all()
142 repos_list = base_q.all()
143
143
144 repos_data = RepoModel().get_repos_as_dict(
144 repos_data = RepoModel().get_repos_as_dict(
145 repo_list=repos_list, admin=True, super_user_actions=True)
145 repo_list=repos_list, admin=True, super_user_actions=True)
146
146
147 data = ({
147 data = ({
148 'draw': draw,
148 'draw': draw,
149 'data': repos_data,
149 'data': repos_data,
150 'recordsTotal': repos_data_total_count,
150 'recordsTotal': repos_data_total_count,
151 'recordsFiltered': repos_data_total_filtered_count,
151 'recordsFiltered': repos_data_total_filtered_count,
152 })
152 })
153 return data
153 return data
154
154
155 @LoginRequired()
155 @LoginRequired()
156 @NotAnonymous()
156 @NotAnonymous()
157 # perms check inside
157 # perms check inside
158 def repository_new(self):
158 def repository_new(self):
159 c = self.load_default_context()
159 c = self.load_default_context()
160
160
161 new_repo = self.request.GET.get('repo', '')
161 new_repo = self.request.GET.get('repo', '')
162 parent_group_id = safe_int(self.request.GET.get('parent_group'))
162 parent_group_id = safe_int(self.request.GET.get('parent_group'))
163 _gr = RepoGroup.get(parent_group_id)
163 _gr = RepoGroup.get(parent_group_id)
164
164
165 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
165 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
166 # you're not super admin nor have global create permissions,
166 # you're not super admin nor have global create permissions,
167 # but maybe you have at least write permission to a parent group ?
167 # but maybe you have at least write permission to a parent group ?
168
168
169 gr_name = _gr.group_name if _gr else None
169 gr_name = _gr.group_name if _gr else None
170 # create repositories with write permission on group is set to true
170 # create repositories with write permission on group is set to true
171 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
171 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
172 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
172 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
173 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
173 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
174 if not (group_admin or (group_write and create_on_write)):
174 if not (group_admin or (group_write and create_on_write)):
175 raise HTTPForbidden()
175 raise HTTPForbidden()
176
176
177 self._load_form_data(c)
177 self._load_form_data(c)
178 c.new_repo = repo_name_slug(new_repo)
178 c.new_repo = repo_name_slug(new_repo)
179
179
180 # apply the defaults from defaults page
180 # apply the defaults from defaults page
181 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
181 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
182 # set checkbox to autochecked
182 # set checkbox to autochecked
183 defaults['repo_copy_permissions'] = True
183 defaults['repo_copy_permissions'] = True
184
184
185 parent_group_choice = '-1'
185 parent_group_choice = '-1'
186 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
186 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
187 parent_group_choice = self._rhodecode_user.personal_repo_group
187 parent_group_choice = self._rhodecode_user.personal_repo_group
188
188
189 if parent_group_id and _gr:
189 if parent_group_id and _gr:
190 if parent_group_id in [x[0] for x in c.repo_groups]:
190 if parent_group_id in [x[0] for x in c.repo_groups]:
191 parent_group_choice = safe_unicode(parent_group_id)
191 parent_group_choice = safe_unicode(parent_group_id)
192
192
193 defaults.update({'repo_group': parent_group_choice})
193 defaults.update({'repo_group': parent_group_choice})
194
194
195 data = render('rhodecode:templates/admin/repos/repo_add.mako',
195 data = render('rhodecode:templates/admin/repos/repo_add.mako',
196 self._get_template_context(c), self.request)
196 self._get_template_context(c), self.request)
197 html = formencode.htmlfill.render(
197 html = formencode.htmlfill.render(
198 data,
198 data,
199 defaults=defaults,
199 defaults=defaults,
200 encoding="UTF-8",
200 encoding="UTF-8",
201 force_defaults=False
201 force_defaults=False
202 )
202 )
203 return Response(html)
203 return Response(html)
204
204
205 @LoginRequired()
205 @LoginRequired()
206 @NotAnonymous()
206 @NotAnonymous()
207 @CSRFRequired()
207 @CSRFRequired()
208 # perms check inside
208 # perms check inside
209 def repository_create(self):
209 def repository_create(self):
210 c = self.load_default_context()
210 c = self.load_default_context()
211
211
212 form_result = {}
212 form_result = {}
213 self._load_form_data(c)
213 self._load_form_data(c)
214
214
215 try:
215 try:
216 # CanWriteToGroup validators checks permissions of this POST
216 # CanWriteToGroup validators checks permissions of this POST
217 form = RepoForm(
217 form = RepoForm(
218 self.request.translate, repo_groups=c.repo_groups_choices)()
218 self.request.translate, repo_groups=c.repo_groups_choices)()
219 form_result = form.to_python(dict(self.request.POST))
219 form_result = form.to_python(dict(self.request.POST))
220 copy_permissions = form_result.get('repo_copy_permissions')
220 copy_permissions = form_result.get('repo_copy_permissions')
221 # create is done sometimes async on celery, db transaction
221 # create is done sometimes async on celery, db transaction
222 # management is handled there.
222 # management is handled there.
223 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
223 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
224 task_id = get_task_id(task)
224 task_id = get_task_id(task)
225 except formencode.Invalid as errors:
225 except formencode.Invalid as errors:
226 data = render('rhodecode:templates/admin/repos/repo_add.mako',
226 data = render('rhodecode:templates/admin/repos/repo_add.mako',
227 self._get_template_context(c), self.request)
227 self._get_template_context(c), self.request)
228 html = formencode.htmlfill.render(
228 html = formencode.htmlfill.render(
229 data,
229 data,
230 defaults=errors.value,
230 defaults=errors.value,
231 errors=errors.error_dict or {},
231 errors=errors.error_dict or {},
232 prefix_error=False,
232 prefix_error=False,
233 encoding="UTF-8",
233 encoding="UTF-8",
234 force_defaults=False
234 force_defaults=False
235 )
235 )
236 return Response(html)
236 return Response(html)
237
237
238 except Exception as e:
238 except Exception as e:
239 msg = self._log_creation_exception(e, form_result.get('repo_name'))
239 msg = self._log_creation_exception(e, form_result.get('repo_name'))
240 h.flash(msg, category='error')
240 h.flash(msg, category='error')
241 raise HTTPFound(h.route_path('home'))
241 raise HTTPFound(h.route_path('home'))
242
242
243 repo_name = form_result.get('repo_name_full')
243 repo_name = form_result.get('repo_name_full')
244
244
245 affected_user_ids = [self._rhodecode_user.user_id]
245 PermissionModel().trigger_permission_flush()
246 if copy_permissions:
247 # permission flush is done in repo creating
248 pass
249 PermissionModel().trigger_permission_flush(affected_user_ids)
250
246
251 raise HTTPFound(
247 raise HTTPFound(
252 h.route_path('repo_creating', repo_name=repo_name,
248 h.route_path('repo_creating', repo_name=repo_name,
253 _query=dict(task_id=task_id)))
249 _query=dict(task_id=task_id)))
@@ -1,450 +1,450 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2020 RhodeCode GmbH
3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 import string
22 import string
23 import collections
23 import collections
24 import logging
24 import logging
25 import requests
25 import requests
26 import urllib
26 import urllib
27 from requests.adapters import HTTPAdapter
27 from requests.adapters import HTTPAdapter
28 from requests.packages.urllib3.util.retry import Retry
28 from requests.packages.urllib3.util.retry import Retry
29
29
30 from mako import exceptions
30 from mako import exceptions
31
31
32 from rhodecode.lib.utils2 import safe_str
32 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.translation import _
33 from rhodecode.translation import _
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class UrlTmpl(string.Template):
39 class UrlTmpl(string.Template):
40
40
41 def safe_substitute(self, **kws):
41 def safe_substitute(self, **kws):
42 # url encode the kw for usage in url
42 # url encode the kw for usage in url
43 kws = {k: urllib.quote(safe_str(v)) for k, v in kws.items()}
43 kws = {k: urllib.quote(safe_str(v)) for k, v in kws.items()}
44 return super(UrlTmpl, self).safe_substitute(**kws)
44 return super(UrlTmpl, self).safe_substitute(**kws)
45
45
46
46
47 class IntegrationTypeBase(object):
47 class IntegrationTypeBase(object):
48 """ Base class for IntegrationType plugins """
48 """ Base class for IntegrationType plugins """
49 is_dummy = False
49 is_dummy = False
50 description = ''
50 description = ''
51
51
52 @classmethod
52 @classmethod
53 def icon(cls):
53 def icon(cls):
54 return '''
54 return '''
55 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
55 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
56 <svg
56 <svg
57 xmlns:dc="http://purl.org/dc/elements/1.1/"
57 xmlns:dc="http://purl.org/dc/elements/1.1/"
58 xmlns:cc="http://creativecommons.org/ns#"
58 xmlns:cc="http://creativecommons.org/ns#"
59 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
59 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
60 xmlns:svg="http://www.w3.org/2000/svg"
60 xmlns:svg="http://www.w3.org/2000/svg"
61 xmlns="http://www.w3.org/2000/svg"
61 xmlns="http://www.w3.org/2000/svg"
62 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
62 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
63 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
63 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
64 viewBox="0 -256 1792 1792"
64 viewBox="0 -256 1792 1792"
65 id="svg3025"
65 id="svg3025"
66 version="1.1"
66 version="1.1"
67 inkscape:version="0.48.3.1 r9886"
67 inkscape:version="0.48.3.1 r9886"
68 width="100%"
68 width="100%"
69 height="100%"
69 height="100%"
70 sodipodi:docname="cog_font_awesome.svg">
70 sodipodi:docname="cog_font_awesome.svg">
71 <metadata
71 <metadata
72 id="metadata3035">
72 id="metadata3035">
73 <rdf:RDF>
73 <rdf:RDF>
74 <cc:Work
74 <cc:Work
75 rdf:about="">
75 rdf:about="">
76 <dc:format>image/svg+xml</dc:format>
76 <dc:format>image/svg+xml</dc:format>
77 <dc:type
77 <dc:type
78 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
78 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
79 </cc:Work>
79 </cc:Work>
80 </rdf:RDF>
80 </rdf:RDF>
81 </metadata>
81 </metadata>
82 <defs
82 <defs
83 id="defs3033" />
83 id="defs3033" />
84 <sodipodi:namedview
84 <sodipodi:namedview
85 pagecolor="#ffffff"
85 pagecolor="#ffffff"
86 bordercolor="#666666"
86 bordercolor="#666666"
87 borderopacity="1"
87 borderopacity="1"
88 objecttolerance="10"
88 objecttolerance="10"
89 gridtolerance="10"
89 gridtolerance="10"
90 guidetolerance="10"
90 guidetolerance="10"
91 inkscape:pageopacity="0"
91 inkscape:pageopacity="0"
92 inkscape:pageshadow="2"
92 inkscape:pageshadow="2"
93 inkscape:window-width="640"
93 inkscape:window-width="640"
94 inkscape:window-height="480"
94 inkscape:window-height="480"
95 id="namedview3031"
95 id="namedview3031"
96 showgrid="false"
96 showgrid="false"
97 inkscape:zoom="0.13169643"
97 inkscape:zoom="0.13169643"
98 inkscape:cx="896"
98 inkscape:cx="896"
99 inkscape:cy="896"
99 inkscape:cy="896"
100 inkscape:window-x="0"
100 inkscape:window-x="0"
101 inkscape:window-y="25"
101 inkscape:window-y="25"
102 inkscape:window-maximized="0"
102 inkscape:window-maximized="0"
103 inkscape:current-layer="svg3025" />
103 inkscape:current-layer="svg3025" />
104 <g
104 <g
105 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
105 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
106 id="g3027">
106 id="g3027">
107 <path
107 <path
108 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
108 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
109 id="path3029"
109 id="path3029"
110 inkscape:connector-curvature="0"
110 inkscape:connector-curvature="0"
111 style="fill:currentColor" />
111 style="fill:currentColor" />
112 </g>
112 </g>
113 </svg>
113 </svg>
114 '''
114 '''
115
115
116 def __init__(self, settings):
116 def __init__(self, settings):
117 """
117 """
118 :param settings: dict of settings to be used for the integration
118 :param settings: dict of settings to be used for the integration
119 """
119 """
120 self.settings = settings
120 self.settings = settings
121
121
122 def settings_schema(self):
122 def settings_schema(self):
123 """
123 """
124 A colander schema of settings for the integration type
124 A colander schema of settings for the integration type
125 """
125 """
126 return colander.Schema()
126 return colander.Schema()
127
127
128 def event_enabled(self, event):
128 def event_enabled(self, event):
129 """
129 """
130 Checks if submitted event is enabled based on the plugin settings
130 Checks if submitted event is enabled based on the plugin settings
131 :param event:
131 :param event:
132 :return: bool
132 :return: bool
133 """
133 """
134 allowed_events = self.settings['events']
134 allowed_events = self.settings.get('events') or []
135 if event.name not in allowed_events:
135 if event.name not in allowed_events:
136 log.debug('event ignored: %r event %s not in allowed set of events %s',
136 log.debug('event ignored: %r event %s not in allowed set of events %s',
137 event, event.name, allowed_events)
137 event, event.name, allowed_events)
138 return False
138 return False
139 return True
139 return True
140
140
141
141
142 class EEIntegration(IntegrationTypeBase):
142 class EEIntegration(IntegrationTypeBase):
143 description = 'Integration available in RhodeCode EE edition.'
143 description = 'Integration available in RhodeCode EE edition.'
144 is_dummy = True
144 is_dummy = True
145
145
146 def __init__(self, name, key, settings=None):
146 def __init__(self, name, key, settings=None):
147 self.display_name = name
147 self.display_name = name
148 self.key = key
148 self.key = key
149 super(EEIntegration, self).__init__(settings)
149 super(EEIntegration, self).__init__(settings)
150
150
151
151
152 # Helpers #
152 # Helpers #
153 # updating this required to update the `common_vars` as well.
153 # updating this required to update the `common_vars` as well.
154 WEBHOOK_URL_VARS = [
154 WEBHOOK_URL_VARS = [
155 # GENERAL
155 # GENERAL
156 ('General', [
156 ('General', [
157 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
157 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
158 ('repo_name', 'Full name of the repository'),
158 ('repo_name', 'Full name of the repository'),
159 ('repo_type', 'VCS type of repository'),
159 ('repo_type', 'VCS type of repository'),
160 ('repo_id', 'Unique id of repository'),
160 ('repo_id', 'Unique id of repository'),
161 ('repo_url', 'Repository url'),
161 ('repo_url', 'Repository url'),
162 ]
162 ]
163 ),
163 ),
164 # extra repo fields
164 # extra repo fields
165 ('Repository', [
165 ('Repository', [
166 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
166 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
167 ]
167 ]
168 ),
168 ),
169 # special attrs below that we handle, using multi-call
169 # special attrs below that we handle, using multi-call
170 ('Commit push - Multicalls', [
170 ('Commit push - Multicalls', [
171 ('branch', 'Name of each branch submitted, if any.'),
171 ('branch', 'Name of each branch submitted, if any.'),
172 ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'),
172 ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'),
173 ('commit_id', 'ID (full sha) of each commit submitted, if any.'),
173 ('commit_id', 'ID (full sha) of each commit submitted, if any.'),
174 ]
174 ]
175 ),
175 ),
176 # pr events vars
176 # pr events vars
177 ('Pull request', [
177 ('Pull request', [
178 ('pull_request_id', 'Unique ID of the pull request.'),
178 ('pull_request_id', 'Unique ID of the pull request.'),
179 ('pull_request_title', 'Title of the pull request.'),
179 ('pull_request_title', 'Title of the pull request.'),
180 ('pull_request_url', 'Pull request url.'),
180 ('pull_request_url', 'Pull request url.'),
181 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
181 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
182 ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. '
182 ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. '
183 'Changes after PR update'),
183 'Changes after PR update'),
184 ]
184 ]
185 ),
185 ),
186 # commit comment event vars
186 # commit comment event vars
187 ('Commit comment', [
187 ('Commit comment', [
188 ('commit_comment_id', 'Unique ID of the comment made on a commit.'),
188 ('commit_comment_id', 'Unique ID of the comment made on a commit.'),
189 ('commit_comment_text', 'Text of commit comment.'),
189 ('commit_comment_text', 'Text of commit comment.'),
190 ('commit_comment_type', 'Type of comment, e.g note/todo.'),
190 ('commit_comment_type', 'Type of comment, e.g note/todo.'),
191
191
192 ('commit_comment_f_path', 'Optionally path of file for inline comments.'),
192 ('commit_comment_f_path', 'Optionally path of file for inline comments.'),
193 ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'),
193 ('commit_comment_line_no', 'Line number of the file: eg o10, or n200'),
194
194
195 ('commit_comment_commit_id', 'Commit id that comment was left at.'),
195 ('commit_comment_commit_id', 'Commit id that comment was left at.'),
196 ('commit_comment_commit_branch', 'Commit branch that comment was left at'),
196 ('commit_comment_commit_branch', 'Commit branch that comment was left at'),
197 ('commit_comment_commit_message', 'Commit message that comment was left at'),
197 ('commit_comment_commit_message', 'Commit message that comment was left at'),
198 ]
198 ]
199 ),
199 ),
200 # user who triggers the call
200 # user who triggers the call
201 ('Caller', [
201 ('Caller', [
202 ('username', 'User who triggered the call.'),
202 ('username', 'User who triggered the call.'),
203 ('user_id', 'User id who triggered the call.'),
203 ('user_id', 'User id who triggered the call.'),
204 ]
204 ]
205 ),
205 ),
206 ]
206 ]
207
207
208 # common vars for url template used for CI plugins. Shared with webhook
208 # common vars for url template used for CI plugins. Shared with webhook
209 CI_URL_VARS = WEBHOOK_URL_VARS
209 CI_URL_VARS = WEBHOOK_URL_VARS
210
210
211
211
212 class CommitParsingDataHandler(object):
212 class CommitParsingDataHandler(object):
213
213
214 def aggregate_branch_data(self, branches, commits):
214 def aggregate_branch_data(self, branches, commits):
215 branch_data = collections.OrderedDict()
215 branch_data = collections.OrderedDict()
216 for obj in branches:
216 for obj in branches:
217 branch_data[obj['name']] = obj
217 branch_data[obj['name']] = obj
218
218
219 branches_commits = collections.OrderedDict()
219 branches_commits = collections.OrderedDict()
220 for commit in commits:
220 for commit in commits:
221 if commit.get('git_ref_change'):
221 if commit.get('git_ref_change'):
222 # special case for GIT that allows creating tags,
222 # special case for GIT that allows creating tags,
223 # deleting branches without associated commit
223 # deleting branches without associated commit
224 continue
224 continue
225 commit_branch = commit['branch']
225 commit_branch = commit['branch']
226
226
227 if commit_branch not in branches_commits:
227 if commit_branch not in branches_commits:
228 _branch = branch_data[commit_branch] \
228 _branch = branch_data[commit_branch] \
229 if commit_branch else commit_branch
229 if commit_branch else commit_branch
230 branch_commits = {'branch': _branch,
230 branch_commits = {'branch': _branch,
231 'branch_head': '',
231 'branch_head': '',
232 'commits': []}
232 'commits': []}
233 branches_commits[commit_branch] = branch_commits
233 branches_commits[commit_branch] = branch_commits
234
234
235 branch_commits = branches_commits[commit_branch]
235 branch_commits = branches_commits[commit_branch]
236 branch_commits['commits'].append(commit)
236 branch_commits['commits'].append(commit)
237 branch_commits['branch_head'] = commit['raw_id']
237 branch_commits['branch_head'] = commit['raw_id']
238 return branches_commits
238 return branches_commits
239
239
240
240
241 class WebhookDataHandler(CommitParsingDataHandler):
241 class WebhookDataHandler(CommitParsingDataHandler):
242 name = 'webhook'
242 name = 'webhook'
243
243
244 def __init__(self, template_url, headers):
244 def __init__(self, template_url, headers):
245 self.template_url = template_url
245 self.template_url = template_url
246 self.headers = headers
246 self.headers = headers
247
247
248 def get_base_parsed_template(self, data):
248 def get_base_parsed_template(self, data):
249 """
249 """
250 initially parses the passed in template with some common variables
250 initially parses the passed in template with some common variables
251 available on ALL calls
251 available on ALL calls
252 """
252 """
253 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
253 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
254 common_vars = {
254 common_vars = {
255 'repo_name': data['repo']['repo_name'],
255 'repo_name': data['repo']['repo_name'],
256 'repo_type': data['repo']['repo_type'],
256 'repo_type': data['repo']['repo_type'],
257 'repo_id': data['repo']['repo_id'],
257 'repo_id': data['repo']['repo_id'],
258 'repo_url': data['repo']['url'],
258 'repo_url': data['repo']['url'],
259 'username': data['actor']['username'],
259 'username': data['actor']['username'],
260 'user_id': data['actor']['user_id'],
260 'user_id': data['actor']['user_id'],
261 'event_name': data['name']
261 'event_name': data['name']
262 }
262 }
263
263
264 extra_vars = {}
264 extra_vars = {}
265 for extra_key, extra_val in data['repo']['extra_fields'].items():
265 for extra_key, extra_val in data['repo']['extra_fields'].items():
266 extra_vars['extra__{}'.format(extra_key)] = extra_val
266 extra_vars['extra__{}'.format(extra_key)] = extra_val
267 common_vars.update(extra_vars)
267 common_vars.update(extra_vars)
268
268
269 template_url = self.template_url.replace('${extra:', '${extra__')
269 template_url = self.template_url.replace('${extra:', '${extra__')
270 for k, v in common_vars.items():
270 for k, v in common_vars.items():
271 template_url = UrlTmpl(template_url).safe_substitute(**{k: v})
271 template_url = UrlTmpl(template_url).safe_substitute(**{k: v})
272 return template_url
272 return template_url
273
273
274 def repo_push_event_handler(self, event, data):
274 def repo_push_event_handler(self, event, data):
275 url = self.get_base_parsed_template(data)
275 url = self.get_base_parsed_template(data)
276 url_calls = []
276 url_calls = []
277
277
278 branches_commits = self.aggregate_branch_data(
278 branches_commits = self.aggregate_branch_data(
279 data['push']['branches'], data['push']['commits'])
279 data['push']['branches'], data['push']['commits'])
280 if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url:
280 if '${branch}' in url or '${branch_head}' in url or '${commit_id}' in url:
281 # call it multiple times, for each branch if used in variables
281 # call it multiple times, for each branch if used in variables
282 for branch, commit_ids in branches_commits.items():
282 for branch, commit_ids in branches_commits.items():
283 branch_url = UrlTmpl(url).safe_substitute(branch=branch)
283 branch_url = UrlTmpl(url).safe_substitute(branch=branch)
284
284
285 if '${branch_head}' in branch_url:
285 if '${branch_head}' in branch_url:
286 # last commit in the aggregate is the head of the branch
286 # last commit in the aggregate is the head of the branch
287 branch_head = commit_ids['branch_head']
287 branch_head = commit_ids['branch_head']
288 branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head)
288 branch_url = UrlTmpl(branch_url).safe_substitute(branch_head=branch_head)
289
289
290 # call further down for each commit if used
290 # call further down for each commit if used
291 if '${commit_id}' in branch_url:
291 if '${commit_id}' in branch_url:
292 for commit_data in commit_ids['commits']:
292 for commit_data in commit_ids['commits']:
293 commit_id = commit_data['raw_id']
293 commit_id = commit_data['raw_id']
294 commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id)
294 commit_url = UrlTmpl(branch_url).safe_substitute(commit_id=commit_id)
295 # register per-commit call
295 # register per-commit call
296 log.debug(
296 log.debug(
297 'register %s call(%s) to url %s',
297 'register %s call(%s) to url %s',
298 self.name, event, commit_url)
298 self.name, event, commit_url)
299 url_calls.append(
299 url_calls.append(
300 (commit_url, self.headers, data))
300 (commit_url, self.headers, data))
301
301
302 else:
302 else:
303 # register per-branch call
303 # register per-branch call
304 log.debug('register %s call(%s) to url %s',
304 log.debug('register %s call(%s) to url %s',
305 self.name, event, branch_url)
305 self.name, event, branch_url)
306 url_calls.append((branch_url, self.headers, data))
306 url_calls.append((branch_url, self.headers, data))
307
307
308 else:
308 else:
309 log.debug('register %s call(%s) to url %s', self.name, event, url)
309 log.debug('register %s call(%s) to url %s', self.name, event, url)
310 url_calls.append((url, self.headers, data))
310 url_calls.append((url, self.headers, data))
311
311
312 return url_calls
312 return url_calls
313
313
314 def repo_commit_comment_handler(self, event, data):
314 def repo_commit_comment_handler(self, event, data):
315 url = self.get_base_parsed_template(data)
315 url = self.get_base_parsed_template(data)
316 log.debug('register %s call(%s) to url %s', self.name, event, url)
316 log.debug('register %s call(%s) to url %s', self.name, event, url)
317 comment_vars = [
317 comment_vars = [
318 ('commit_comment_id', data['comment']['comment_id']),
318 ('commit_comment_id', data['comment']['comment_id']),
319 ('commit_comment_text', data['comment']['comment_text']),
319 ('commit_comment_text', data['comment']['comment_text']),
320 ('commit_comment_type', data['comment']['comment_type']),
320 ('commit_comment_type', data['comment']['comment_type']),
321
321
322 ('commit_comment_f_path', data['comment']['comment_f_path']),
322 ('commit_comment_f_path', data['comment']['comment_f_path']),
323 ('commit_comment_line_no', data['comment']['comment_line_no']),
323 ('commit_comment_line_no', data['comment']['comment_line_no']),
324
324
325 ('commit_comment_commit_id', data['commit']['commit_id']),
325 ('commit_comment_commit_id', data['commit']['commit_id']),
326 ('commit_comment_commit_branch', data['commit']['commit_branch']),
326 ('commit_comment_commit_branch', data['commit']['commit_branch']),
327 ('commit_comment_commit_message', data['commit']['commit_message']),
327 ('commit_comment_commit_message', data['commit']['commit_message']),
328 ]
328 ]
329 for k, v in comment_vars:
329 for k, v in comment_vars:
330 url = UrlTmpl(url).safe_substitute(**{k: v})
330 url = UrlTmpl(url).safe_substitute(**{k: v})
331
331
332 return [(url, self.headers, data)]
332 return [(url, self.headers, data)]
333
333
334 def repo_commit_comment_edit_handler(self, event, data):
334 def repo_commit_comment_edit_handler(self, event, data):
335 url = self.get_base_parsed_template(data)
335 url = self.get_base_parsed_template(data)
336 log.debug('register %s call(%s) to url %s', self.name, event, url)
336 log.debug('register %s call(%s) to url %s', self.name, event, url)
337 comment_vars = [
337 comment_vars = [
338 ('commit_comment_id', data['comment']['comment_id']),
338 ('commit_comment_id', data['comment']['comment_id']),
339 ('commit_comment_text', data['comment']['comment_text']),
339 ('commit_comment_text', data['comment']['comment_text']),
340 ('commit_comment_type', data['comment']['comment_type']),
340 ('commit_comment_type', data['comment']['comment_type']),
341
341
342 ('commit_comment_f_path', data['comment']['comment_f_path']),
342 ('commit_comment_f_path', data['comment']['comment_f_path']),
343 ('commit_comment_line_no', data['comment']['comment_line_no']),
343 ('commit_comment_line_no', data['comment']['comment_line_no']),
344
344
345 ('commit_comment_commit_id', data['commit']['commit_id']),
345 ('commit_comment_commit_id', data['commit']['commit_id']),
346 ('commit_comment_commit_branch', data['commit']['commit_branch']),
346 ('commit_comment_commit_branch', data['commit']['commit_branch']),
347 ('commit_comment_commit_message', data['commit']['commit_message']),
347 ('commit_comment_commit_message', data['commit']['commit_message']),
348 ]
348 ]
349 for k, v in comment_vars:
349 for k, v in comment_vars:
350 url = UrlTmpl(url).safe_substitute(**{k: v})
350 url = UrlTmpl(url).safe_substitute(**{k: v})
351
351
352 return [(url, self.headers, data)]
352 return [(url, self.headers, data)]
353
353
354 def repo_create_event_handler(self, event, data):
354 def repo_create_event_handler(self, event, data):
355 url = self.get_base_parsed_template(data)
355 url = self.get_base_parsed_template(data)
356 log.debug('register %s call(%s) to url %s', self.name, event, url)
356 log.debug('register %s call(%s) to url %s', self.name, event, url)
357 return [(url, self.headers, data)]
357 return [(url, self.headers, data)]
358
358
359 def pull_request_event_handler(self, event, data):
359 def pull_request_event_handler(self, event, data):
360 url = self.get_base_parsed_template(data)
360 url = self.get_base_parsed_template(data)
361 log.debug('register %s call(%s) to url %s', self.name, event, url)
361 log.debug('register %s call(%s) to url %s', self.name, event, url)
362 pr_vars = [
362 pr_vars = [
363 ('pull_request_id', data['pullrequest']['pull_request_id']),
363 ('pull_request_id', data['pullrequest']['pull_request_id']),
364 ('pull_request_title', data['pullrequest']['title']),
364 ('pull_request_title', data['pullrequest']['title']),
365 ('pull_request_url', data['pullrequest']['url']),
365 ('pull_request_url', data['pullrequest']['url']),
366 ('pull_request_shadow_url', data['pullrequest']['shadow_url']),
366 ('pull_request_shadow_url', data['pullrequest']['shadow_url']),
367 ('pull_request_commits_uid', data['pullrequest']['commits_uid']),
367 ('pull_request_commits_uid', data['pullrequest']['commits_uid']),
368 ]
368 ]
369 for k, v in pr_vars:
369 for k, v in pr_vars:
370 url = UrlTmpl(url).safe_substitute(**{k: v})
370 url = UrlTmpl(url).safe_substitute(**{k: v})
371
371
372 return [(url, self.headers, data)]
372 return [(url, self.headers, data)]
373
373
374 def __call__(self, event, data):
374 def __call__(self, event, data):
375 from rhodecode import events
375 from rhodecode import events
376
376
377 if isinstance(event, events.RepoPushEvent):
377 if isinstance(event, events.RepoPushEvent):
378 return self.repo_push_event_handler(event, data)
378 return self.repo_push_event_handler(event, data)
379 elif isinstance(event, events.RepoCreateEvent):
379 elif isinstance(event, events.RepoCreateEvent):
380 return self.repo_create_event_handler(event, data)
380 return self.repo_create_event_handler(event, data)
381 elif isinstance(event, events.RepoCommitCommentEvent):
381 elif isinstance(event, events.RepoCommitCommentEvent):
382 return self.repo_commit_comment_handler(event, data)
382 return self.repo_commit_comment_handler(event, data)
383 elif isinstance(event, events.RepoCommitCommentEditEvent):
383 elif isinstance(event, events.RepoCommitCommentEditEvent):
384 return self.repo_commit_comment_edit_handler(event, data)
384 return self.repo_commit_comment_edit_handler(event, data)
385 elif isinstance(event, events.PullRequestEvent):
385 elif isinstance(event, events.PullRequestEvent):
386 return self.pull_request_event_handler(event, data)
386 return self.pull_request_event_handler(event, data)
387 else:
387 else:
388 raise ValueError(
388 raise ValueError(
389 'event type `{}` has no handler defined'.format(event.__class__))
389 'event type `{}` has no handler defined'.format(event.__class__))
390
390
391
391
392 def get_auth(settings):
392 def get_auth(settings):
393 from requests.auth import HTTPBasicAuth
393 from requests.auth import HTTPBasicAuth
394 username = settings.get('username')
394 username = settings.get('username')
395 password = settings.get('password')
395 password = settings.get('password')
396 if username and password:
396 if username and password:
397 return HTTPBasicAuth(username, password)
397 return HTTPBasicAuth(username, password)
398 return None
398 return None
399
399
400
400
401 def get_web_token(settings):
401 def get_web_token(settings):
402 return settings['secret_token']
402 return settings['secret_token']
403
403
404
404
405 def get_url_vars(url_vars):
405 def get_url_vars(url_vars):
406 items = []
406 items = []
407
407
408 for section, section_items in url_vars:
408 for section, section_items in url_vars:
409 items.append('\n*{}*'.format(section))
409 items.append('\n*{}*'.format(section))
410 for key, explanation in section_items:
410 for key, explanation in section_items:
411 items.append(' {} - {}'.format('${' + key + '}', explanation))
411 items.append(' {} - {}'.format('${' + key + '}', explanation))
412 return '\n'.join(items)
412 return '\n'.join(items)
413
413
414
414
415 def render_with_traceback(template, *args, **kwargs):
415 def render_with_traceback(template, *args, **kwargs):
416 try:
416 try:
417 return template.render(*args, **kwargs)
417 return template.render(*args, **kwargs)
418 except Exception:
418 except Exception:
419 log.error(exceptions.text_error_template().render())
419 log.error(exceptions.text_error_template().render())
420 raise
420 raise
421
421
422
422
423 STATUS_400 = (400, 401, 403)
423 STATUS_400 = (400, 401, 403)
424 STATUS_500 = (500, 502, 504)
424 STATUS_500 = (500, 502, 504)
425
425
426
426
427 def requests_retry_call(
427 def requests_retry_call(
428 retries=3, backoff_factor=0.3, status_forcelist=STATUS_400+STATUS_500,
428 retries=3, backoff_factor=0.3, status_forcelist=STATUS_400+STATUS_500,
429 session=None):
429 session=None):
430 """
430 """
431 session = requests_retry_session()
431 session = requests_retry_session()
432 response = session.get('http://example.com')
432 response = session.get('http://example.com')
433
433
434 :param retries:
434 :param retries:
435 :param backoff_factor:
435 :param backoff_factor:
436 :param status_forcelist:
436 :param status_forcelist:
437 :param session:
437 :param session:
438 """
438 """
439 session = session or requests.Session()
439 session = session or requests.Session()
440 retry = Retry(
440 retry = Retry(
441 total=retries,
441 total=retries,
442 read=retries,
442 read=retries,
443 connect=retries,
443 connect=retries,
444 backoff_factor=backoff_factor,
444 backoff_factor=backoff_factor,
445 status_forcelist=status_forcelist,
445 status_forcelist=status_forcelist,
446 )
446 )
447 adapter = HTTPAdapter(max_retries=retry)
447 adapter = HTTPAdapter(max_retries=retry)
448 session.mount('http://', adapter)
448 session.mount('http://', adapter)
449 session.mount('https://', adapter)
449 session.mount('https://', adapter)
450 return session
450 return session
@@ -1,403 +1,407 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2020 RhodeCode GmbH
3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 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 import os
26 import os
27 import time
27 import time
28
28
29 from pyramid import compat
29 from pyramid import compat
30 from pyramid_mailer.mailer import Mailer
30 from pyramid_mailer.mailer import Mailer
31 from pyramid_mailer.message import Message
31 from pyramid_mailer.message import Message
32 from email.utils import formatdate
32 from email.utils import formatdate
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.lib import audit_logger
35 from rhodecode.lib import audit_logger
36 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask, run_task
36 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask, run_task
37 from rhodecode.lib import hooks_base
37 from rhodecode.lib import hooks_base
38 from rhodecode.lib.utils2 import safe_int, str2bool, aslist
38 from rhodecode.lib.utils2 import safe_int, str2bool, aslist
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 Session, IntegrityError, true, Repository, RepoGroup, User)
40 Session, IntegrityError, true, Repository, RepoGroup, User)
41 from rhodecode.model.permission import PermissionModel
41
42
42
43
43 @async_task(ignore_result=True, base=RequestContextTask)
44 @async_task(ignore_result=True, base=RequestContextTask)
44 def send_email(recipients, subject, body='', html_body='', email_config=None,
45 def send_email(recipients, subject, body='', html_body='', email_config=None,
45 extra_headers=None):
46 extra_headers=None):
46 """
47 """
47 Sends an email with defined parameters from the .ini files.
48 Sends an email with defined parameters from the .ini files.
48
49
49 :param recipients: list of recipients, it this is empty the defined email
50 :param recipients: list of recipients, it this is empty the defined email
50 address from field 'email_to' is used instead
51 address from field 'email_to' is used instead
51 :param subject: subject of the mail
52 :param subject: subject of the mail
52 :param body: body of the mail
53 :param body: body of the mail
53 :param html_body: html version of body
54 :param html_body: html version of body
54 :param email_config: specify custom configuration for mailer
55 :param email_config: specify custom configuration for mailer
55 :param extra_headers: specify custom headers
56 :param extra_headers: specify custom headers
56 """
57 """
57 log = get_logger(send_email)
58 log = get_logger(send_email)
58
59
59 email_config = email_config or rhodecode.CONFIG
60 email_config = email_config or rhodecode.CONFIG
60
61
61 mail_server = email_config.get('smtp_server') or None
62 mail_server = email_config.get('smtp_server') or None
62 if mail_server is None:
63 if mail_server is None:
63 log.error("SMTP server information missing. Sending email failed. "
64 log.error("SMTP server information missing. Sending email failed. "
64 "Make sure that `smtp_server` variable is configured "
65 "Make sure that `smtp_server` variable is configured "
65 "inside the .ini file")
66 "inside the .ini file")
66 return False
67 return False
67
68
68 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
69 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
69
70
70 if recipients:
71 if recipients:
71 if isinstance(recipients, compat.string_types):
72 if isinstance(recipients, compat.string_types):
72 recipients = recipients.split(',')
73 recipients = recipients.split(',')
73 else:
74 else:
74 # if recipients are not defined we send to email_config + all admins
75 # if recipients are not defined we send to email_config + all admins
75 admins = []
76 admins = []
76 for u in User.query().filter(User.admin == true()).all():
77 for u in User.query().filter(User.admin == true()).all():
77 if u.email:
78 if u.email:
78 admins.append(u.email)
79 admins.append(u.email)
79 recipients = []
80 recipients = []
80 config_email = email_config.get('email_to')
81 config_email = email_config.get('email_to')
81 if config_email:
82 if config_email:
82 recipients += [config_email]
83 recipients += [config_email]
83 recipients += admins
84 recipients += admins
84
85
85 # translate our LEGACY config into the one that pyramid_mailer supports
86 # translate our LEGACY config into the one that pyramid_mailer supports
86 email_conf = dict(
87 email_conf = dict(
87 host=mail_server,
88 host=mail_server,
88 port=email_config.get('smtp_port', 25),
89 port=email_config.get('smtp_port', 25),
89 username=email_config.get('smtp_username'),
90 username=email_config.get('smtp_username'),
90 password=email_config.get('smtp_password'),
91 password=email_config.get('smtp_password'),
91
92
92 tls=str2bool(email_config.get('smtp_use_tls')),
93 tls=str2bool(email_config.get('smtp_use_tls')),
93 ssl=str2bool(email_config.get('smtp_use_ssl')),
94 ssl=str2bool(email_config.get('smtp_use_ssl')),
94
95
95 # SSL key file
96 # SSL key file
96 # keyfile='',
97 # keyfile='',
97
98
98 # SSL certificate file
99 # SSL certificate file
99 # certfile='',
100 # certfile='',
100
101
101 # Location of maildir
102 # Location of maildir
102 # queue_path='',
103 # queue_path='',
103
104
104 default_sender=email_config.get('app_email_from', 'RhodeCode'),
105 default_sender=email_config.get('app_email_from', 'RhodeCode'),
105
106
106 debug=str2bool(email_config.get('smtp_debug')),
107 debug=str2bool(email_config.get('smtp_debug')),
107 # /usr/sbin/sendmail Sendmail executable
108 # /usr/sbin/sendmail Sendmail executable
108 # sendmail_app='',
109 # sendmail_app='',
109
110
110 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
111 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
111 # sendmail_template='',
112 # sendmail_template='',
112 )
113 )
113
114
114 if extra_headers is None:
115 if extra_headers is None:
115 extra_headers = {}
116 extra_headers = {}
116
117
117 extra_headers.setdefault('Date', formatdate(time.time()))
118 extra_headers.setdefault('Date', formatdate(time.time()))
118
119
119 if 'thread_ids' in extra_headers:
120 if 'thread_ids' in extra_headers:
120 thread_ids = extra_headers.pop('thread_ids')
121 thread_ids = extra_headers.pop('thread_ids')
121 extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids)
122 extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids)
122
123
123 try:
124 try:
124 mailer = Mailer(**email_conf)
125 mailer = Mailer(**email_conf)
125
126
126 message = Message(subject=subject,
127 message = Message(subject=subject,
127 sender=email_conf['default_sender'],
128 sender=email_conf['default_sender'],
128 recipients=recipients,
129 recipients=recipients,
129 body=body, html=html_body,
130 body=body, html=html_body,
130 extra_headers=extra_headers)
131 extra_headers=extra_headers)
131 mailer.send_immediately(message)
132 mailer.send_immediately(message)
132
133
133 except Exception:
134 except Exception:
134 log.exception('Mail sending failed')
135 log.exception('Mail sending failed')
135 return False
136 return False
136 return True
137 return True
137
138
138
139
139 @async_task(ignore_result=True, base=RequestContextTask)
140 @async_task(ignore_result=True, base=RequestContextTask)
140 def create_repo(form_data, cur_user):
141 def create_repo(form_data, cur_user):
141 from rhodecode.model.repo import RepoModel
142 from rhodecode.model.repo import RepoModel
142 from rhodecode.model.user import UserModel
143 from rhodecode.model.user import UserModel
143 from rhodecode.model.scm import ScmModel
144 from rhodecode.model.scm import ScmModel
144 from rhodecode.model.settings import SettingsModel
145 from rhodecode.model.settings import SettingsModel
145
146
146 log = get_logger(create_repo)
147 log = get_logger(create_repo)
147
148
148 cur_user = UserModel()._get_user(cur_user)
149 cur_user = UserModel()._get_user(cur_user)
149 owner = cur_user
150 owner = cur_user
150
151
151 repo_name = form_data['repo_name']
152 repo_name = form_data['repo_name']
152 repo_name_full = form_data['repo_name_full']
153 repo_name_full = form_data['repo_name_full']
153 repo_type = form_data['repo_type']
154 repo_type = form_data['repo_type']
154 description = form_data['repo_description']
155 description = form_data['repo_description']
155 private = form_data['repo_private']
156 private = form_data['repo_private']
156 clone_uri = form_data.get('clone_uri')
157 clone_uri = form_data.get('clone_uri')
157 repo_group = safe_int(form_data['repo_group'])
158 repo_group = safe_int(form_data['repo_group'])
158 copy_fork_permissions = form_data.get('copy_permissions')
159 copy_fork_permissions = form_data.get('copy_permissions')
159 copy_group_permissions = form_data.get('repo_copy_permissions')
160 copy_group_permissions = form_data.get('repo_copy_permissions')
160 fork_of = form_data.get('fork_parent_id')
161 fork_of = form_data.get('fork_parent_id')
161 state = form_data.get('repo_state', Repository.STATE_PENDING)
162 state = form_data.get('repo_state', Repository.STATE_PENDING)
162
163
163 # repo creation defaults, private and repo_type are filled in form
164 # repo creation defaults, private and repo_type are filled in form
164 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
165 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
165 enable_statistics = form_data.get(
166 enable_statistics = form_data.get(
166 'enable_statistics', defs.get('repo_enable_statistics'))
167 'enable_statistics', defs.get('repo_enable_statistics'))
167 enable_locking = form_data.get(
168 enable_locking = form_data.get(
168 'enable_locking', defs.get('repo_enable_locking'))
169 'enable_locking', defs.get('repo_enable_locking'))
169 enable_downloads = form_data.get(
170 enable_downloads = form_data.get(
170 'enable_downloads', defs.get('repo_enable_downloads'))
171 'enable_downloads', defs.get('repo_enable_downloads'))
171
172
172 # set landing rev based on default branches for SCM
173 # set landing rev based on default branches for SCM
173 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
174 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
174
175
175 try:
176 try:
176 RepoModel()._create_repo(
177 RepoModel()._create_repo(
177 repo_name=repo_name_full,
178 repo_name=repo_name_full,
178 repo_type=repo_type,
179 repo_type=repo_type,
179 description=description,
180 description=description,
180 owner=owner,
181 owner=owner,
181 private=private,
182 private=private,
182 clone_uri=clone_uri,
183 clone_uri=clone_uri,
183 repo_group=repo_group,
184 repo_group=repo_group,
184 landing_rev=landing_ref,
185 landing_rev=landing_ref,
185 fork_of=fork_of,
186 fork_of=fork_of,
186 copy_fork_permissions=copy_fork_permissions,
187 copy_fork_permissions=copy_fork_permissions,
187 copy_group_permissions=copy_group_permissions,
188 copy_group_permissions=copy_group_permissions,
188 enable_statistics=enable_statistics,
189 enable_statistics=enable_statistics,
189 enable_locking=enable_locking,
190 enable_locking=enable_locking,
190 enable_downloads=enable_downloads,
191 enable_downloads=enable_downloads,
191 state=state
192 state=state
192 )
193 )
193 Session().commit()
194 Session().commit()
194
195
195 # now create this repo on Filesystem
196 # now create this repo on Filesystem
196 RepoModel()._create_filesystem_repo(
197 RepoModel()._create_filesystem_repo(
197 repo_name=repo_name,
198 repo_name=repo_name,
198 repo_type=repo_type,
199 repo_type=repo_type,
199 repo_group=RepoModel()._get_repo_group(repo_group),
200 repo_group=RepoModel()._get_repo_group(repo_group),
200 clone_uri=clone_uri,
201 clone_uri=clone_uri,
201 )
202 )
202 repo = Repository.get_by_repo_name(repo_name_full)
203 repo = Repository.get_by_repo_name(repo_name_full)
203 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
204 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
204
205
205 # update repo commit caches initially
206 # update repo commit caches initially
206 repo.update_commit_cache()
207 repo.update_commit_cache()
207
208
208 # set new created state
209 # set new created state
209 repo.set_state(Repository.STATE_CREATED)
210 repo.set_state(Repository.STATE_CREATED)
210 repo_id = repo.repo_id
211 repo_id = repo.repo_id
211 repo_data = repo.get_api_data()
212 repo_data = repo.get_api_data()
212
213
213 audit_logger.store(
214 audit_logger.store(
214 'repo.create', action_data={'data': repo_data},
215 'repo.create', action_data={'data': repo_data},
215 user=cur_user,
216 user=cur_user,
216 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
217 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
217
218
218 Session().commit()
219 Session().commit()
220
221 PermissionModel().trigger_permission_flush()
222
219 except Exception as e:
223 except Exception as e:
220 log.warning('Exception occurred when creating repository, '
224 log.warning('Exception occurred when creating repository, '
221 'doing cleanup...', exc_info=True)
225 'doing cleanup...', exc_info=True)
222 if isinstance(e, IntegrityError):
226 if isinstance(e, IntegrityError):
223 Session().rollback()
227 Session().rollback()
224
228
225 # rollback things manually !
229 # rollback things manually !
226 repo = Repository.get_by_repo_name(repo_name_full)
230 repo = Repository.get_by_repo_name(repo_name_full)
227 if repo:
231 if repo:
228 Repository.delete(repo.repo_id)
232 Repository.delete(repo.repo_id)
229 Session().commit()
233 Session().commit()
230 RepoModel()._delete_filesystem_repo(repo)
234 RepoModel()._delete_filesystem_repo(repo)
231 log.info('Cleanup of repo %s finished', repo_name_full)
235 log.info('Cleanup of repo %s finished', repo_name_full)
232 raise
236 raise
233
237
234 return True
238 return True
235
239
236
240
237 @async_task(ignore_result=True, base=RequestContextTask)
241 @async_task(ignore_result=True, base=RequestContextTask)
238 def create_repo_fork(form_data, cur_user):
242 def create_repo_fork(form_data, cur_user):
239 """
243 """
240 Creates a fork of repository using internal VCS methods
244 Creates a fork of repository using internal VCS methods
241 """
245 """
242 from rhodecode.model.repo import RepoModel
246 from rhodecode.model.repo import RepoModel
243 from rhodecode.model.user import UserModel
247 from rhodecode.model.user import UserModel
244
248
245 log = get_logger(create_repo_fork)
249 log = get_logger(create_repo_fork)
246
250
247 cur_user = UserModel()._get_user(cur_user)
251 cur_user = UserModel()._get_user(cur_user)
248 owner = cur_user
252 owner = cur_user
249
253
250 repo_name = form_data['repo_name'] # fork in this case
254 repo_name = form_data['repo_name'] # fork in this case
251 repo_name_full = form_data['repo_name_full']
255 repo_name_full = form_data['repo_name_full']
252 repo_type = form_data['repo_type']
256 repo_type = form_data['repo_type']
253 description = form_data['description']
257 description = form_data['description']
254 private = form_data['private']
258 private = form_data['private']
255 clone_uri = form_data.get('clone_uri')
259 clone_uri = form_data.get('clone_uri')
256 repo_group = safe_int(form_data['repo_group'])
260 repo_group = safe_int(form_data['repo_group'])
257 landing_ref = form_data['landing_rev']
261 landing_ref = form_data['landing_rev']
258 copy_fork_permissions = form_data.get('copy_permissions')
262 copy_fork_permissions = form_data.get('copy_permissions')
259 fork_id = safe_int(form_data.get('fork_parent_id'))
263 fork_id = safe_int(form_data.get('fork_parent_id'))
260
264
261 try:
265 try:
262 fork_of = RepoModel()._get_repo(fork_id)
266 fork_of = RepoModel()._get_repo(fork_id)
263 RepoModel()._create_repo(
267 RepoModel()._create_repo(
264 repo_name=repo_name_full,
268 repo_name=repo_name_full,
265 repo_type=repo_type,
269 repo_type=repo_type,
266 description=description,
270 description=description,
267 owner=owner,
271 owner=owner,
268 private=private,
272 private=private,
269 clone_uri=clone_uri,
273 clone_uri=clone_uri,
270 repo_group=repo_group,
274 repo_group=repo_group,
271 landing_rev=landing_ref,
275 landing_rev=landing_ref,
272 fork_of=fork_of,
276 fork_of=fork_of,
273 copy_fork_permissions=copy_fork_permissions
277 copy_fork_permissions=copy_fork_permissions
274 )
278 )
275
279
276 Session().commit()
280 Session().commit()
277
281
278 base_path = Repository.base_path()
282 base_path = Repository.base_path()
279 source_repo_path = os.path.join(base_path, fork_of.repo_name)
283 source_repo_path = os.path.join(base_path, fork_of.repo_name)
280
284
281 # now create this repo on Filesystem
285 # now create this repo on Filesystem
282 RepoModel()._create_filesystem_repo(
286 RepoModel()._create_filesystem_repo(
283 repo_name=repo_name,
287 repo_name=repo_name,
284 repo_type=repo_type,
288 repo_type=repo_type,
285 repo_group=RepoModel()._get_repo_group(repo_group),
289 repo_group=RepoModel()._get_repo_group(repo_group),
286 clone_uri=source_repo_path,
290 clone_uri=source_repo_path,
287 )
291 )
288 repo = Repository.get_by_repo_name(repo_name_full)
292 repo = Repository.get_by_repo_name(repo_name_full)
289 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
293 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
290
294
291 # update repo commit caches initially
295 # update repo commit caches initially
292 config = repo._config
296 config = repo._config
293 config.set('extensions', 'largefiles', '')
297 config.set('extensions', 'largefiles', '')
294 repo.update_commit_cache(config=config)
298 repo.update_commit_cache(config=config)
295
299
296 # set new created state
300 # set new created state
297 repo.set_state(Repository.STATE_CREATED)
301 repo.set_state(Repository.STATE_CREATED)
298
302
299 repo_id = repo.repo_id
303 repo_id = repo.repo_id
300 repo_data = repo.get_api_data()
304 repo_data = repo.get_api_data()
301 audit_logger.store(
305 audit_logger.store(
302 'repo.fork', action_data={'data': repo_data},
306 'repo.fork', action_data={'data': repo_data},
303 user=cur_user,
307 user=cur_user,
304 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
308 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
305
309
306 Session().commit()
310 Session().commit()
307 except Exception as e:
311 except Exception as e:
308 log.warning('Exception occurred when forking repository, '
312 log.warning('Exception occurred when forking repository, '
309 'doing cleanup...', exc_info=True)
313 'doing cleanup...', exc_info=True)
310 if isinstance(e, IntegrityError):
314 if isinstance(e, IntegrityError):
311 Session().rollback()
315 Session().rollback()
312
316
313 # rollback things manually !
317 # rollback things manually !
314 repo = Repository.get_by_repo_name(repo_name_full)
318 repo = Repository.get_by_repo_name(repo_name_full)
315 if repo:
319 if repo:
316 Repository.delete(repo.repo_id)
320 Repository.delete(repo.repo_id)
317 Session().commit()
321 Session().commit()
318 RepoModel()._delete_filesystem_repo(repo)
322 RepoModel()._delete_filesystem_repo(repo)
319 log.info('Cleanup of repo %s finished', repo_name_full)
323 log.info('Cleanup of repo %s finished', repo_name_full)
320 raise
324 raise
321
325
322 return True
326 return True
323
327
324
328
325 @async_task(ignore_result=True)
329 @async_task(ignore_result=True)
326 def repo_maintenance(repoid):
330 def repo_maintenance(repoid):
327 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
331 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
328 log = get_logger(repo_maintenance)
332 log = get_logger(repo_maintenance)
329 repo = Repository.get_by_id_or_repo_name(repoid)
333 repo = Repository.get_by_id_or_repo_name(repoid)
330 if repo:
334 if repo:
331 maintenance = repo_maintenance_lib.RepoMaintenance()
335 maintenance = repo_maintenance_lib.RepoMaintenance()
332 tasks = maintenance.get_tasks_for_repo(repo)
336 tasks = maintenance.get_tasks_for_repo(repo)
333 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
337 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
334 executed_types = maintenance.execute(repo)
338 executed_types = maintenance.execute(repo)
335 log.debug('Got execution results %s', executed_types)
339 log.debug('Got execution results %s', executed_types)
336 else:
340 else:
337 log.debug('Repo `%s` not found or without a clone_url', repoid)
341 log.debug('Repo `%s` not found or without a clone_url', repoid)
338
342
339
343
340 @async_task(ignore_result=True)
344 @async_task(ignore_result=True)
341 def check_for_update(send_email_notification=True, email_recipients=None):
345 def check_for_update(send_email_notification=True, email_recipients=None):
342 from rhodecode.model.update import UpdateModel
346 from rhodecode.model.update import UpdateModel
343 from rhodecode.model.notification import EmailNotificationModel
347 from rhodecode.model.notification import EmailNotificationModel
344
348
345 log = get_logger(check_for_update)
349 log = get_logger(check_for_update)
346 update_url = UpdateModel().get_update_url()
350 update_url = UpdateModel().get_update_url()
347 cur_ver = rhodecode.__version__
351 cur_ver = rhodecode.__version__
348
352
349 try:
353 try:
350 data = UpdateModel().get_update_data(update_url)
354 data = UpdateModel().get_update_data(update_url)
351
355
352 current_ver = UpdateModel().get_stored_version(fallback=cur_ver)
356 current_ver = UpdateModel().get_stored_version(fallback=cur_ver)
353 latest_ver = data['versions'][0]['version']
357 latest_ver = data['versions'][0]['version']
354 UpdateModel().store_version(latest_ver)
358 UpdateModel().store_version(latest_ver)
355
359
356 if send_email_notification:
360 if send_email_notification:
357 log.debug('Send email notification is enabled. '
361 log.debug('Send email notification is enabled. '
358 'Current RhodeCode version: %s, latest known: %s', current_ver, latest_ver)
362 'Current RhodeCode version: %s, latest known: %s', current_ver, latest_ver)
359 if UpdateModel().is_outdated(current_ver, latest_ver):
363 if UpdateModel().is_outdated(current_ver, latest_ver):
360
364
361 email_kwargs = {
365 email_kwargs = {
362 'current_ver': current_ver,
366 'current_ver': current_ver,
363 'latest_ver': latest_ver,
367 'latest_ver': latest_ver,
364 }
368 }
365
369
366 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
370 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
367 EmailNotificationModel.TYPE_UPDATE_AVAILABLE, **email_kwargs)
371 EmailNotificationModel.TYPE_UPDATE_AVAILABLE, **email_kwargs)
368
372
369 email_recipients = aslist(email_recipients, sep=',') or \
373 email_recipients = aslist(email_recipients, sep=',') or \
370 [user.email for user in User.get_all_super_admins()]
374 [user.email for user in User.get_all_super_admins()]
371 run_task(send_email, email_recipients, subject,
375 run_task(send_email, email_recipients, subject,
372 email_body_plaintext, email_body)
376 email_body_plaintext, email_body)
373
377
374 except Exception:
378 except Exception:
375 pass
379 pass
376
380
377
381
378 @async_task(ignore_result=False)
382 @async_task(ignore_result=False)
379 def beat_check(*args, **kwargs):
383 def beat_check(*args, **kwargs):
380 log = get_logger(beat_check)
384 log = get_logger(beat_check)
381 log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs)
385 log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs)
382 return time.time()
386 return time.time()
383
387
384
388
385 @async_task(ignore_result=True)
389 @async_task(ignore_result=True)
386 def sync_last_update(*args, **kwargs):
390 def sync_last_update(*args, **kwargs):
387
391
388 skip_repos = kwargs.get('skip_repos')
392 skip_repos = kwargs.get('skip_repos')
389 if not skip_repos:
393 if not skip_repos:
390 repos = Repository.query() \
394 repos = Repository.query() \
391 .order_by(Repository.group_id.asc())
395 .order_by(Repository.group_id.asc())
392
396
393 for repo in repos:
397 for repo in repos:
394 repo.update_commit_cache()
398 repo.update_commit_cache()
395
399
396 skip_groups = kwargs.get('skip_groups')
400 skip_groups = kwargs.get('skip_groups')
397 if not skip_groups:
401 if not skip_groups:
398 repo_groups = RepoGroup.query() \
402 repo_groups = RepoGroup.query() \
399 .filter(RepoGroup.group_parent_id == None)
403 .filter(RepoGroup.group_parent_id == None)
400
404
401 for root_gr in repo_groups:
405 for root_gr in repo_groups:
402 for repo_gr in reversed(root_gr.recursive_groups()):
406 for repo_gr in reversed(root_gr.recursive_groups()):
403 repo_gr.update_commit_cache()
407 repo_gr.update_commit_cache()
General Comments 0
You need to be logged in to leave comments. Login now