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 |
|
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