##// END OF EJS Templates
comments: added rcextensions hoooks for comment editing, and renamed methods to remove odd log_ prefix which...
marcink -
r4445:5a5c90c0 default
parent child Browse files
Show More
@@ -1,60 +1,64 b''
1 # Copyright (C) 2016-2020 RhodeCode GmbH
1 # Copyright (C) 2016-2020 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 rcextensions module, please edit `hooks.py` to over write hooks logic
20 rcextensions module, please edit `hooks.py` to over write hooks logic
21 """
21 """
22
22
23 from .hooks import (
23 from .hooks import (
24 _create_repo_hook,
24 _create_repo_hook,
25 _create_repo_group_hook,
25 _create_repo_group_hook,
26 _pre_create_user_hook,
26 _pre_create_user_hook,
27 _create_user_hook,
27 _create_user_hook,
28 _comment_commit_repo_hook,
28 _comment_commit_repo_hook,
29 _comment_edit_commit_repo_hook,
29 _delete_repo_hook,
30 _delete_repo_hook,
30 _delete_user_hook,
31 _delete_user_hook,
31 _pre_push_hook,
32 _pre_push_hook,
32 _push_hook,
33 _push_hook,
33 _pre_pull_hook,
34 _pre_pull_hook,
34 _pull_hook,
35 _pull_hook,
35 _create_pull_request_hook,
36 _create_pull_request_hook,
36 _review_pull_request_hook,
37 _review_pull_request_hook,
37 _comment_pull_request_hook,
38 _comment_pull_request_hook,
39 _comment_edit_pull_request_hook,
38 _update_pull_request_hook,
40 _update_pull_request_hook,
39 _merge_pull_request_hook,
41 _merge_pull_request_hook,
40 _close_pull_request_hook,
42 _close_pull_request_hook,
41 )
43 )
42
44
43 # set as module attributes, we use those to call hooks. *do not change this*
45 # set as module attributes, we use those to call hooks. *do not change this*
44 CREATE_REPO_HOOK = _create_repo_hook
46 CREATE_REPO_HOOK = _create_repo_hook
45 COMMENT_COMMIT_REPO_HOOK = _comment_commit_repo_hook
47 COMMENT_COMMIT_REPO_HOOK = _comment_commit_repo_hook
48 COMMENT_EDIT_COMMIT_REPO_HOOK = _comment_edit_commit_repo_hook
46 CREATE_REPO_GROUP_HOOK = _create_repo_group_hook
49 CREATE_REPO_GROUP_HOOK = _create_repo_group_hook
47 PRE_CREATE_USER_HOOK = _pre_create_user_hook
50 PRE_CREATE_USER_HOOK = _pre_create_user_hook
48 CREATE_USER_HOOK = _create_user_hook
51 CREATE_USER_HOOK = _create_user_hook
49 DELETE_REPO_HOOK = _delete_repo_hook
52 DELETE_REPO_HOOK = _delete_repo_hook
50 DELETE_USER_HOOK = _delete_user_hook
53 DELETE_USER_HOOK = _delete_user_hook
51 PRE_PUSH_HOOK = _pre_push_hook
54 PRE_PUSH_HOOK = _pre_push_hook
52 PUSH_HOOK = _push_hook
55 PUSH_HOOK = _push_hook
53 PRE_PULL_HOOK = _pre_pull_hook
56 PRE_PULL_HOOK = _pre_pull_hook
54 PULL_HOOK = _pull_hook
57 PULL_HOOK = _pull_hook
55 CREATE_PULL_REQUEST = _create_pull_request_hook
58 CREATE_PULL_REQUEST = _create_pull_request_hook
56 REVIEW_PULL_REQUEST = _review_pull_request_hook
59 REVIEW_PULL_REQUEST = _review_pull_request_hook
57 COMMENT_PULL_REQUEST = _comment_pull_request_hook
60 COMMENT_PULL_REQUEST = _comment_pull_request_hook
61 COMMENT_EDIT_PULL_REQUEST = _comment_edit_pull_request_hook
58 UPDATE_PULL_REQUEST = _update_pull_request_hook
62 UPDATE_PULL_REQUEST = _update_pull_request_hook
59 MERGE_PULL_REQUEST = _merge_pull_request_hook
63 MERGE_PULL_REQUEST = _merge_pull_request_hook
60 CLOSE_PULL_REQUEST = _close_pull_request_hook
64 CLOSE_PULL_REQUEST = _close_pull_request_hook
@@ -1,492 +1,551 b''
1 # Copyright (C) 2016-2020 RhodeCode GmbH
1 # Copyright (C) 2016-2020 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 from .utils import DotDict, HookResponse, has_kwargs
20 from .utils import DotDict, HookResponse, has_kwargs
21
21
22 log = logging.getLogger('rhodecode.' + __name__)
22 log = logging.getLogger('rhodecode.' + __name__)
23
23
24 # Config shortcut to keep, all configuration in one place
24 # Config shortcut to keep, all configuration in one place
25 # Example: api_key = CONFIG.my_config.api_key
25 # Example: api_key = CONFIG.my_config.api_key
26 CONFIG = DotDict(
26 CONFIG = DotDict(
27 my_config=DotDict(
27 my_config=DotDict(
28 api_key='<secret>',
28 api_key='<secret>',
29 ),
29 ),
30
30
31 )
31 )
32
32
33
33
34 @has_kwargs({
34 @has_kwargs({
35 'repo_name': '',
35 'repo_name': '',
36 'repo_type': '',
36 'repo_type': '',
37 'description': '',
37 'description': '',
38 'private': '',
38 'private': '',
39 'created_on': '',
39 'created_on': '',
40 'enable_downloads': '',
40 'enable_downloads': '',
41 'repo_id': '',
41 'repo_id': '',
42 'user_id': '',
42 'user_id': '',
43 'enable_statistics': '',
43 'enable_statistics': '',
44 'clone_uri': '',
44 'clone_uri': '',
45 'fork_id': '',
45 'fork_id': '',
46 'group_id': '',
46 'group_id': '',
47 'created_by': ''
47 'created_by': ''
48 })
48 })
49 def _create_repo_hook(*args, **kwargs):
49 def _create_repo_hook(*args, **kwargs):
50 """
50 """
51 POST CREATE REPOSITORY HOOK. This function will be executed after
51 POST CREATE REPOSITORY HOOK. This function will be executed after
52 each repository is created. kwargs available:
52 each repository is created. kwargs available:
53
53
54 """
54 """
55 return HookResponse(0, '')
55 return HookResponse(0, '')
56
56
57
57
58 @has_kwargs({
58 @has_kwargs({
59 'repo_name': '',
59 'repo_name': '',
60 'repo_type': '',
60 'repo_type': '',
61 'description': '',
61 'description': '',
62 'private': '',
62 'private': '',
63 'created_on': '',
63 'created_on': '',
64 'enable_downloads': '',
64 'enable_downloads': '',
65 'repo_id': '',
65 'repo_id': '',
66 'user_id': '',
66 'user_id': '',
67 'enable_statistics': '',
67 'enable_statistics': '',
68 'clone_uri': '',
68 'clone_uri': '',
69 'fork_id': '',
69 'fork_id': '',
70 'group_id': '',
70 'group_id': '',
71 'created_by': '',
71 'created_by': '',
72 'repository': '',
72 'repository': '',
73 'comment': '',
73 'comment': '',
74 'commit': ''
74 'commit': ''
75 })
75 })
76 def _comment_commit_repo_hook(*args, **kwargs):
76 def _comment_commit_repo_hook(*args, **kwargs):
77 """
77 """
78 POST CREATE REPOSITORY COMMENT ON COMMIT HOOK. This function will be executed after
78 POST CREATE REPOSITORY COMMENT ON COMMIT HOOK. This function will be executed after
79 a comment is made on this repository commit.
79 a comment is made on this repository commit.
80
80
81 """
81 """
82 return HookResponse(0, '')
82 return HookResponse(0, '')
83
83
84
84
85 @has_kwargs({
85 @has_kwargs({
86 'repo_name': '',
87 'repo_type': '',
88 'description': '',
89 'private': '',
90 'created_on': '',
91 'enable_downloads': '',
92 'repo_id': '',
93 'user_id': '',
94 'enable_statistics': '',
95 'clone_uri': '',
96 'fork_id': '',
97 'group_id': '',
98 'created_by': '',
99 'repository': '',
100 'comment': '',
101 'commit': ''
102 })
103 def _comment_edit_commit_repo_hook(*args, **kwargs):
104 """
105 POST CREATE REPOSITORY COMMENT ON COMMIT HOOK. This function will be executed after
106 a comment is made on this repository commit.
107
108 """
109 return HookResponse(0, '')
110
111
112 @has_kwargs({
86 'group_name': '',
113 'group_name': '',
87 'group_parent_id': '',
114 'group_parent_id': '',
88 'group_description': '',
115 'group_description': '',
89 'group_id': '',
116 'group_id': '',
90 'user_id': '',
117 'user_id': '',
91 'created_by': '',
118 'created_by': '',
92 'created_on': '',
119 'created_on': '',
93 'enable_locking': ''
120 'enable_locking': ''
94 })
121 })
95 def _create_repo_group_hook(*args, **kwargs):
122 def _create_repo_group_hook(*args, **kwargs):
96 """
123 """
97 POST CREATE REPOSITORY GROUP HOOK, this function will be
124 POST CREATE REPOSITORY GROUP HOOK, this function will be
98 executed after each repository group is created. kwargs available:
125 executed after each repository group is created. kwargs available:
99 """
126 """
100 return HookResponse(0, '')
127 return HookResponse(0, '')
101
128
102
129
103 @has_kwargs({
130 @has_kwargs({
104 'username': '',
131 'username': '',
105 'password': '',
132 'password': '',
106 'email': '',
133 'email': '',
107 'firstname': '',
134 'firstname': '',
108 'lastname': '',
135 'lastname': '',
109 'active': '',
136 'active': '',
110 'admin': '',
137 'admin': '',
111 'created_by': '',
138 'created_by': '',
112 })
139 })
113 def _pre_create_user_hook(*args, **kwargs):
140 def _pre_create_user_hook(*args, **kwargs):
114 """
141 """
115 PRE CREATE USER HOOK, this function will be executed before each
142 PRE CREATE USER HOOK, this function will be executed before each
116 user is created, it returns a tuple of bool, reason.
143 user is created, it returns a tuple of bool, reason.
117 If bool is False the user creation will be stopped and reason
144 If bool is False the user creation will be stopped and reason
118 will be displayed to the user.
145 will be displayed to the user.
119
146
120 Return HookResponse(1, reason) to block user creation
147 Return HookResponse(1, reason) to block user creation
121
148
122 """
149 """
123
150
124 reason = 'allowed'
151 reason = 'allowed'
125 return HookResponse(0, reason)
152 return HookResponse(0, reason)
126
153
127
154
128 @has_kwargs({
155 @has_kwargs({
129 'username': '',
156 'username': '',
130 'full_name_or_username': '',
157 'full_name_or_username': '',
131 'full_contact': '',
158 'full_contact': '',
132 'user_id': '',
159 'user_id': '',
133 'name': '',
160 'name': '',
134 'firstname': '',
161 'firstname': '',
135 'short_contact': '',
162 'short_contact': '',
136 'admin': '',
163 'admin': '',
137 'lastname': '',
164 'lastname': '',
138 'ip_addresses': '',
165 'ip_addresses': '',
139 'extern_type': '',
166 'extern_type': '',
140 'extern_name': '',
167 'extern_name': '',
141 'email': '',
168 'email': '',
142 'api_key': '',
169 'api_key': '',
143 'api_keys': '',
170 'api_keys': '',
144 'last_login': '',
171 'last_login': '',
145 'full_name': '',
172 'full_name': '',
146 'active': '',
173 'active': '',
147 'password': '',
174 'password': '',
148 'emails': '',
175 'emails': '',
149 'inherit_default_permissions': '',
176 'inherit_default_permissions': '',
150 'created_by': '',
177 'created_by': '',
151 'created_on': '',
178 'created_on': '',
152 })
179 })
153 def _create_user_hook(*args, **kwargs):
180 def _create_user_hook(*args, **kwargs):
154 """
181 """
155 POST CREATE USER HOOK, this function will be executed after each user is created
182 POST CREATE USER HOOK, this function will be executed after each user is created
156 """
183 """
157 return HookResponse(0, '')
184 return HookResponse(0, '')
158
185
159
186
160 @has_kwargs({
187 @has_kwargs({
161 'repo_name': '',
188 'repo_name': '',
162 'repo_type': '',
189 'repo_type': '',
163 'description': '',
190 'description': '',
164 'private': '',
191 'private': '',
165 'created_on': '',
192 'created_on': '',
166 'enable_downloads': '',
193 'enable_downloads': '',
167 'repo_id': '',
194 'repo_id': '',
168 'user_id': '',
195 'user_id': '',
169 'enable_statistics': '',
196 'enable_statistics': '',
170 'clone_uri': '',
197 'clone_uri': '',
171 'fork_id': '',
198 'fork_id': '',
172 'group_id': '',
199 'group_id': '',
173 'deleted_by': '',
200 'deleted_by': '',
174 'deleted_on': '',
201 'deleted_on': '',
175 })
202 })
176 def _delete_repo_hook(*args, **kwargs):
203 def _delete_repo_hook(*args, **kwargs):
177 """
204 """
178 POST DELETE REPOSITORY HOOK, this function will be executed after
205 POST DELETE REPOSITORY HOOK, this function will be executed after
179 each repository deletion
206 each repository deletion
180 """
207 """
181 return HookResponse(0, '')
208 return HookResponse(0, '')
182
209
183
210
184 @has_kwargs({
211 @has_kwargs({
185 'username': '',
212 'username': '',
186 'full_name_or_username': '',
213 'full_name_or_username': '',
187 'full_contact': '',
214 'full_contact': '',
188 'user_id': '',
215 'user_id': '',
189 'name': '',
216 'name': '',
190 'short_contact': '',
217 'short_contact': '',
191 'admin': '',
218 'admin': '',
192 'firstname': '',
219 'firstname': '',
193 'lastname': '',
220 'lastname': '',
194 'ip_addresses': '',
221 'ip_addresses': '',
195 'email': '',
222 'email': '',
196 'api_key': '',
223 'api_key': '',
197 'last_login': '',
224 'last_login': '',
198 'full_name': '',
225 'full_name': '',
199 'active': '',
226 'active': '',
200 'password': '',
227 'password': '',
201 'emails': '',
228 'emails': '',
202 'inherit_default_permissions': '',
229 'inherit_default_permissions': '',
203 'deleted_by': '',
230 'deleted_by': '',
204 })
231 })
205 def _delete_user_hook(*args, **kwargs):
232 def _delete_user_hook(*args, **kwargs):
206 """
233 """
207 POST DELETE USER HOOK, this function will be executed after each
234 POST DELETE USER HOOK, this function will be executed after each
208 user is deleted kwargs available:
235 user is deleted kwargs available:
209 """
236 """
210 return HookResponse(0, '')
237 return HookResponse(0, '')
211
238
212
239
213 # =============================================================================
240 # =============================================================================
214 # PUSH/PULL RELATED HOOKS
241 # PUSH/PULL RELATED HOOKS
215 # =============================================================================
242 # =============================================================================
216 @has_kwargs({
243 @has_kwargs({
217 'server_url': 'url of instance that triggered this hook',
244 'server_url': 'url of instance that triggered this hook',
218 'config': 'path to .ini config used',
245 'config': 'path to .ini config used',
219 'scm': 'type of version control "git", "hg", "svn"',
246 'scm': 'type of version control "git", "hg", "svn"',
220 'username': 'username of actor who triggered this event',
247 'username': 'username of actor who triggered this event',
221 'ip': 'ip address of actor who triggered this hook',
248 'ip': 'ip address of actor who triggered this hook',
222 'action': '',
249 'action': '',
223 'repository': 'repository name',
250 'repository': 'repository name',
224 'repo_store_path': 'full path to where repositories are stored',
251 'repo_store_path': 'full path to where repositories are stored',
225 'commit_ids': 'pre transaction metadata for commit ids',
252 'commit_ids': 'pre transaction metadata for commit ids',
226 'hook_type': '',
253 'hook_type': '',
227 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
254 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
228 })
255 })
229 def _pre_push_hook(*args, **kwargs):
256 def _pre_push_hook(*args, **kwargs):
230 """
257 """
231 Post push hook
258 Post push hook
232 To stop version control from storing the transaction and send a message to user
259 To stop version control from storing the transaction and send a message to user
233 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
260 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
234
261
235 This message will be shown back to client during PUSH operation
262 This message will be shown back to client during PUSH operation
236
263
237 Commit ids might look like that::
264 Commit ids might look like that::
238
265
239 [{u'hg_env|git_env': ...,
266 [{u'hg_env|git_env': ...,
240 u'multiple_heads': [],
267 u'multiple_heads': [],
241 u'name': u'default',
268 u'name': u'default',
242 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
269 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
243 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
270 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
244 u'ref': u'',
271 u'ref': u'',
245 u'total_commits': 2,
272 u'total_commits': 2,
246 u'type': u'branch'}]
273 u'type': u'branch'}]
247 """
274 """
248 return HookResponse(0, '')
275 return HookResponse(0, '')
249
276
250
277
251 @has_kwargs({
278 @has_kwargs({
252 'server_url': 'url of instance that triggered this hook',
279 'server_url': 'url of instance that triggered this hook',
253 'config': 'path to .ini config used',
280 'config': 'path to .ini config used',
254 'scm': 'type of version control "git", "hg", "svn"',
281 'scm': 'type of version control "git", "hg", "svn"',
255 'username': 'username of actor who triggered this event',
282 'username': 'username of actor who triggered this event',
256 'ip': 'ip address of actor who triggered this hook',
283 'ip': 'ip address of actor who triggered this hook',
257 'action': '',
284 'action': '',
258 'repository': 'repository name',
285 'repository': 'repository name',
259 'repo_store_path': 'full path to where repositories are stored',
286 'repo_store_path': 'full path to where repositories are stored',
260 'commit_ids': 'list of pushed commit_ids (sha1)',
287 'commit_ids': 'list of pushed commit_ids (sha1)',
261 'hook_type': '',
288 'hook_type': '',
262 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
289 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
263 })
290 })
264 def _push_hook(*args, **kwargs):
291 def _push_hook(*args, **kwargs):
265 """
292 """
266 POST PUSH HOOK, this function will be executed after each push it's
293 POST PUSH HOOK, this function will be executed after each push it's
267 executed after the build-in hook that RhodeCode uses for logging pushes
294 executed after the build-in hook that RhodeCode uses for logging pushes
268 """
295 """
269 return HookResponse(0, '')
296 return HookResponse(0, '')
270
297
271
298
272 @has_kwargs({
299 @has_kwargs({
273 'server_url': 'url of instance that triggered this hook',
300 'server_url': 'url of instance that triggered this hook',
274 'repo_store_path': 'full path to where repositories are stored',
301 'repo_store_path': 'full path to where repositories are stored',
275 'config': 'path to .ini config used',
302 'config': 'path to .ini config used',
276 'scm': 'type of version control "git", "hg", "svn"',
303 'scm': 'type of version control "git", "hg", "svn"',
277 'username': 'username of actor who triggered this event',
304 'username': 'username of actor who triggered this event',
278 'ip': 'ip address of actor who triggered this hook',
305 'ip': 'ip address of actor who triggered this hook',
279 'action': '',
306 'action': '',
280 'repository': 'repository name',
307 'repository': 'repository name',
281 'hook_type': '',
308 'hook_type': '',
282 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
309 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
283 })
310 })
284 def _pre_pull_hook(*args, **kwargs):
311 def _pre_pull_hook(*args, **kwargs):
285 """
312 """
286 Post pull hook
313 Post pull hook
287 """
314 """
288 return HookResponse(0, '')
315 return HookResponse(0, '')
289
316
290
317
291 @has_kwargs({
318 @has_kwargs({
292 'server_url': 'url of instance that triggered this hook',
319 'server_url': 'url of instance that triggered this hook',
293 'repo_store_path': 'full path to where repositories are stored',
320 'repo_store_path': 'full path to where repositories are stored',
294 'config': 'path to .ini config used',
321 'config': 'path to .ini config used',
295 'scm': 'type of version control "git", "hg", "svn"',
322 'scm': 'type of version control "git", "hg", "svn"',
296 'username': 'username of actor who triggered this event',
323 'username': 'username of actor who triggered this event',
297 'ip': 'ip address of actor who triggered this hook',
324 'ip': 'ip address of actor who triggered this hook',
298 'action': '',
325 'action': '',
299 'repository': 'repository name',
326 'repository': 'repository name',
300 'hook_type': '',
327 'hook_type': '',
301 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
328 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
302 })
329 })
303 def _pull_hook(*args, **kwargs):
330 def _pull_hook(*args, **kwargs):
304 """
331 """
305 This hook will be executed after each code pull.
332 This hook will be executed after each code pull.
306 """
333 """
307 return HookResponse(0, '')
334 return HookResponse(0, '')
308
335
309
336
310 # =============================================================================
337 # =============================================================================
311 # PULL REQUEST RELATED HOOKS
338 # PULL REQUEST RELATED HOOKS
312 # =============================================================================
339 # =============================================================================
313 @has_kwargs({
340 @has_kwargs({
314 'server_url': 'url of instance that triggered this hook',
341 'server_url': 'url of instance that triggered this hook',
315 'config': 'path to .ini config used',
342 'config': 'path to .ini config used',
316 'scm': 'type of version control "git", "hg", "svn"',
343 'scm': 'type of version control "git", "hg", "svn"',
317 'username': 'username of actor who triggered this event',
344 'username': 'username of actor who triggered this event',
318 'ip': 'ip address of actor who triggered this hook',
345 'ip': 'ip address of actor who triggered this hook',
319 'action': '',
346 'action': '',
320 'repository': 'repository name',
347 'repository': 'repository name',
321 'pull_request_id': '',
348 'pull_request_id': '',
322 'url': '',
349 'url': '',
323 'title': '',
350 'title': '',
324 'description': '',
351 'description': '',
325 'status': '',
352 'status': '',
326 'created_on': '',
353 'created_on': '',
327 'updated_on': '',
354 'updated_on': '',
328 'commit_ids': '',
355 'commit_ids': '',
329 'review_status': '',
356 'review_status': '',
330 'mergeable': '',
357 'mergeable': '',
331 'source': '',
358 'source': '',
332 'target': '',
359 'target': '',
333 'author': '',
360 'author': '',
334 'reviewers': '',
361 'reviewers': '',
335 })
362 })
336 def _create_pull_request_hook(*args, **kwargs):
363 def _create_pull_request_hook(*args, **kwargs):
337 """
364 """
338 This hook will be executed after creation of a pull request.
365 This hook will be executed after creation of a pull request.
339 """
366 """
340 return HookResponse(0, '')
367 return HookResponse(0, '')
341
368
342
369
343 @has_kwargs({
370 @has_kwargs({
344 'server_url': 'url of instance that triggered this hook',
371 'server_url': 'url of instance that triggered this hook',
345 'config': 'path to .ini config used',
372 'config': 'path to .ini config used',
346 'scm': 'type of version control "git", "hg", "svn"',
373 'scm': 'type of version control "git", "hg", "svn"',
347 'username': 'username of actor who triggered this event',
374 'username': 'username of actor who triggered this event',
348 'ip': 'ip address of actor who triggered this hook',
375 'ip': 'ip address of actor who triggered this hook',
349 'action': '',
376 'action': '',
350 'repository': 'repository name',
377 'repository': 'repository name',
351 'pull_request_id': '',
378 'pull_request_id': '',
352 'url': '',
379 'url': '',
353 'title': '',
380 'title': '',
354 'description': '',
381 'description': '',
355 'status': '',
382 'status': '',
356 'created_on': '',
383 'created_on': '',
357 'updated_on': '',
384 'updated_on': '',
358 'commit_ids': '',
385 'commit_ids': '',
359 'review_status': '',
386 'review_status': '',
360 'mergeable': '',
387 'mergeable': '',
361 'source': '',
388 'source': '',
362 'target': '',
389 'target': '',
363 'author': '',
390 'author': '',
364 'reviewers': '',
391 'reviewers': '',
365 })
392 })
366 def _review_pull_request_hook(*args, **kwargs):
393 def _review_pull_request_hook(*args, **kwargs):
367 """
394 """
368 This hook will be executed after review action was made on a pull request.
395 This hook will be executed after review action was made on a pull request.
369 """
396 """
370 return HookResponse(0, '')
397 return HookResponse(0, '')
371
398
372
399
373 @has_kwargs({
400 @has_kwargs({
374 'server_url': 'url of instance that triggered this hook',
401 'server_url': 'url of instance that triggered this hook',
375 'config': 'path to .ini config used',
402 'config': 'path to .ini config used',
376 'scm': 'type of version control "git", "hg", "svn"',
403 'scm': 'type of version control "git", "hg", "svn"',
377 'username': 'username of actor who triggered this event',
404 'username': 'username of actor who triggered this event',
378 'ip': 'ip address of actor who triggered this hook',
405 'ip': 'ip address of actor who triggered this hook',
379
406
380 'action': '',
407 'action': '',
381 'repository': 'repository name',
408 'repository': 'repository name',
382 'pull_request_id': '',
409 'pull_request_id': '',
383 'url': '',
410 'url': '',
384 'title': '',
411 'title': '',
385 'description': '',
412 'description': '',
386 'status': '',
413 'status': '',
387 'comment': '',
414 'comment': '',
388 'created_on': '',
415 'created_on': '',
389 'updated_on': '',
416 'updated_on': '',
390 'commit_ids': '',
417 'commit_ids': '',
391 'review_status': '',
418 'review_status': '',
392 'mergeable': '',
419 'mergeable': '',
393 'source': '',
420 'source': '',
394 'target': '',
421 'target': '',
395 'author': '',
422 'author': '',
396 'reviewers': '',
423 'reviewers': '',
397 })
424 })
398 def _comment_pull_request_hook(*args, **kwargs):
425 def _comment_pull_request_hook(*args, **kwargs):
399 """
426 """
400 This hook will be executed after comment is made on a pull request
427 This hook will be executed after comment is made on a pull request
401 """
428 """
402 return HookResponse(0, '')
429 return HookResponse(0, '')
403
430
404
431
405 @has_kwargs({
432 @has_kwargs({
406 'server_url': 'url of instance that triggered this hook',
433 'server_url': 'url of instance that triggered this hook',
407 'config': 'path to .ini config used',
434 'config': 'path to .ini config used',
408 'scm': 'type of version control "git", "hg", "svn"',
435 'scm': 'type of version control "git", "hg", "svn"',
409 'username': 'username of actor who triggered this event',
436 'username': 'username of actor who triggered this event',
410 'ip': 'ip address of actor who triggered this hook',
437 'ip': 'ip address of actor who triggered this hook',
438
439 'action': '',
440 'repository': 'repository name',
441 'pull_request_id': '',
442 'url': '',
443 'title': '',
444 'description': '',
445 'status': '',
446 'comment': '',
447 'created_on': '',
448 'updated_on': '',
449 'commit_ids': '',
450 'review_status': '',
451 'mergeable': '',
452 'source': '',
453 'target': '',
454 'author': '',
455 'reviewers': '',
456 })
457 def _comment_edit_pull_request_hook(*args, **kwargs):
458 """
459 This hook will be executed after comment is made on a pull request
460 """
461 return HookResponse(0, '')
462
463
464 @has_kwargs({
465 'server_url': 'url of instance that triggered this hook',
466 'config': 'path to .ini config used',
467 'scm': 'type of version control "git", "hg", "svn"',
468 'username': 'username of actor who triggered this event',
469 'ip': 'ip address of actor who triggered this hook',
411 'action': '',
470 'action': '',
412 'repository': 'repository name',
471 'repository': 'repository name',
413 'pull_request_id': '',
472 'pull_request_id': '',
414 'url': '',
473 'url': '',
415 'title': '',
474 'title': '',
416 'description': '',
475 'description': '',
417 'status': '',
476 'status': '',
418 'created_on': '',
477 'created_on': '',
419 'updated_on': '',
478 'updated_on': '',
420 'commit_ids': '',
479 'commit_ids': '',
421 'review_status': '',
480 'review_status': '',
422 'mergeable': '',
481 'mergeable': '',
423 'source': '',
482 'source': '',
424 'target': '',
483 'target': '',
425 'author': '',
484 'author': '',
426 'reviewers': '',
485 'reviewers': '',
427 })
486 })
428 def _update_pull_request_hook(*args, **kwargs):
487 def _update_pull_request_hook(*args, **kwargs):
429 """
488 """
430 This hook will be executed after pull requests has been updated with new commits.
489 This hook will be executed after pull requests has been updated with new commits.
431 """
490 """
432 return HookResponse(0, '')
491 return HookResponse(0, '')
433
492
434
493
435 @has_kwargs({
494 @has_kwargs({
436 'server_url': 'url of instance that triggered this hook',
495 'server_url': 'url of instance that triggered this hook',
437 'config': 'path to .ini config used',
496 'config': 'path to .ini config used',
438 'scm': 'type of version control "git", "hg", "svn"',
497 'scm': 'type of version control "git", "hg", "svn"',
439 'username': 'username of actor who triggered this event',
498 'username': 'username of actor who triggered this event',
440 'ip': 'ip address of actor who triggered this hook',
499 'ip': 'ip address of actor who triggered this hook',
441 'action': '',
500 'action': '',
442 'repository': 'repository name',
501 'repository': 'repository name',
443 'pull_request_id': '',
502 'pull_request_id': '',
444 'url': '',
503 'url': '',
445 'title': '',
504 'title': '',
446 'description': '',
505 'description': '',
447 'status': '',
506 'status': '',
448 'created_on': '',
507 'created_on': '',
449 'updated_on': '',
508 'updated_on': '',
450 'commit_ids': '',
509 'commit_ids': '',
451 'review_status': '',
510 'review_status': '',
452 'mergeable': '',
511 'mergeable': '',
453 'source': '',
512 'source': '',
454 'target': '',
513 'target': '',
455 'author': '',
514 'author': '',
456 'reviewers': '',
515 'reviewers': '',
457 })
516 })
458 def _merge_pull_request_hook(*args, **kwargs):
517 def _merge_pull_request_hook(*args, **kwargs):
459 """
518 """
460 This hook will be executed after merge of a pull request.
519 This hook will be executed after merge of a pull request.
461 """
520 """
462 return HookResponse(0, '')
521 return HookResponse(0, '')
463
522
464
523
465 @has_kwargs({
524 @has_kwargs({
466 'server_url': 'url of instance that triggered this hook',
525 'server_url': 'url of instance that triggered this hook',
467 'config': 'path to .ini config used',
526 'config': 'path to .ini config used',
468 'scm': 'type of version control "git", "hg", "svn"',
527 'scm': 'type of version control "git", "hg", "svn"',
469 'username': 'username of actor who triggered this event',
528 'username': 'username of actor who triggered this event',
470 'ip': 'ip address of actor who triggered this hook',
529 'ip': 'ip address of actor who triggered this hook',
471 'action': '',
530 'action': '',
472 'repository': 'repository name',
531 'repository': 'repository name',
473 'pull_request_id': '',
532 'pull_request_id': '',
474 'url': '',
533 'url': '',
475 'title': '',
534 'title': '',
476 'description': '',
535 'description': '',
477 'status': '',
536 'status': '',
478 'created_on': '',
537 'created_on': '',
479 'updated_on': '',
538 'updated_on': '',
480 'commit_ids': '',
539 'commit_ids': '',
481 'review_status': '',
540 'review_status': '',
482 'mergeable': '',
541 'mergeable': '',
483 'source': '',
542 'source': '',
484 'target': '',
543 'target': '',
485 'author': '',
544 'author': '',
486 'reviewers': '',
545 'reviewers': '',
487 })
546 })
488 def _close_pull_request_hook(*args, **kwargs):
547 def _close_pull_request_hook(*args, **kwargs):
489 """
548 """
490 This hook will be executed after close of a pull request.
549 This hook will be executed after close of a pull request.
491 """
550 """
492 return HookResponse(0, '')
551 return HookResponse(0, '')
@@ -1,366 +1,366 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
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
35 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
36 from rhodecode.lib.hooks_base import log_create_repository
36 from rhodecode.lib import hooks_base
37 from rhodecode.lib.utils2 import safe_int, str2bool
37 from rhodecode.lib.utils2 import safe_int, str2bool
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, IntegrityError, true, Repository, RepoGroup, User)
39 Session, IntegrityError, true, Repository, RepoGroup, User)
40
40
41
41
42 @async_task(ignore_result=True, base=RequestContextTask)
42 @async_task(ignore_result=True, base=RequestContextTask)
43 def send_email(recipients, subject, body='', html_body='', email_config=None):
43 def send_email(recipients, subject, body='', html_body='', email_config=None):
44 """
44 """
45 Sends an email with defined parameters from the .ini files.
45 Sends an email with defined parameters from the .ini files.
46
46
47 :param recipients: list of recipients, it this is empty the defined email
47 :param recipients: list of recipients, it this is empty the defined email
48 address from field 'email_to' is used instead
48 address from field 'email_to' is used instead
49 :param subject: subject of the mail
49 :param subject: subject of the mail
50 :param body: body of the mail
50 :param body: body of the mail
51 :param html_body: html version of body
51 :param html_body: html version of body
52 :param email_config: specify custom configuration for mailer
52 :param email_config: specify custom configuration for mailer
53 """
53 """
54 log = get_logger(send_email)
54 log = get_logger(send_email)
55
55
56 email_config = email_config or rhodecode.CONFIG
56 email_config = email_config or rhodecode.CONFIG
57
57
58 mail_server = email_config.get('smtp_server') or None
58 mail_server = email_config.get('smtp_server') or None
59 if mail_server is None:
59 if mail_server is None:
60 log.error("SMTP server information missing. Sending email failed. "
60 log.error("SMTP server information missing. Sending email failed. "
61 "Make sure that `smtp_server` variable is configured "
61 "Make sure that `smtp_server` variable is configured "
62 "inside the .ini file")
62 "inside the .ini file")
63 return False
63 return False
64
64
65 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
65 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
66
66
67 if recipients:
67 if recipients:
68 if isinstance(recipients, compat.string_types):
68 if isinstance(recipients, compat.string_types):
69 recipients = recipients.split(',')
69 recipients = recipients.split(',')
70 else:
70 else:
71 # if recipients are not defined we send to email_config + all admins
71 # if recipients are not defined we send to email_config + all admins
72 admins = []
72 admins = []
73 for u in User.query().filter(User.admin == true()).all():
73 for u in User.query().filter(User.admin == true()).all():
74 if u.email:
74 if u.email:
75 admins.append(u.email)
75 admins.append(u.email)
76 recipients = []
76 recipients = []
77 config_email = email_config.get('email_to')
77 config_email = email_config.get('email_to')
78 if config_email:
78 if config_email:
79 recipients += [config_email]
79 recipients += [config_email]
80 recipients += admins
80 recipients += admins
81
81
82 # translate our LEGACY config into the one that pyramid_mailer supports
82 # translate our LEGACY config into the one that pyramid_mailer supports
83 email_conf = dict(
83 email_conf = dict(
84 host=mail_server,
84 host=mail_server,
85 port=email_config.get('smtp_port', 25),
85 port=email_config.get('smtp_port', 25),
86 username=email_config.get('smtp_username'),
86 username=email_config.get('smtp_username'),
87 password=email_config.get('smtp_password'),
87 password=email_config.get('smtp_password'),
88
88
89 tls=str2bool(email_config.get('smtp_use_tls')),
89 tls=str2bool(email_config.get('smtp_use_tls')),
90 ssl=str2bool(email_config.get('smtp_use_ssl')),
90 ssl=str2bool(email_config.get('smtp_use_ssl')),
91
91
92 # SSL key file
92 # SSL key file
93 # keyfile='',
93 # keyfile='',
94
94
95 # SSL certificate file
95 # SSL certificate file
96 # certfile='',
96 # certfile='',
97
97
98 # Location of maildir
98 # Location of maildir
99 # queue_path='',
99 # queue_path='',
100
100
101 default_sender=email_config.get('app_email_from', 'RhodeCode'),
101 default_sender=email_config.get('app_email_from', 'RhodeCode'),
102
102
103 debug=str2bool(email_config.get('smtp_debug')),
103 debug=str2bool(email_config.get('smtp_debug')),
104 # /usr/sbin/sendmail Sendmail executable
104 # /usr/sbin/sendmail Sendmail executable
105 # sendmail_app='',
105 # sendmail_app='',
106
106
107 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
107 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
108 # sendmail_template='',
108 # sendmail_template='',
109 )
109 )
110
110
111 try:
111 try:
112 mailer = Mailer(**email_conf)
112 mailer = Mailer(**email_conf)
113
113
114 message = Message(subject=subject,
114 message = Message(subject=subject,
115 sender=email_conf['default_sender'],
115 sender=email_conf['default_sender'],
116 recipients=recipients,
116 recipients=recipients,
117 body=body, html=html_body)
117 body=body, html=html_body)
118 mailer.send_immediately(message)
118 mailer.send_immediately(message)
119
119
120 except Exception:
120 except Exception:
121 log.exception('Mail sending failed')
121 log.exception('Mail sending failed')
122 return False
122 return False
123 return True
123 return True
124
124
125
125
126 @async_task(ignore_result=True, base=RequestContextTask)
126 @async_task(ignore_result=True, base=RequestContextTask)
127 def create_repo(form_data, cur_user):
127 def create_repo(form_data, cur_user):
128 from rhodecode.model.repo import RepoModel
128 from rhodecode.model.repo import RepoModel
129 from rhodecode.model.user import UserModel
129 from rhodecode.model.user import UserModel
130 from rhodecode.model.scm import ScmModel
130 from rhodecode.model.scm import ScmModel
131 from rhodecode.model.settings import SettingsModel
131 from rhodecode.model.settings import SettingsModel
132
132
133 log = get_logger(create_repo)
133 log = get_logger(create_repo)
134
134
135 cur_user = UserModel()._get_user(cur_user)
135 cur_user = UserModel()._get_user(cur_user)
136 owner = cur_user
136 owner = cur_user
137
137
138 repo_name = form_data['repo_name']
138 repo_name = form_data['repo_name']
139 repo_name_full = form_data['repo_name_full']
139 repo_name_full = form_data['repo_name_full']
140 repo_type = form_data['repo_type']
140 repo_type = form_data['repo_type']
141 description = form_data['repo_description']
141 description = form_data['repo_description']
142 private = form_data['repo_private']
142 private = form_data['repo_private']
143 clone_uri = form_data.get('clone_uri')
143 clone_uri = form_data.get('clone_uri')
144 repo_group = safe_int(form_data['repo_group'])
144 repo_group = safe_int(form_data['repo_group'])
145 copy_fork_permissions = form_data.get('copy_permissions')
145 copy_fork_permissions = form_data.get('copy_permissions')
146 copy_group_permissions = form_data.get('repo_copy_permissions')
146 copy_group_permissions = form_data.get('repo_copy_permissions')
147 fork_of = form_data.get('fork_parent_id')
147 fork_of = form_data.get('fork_parent_id')
148 state = form_data.get('repo_state', Repository.STATE_PENDING)
148 state = form_data.get('repo_state', Repository.STATE_PENDING)
149
149
150 # repo creation defaults, private and repo_type are filled in form
150 # repo creation defaults, private and repo_type are filled in form
151 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
151 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
152 enable_statistics = form_data.get(
152 enable_statistics = form_data.get(
153 'enable_statistics', defs.get('repo_enable_statistics'))
153 'enable_statistics', defs.get('repo_enable_statistics'))
154 enable_locking = form_data.get(
154 enable_locking = form_data.get(
155 'enable_locking', defs.get('repo_enable_locking'))
155 'enable_locking', defs.get('repo_enable_locking'))
156 enable_downloads = form_data.get(
156 enable_downloads = form_data.get(
157 'enable_downloads', defs.get('repo_enable_downloads'))
157 'enable_downloads', defs.get('repo_enable_downloads'))
158
158
159 # set landing rev based on default branches for SCM
159 # set landing rev based on default branches for SCM
160 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
160 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
161
161
162 try:
162 try:
163 RepoModel()._create_repo(
163 RepoModel()._create_repo(
164 repo_name=repo_name_full,
164 repo_name=repo_name_full,
165 repo_type=repo_type,
165 repo_type=repo_type,
166 description=description,
166 description=description,
167 owner=owner,
167 owner=owner,
168 private=private,
168 private=private,
169 clone_uri=clone_uri,
169 clone_uri=clone_uri,
170 repo_group=repo_group,
170 repo_group=repo_group,
171 landing_rev=landing_ref,
171 landing_rev=landing_ref,
172 fork_of=fork_of,
172 fork_of=fork_of,
173 copy_fork_permissions=copy_fork_permissions,
173 copy_fork_permissions=copy_fork_permissions,
174 copy_group_permissions=copy_group_permissions,
174 copy_group_permissions=copy_group_permissions,
175 enable_statistics=enable_statistics,
175 enable_statistics=enable_statistics,
176 enable_locking=enable_locking,
176 enable_locking=enable_locking,
177 enable_downloads=enable_downloads,
177 enable_downloads=enable_downloads,
178 state=state
178 state=state
179 )
179 )
180 Session().commit()
180 Session().commit()
181
181
182 # now create this repo on Filesystem
182 # now create this repo on Filesystem
183 RepoModel()._create_filesystem_repo(
183 RepoModel()._create_filesystem_repo(
184 repo_name=repo_name,
184 repo_name=repo_name,
185 repo_type=repo_type,
185 repo_type=repo_type,
186 repo_group=RepoModel()._get_repo_group(repo_group),
186 repo_group=RepoModel()._get_repo_group(repo_group),
187 clone_uri=clone_uri,
187 clone_uri=clone_uri,
188 )
188 )
189 repo = Repository.get_by_repo_name(repo_name_full)
189 repo = Repository.get_by_repo_name(repo_name_full)
190 log_create_repository(created_by=owner.username, **repo.get_dict())
190 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
191
191
192 # update repo commit caches initially
192 # update repo commit caches initially
193 repo.update_commit_cache()
193 repo.update_commit_cache()
194
194
195 # set new created state
195 # set new created state
196 repo.set_state(Repository.STATE_CREATED)
196 repo.set_state(Repository.STATE_CREATED)
197 repo_id = repo.repo_id
197 repo_id = repo.repo_id
198 repo_data = repo.get_api_data()
198 repo_data = repo.get_api_data()
199
199
200 audit_logger.store(
200 audit_logger.store(
201 'repo.create', action_data={'data': repo_data},
201 'repo.create', action_data={'data': repo_data},
202 user=cur_user,
202 user=cur_user,
203 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
203 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
204
204
205 Session().commit()
205 Session().commit()
206 except Exception as e:
206 except Exception as e:
207 log.warning('Exception occurred when creating repository, '
207 log.warning('Exception occurred when creating repository, '
208 'doing cleanup...', exc_info=True)
208 'doing cleanup...', exc_info=True)
209 if isinstance(e, IntegrityError):
209 if isinstance(e, IntegrityError):
210 Session().rollback()
210 Session().rollback()
211
211
212 # rollback things manually !
212 # rollback things manually !
213 repo = Repository.get_by_repo_name(repo_name_full)
213 repo = Repository.get_by_repo_name(repo_name_full)
214 if repo:
214 if repo:
215 Repository.delete(repo.repo_id)
215 Repository.delete(repo.repo_id)
216 Session().commit()
216 Session().commit()
217 RepoModel()._delete_filesystem_repo(repo)
217 RepoModel()._delete_filesystem_repo(repo)
218 log.info('Cleanup of repo %s finished', repo_name_full)
218 log.info('Cleanup of repo %s finished', repo_name_full)
219 raise
219 raise
220
220
221 return True
221 return True
222
222
223
223
224 @async_task(ignore_result=True, base=RequestContextTask)
224 @async_task(ignore_result=True, base=RequestContextTask)
225 def create_repo_fork(form_data, cur_user):
225 def create_repo_fork(form_data, cur_user):
226 """
226 """
227 Creates a fork of repository using internal VCS methods
227 Creates a fork of repository using internal VCS methods
228 """
228 """
229 from rhodecode.model.repo import RepoModel
229 from rhodecode.model.repo import RepoModel
230 from rhodecode.model.user import UserModel
230 from rhodecode.model.user import UserModel
231
231
232 log = get_logger(create_repo_fork)
232 log = get_logger(create_repo_fork)
233
233
234 cur_user = UserModel()._get_user(cur_user)
234 cur_user = UserModel()._get_user(cur_user)
235 owner = cur_user
235 owner = cur_user
236
236
237 repo_name = form_data['repo_name'] # fork in this case
237 repo_name = form_data['repo_name'] # fork in this case
238 repo_name_full = form_data['repo_name_full']
238 repo_name_full = form_data['repo_name_full']
239 repo_type = form_data['repo_type']
239 repo_type = form_data['repo_type']
240 description = form_data['description']
240 description = form_data['description']
241 private = form_data['private']
241 private = form_data['private']
242 clone_uri = form_data.get('clone_uri')
242 clone_uri = form_data.get('clone_uri')
243 repo_group = safe_int(form_data['repo_group'])
243 repo_group = safe_int(form_data['repo_group'])
244 landing_ref = form_data['landing_rev']
244 landing_ref = form_data['landing_rev']
245 copy_fork_permissions = form_data.get('copy_permissions')
245 copy_fork_permissions = form_data.get('copy_permissions')
246 fork_id = safe_int(form_data.get('fork_parent_id'))
246 fork_id = safe_int(form_data.get('fork_parent_id'))
247
247
248 try:
248 try:
249 fork_of = RepoModel()._get_repo(fork_id)
249 fork_of = RepoModel()._get_repo(fork_id)
250 RepoModel()._create_repo(
250 RepoModel()._create_repo(
251 repo_name=repo_name_full,
251 repo_name=repo_name_full,
252 repo_type=repo_type,
252 repo_type=repo_type,
253 description=description,
253 description=description,
254 owner=owner,
254 owner=owner,
255 private=private,
255 private=private,
256 clone_uri=clone_uri,
256 clone_uri=clone_uri,
257 repo_group=repo_group,
257 repo_group=repo_group,
258 landing_rev=landing_ref,
258 landing_rev=landing_ref,
259 fork_of=fork_of,
259 fork_of=fork_of,
260 copy_fork_permissions=copy_fork_permissions
260 copy_fork_permissions=copy_fork_permissions
261 )
261 )
262
262
263 Session().commit()
263 Session().commit()
264
264
265 base_path = Repository.base_path()
265 base_path = Repository.base_path()
266 source_repo_path = os.path.join(base_path, fork_of.repo_name)
266 source_repo_path = os.path.join(base_path, fork_of.repo_name)
267
267
268 # now create this repo on Filesystem
268 # now create this repo on Filesystem
269 RepoModel()._create_filesystem_repo(
269 RepoModel()._create_filesystem_repo(
270 repo_name=repo_name,
270 repo_name=repo_name,
271 repo_type=repo_type,
271 repo_type=repo_type,
272 repo_group=RepoModel()._get_repo_group(repo_group),
272 repo_group=RepoModel()._get_repo_group(repo_group),
273 clone_uri=source_repo_path,
273 clone_uri=source_repo_path,
274 )
274 )
275 repo = Repository.get_by_repo_name(repo_name_full)
275 repo = Repository.get_by_repo_name(repo_name_full)
276 log_create_repository(created_by=owner.username, **repo.get_dict())
276 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
277
277
278 # update repo commit caches initially
278 # update repo commit caches initially
279 config = repo._config
279 config = repo._config
280 config.set('extensions', 'largefiles', '')
280 config.set('extensions', 'largefiles', '')
281 repo.update_commit_cache(config=config)
281 repo.update_commit_cache(config=config)
282
282
283 # set new created state
283 # set new created state
284 repo.set_state(Repository.STATE_CREATED)
284 repo.set_state(Repository.STATE_CREATED)
285
285
286 repo_id = repo.repo_id
286 repo_id = repo.repo_id
287 repo_data = repo.get_api_data()
287 repo_data = repo.get_api_data()
288 audit_logger.store(
288 audit_logger.store(
289 'repo.fork', action_data={'data': repo_data},
289 'repo.fork', action_data={'data': repo_data},
290 user=cur_user,
290 user=cur_user,
291 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
291 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
292
292
293 Session().commit()
293 Session().commit()
294 except Exception as e:
294 except Exception as e:
295 log.warning('Exception occurred when forking repository, '
295 log.warning('Exception occurred when forking repository, '
296 'doing cleanup...', exc_info=True)
296 'doing cleanup...', exc_info=True)
297 if isinstance(e, IntegrityError):
297 if isinstance(e, IntegrityError):
298 Session().rollback()
298 Session().rollback()
299
299
300 # rollback things manually !
300 # rollback things manually !
301 repo = Repository.get_by_repo_name(repo_name_full)
301 repo = Repository.get_by_repo_name(repo_name_full)
302 if repo:
302 if repo:
303 Repository.delete(repo.repo_id)
303 Repository.delete(repo.repo_id)
304 Session().commit()
304 Session().commit()
305 RepoModel()._delete_filesystem_repo(repo)
305 RepoModel()._delete_filesystem_repo(repo)
306 log.info('Cleanup of repo %s finished', repo_name_full)
306 log.info('Cleanup of repo %s finished', repo_name_full)
307 raise
307 raise
308
308
309 return True
309 return True
310
310
311
311
312 @async_task(ignore_result=True)
312 @async_task(ignore_result=True)
313 def repo_maintenance(repoid):
313 def repo_maintenance(repoid):
314 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
314 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
315 log = get_logger(repo_maintenance)
315 log = get_logger(repo_maintenance)
316 repo = Repository.get_by_id_or_repo_name(repoid)
316 repo = Repository.get_by_id_or_repo_name(repoid)
317 if repo:
317 if repo:
318 maintenance = repo_maintenance_lib.RepoMaintenance()
318 maintenance = repo_maintenance_lib.RepoMaintenance()
319 tasks = maintenance.get_tasks_for_repo(repo)
319 tasks = maintenance.get_tasks_for_repo(repo)
320 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
320 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
321 executed_types = maintenance.execute(repo)
321 executed_types = maintenance.execute(repo)
322 log.debug('Got execution results %s', executed_types)
322 log.debug('Got execution results %s', executed_types)
323 else:
323 else:
324 log.debug('Repo `%s` not found or without a clone_url', repoid)
324 log.debug('Repo `%s` not found or without a clone_url', repoid)
325
325
326
326
327 @async_task(ignore_result=True)
327 @async_task(ignore_result=True)
328 def check_for_update():
328 def check_for_update():
329 from rhodecode.model.update import UpdateModel
329 from rhodecode.model.update import UpdateModel
330 update_url = UpdateModel().get_update_url()
330 update_url = UpdateModel().get_update_url()
331 cur_ver = rhodecode.__version__
331 cur_ver = rhodecode.__version__
332
332
333 try:
333 try:
334 data = UpdateModel().get_update_data(update_url)
334 data = UpdateModel().get_update_data(update_url)
335 latest = data['versions'][0]
335 latest = data['versions'][0]
336 UpdateModel().store_version(latest['version'])
336 UpdateModel().store_version(latest['version'])
337 except Exception:
337 except Exception:
338 pass
338 pass
339
339
340
340
341 @async_task(ignore_result=False)
341 @async_task(ignore_result=False)
342 def beat_check(*args, **kwargs):
342 def beat_check(*args, **kwargs):
343 log = get_logger(beat_check)
343 log = get_logger(beat_check)
344 log.info('Got args: %r and kwargs %r', args, kwargs)
344 log.info('Got args: %r and kwargs %r', args, kwargs)
345 return time.time()
345 return time.time()
346
346
347
347
348 @async_task(ignore_result=True)
348 @async_task(ignore_result=True)
349 def sync_last_update(*args, **kwargs):
349 def sync_last_update(*args, **kwargs):
350
350
351 skip_repos = kwargs.get('skip_repos')
351 skip_repos = kwargs.get('skip_repos')
352 if not skip_repos:
352 if not skip_repos:
353 repos = Repository.query() \
353 repos = Repository.query() \
354 .order_by(Repository.group_id.asc())
354 .order_by(Repository.group_id.asc())
355
355
356 for repo in repos:
356 for repo in repos:
357 repo.update_commit_cache()
357 repo.update_commit_cache()
358
358
359 skip_groups = kwargs.get('skip_groups')
359 skip_groups = kwargs.get('skip_groups')
360 if not skip_groups:
360 if not skip_groups:
361 repo_groups = RepoGroup.query() \
361 repo_groups = RepoGroup.query() \
362 .filter(RepoGroup.group_parent_id == None)
362 .filter(RepoGroup.group_parent_id == None)
363
363
364 for root_gr in repo_groups:
364 for root_gr in repo_groups:
365 for repo_gr in reversed(root_gr.recursive_groups()):
365 for repo_gr in reversed(root_gr.recursive_groups()):
366 repo_gr.update_commit_cache()
366 repo_gr.update_commit_cache()
@@ -1,509 +1,526 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2020 RhodeCode GmbH
3 # Copyright (C) 2013-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 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib import audit_logger
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.exceptions import (
34 from rhodecode.lib.exceptions import (
35 HTTPLockedRC, HTTPBranchProtected, UserCreationError)
35 HTTPLockedRC, HTTPBranchProtected, UserCreationError)
36 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class HookResponse(object):
41 class HookResponse(object):
42 def __init__(self, status, output):
42 def __init__(self, status, output):
43 self.status = status
43 self.status = status
44 self.output = output
44 self.output = output
45
45
46 def __add__(self, other):
46 def __add__(self, other):
47 other_status = getattr(other, 'status', 0)
47 other_status = getattr(other, 'status', 0)
48 new_status = max(self.status, other_status)
48 new_status = max(self.status, other_status)
49 other_output = getattr(other, 'output', '')
49 other_output = getattr(other, 'output', '')
50 new_output = self.output + other_output
50 new_output = self.output + other_output
51
51
52 return HookResponse(new_status, new_output)
52 return HookResponse(new_status, new_output)
53
53
54 def __bool__(self):
54 def __bool__(self):
55 return self.status == 0
55 return self.status == 0
56
56
57
57
58 def is_shadow_repo(extras):
58 def is_shadow_repo(extras):
59 """
59 """
60 Returns ``True`` if this is an action executed against a shadow repository.
60 Returns ``True`` if this is an action executed against a shadow repository.
61 """
61 """
62 return extras['is_shadow_repo']
62 return extras['is_shadow_repo']
63
63
64
64
65 def _get_scm_size(alias, root_path):
65 def _get_scm_size(alias, root_path):
66
66
67 if not alias.startswith('.'):
67 if not alias.startswith('.'):
68 alias += '.'
68 alias += '.'
69
69
70 size_scm, size_root = 0, 0
70 size_scm, size_root = 0, 0
71 for path, unused_dirs, files in os.walk(safe_str(root_path)):
71 for path, unused_dirs, files in os.walk(safe_str(root_path)):
72 if path.find(alias) != -1:
72 if path.find(alias) != -1:
73 for f in files:
73 for f in files:
74 try:
74 try:
75 size_scm += os.path.getsize(os.path.join(path, f))
75 size_scm += os.path.getsize(os.path.join(path, f))
76 except OSError:
76 except OSError:
77 pass
77 pass
78 else:
78 else:
79 for f in files:
79 for f in files:
80 try:
80 try:
81 size_root += os.path.getsize(os.path.join(path, f))
81 size_root += os.path.getsize(os.path.join(path, f))
82 except OSError:
82 except OSError:
83 pass
83 pass
84
84
85 size_scm_f = h.format_byte_size_binary(size_scm)
85 size_scm_f = h.format_byte_size_binary(size_scm)
86 size_root_f = h.format_byte_size_binary(size_root)
86 size_root_f = h.format_byte_size_binary(size_root)
87 size_total_f = h.format_byte_size_binary(size_root + size_scm)
87 size_total_f = h.format_byte_size_binary(size_root + size_scm)
88
88
89 return size_scm_f, size_root_f, size_total_f
89 return size_scm_f, size_root_f, size_total_f
90
90
91
91
92 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
92 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
93 def repo_size(extras):
93 def repo_size(extras):
94 """Present size of repository after push."""
94 """Present size of repository after push."""
95 repo = Repository.get_by_repo_name(extras.repository)
95 repo = Repository.get_by_repo_name(extras.repository)
96 vcs_part = safe_str(u'.%s' % repo.repo_type)
96 vcs_part = safe_str(u'.%s' % repo.repo_type)
97 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
97 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
98 repo.repo_full_path)
98 repo.repo_full_path)
99 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
99 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
100 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
100 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
101 return HookResponse(0, msg)
101 return HookResponse(0, msg)
102
102
103
103
104 def pre_push(extras):
104 def pre_push(extras):
105 """
105 """
106 Hook executed before pushing code.
106 Hook executed before pushing code.
107
107
108 It bans pushing when the repository is locked.
108 It bans pushing when the repository is locked.
109 """
109 """
110
110
111 user = User.get_by_username(extras.username)
111 user = User.get_by_username(extras.username)
112 output = ''
112 output = ''
113 if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
113 if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
114 locked_by = User.get(extras.locked_by[0]).username
114 locked_by = User.get(extras.locked_by[0]).username
115 reason = extras.locked_by[2]
115 reason = extras.locked_by[2]
116 # this exception is interpreted in git/hg middlewares and based
116 # this exception is interpreted in git/hg middlewares and based
117 # on that proper return code is server to client
117 # on that proper return code is server to client
118 _http_ret = HTTPLockedRC(
118 _http_ret = HTTPLockedRC(
119 _locked_by_explanation(extras.repository, locked_by, reason))
119 _locked_by_explanation(extras.repository, locked_by, reason))
120 if str(_http_ret.code).startswith('2'):
120 if str(_http_ret.code).startswith('2'):
121 # 2xx Codes don't raise exceptions
121 # 2xx Codes don't raise exceptions
122 output = _http_ret.title
122 output = _http_ret.title
123 else:
123 else:
124 raise _http_ret
124 raise _http_ret
125
125
126 hook_response = ''
126 hook_response = ''
127 if not is_shadow_repo(extras):
127 if not is_shadow_repo(extras):
128 if extras.commit_ids and extras.check_branch_perms:
128 if extras.commit_ids and extras.check_branch_perms:
129
129
130 auth_user = user.AuthUser()
130 auth_user = user.AuthUser()
131 repo = Repository.get_by_repo_name(extras.repository)
131 repo = Repository.get_by_repo_name(extras.repository)
132 affected_branches = []
132 affected_branches = []
133 if repo.repo_type == 'hg':
133 if repo.repo_type == 'hg':
134 for entry in extras.commit_ids:
134 for entry in extras.commit_ids:
135 if entry['type'] == 'branch':
135 if entry['type'] == 'branch':
136 is_forced = bool(entry['multiple_heads'])
136 is_forced = bool(entry['multiple_heads'])
137 affected_branches.append([entry['name'], is_forced])
137 affected_branches.append([entry['name'], is_forced])
138 elif repo.repo_type == 'git':
138 elif repo.repo_type == 'git':
139 for entry in extras.commit_ids:
139 for entry in extras.commit_ids:
140 if entry['type'] == 'heads':
140 if entry['type'] == 'heads':
141 is_forced = bool(entry['pruned_sha'])
141 is_forced = bool(entry['pruned_sha'])
142 affected_branches.append([entry['name'], is_forced])
142 affected_branches.append([entry['name'], is_forced])
143
143
144 for branch_name, is_forced in affected_branches:
144 for branch_name, is_forced in affected_branches:
145
145
146 rule, branch_perm = auth_user.get_rule_and_branch_permission(
146 rule, branch_perm = auth_user.get_rule_and_branch_permission(
147 extras.repository, branch_name)
147 extras.repository, branch_name)
148 if not branch_perm:
148 if not branch_perm:
149 # no branch permission found for this branch, just keep checking
149 # no branch permission found for this branch, just keep checking
150 continue
150 continue
151
151
152 if branch_perm == 'branch.push_force':
152 if branch_perm == 'branch.push_force':
153 continue
153 continue
154 elif branch_perm == 'branch.push' and is_forced is False:
154 elif branch_perm == 'branch.push' and is_forced is False:
155 continue
155 continue
156 elif branch_perm == 'branch.push' and is_forced is True:
156 elif branch_perm == 'branch.push' and is_forced is True:
157 halt_message = 'Branch `{}` changes rejected by rule {}. ' \
157 halt_message = 'Branch `{}` changes rejected by rule {}. ' \
158 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
158 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
159 else:
159 else:
160 halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
160 halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
161 branch_name, rule)
161 branch_name, rule)
162
162
163 if halt_message:
163 if halt_message:
164 _http_ret = HTTPBranchProtected(halt_message)
164 _http_ret = HTTPBranchProtected(halt_message)
165 raise _http_ret
165 raise _http_ret
166
166
167 # Propagate to external components. This is done after checking the
167 # Propagate to external components. This is done after checking the
168 # lock, for consistent behavior.
168 # lock, for consistent behavior.
169 hook_response = pre_push_extension(
169 hook_response = pre_push_extension(
170 repo_store_path=Repository.base_path(), **extras)
170 repo_store_path=Repository.base_path(), **extras)
171 events.trigger(events.RepoPrePushEvent(
171 events.trigger(events.RepoPrePushEvent(
172 repo_name=extras.repository, extras=extras))
172 repo_name=extras.repository, extras=extras))
173
173
174 return HookResponse(0, output) + hook_response
174 return HookResponse(0, output) + hook_response
175
175
176
176
177 def pre_pull(extras):
177 def pre_pull(extras):
178 """
178 """
179 Hook executed before pulling the code.
179 Hook executed before pulling the code.
180
180
181 It bans pulling when the repository is locked.
181 It bans pulling when the repository is locked.
182 """
182 """
183
183
184 output = ''
184 output = ''
185 if extras.locked_by[0]:
185 if extras.locked_by[0]:
186 locked_by = User.get(extras.locked_by[0]).username
186 locked_by = User.get(extras.locked_by[0]).username
187 reason = extras.locked_by[2]
187 reason = extras.locked_by[2]
188 # this exception is interpreted in git/hg middlewares and based
188 # this exception is interpreted in git/hg middlewares and based
189 # on that proper return code is server to client
189 # on that proper return code is server to client
190 _http_ret = HTTPLockedRC(
190 _http_ret = HTTPLockedRC(
191 _locked_by_explanation(extras.repository, locked_by, reason))
191 _locked_by_explanation(extras.repository, locked_by, reason))
192 if str(_http_ret.code).startswith('2'):
192 if str(_http_ret.code).startswith('2'):
193 # 2xx Codes don't raise exceptions
193 # 2xx Codes don't raise exceptions
194 output = _http_ret.title
194 output = _http_ret.title
195 else:
195 else:
196 raise _http_ret
196 raise _http_ret
197
197
198 # Propagate to external components. This is done after checking the
198 # Propagate to external components. This is done after checking the
199 # lock, for consistent behavior.
199 # lock, for consistent behavior.
200 hook_response = ''
200 hook_response = ''
201 if not is_shadow_repo(extras):
201 if not is_shadow_repo(extras):
202 extras.hook_type = extras.hook_type or 'pre_pull'
202 extras.hook_type = extras.hook_type or 'pre_pull'
203 hook_response = pre_pull_extension(
203 hook_response = pre_pull_extension(
204 repo_store_path=Repository.base_path(), **extras)
204 repo_store_path=Repository.base_path(), **extras)
205 events.trigger(events.RepoPrePullEvent(
205 events.trigger(events.RepoPrePullEvent(
206 repo_name=extras.repository, extras=extras))
206 repo_name=extras.repository, extras=extras))
207
207
208 return HookResponse(0, output) + hook_response
208 return HookResponse(0, output) + hook_response
209
209
210
210
211 def post_pull(extras):
211 def post_pull(extras):
212 """Hook executed after client pulls the code."""
212 """Hook executed after client pulls the code."""
213
213
214 audit_user = audit_logger.UserWrap(
214 audit_user = audit_logger.UserWrap(
215 username=extras.username,
215 username=extras.username,
216 ip_addr=extras.ip)
216 ip_addr=extras.ip)
217 repo = audit_logger.RepoWrap(repo_name=extras.repository)
217 repo = audit_logger.RepoWrap(repo_name=extras.repository)
218 audit_logger.store(
218 audit_logger.store(
219 'user.pull', action_data={'user_agent': extras.user_agent},
219 'user.pull', action_data={'user_agent': extras.user_agent},
220 user=audit_user, repo=repo, commit=True)
220 user=audit_user, repo=repo, commit=True)
221
221
222 output = ''
222 output = ''
223 # make lock is a tri state False, True, None. We only make lock on True
223 # make lock is a tri state False, True, None. We only make lock on True
224 if extras.make_lock is True and not is_shadow_repo(extras):
224 if extras.make_lock is True and not is_shadow_repo(extras):
225 user = User.get_by_username(extras.username)
225 user = User.get_by_username(extras.username)
226 Repository.lock(Repository.get_by_repo_name(extras.repository),
226 Repository.lock(Repository.get_by_repo_name(extras.repository),
227 user.user_id,
227 user.user_id,
228 lock_reason=Repository.LOCK_PULL)
228 lock_reason=Repository.LOCK_PULL)
229 msg = 'Made lock on repo `%s`' % (extras.repository,)
229 msg = 'Made lock on repo `%s`' % (extras.repository,)
230 output += msg
230 output += msg
231
231
232 if extras.locked_by[0]:
232 if extras.locked_by[0]:
233 locked_by = User.get(extras.locked_by[0]).username
233 locked_by = User.get(extras.locked_by[0]).username
234 reason = extras.locked_by[2]
234 reason = extras.locked_by[2]
235 _http_ret = HTTPLockedRC(
235 _http_ret = HTTPLockedRC(
236 _locked_by_explanation(extras.repository, locked_by, reason))
236 _locked_by_explanation(extras.repository, locked_by, reason))
237 if str(_http_ret.code).startswith('2'):
237 if str(_http_ret.code).startswith('2'):
238 # 2xx Codes don't raise exceptions
238 # 2xx Codes don't raise exceptions
239 output += _http_ret.title
239 output += _http_ret.title
240
240
241 # Propagate to external components.
241 # Propagate to external components.
242 hook_response = ''
242 hook_response = ''
243 if not is_shadow_repo(extras):
243 if not is_shadow_repo(extras):
244 extras.hook_type = extras.hook_type or 'post_pull'
244 extras.hook_type = extras.hook_type or 'post_pull'
245 hook_response = post_pull_extension(
245 hook_response = post_pull_extension(
246 repo_store_path=Repository.base_path(), **extras)
246 repo_store_path=Repository.base_path(), **extras)
247 events.trigger(events.RepoPullEvent(
247 events.trigger(events.RepoPullEvent(
248 repo_name=extras.repository, extras=extras))
248 repo_name=extras.repository, extras=extras))
249
249
250 return HookResponse(0, output) + hook_response
250 return HookResponse(0, output) + hook_response
251
251
252
252
253 def post_push(extras):
253 def post_push(extras):
254 """Hook executed after user pushes to the repository."""
254 """Hook executed after user pushes to the repository."""
255 commit_ids = extras.commit_ids
255 commit_ids = extras.commit_ids
256
256
257 # log the push call
257 # log the push call
258 audit_user = audit_logger.UserWrap(
258 audit_user = audit_logger.UserWrap(
259 username=extras.username, ip_addr=extras.ip)
259 username=extras.username, ip_addr=extras.ip)
260 repo = audit_logger.RepoWrap(repo_name=extras.repository)
260 repo = audit_logger.RepoWrap(repo_name=extras.repository)
261 audit_logger.store(
261 audit_logger.store(
262 'user.push', action_data={
262 'user.push', action_data={
263 'user_agent': extras.user_agent,
263 'user_agent': extras.user_agent,
264 'commit_ids': commit_ids[:400]},
264 'commit_ids': commit_ids[:400]},
265 user=audit_user, repo=repo, commit=True)
265 user=audit_user, repo=repo, commit=True)
266
266
267 # Propagate to external components.
267 # Propagate to external components.
268 output = ''
268 output = ''
269 # make lock is a tri state False, True, None. We only release lock on False
269 # make lock is a tri state False, True, None. We only release lock on False
270 if extras.make_lock is False and not is_shadow_repo(extras):
270 if extras.make_lock is False and not is_shadow_repo(extras):
271 Repository.unlock(Repository.get_by_repo_name(extras.repository))
271 Repository.unlock(Repository.get_by_repo_name(extras.repository))
272 msg = 'Released lock on repo `{}`\n'.format(safe_str(extras.repository))
272 msg = 'Released lock on repo `{}`\n'.format(safe_str(extras.repository))
273 output += msg
273 output += msg
274
274
275 if extras.locked_by[0]:
275 if extras.locked_by[0]:
276 locked_by = User.get(extras.locked_by[0]).username
276 locked_by = User.get(extras.locked_by[0]).username
277 reason = extras.locked_by[2]
277 reason = extras.locked_by[2]
278 _http_ret = HTTPLockedRC(
278 _http_ret = HTTPLockedRC(
279 _locked_by_explanation(extras.repository, locked_by, reason))
279 _locked_by_explanation(extras.repository, locked_by, reason))
280 # TODO: johbo: if not?
280 # TODO: johbo: if not?
281 if str(_http_ret.code).startswith('2'):
281 if str(_http_ret.code).startswith('2'):
282 # 2xx Codes don't raise exceptions
282 # 2xx Codes don't raise exceptions
283 output += _http_ret.title
283 output += _http_ret.title
284
284
285 if extras.new_refs:
285 if extras.new_refs:
286 tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format(
286 tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format(
287 safe_str(extras.server_url), safe_str(extras.repository))
287 safe_str(extras.server_url), safe_str(extras.repository))
288
288
289 for branch_name in extras.new_refs['branches']:
289 for branch_name in extras.new_refs['branches']:
290 output += 'RhodeCode: open pull request link: {}\n'.format(
290 output += 'RhodeCode: open pull request link: {}\n'.format(
291 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
291 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
292
292
293 for book_name in extras.new_refs['bookmarks']:
293 for book_name in extras.new_refs['bookmarks']:
294 output += 'RhodeCode: open pull request link: {}\n'.format(
294 output += 'RhodeCode: open pull request link: {}\n'.format(
295 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
295 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
296
296
297 hook_response = ''
297 hook_response = ''
298 if not is_shadow_repo(extras):
298 if not is_shadow_repo(extras):
299 hook_response = post_push_extension(
299 hook_response = post_push_extension(
300 repo_store_path=Repository.base_path(),
300 repo_store_path=Repository.base_path(),
301 **extras)
301 **extras)
302 events.trigger(events.RepoPushEvent(
302 events.trigger(events.RepoPushEvent(
303 repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
303 repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
304
304
305 output += 'RhodeCode: push completed\n'
305 output += 'RhodeCode: push completed\n'
306 return HookResponse(0, output) + hook_response
306 return HookResponse(0, output) + hook_response
307
307
308
308
309 def _locked_by_explanation(repo_name, user_name, reason):
309 def _locked_by_explanation(repo_name, user_name, reason):
310 message = (
310 message = (
311 'Repository `%s` locked by user `%s`. Reason:`%s`'
311 'Repository `%s` locked by user `%s`. Reason:`%s`'
312 % (repo_name, user_name, reason))
312 % (repo_name, user_name, reason))
313 return message
313 return message
314
314
315
315
316 def check_allowed_create_user(user_dict, created_by, **kwargs):
316 def check_allowed_create_user(user_dict, created_by, **kwargs):
317 # pre create hooks
317 # pre create hooks
318 if pre_create_user.is_active():
318 if pre_create_user.is_active():
319 hook_result = pre_create_user(created_by=created_by, **user_dict)
319 hook_result = pre_create_user(created_by=created_by, **user_dict)
320 allowed = hook_result.status == 0
320 allowed = hook_result.status == 0
321 if not allowed:
321 if not allowed:
322 reason = hook_result.output
322 reason = hook_result.output
323 raise UserCreationError(reason)
323 raise UserCreationError(reason)
324
324
325
325
326 class ExtensionCallback(object):
326 class ExtensionCallback(object):
327 """
327 """
328 Forwards a given call to rcextensions, sanitizes keyword arguments.
328 Forwards a given call to rcextensions, sanitizes keyword arguments.
329
329
330 Does check if there is an extension active for that hook. If it is
330 Does check if there is an extension active for that hook. If it is
331 there, it will forward all `kwargs_keys` keyword arguments to the
331 there, it will forward all `kwargs_keys` keyword arguments to the
332 extension callback.
332 extension callback.
333 """
333 """
334
334
335 def __init__(self, hook_name, kwargs_keys):
335 def __init__(self, hook_name, kwargs_keys):
336 self._hook_name = hook_name
336 self._hook_name = hook_name
337 self._kwargs_keys = set(kwargs_keys)
337 self._kwargs_keys = set(kwargs_keys)
338
338
339 def __call__(self, *args, **kwargs):
339 def __call__(self, *args, **kwargs):
340 log.debug('Calling extension callback for `%s`', self._hook_name)
340 log.debug('Calling extension callback for `%s`', self._hook_name)
341 callback = self._get_callback()
341 callback = self._get_callback()
342 if not callback:
342 if not callback:
343 log.debug('extension callback `%s` not found, skipping...', self._hook_name)
343 log.debug('extension callback `%s` not found, skipping...', self._hook_name)
344 return
344 return
345
345
346 kwargs_to_pass = {}
346 kwargs_to_pass = {}
347 for key in self._kwargs_keys:
347 for key in self._kwargs_keys:
348 try:
348 try:
349 kwargs_to_pass[key] = kwargs[key]
349 kwargs_to_pass[key] = kwargs[key]
350 except KeyError:
350 except KeyError:
351 log.error('Failed to fetch %s key from given kwargs. '
351 log.error('Failed to fetch %s key from given kwargs. '
352 'Expected keys: %s', key, self._kwargs_keys)
352 'Expected keys: %s', key, self._kwargs_keys)
353 raise
353 raise
354
354
355 # backward compat for removed api_key for old hooks. This was it works
355 # backward compat for removed api_key for old hooks. This was it works
356 # with older rcextensions that require api_key present
356 # with older rcextensions that require api_key present
357 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
357 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
358 kwargs_to_pass['api_key'] = '_DEPRECATED_'
358 kwargs_to_pass['api_key'] = '_DEPRECATED_'
359 return callback(**kwargs_to_pass)
359 return callback(**kwargs_to_pass)
360
360
361 def is_active(self):
361 def is_active(self):
362 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
362 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
363
363
364 def _get_callback(self):
364 def _get_callback(self):
365 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
365 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
366
366
367
367
368 pre_pull_extension = ExtensionCallback(
368 pre_pull_extension = ExtensionCallback(
369 hook_name='PRE_PULL_HOOK',
369 hook_name='PRE_PULL_HOOK',
370 kwargs_keys=(
370 kwargs_keys=(
371 'server_url', 'config', 'scm', 'username', 'ip', 'action',
371 'server_url', 'config', 'scm', 'username', 'ip', 'action',
372 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
372 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
373
373
374
374
375 post_pull_extension = ExtensionCallback(
375 post_pull_extension = ExtensionCallback(
376 hook_name='PULL_HOOK',
376 hook_name='PULL_HOOK',
377 kwargs_keys=(
377 kwargs_keys=(
378 'server_url', 'config', 'scm', 'username', 'ip', 'action',
378 'server_url', 'config', 'scm', 'username', 'ip', 'action',
379 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
379 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
380
380
381
381
382 pre_push_extension = ExtensionCallback(
382 pre_push_extension = ExtensionCallback(
383 hook_name='PRE_PUSH_HOOK',
383 hook_name='PRE_PUSH_HOOK',
384 kwargs_keys=(
384 kwargs_keys=(
385 'server_url', 'config', 'scm', 'username', 'ip', 'action',
385 'server_url', 'config', 'scm', 'username', 'ip', 'action',
386 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
386 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
387
387
388
388
389 post_push_extension = ExtensionCallback(
389 post_push_extension = ExtensionCallback(
390 hook_name='PUSH_HOOK',
390 hook_name='PUSH_HOOK',
391 kwargs_keys=(
391 kwargs_keys=(
392 'server_url', 'config', 'scm', 'username', 'ip', 'action',
392 'server_url', 'config', 'scm', 'username', 'ip', 'action',
393 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
393 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
394
394
395
395
396 pre_create_user = ExtensionCallback(
396 pre_create_user = ExtensionCallback(
397 hook_name='PRE_CREATE_USER_HOOK',
397 hook_name='PRE_CREATE_USER_HOOK',
398 kwargs_keys=(
398 kwargs_keys=(
399 'username', 'password', 'email', 'firstname', 'lastname', 'active',
399 'username', 'password', 'email', 'firstname', 'lastname', 'active',
400 'admin', 'created_by'))
400 'admin', 'created_by'))
401
401
402
402
403 log_create_pull_request = ExtensionCallback(
403 create_pull_request = ExtensionCallback(
404 hook_name='CREATE_PULL_REQUEST',
404 hook_name='CREATE_PULL_REQUEST',
405 kwargs_keys=(
405 kwargs_keys=(
406 'server_url', 'config', 'scm', 'username', 'ip', 'action',
406 'server_url', 'config', 'scm', 'username', 'ip', 'action',
407 'repository', 'pull_request_id', 'url', 'title', 'description',
407 'repository', 'pull_request_id', 'url', 'title', 'description',
408 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
408 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
409 'mergeable', 'source', 'target', 'author', 'reviewers'))
409 'mergeable', 'source', 'target', 'author', 'reviewers'))
410
410
411
411
412 log_merge_pull_request = ExtensionCallback(
412 merge_pull_request = ExtensionCallback(
413 hook_name='MERGE_PULL_REQUEST',
413 hook_name='MERGE_PULL_REQUEST',
414 kwargs_keys=(
414 kwargs_keys=(
415 'server_url', 'config', 'scm', 'username', 'ip', 'action',
415 'server_url', 'config', 'scm', 'username', 'ip', 'action',
416 'repository', 'pull_request_id', 'url', 'title', 'description',
416 'repository', 'pull_request_id', 'url', 'title', 'description',
417 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
417 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
418 'mergeable', 'source', 'target', 'author', 'reviewers'))
418 'mergeable', 'source', 'target', 'author', 'reviewers'))
419
419
420
420
421 log_close_pull_request = ExtensionCallback(
421 close_pull_request = ExtensionCallback(
422 hook_name='CLOSE_PULL_REQUEST',
422 hook_name='CLOSE_PULL_REQUEST',
423 kwargs_keys=(
423 kwargs_keys=(
424 'server_url', 'config', 'scm', 'username', 'ip', 'action',
424 'server_url', 'config', 'scm', 'username', 'ip', 'action',
425 'repository', 'pull_request_id', 'url', 'title', 'description',
425 'repository', 'pull_request_id', 'url', 'title', 'description',
426 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
426 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
427 'mergeable', 'source', 'target', 'author', 'reviewers'))
427 'mergeable', 'source', 'target', 'author', 'reviewers'))
428
428
429
429
430 log_review_pull_request = ExtensionCallback(
430 review_pull_request = ExtensionCallback(
431 hook_name='REVIEW_PULL_REQUEST',
431 hook_name='REVIEW_PULL_REQUEST',
432 kwargs_keys=(
432 kwargs_keys=(
433 'server_url', 'config', 'scm', 'username', 'ip', 'action',
433 'server_url', 'config', 'scm', 'username', 'ip', 'action',
434 'repository', 'pull_request_id', 'url', 'title', 'description',
434 'repository', 'pull_request_id', 'url', 'title', 'description',
435 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
435 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
436 'mergeable', 'source', 'target', 'author', 'reviewers'))
436 'mergeable', 'source', 'target', 'author', 'reviewers'))
437
437
438
438
439 log_comment_pull_request = ExtensionCallback(
439 comment_pull_request = ExtensionCallback(
440 hook_name='COMMENT_PULL_REQUEST',
440 hook_name='COMMENT_PULL_REQUEST',
441 kwargs_keys=(
441 kwargs_keys=(
442 'server_url', 'config', 'scm', 'username', 'ip', 'action',
442 'server_url', 'config', 'scm', 'username', 'ip', 'action',
443 'repository', 'pull_request_id', 'url', 'title', 'description',
443 'repository', 'pull_request_id', 'url', 'title', 'description',
444 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
444 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
445 'mergeable', 'source', 'target', 'author', 'reviewers'))
445 'mergeable', 'source', 'target', 'author', 'reviewers'))
446
446
447
447
448 log_update_pull_request = ExtensionCallback(
448 comment_edit_pull_request = ExtensionCallback(
449 hook_name='COMMENT_EDIT_PULL_REQUEST',
450 kwargs_keys=(
451 'server_url', 'config', 'scm', 'username', 'ip', 'action',
452 'repository', 'pull_request_id', 'url', 'title', 'description',
453 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
454 'mergeable', 'source', 'target', 'author', 'reviewers'))
455
456
457 update_pull_request = ExtensionCallback(
449 hook_name='UPDATE_PULL_REQUEST',
458 hook_name='UPDATE_PULL_REQUEST',
450 kwargs_keys=(
459 kwargs_keys=(
451 'server_url', 'config', 'scm', 'username', 'ip', 'action',
460 'server_url', 'config', 'scm', 'username', 'ip', 'action',
452 'repository', 'pull_request_id', 'url', 'title', 'description',
461 'repository', 'pull_request_id', 'url', 'title', 'description',
453 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
462 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
454 'mergeable', 'source', 'target', 'author', 'reviewers'))
463 'mergeable', 'source', 'target', 'author', 'reviewers'))
455
464
456
465
457 log_create_user = ExtensionCallback(
466 create_user = ExtensionCallback(
458 hook_name='CREATE_USER_HOOK',
467 hook_name='CREATE_USER_HOOK',
459 kwargs_keys=(
468 kwargs_keys=(
460 'username', 'full_name_or_username', 'full_contact', 'user_id',
469 'username', 'full_name_or_username', 'full_contact', 'user_id',
461 'name', 'firstname', 'short_contact', 'admin', 'lastname',
470 'name', 'firstname', 'short_contact', 'admin', 'lastname',
462 'ip_addresses', 'extern_type', 'extern_name',
471 'ip_addresses', 'extern_type', 'extern_name',
463 'email', 'api_keys', 'last_login',
472 'email', 'api_keys', 'last_login',
464 'full_name', 'active', 'password', 'emails',
473 'full_name', 'active', 'password', 'emails',
465 'inherit_default_permissions', 'created_by', 'created_on'))
474 'inherit_default_permissions', 'created_by', 'created_on'))
466
475
467
476
468 log_delete_user = ExtensionCallback(
477 delete_user = ExtensionCallback(
469 hook_name='DELETE_USER_HOOK',
478 hook_name='DELETE_USER_HOOK',
470 kwargs_keys=(
479 kwargs_keys=(
471 'username', 'full_name_or_username', 'full_contact', 'user_id',
480 'username', 'full_name_or_username', 'full_contact', 'user_id',
472 'name', 'firstname', 'short_contact', 'admin', 'lastname',
481 'name', 'firstname', 'short_contact', 'admin', 'lastname',
473 'ip_addresses',
482 'ip_addresses',
474 'email', 'last_login',
483 'email', 'last_login',
475 'full_name', 'active', 'password', 'emails',
484 'full_name', 'active', 'password', 'emails',
476 'inherit_default_permissions', 'deleted_by'))
485 'inherit_default_permissions', 'deleted_by'))
477
486
478
487
479 log_create_repository = ExtensionCallback(
488 create_repository = ExtensionCallback(
480 hook_name='CREATE_REPO_HOOK',
489 hook_name='CREATE_REPO_HOOK',
481 kwargs_keys=(
490 kwargs_keys=(
482 'repo_name', 'repo_type', 'description', 'private', 'created_on',
491 'repo_name', 'repo_type', 'description', 'private', 'created_on',
483 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
492 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
484 'clone_uri', 'fork_id', 'group_id', 'created_by'))
493 'clone_uri', 'fork_id', 'group_id', 'created_by'))
485
494
486
495
487 log_delete_repository = ExtensionCallback(
496 delete_repository = ExtensionCallback(
488 hook_name='DELETE_REPO_HOOK',
497 hook_name='DELETE_REPO_HOOK',
489 kwargs_keys=(
498 kwargs_keys=(
490 'repo_name', 'repo_type', 'description', 'private', 'created_on',
499 'repo_name', 'repo_type', 'description', 'private', 'created_on',
491 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
500 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
492 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
501 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
493
502
494
503
495 log_comment_commit_repository = ExtensionCallback(
504 comment_commit_repository = ExtensionCallback(
496 hook_name='COMMENT_COMMIT_REPO_HOOK',
505 hook_name='COMMENT_COMMIT_REPO_HOOK',
497 kwargs_keys=(
506 kwargs_keys=(
498 'repo_name', 'repo_type', 'description', 'private', 'created_on',
507 'repo_name', 'repo_type', 'description', 'private', 'created_on',
499 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
508 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
500 'clone_uri', 'fork_id', 'group_id',
509 'clone_uri', 'fork_id', 'group_id',
501 'repository', 'created_by', 'comment', 'commit'))
510 'repository', 'created_by', 'comment', 'commit'))
502
511
512 comment_edit_commit_repository = ExtensionCallback(
513 hook_name='COMMENT_EDIT_COMMIT_REPO_HOOK',
514 kwargs_keys=(
515 'repo_name', 'repo_type', 'description', 'private', 'created_on',
516 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
517 'clone_uri', 'fork_id', 'group_id',
518 'repository', 'created_by', 'comment', 'commit'))
503
519
504 log_create_repository_group = ExtensionCallback(
520
521 create_repository_group = ExtensionCallback(
505 hook_name='CREATE_REPO_GROUP_HOOK',
522 hook_name='CREATE_REPO_GROUP_HOOK',
506 kwargs_keys=(
523 kwargs_keys=(
507 'group_name', 'group_parent_id', 'group_description',
524 'group_name', 'group_parent_id', 'group_description',
508 'group_id', 'user_id', 'created_by', 'created_on',
525 'group_id', 'user_id', 'created_by', 'created_on',
509 'enable_locking'))
526 'enable_locking'))
@@ -1,266 +1,264 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-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 webob
21 import webob
22 from pyramid.threadlocal import get_current_request
22 from pyramid.threadlocal import get_current_request
23
23
24 from rhodecode import events
24 from rhodecode import events
25 from rhodecode.lib import hooks_base
25 from rhodecode.lib import hooks_base
26 from rhodecode.lib import utils2
26 from rhodecode.lib import utils2
27
27
28
28
29 def _supports_repo_type(repo_type):
29 def _supports_repo_type(repo_type):
30 if repo_type in ('hg', 'git'):
30 if repo_type in ('hg', 'git'):
31 return True
31 return True
32 return False
32 return False
33
33
34
34
35 def _get_vcs_operation_context(username, repo_name, repo_type, action):
35 def _get_vcs_operation_context(username, repo_name, repo_type, action):
36 # NOTE(dan): import loop
36 # NOTE(dan): import loop
37 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.base import vcs_operation_context
38
38
39 check_locking = action in ('pull', 'push')
39 check_locking = action in ('pull', 'push')
40
40
41 request = get_current_request()
41 request = get_current_request()
42
42
43 try:
43 try:
44 environ = request.environ
44 environ = request.environ
45 except TypeError:
45 except TypeError:
46 # we might use this outside of request context
46 # we might use this outside of request context
47 environ = {}
47 environ = {}
48
48
49 if not environ:
49 if not environ:
50 environ = webob.Request.blank('').environ
50 environ = webob.Request.blank('').environ
51
51
52 extras = vcs_operation_context(environ, repo_name, username, action, repo_type, check_locking)
52 extras = vcs_operation_context(environ, repo_name, username, action, repo_type, check_locking)
53 return utils2.AttributeDict(extras)
53 return utils2.AttributeDict(extras)
54
54
55
55
56 def trigger_post_push_hook(username, action, hook_type, repo_name, repo_type, commit_ids):
56 def trigger_post_push_hook(username, action, hook_type, repo_name, repo_type, commit_ids):
57 """
57 """
58 Triggers push action hooks
58 Triggers push action hooks
59
59
60 :param username: username who pushes
60 :param username: username who pushes
61 :param action: push/push_local/push_remote
61 :param action: push/push_local/push_remote
62 :param hook_type: type of hook executed
62 :param hook_type: type of hook executed
63 :param repo_name: name of repo
63 :param repo_name: name of repo
64 :param repo_type: the type of SCM repo
64 :param repo_type: the type of SCM repo
65 :param commit_ids: list of commit ids that we pushed
65 :param commit_ids: list of commit ids that we pushed
66 """
66 """
67 extras = _get_vcs_operation_context(username, repo_name, repo_type, action)
67 extras = _get_vcs_operation_context(username, repo_name, repo_type, action)
68 extras.commit_ids = commit_ids
68 extras.commit_ids = commit_ids
69 extras.hook_type = hook_type
69 extras.hook_type = hook_type
70 hooks_base.post_push(extras)
70 hooks_base.post_push(extras)
71
71
72
72
73 def trigger_comment_commit_hooks(username, repo_name, repo_type, repo, data=None):
73 def trigger_comment_commit_hooks(username, repo_name, repo_type, repo, data=None):
74 """
74 """
75 Triggers when a comment is made on a commit
75 Triggers when a comment is made on a commit
76
76
77 :param username: username who creates the comment
77 :param username: username who creates the comment
78 :param repo_name: name of target repo
78 :param repo_name: name of target repo
79 :param repo_type: the type of SCM target repo
79 :param repo_type: the type of SCM target repo
80 :param repo: the repo object we trigger the event for
80 :param repo: the repo object we trigger the event for
81 :param data: extra data for specific events e.g {'comment': comment_obj, 'commit': commit_obj}
81 :param data: extra data for specific events e.g {'comment': comment_obj, 'commit': commit_obj}
82 """
82 """
83 if not _supports_repo_type(repo_type):
83 if not _supports_repo_type(repo_type):
84 return
84 return
85
85
86 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_commit')
86 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_commit')
87
87
88 comment = data['comment']
88 comment = data['comment']
89 commit = data['commit']
89 commit = data['commit']
90
90
91 events.trigger(events.RepoCommitCommentEvent(repo, commit, comment))
91 events.trigger(events.RepoCommitCommentEvent(repo, commit, comment))
92 extras.update(repo.get_dict())
92 extras.update(repo.get_dict())
93
93
94 extras.commit = commit.serialize()
94 extras.commit = commit.serialize()
95 extras.comment = comment.get_api_data()
95 extras.comment = comment.get_api_data()
96 extras.created_by = username
96 extras.created_by = username
97 hooks_base.log_comment_commit_repository(**extras)
97 hooks_base.comment_commit_repository(**extras)
98
98
99
99
100 def trigger_comment_commit_edit_hooks(username, repo_name, repo_type, repo, data=None):
100 def trigger_comment_commit_edit_hooks(username, repo_name, repo_type, repo, data=None):
101 """
101 """
102 Triggers when a comment is edited on a commit
102 Triggers when a comment is edited on a commit
103
103
104 :param username: username who edits the comment
104 :param username: username who edits the comment
105 :param repo_name: name of target repo
105 :param repo_name: name of target repo
106 :param repo_type: the type of SCM target repo
106 :param repo_type: the type of SCM target repo
107 :param repo: the repo object we trigger the event for
107 :param repo: the repo object we trigger the event for
108 :param data: extra data for specific events e.g {'comment': comment_obj, 'commit': commit_obj}
108 :param data: extra data for specific events e.g {'comment': comment_obj, 'commit': commit_obj}
109 """
109 """
110 if not _supports_repo_type(repo_type):
110 if not _supports_repo_type(repo_type):
111 return
111 return
112
112
113 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_commit')
113 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_commit')
114
114
115 comment = data['comment']
115 comment = data['comment']
116 commit = data['commit']
116 commit = data['commit']
117
117
118 events.trigger(events.RepoCommitCommentEditEvent(repo, commit, comment))
118 events.trigger(events.RepoCommitCommentEditEvent(repo, commit, comment))
119 extras.update(repo.get_dict())
119 extras.update(repo.get_dict())
120
120
121 extras.commit = commit.serialize()
121 extras.commit = commit.serialize()
122 extras.comment = comment.get_api_data()
122 extras.comment = comment.get_api_data()
123 extras.created_by = username
123 extras.created_by = username
124 # TODO(marcink): rcextensions handlers ??
124 hooks_base.comment_edit_commit_repository(**extras)
125 hooks_base.log_comment_commit_repository(**extras)
126
125
127
126
128 def trigger_create_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
127 def trigger_create_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
129 """
128 """
130 Triggers create pull request action hooks
129 Triggers create pull request action hooks
131
130
132 :param username: username who creates the pull request
131 :param username: username who creates the pull request
133 :param repo_name: name of target repo
132 :param repo_name: name of target repo
134 :param repo_type: the type of SCM target repo
133 :param repo_type: the type of SCM target repo
135 :param pull_request: the pull request that was created
134 :param pull_request: the pull request that was created
136 :param data: extra data for specific events e.g {'comment': comment_obj}
135 :param data: extra data for specific events e.g {'comment': comment_obj}
137 """
136 """
138 if not _supports_repo_type(repo_type):
137 if not _supports_repo_type(repo_type):
139 return
138 return
140
139
141 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'create_pull_request')
140 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'create_pull_request')
142 events.trigger(events.PullRequestCreateEvent(pull_request))
141 events.trigger(events.PullRequestCreateEvent(pull_request))
143 extras.update(pull_request.get_api_data(with_merge_state=False))
142 extras.update(pull_request.get_api_data(with_merge_state=False))
144 hooks_base.log_create_pull_request(**extras)
143 hooks_base.create_pull_request(**extras)
145
144
146
145
147 def trigger_merge_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
146 def trigger_merge_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
148 """
147 """
149 Triggers merge pull request action hooks
148 Triggers merge pull request action hooks
150
149
151 :param username: username who creates the pull request
150 :param username: username who creates the pull request
152 :param repo_name: name of target repo
151 :param repo_name: name of target repo
153 :param repo_type: the type of SCM target repo
152 :param repo_type: the type of SCM target repo
154 :param pull_request: the pull request that was merged
153 :param pull_request: the pull request that was merged
155 :param data: extra data for specific events e.g {'comment': comment_obj}
154 :param data: extra data for specific events e.g {'comment': comment_obj}
156 """
155 """
157 if not _supports_repo_type(repo_type):
156 if not _supports_repo_type(repo_type):
158 return
157 return
159
158
160 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'merge_pull_request')
159 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'merge_pull_request')
161 events.trigger(events.PullRequestMergeEvent(pull_request))
160 events.trigger(events.PullRequestMergeEvent(pull_request))
162 extras.update(pull_request.get_api_data())
161 extras.update(pull_request.get_api_data())
163 hooks_base.log_merge_pull_request(**extras)
162 hooks_base.merge_pull_request(**extras)
164
163
165
164
166 def trigger_close_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
165 def trigger_close_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
167 """
166 """
168 Triggers close pull request action hooks
167 Triggers close pull request action hooks
169
168
170 :param username: username who creates the pull request
169 :param username: username who creates the pull request
171 :param repo_name: name of target repo
170 :param repo_name: name of target repo
172 :param repo_type: the type of SCM target repo
171 :param repo_type: the type of SCM target repo
173 :param pull_request: the pull request that was closed
172 :param pull_request: the pull request that was closed
174 :param data: extra data for specific events e.g {'comment': comment_obj}
173 :param data: extra data for specific events e.g {'comment': comment_obj}
175 """
174 """
176 if not _supports_repo_type(repo_type):
175 if not _supports_repo_type(repo_type):
177 return
176 return
178
177
179 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'close_pull_request')
178 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'close_pull_request')
180 events.trigger(events.PullRequestCloseEvent(pull_request))
179 events.trigger(events.PullRequestCloseEvent(pull_request))
181 extras.update(pull_request.get_api_data())
180 extras.update(pull_request.get_api_data())
182 hooks_base.log_close_pull_request(**extras)
181 hooks_base.close_pull_request(**extras)
183
182
184
183
185 def trigger_review_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
184 def trigger_review_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
186 """
185 """
187 Triggers review status change pull request action hooks
186 Triggers review status change pull request action hooks
188
187
189 :param username: username who creates the pull request
188 :param username: username who creates the pull request
190 :param repo_name: name of target repo
189 :param repo_name: name of target repo
191 :param repo_type: the type of SCM target repo
190 :param repo_type: the type of SCM target repo
192 :param pull_request: the pull request that review status changed
191 :param pull_request: the pull request that review status changed
193 :param data: extra data for specific events e.g {'comment': comment_obj}
192 :param data: extra data for specific events e.g {'comment': comment_obj}
194 """
193 """
195 if not _supports_repo_type(repo_type):
194 if not _supports_repo_type(repo_type):
196 return
195 return
197
196
198 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'review_pull_request')
197 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'review_pull_request')
199 status = data.get('status')
198 status = data.get('status')
200 events.trigger(events.PullRequestReviewEvent(pull_request, status))
199 events.trigger(events.PullRequestReviewEvent(pull_request, status))
201 extras.update(pull_request.get_api_data())
200 extras.update(pull_request.get_api_data())
202 hooks_base.log_review_pull_request(**extras)
201 hooks_base.review_pull_request(**extras)
203
202
204
203
205 def trigger_comment_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
204 def trigger_comment_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
206 """
205 """
207 Triggers when a comment is made on a pull request
206 Triggers when a comment is made on a pull request
208
207
209 :param username: username who creates the pull request
208 :param username: username who creates the pull request
210 :param repo_name: name of target repo
209 :param repo_name: name of target repo
211 :param repo_type: the type of SCM target repo
210 :param repo_type: the type of SCM target repo
212 :param pull_request: the pull request that comment was made on
211 :param pull_request: the pull request that comment was made on
213 :param data: extra data for specific events e.g {'comment': comment_obj}
212 :param data: extra data for specific events e.g {'comment': comment_obj}
214 """
213 """
215 if not _supports_repo_type(repo_type):
214 if not _supports_repo_type(repo_type):
216 return
215 return
217
216
218 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_pull_request')
217 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_pull_request')
219
218
220 comment = data['comment']
219 comment = data['comment']
221 events.trigger(events.PullRequestCommentEvent(pull_request, comment))
220 events.trigger(events.PullRequestCommentEvent(pull_request, comment))
222 extras.update(pull_request.get_api_data())
221 extras.update(pull_request.get_api_data())
223 extras.comment = comment.get_api_data()
222 extras.comment = comment.get_api_data()
224 hooks_base.log_comment_pull_request(**extras)
223 hooks_base.comment_pull_request(**extras)
225
224
226
225
227 def trigger_comment_pull_request_edit_hook(username, repo_name, repo_type, pull_request, data=None):
226 def trigger_comment_pull_request_edit_hook(username, repo_name, repo_type, pull_request, data=None):
228 """
227 """
229 Triggers when a comment was edited on a pull request
228 Triggers when a comment was edited on a pull request
230
229
231 :param username: username who made the edit
230 :param username: username who made the edit
232 :param repo_name: name of target repo
231 :param repo_name: name of target repo
233 :param repo_type: the type of SCM target repo
232 :param repo_type: the type of SCM target repo
234 :param pull_request: the pull request that comment was made on
233 :param pull_request: the pull request that comment was made on
235 :param data: extra data for specific events e.g {'comment': comment_obj}
234 :param data: extra data for specific events e.g {'comment': comment_obj}
236 """
235 """
237 if not _supports_repo_type(repo_type):
236 if not _supports_repo_type(repo_type):
238 return
237 return
239
238
240 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_pull_request')
239 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_pull_request')
241
240
242 comment = data['comment']
241 comment = data['comment']
243 events.trigger(events.PullRequestCommentEditEvent(pull_request, comment))
242 events.trigger(events.PullRequestCommentEditEvent(pull_request, comment))
244 extras.update(pull_request.get_api_data())
243 extras.update(pull_request.get_api_data())
245 extras.comment = comment.get_api_data()
244 extras.comment = comment.get_api_data()
246 # TODO(marcink): handle rcextensions...
245 hooks_base.comment_edit_pull_request(**extras)
247 hooks_base.log_comment_pull_request(**extras)
248
246
249
247
250 def trigger_update_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
248 def trigger_update_pull_request_hook(username, repo_name, repo_type, pull_request, data=None):
251 """
249 """
252 Triggers update pull request action hooks
250 Triggers update pull request action hooks
253
251
254 :param username: username who creates the pull request
252 :param username: username who creates the pull request
255 :param repo_name: name of target repo
253 :param repo_name: name of target repo
256 :param repo_type: the type of SCM target repo
254 :param repo_type: the type of SCM target repo
257 :param pull_request: the pull request that was updated
255 :param pull_request: the pull request that was updated
258 :param data: extra data for specific events e.g {'comment': comment_obj}
256 :param data: extra data for specific events e.g {'comment': comment_obj}
259 """
257 """
260 if not _supports_repo_type(repo_type):
258 if not _supports_repo_type(repo_type):
261 return
259 return
262
260
263 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'update_pull_request')
261 extras = _get_vcs_operation_context(username, repo_name, repo_type, 'update_pull_request')
264 events.trigger(events.PullRequestUpdateEvent(pull_request))
262 events.trigger(events.PullRequestUpdateEvent(pull_request))
265 extras.update(pull_request.get_api_data())
263 extras.update(pull_request.get_api_data())
266 hooks_base.log_update_pull_request(**extras)
264 hooks_base.update_pull_request(**extras)
@@ -1,1172 +1,1172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-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 os
21 import os
22 import re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 from rhodecode.lib.hooks_base import log_delete_repository
36 from rhodecode.lib import hooks_base
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, action_logger_generic)
41 get_current_rhodecode_user, safe_int, action_logger_generic)
42 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.settings import VcsSettingsModel
49 from rhodecode.model.settings import VcsSettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class RepoModel(BaseModel):
54 class RepoModel(BaseModel):
55
55
56 cls = Repository
56 cls = Repository
57
57
58 def _get_user_group(self, users_group):
58 def _get_user_group(self, users_group):
59 return self._get_instance(UserGroup, users_group,
59 return self._get_instance(UserGroup, users_group,
60 callback=UserGroup.get_by_group_name)
60 callback=UserGroup.get_by_group_name)
61
61
62 def _get_repo_group(self, repo_group):
62 def _get_repo_group(self, repo_group):
63 return self._get_instance(RepoGroup, repo_group,
63 return self._get_instance(RepoGroup, repo_group,
64 callback=RepoGroup.get_by_group_name)
64 callback=RepoGroup.get_by_group_name)
65
65
66 def _create_default_perms(self, repository, private):
66 def _create_default_perms(self, repository, private):
67 # create default permission
67 # create default permission
68 default = 'repository.read'
68 default = 'repository.read'
69 def_user = User.get_default_user()
69 def_user = User.get_default_user()
70 for p in def_user.user_perms:
70 for p in def_user.user_perms:
71 if p.permission.permission_name.startswith('repository.'):
71 if p.permission.permission_name.startswith('repository.'):
72 default = p.permission.permission_name
72 default = p.permission.permission_name
73 break
73 break
74
74
75 default_perm = 'repository.none' if private else default
75 default_perm = 'repository.none' if private else default
76
76
77 repo_to_perm = UserRepoToPerm()
77 repo_to_perm = UserRepoToPerm()
78 repo_to_perm.permission = Permission.get_by_key(default_perm)
78 repo_to_perm.permission = Permission.get_by_key(default_perm)
79
79
80 repo_to_perm.repository = repository
80 repo_to_perm.repository = repository
81 repo_to_perm.user_id = def_user.user_id
81 repo_to_perm.user_id = def_user.user_id
82
82
83 return repo_to_perm
83 return repo_to_perm
84
84
85 @LazyProperty
85 @LazyProperty
86 def repos_path(self):
86 def repos_path(self):
87 """
87 """
88 Gets the repositories root path from database
88 Gets the repositories root path from database
89 """
89 """
90 settings_model = VcsSettingsModel(sa=self.sa)
90 settings_model = VcsSettingsModel(sa=self.sa)
91 return settings_model.get_repos_location()
91 return settings_model.get_repos_location()
92
92
93 def get(self, repo_id):
93 def get(self, repo_id):
94 repo = self.sa.query(Repository) \
94 repo = self.sa.query(Repository) \
95 .filter(Repository.repo_id == repo_id)
95 .filter(Repository.repo_id == repo_id)
96
96
97 return repo.scalar()
97 return repo.scalar()
98
98
99 def get_repo(self, repository):
99 def get_repo(self, repository):
100 return self._get_repo(repository)
100 return self._get_repo(repository)
101
101
102 def get_by_repo_name(self, repo_name, cache=False):
102 def get_by_repo_name(self, repo_name, cache=False):
103 repo = self.sa.query(Repository) \
103 repo = self.sa.query(Repository) \
104 .filter(Repository.repo_name == repo_name)
104 .filter(Repository.repo_name == repo_name)
105
105
106 if cache:
106 if cache:
107 name_key = _hash_key(repo_name)
107 name_key = _hash_key(repo_name)
108 repo = repo.options(
108 repo = repo.options(
109 FromCache("sql_cache_short", "get_repo_%s" % name_key))
109 FromCache("sql_cache_short", "get_repo_%s" % name_key))
110 return repo.scalar()
110 return repo.scalar()
111
111
112 def _extract_id_from_repo_name(self, repo_name):
112 def _extract_id_from_repo_name(self, repo_name):
113 if repo_name.startswith('/'):
113 if repo_name.startswith('/'):
114 repo_name = repo_name.lstrip('/')
114 repo_name = repo_name.lstrip('/')
115 by_id_match = re.match(r'^_(\d{1,})', repo_name)
115 by_id_match = re.match(r'^_(\d{1,})', repo_name)
116 if by_id_match:
116 if by_id_match:
117 return by_id_match.groups()[0]
117 return by_id_match.groups()[0]
118
118
119 def get_repo_by_id(self, repo_name):
119 def get_repo_by_id(self, repo_name):
120 """
120 """
121 Extracts repo_name by id from special urls.
121 Extracts repo_name by id from special urls.
122 Example url is _11/repo_name
122 Example url is _11/repo_name
123
123
124 :param repo_name:
124 :param repo_name:
125 :return: repo object if matched else None
125 :return: repo object if matched else None
126 """
126 """
127
127
128 try:
128 try:
129 _repo_id = self._extract_id_from_repo_name(repo_name)
129 _repo_id = self._extract_id_from_repo_name(repo_name)
130 if _repo_id:
130 if _repo_id:
131 return self.get(_repo_id)
131 return self.get(_repo_id)
132 except Exception:
132 except Exception:
133 log.exception('Failed to extract repo_name from URL')
133 log.exception('Failed to extract repo_name from URL')
134
134
135 return None
135 return None
136
136
137 def get_repos_for_root(self, root, traverse=False):
137 def get_repos_for_root(self, root, traverse=False):
138 if traverse:
138 if traverse:
139 like_expression = u'{}%'.format(safe_unicode(root))
139 like_expression = u'{}%'.format(safe_unicode(root))
140 repos = Repository.query().filter(
140 repos = Repository.query().filter(
141 Repository.repo_name.like(like_expression)).all()
141 Repository.repo_name.like(like_expression)).all()
142 else:
142 else:
143 if root and not isinstance(root, RepoGroup):
143 if root and not isinstance(root, RepoGroup):
144 raise ValueError(
144 raise ValueError(
145 'Root must be an instance '
145 'Root must be an instance '
146 'of RepoGroup, got:{} instead'.format(type(root)))
146 'of RepoGroup, got:{} instead'.format(type(root)))
147 repos = Repository.query().filter(Repository.group == root).all()
147 repos = Repository.query().filter(Repository.group == root).all()
148 return repos
148 return repos
149
149
150 def get_url(self, repo, request=None, permalink=False):
150 def get_url(self, repo, request=None, permalink=False):
151 if not request:
151 if not request:
152 request = get_current_request()
152 request = get_current_request()
153
153
154 if not request:
154 if not request:
155 return
155 return
156
156
157 if permalink:
157 if permalink:
158 return request.route_url(
158 return request.route_url(
159 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
159 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
160 else:
160 else:
161 return request.route_url(
161 return request.route_url(
162 'repo_summary', repo_name=safe_str(repo.repo_name))
162 'repo_summary', repo_name=safe_str(repo.repo_name))
163
163
164 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
164 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
165 if not request:
165 if not request:
166 request = get_current_request()
166 request = get_current_request()
167
167
168 if not request:
168 if not request:
169 return
169 return
170
170
171 if permalink:
171 if permalink:
172 return request.route_url(
172 return request.route_url(
173 'repo_commit', repo_name=safe_str(repo.repo_id),
173 'repo_commit', repo_name=safe_str(repo.repo_id),
174 commit_id=commit_id)
174 commit_id=commit_id)
175
175
176 else:
176 else:
177 return request.route_url(
177 return request.route_url(
178 'repo_commit', repo_name=safe_str(repo.repo_name),
178 'repo_commit', repo_name=safe_str(repo.repo_name),
179 commit_id=commit_id)
179 commit_id=commit_id)
180
180
181 def get_repo_log(self, repo, filter_term):
181 def get_repo_log(self, repo, filter_term):
182 repo_log = UserLog.query()\
182 repo_log = UserLog.query()\
183 .filter(or_(UserLog.repository_id == repo.repo_id,
183 .filter(or_(UserLog.repository_id == repo.repo_id,
184 UserLog.repository_name == repo.repo_name))\
184 UserLog.repository_name == repo.repo_name))\
185 .options(joinedload(UserLog.user))\
185 .options(joinedload(UserLog.user))\
186 .options(joinedload(UserLog.repository))\
186 .options(joinedload(UserLog.repository))\
187 .order_by(UserLog.action_date.desc())
187 .order_by(UserLog.action_date.desc())
188
188
189 repo_log = user_log_filter(repo_log, filter_term)
189 repo_log = user_log_filter(repo_log, filter_term)
190 return repo_log
190 return repo_log
191
191
192 @classmethod
192 @classmethod
193 def update_commit_cache(cls, repositories=None):
193 def update_commit_cache(cls, repositories=None):
194 if not repositories:
194 if not repositories:
195 repositories = Repository.getAll()
195 repositories = Repository.getAll()
196 for repo in repositories:
196 for repo in repositories:
197 repo.update_commit_cache()
197 repo.update_commit_cache()
198
198
199 def get_repos_as_dict(self, repo_list=None, admin=False,
199 def get_repos_as_dict(self, repo_list=None, admin=False,
200 super_user_actions=False, short_name=None):
200 super_user_actions=False, short_name=None):
201
201
202 _render = get_current_request().get_partial_renderer(
202 _render = get_current_request().get_partial_renderer(
203 'rhodecode:templates/data_table/_dt_elements.mako')
203 'rhodecode:templates/data_table/_dt_elements.mako')
204 c = _render.get_call_context()
204 c = _render.get_call_context()
205 h = _render.get_helpers()
205 h = _render.get_helpers()
206
206
207 def quick_menu(repo_name):
207 def quick_menu(repo_name):
208 return _render('quick_menu', repo_name)
208 return _render('quick_menu', repo_name)
209
209
210 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
210 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
211 if short_name is not None:
211 if short_name is not None:
212 short_name_var = short_name
212 short_name_var = short_name
213 else:
213 else:
214 short_name_var = not admin
214 short_name_var = not admin
215 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
215 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
216 short_name=short_name_var, admin=False)
216 short_name=short_name_var, admin=False)
217
217
218 def last_change(last_change):
218 def last_change(last_change):
219 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
219 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
220 ts = time.time()
220 ts = time.time()
221 utc_offset = (datetime.datetime.fromtimestamp(ts)
221 utc_offset = (datetime.datetime.fromtimestamp(ts)
222 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
222 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
223 last_change = last_change + datetime.timedelta(seconds=utc_offset)
223 last_change = last_change + datetime.timedelta(seconds=utc_offset)
224
224
225 return _render("last_change", last_change)
225 return _render("last_change", last_change)
226
226
227 def rss_lnk(repo_name):
227 def rss_lnk(repo_name):
228 return _render("rss", repo_name)
228 return _render("rss", repo_name)
229
229
230 def atom_lnk(repo_name):
230 def atom_lnk(repo_name):
231 return _render("atom", repo_name)
231 return _render("atom", repo_name)
232
232
233 def last_rev(repo_name, cs_cache):
233 def last_rev(repo_name, cs_cache):
234 return _render('revision', repo_name, cs_cache.get('revision'),
234 return _render('revision', repo_name, cs_cache.get('revision'),
235 cs_cache.get('raw_id'), cs_cache.get('author'),
235 cs_cache.get('raw_id'), cs_cache.get('author'),
236 cs_cache.get('message'), cs_cache.get('date'))
236 cs_cache.get('message'), cs_cache.get('date'))
237
237
238 def desc(desc):
238 def desc(desc):
239 return _render('repo_desc', desc, c.visual.stylify_metatags)
239 return _render('repo_desc', desc, c.visual.stylify_metatags)
240
240
241 def state(repo_state):
241 def state(repo_state):
242 return _render("repo_state", repo_state)
242 return _render("repo_state", repo_state)
243
243
244 def repo_actions(repo_name):
244 def repo_actions(repo_name):
245 return _render('repo_actions', repo_name, super_user_actions)
245 return _render('repo_actions', repo_name, super_user_actions)
246
246
247 def user_profile(username):
247 def user_profile(username):
248 return _render('user_profile', username)
248 return _render('user_profile', username)
249
249
250 repos_data = []
250 repos_data = []
251 for repo in repo_list:
251 for repo in repo_list:
252 # NOTE(marcink): because we use only raw column we need to load it like that
252 # NOTE(marcink): because we use only raw column we need to load it like that
253 changeset_cache = Repository._load_changeset_cache(
253 changeset_cache = Repository._load_changeset_cache(
254 repo.repo_id, repo._changeset_cache)
254 repo.repo_id, repo._changeset_cache)
255
255
256 row = {
256 row = {
257 "menu": quick_menu(repo.repo_name),
257 "menu": quick_menu(repo.repo_name),
258
258
259 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
259 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
260 repo.private, repo.archived, repo.fork),
260 repo.private, repo.archived, repo.fork),
261
261
262 "desc": desc(h.escape(repo.description)),
262 "desc": desc(h.escape(repo.description)),
263
263
264 "last_change": last_change(repo.updated_on),
264 "last_change": last_change(repo.updated_on),
265
265
266 "last_changeset": last_rev(repo.repo_name, changeset_cache),
266 "last_changeset": last_rev(repo.repo_name, changeset_cache),
267 "last_changeset_raw": changeset_cache.get('revision'),
267 "last_changeset_raw": changeset_cache.get('revision'),
268
268
269 "owner": user_profile(repo.User.username),
269 "owner": user_profile(repo.User.username),
270
270
271 "state": state(repo.repo_state),
271 "state": state(repo.repo_state),
272 "rss": rss_lnk(repo.repo_name),
272 "rss": rss_lnk(repo.repo_name),
273 "atom": atom_lnk(repo.repo_name),
273 "atom": atom_lnk(repo.repo_name),
274 }
274 }
275 if admin:
275 if admin:
276 row.update({
276 row.update({
277 "action": repo_actions(repo.repo_name),
277 "action": repo_actions(repo.repo_name),
278 })
278 })
279 repos_data.append(row)
279 repos_data.append(row)
280
280
281 return repos_data
281 return repos_data
282
282
283 def get_repos_data_table(
283 def get_repos_data_table(
284 self, draw, start, limit,
284 self, draw, start, limit,
285 search_q, order_by, order_dir,
285 search_q, order_by, order_dir,
286 auth_user, repo_group_id):
286 auth_user, repo_group_id):
287 from rhodecode.model.scm import RepoList
287 from rhodecode.model.scm import RepoList
288
288
289 _perms = ['repository.read', 'repository.write', 'repository.admin']
289 _perms = ['repository.read', 'repository.write', 'repository.admin']
290
290
291 repos = Repository.query() \
291 repos = Repository.query() \
292 .filter(Repository.group_id == repo_group_id) \
292 .filter(Repository.group_id == repo_group_id) \
293 .all()
293 .all()
294 auth_repo_list = RepoList(
294 auth_repo_list = RepoList(
295 repos, perm_set=_perms,
295 repos, perm_set=_perms,
296 extra_kwargs=dict(user=auth_user))
296 extra_kwargs=dict(user=auth_user))
297
297
298 allowed_ids = [-1]
298 allowed_ids = [-1]
299 for repo in auth_repo_list:
299 for repo in auth_repo_list:
300 allowed_ids.append(repo.repo_id)
300 allowed_ids.append(repo.repo_id)
301
301
302 repos_data_total_count = Repository.query() \
302 repos_data_total_count = Repository.query() \
303 .filter(Repository.group_id == repo_group_id) \
303 .filter(Repository.group_id == repo_group_id) \
304 .filter(or_(
304 .filter(or_(
305 # generate multiple IN to fix limitation problems
305 # generate multiple IN to fix limitation problems
306 *in_filter_generator(Repository.repo_id, allowed_ids))
306 *in_filter_generator(Repository.repo_id, allowed_ids))
307 ) \
307 ) \
308 .count()
308 .count()
309
309
310 base_q = Session.query(
310 base_q = Session.query(
311 Repository.repo_id,
311 Repository.repo_id,
312 Repository.repo_name,
312 Repository.repo_name,
313 Repository.description,
313 Repository.description,
314 Repository.repo_type,
314 Repository.repo_type,
315 Repository.repo_state,
315 Repository.repo_state,
316 Repository.private,
316 Repository.private,
317 Repository.archived,
317 Repository.archived,
318 Repository.fork,
318 Repository.fork,
319 Repository.updated_on,
319 Repository.updated_on,
320 Repository._changeset_cache,
320 Repository._changeset_cache,
321 User,
321 User,
322 ) \
322 ) \
323 .filter(Repository.group_id == repo_group_id) \
323 .filter(Repository.group_id == repo_group_id) \
324 .filter(or_(
324 .filter(or_(
325 # generate multiple IN to fix limitation problems
325 # generate multiple IN to fix limitation problems
326 *in_filter_generator(Repository.repo_id, allowed_ids))
326 *in_filter_generator(Repository.repo_id, allowed_ids))
327 ) \
327 ) \
328 .join(User, User.user_id == Repository.user_id) \
328 .join(User, User.user_id == Repository.user_id) \
329 .group_by(Repository, User)
329 .group_by(Repository, User)
330
330
331 repos_data_total_filtered_count = base_q.count()
331 repos_data_total_filtered_count = base_q.count()
332
332
333 sort_defined = False
333 sort_defined = False
334 if order_by == 'repo_name':
334 if order_by == 'repo_name':
335 sort_col = func.lower(Repository.repo_name)
335 sort_col = func.lower(Repository.repo_name)
336 sort_defined = True
336 sort_defined = True
337 elif order_by == 'user_username':
337 elif order_by == 'user_username':
338 sort_col = User.username
338 sort_col = User.username
339 else:
339 else:
340 sort_col = getattr(Repository, order_by, None)
340 sort_col = getattr(Repository, order_by, None)
341
341
342 if sort_defined or sort_col:
342 if sort_defined or sort_col:
343 if order_dir == 'asc':
343 if order_dir == 'asc':
344 sort_col = sort_col.asc()
344 sort_col = sort_col.asc()
345 else:
345 else:
346 sort_col = sort_col.desc()
346 sort_col = sort_col.desc()
347
347
348 base_q = base_q.order_by(sort_col)
348 base_q = base_q.order_by(sort_col)
349 base_q = base_q.offset(start).limit(limit)
349 base_q = base_q.offset(start).limit(limit)
350
350
351 repos_list = base_q.all()
351 repos_list = base_q.all()
352
352
353 repos_data = RepoModel().get_repos_as_dict(
353 repos_data = RepoModel().get_repos_as_dict(
354 repo_list=repos_list, admin=False)
354 repo_list=repos_list, admin=False)
355
355
356 data = ({
356 data = ({
357 'draw': draw,
357 'draw': draw,
358 'data': repos_data,
358 'data': repos_data,
359 'recordsTotal': repos_data_total_count,
359 'recordsTotal': repos_data_total_count,
360 'recordsFiltered': repos_data_total_filtered_count,
360 'recordsFiltered': repos_data_total_filtered_count,
361 })
361 })
362 return data
362 return data
363
363
364 def _get_defaults(self, repo_name):
364 def _get_defaults(self, repo_name):
365 """
365 """
366 Gets information about repository, and returns a dict for
366 Gets information about repository, and returns a dict for
367 usage in forms
367 usage in forms
368
368
369 :param repo_name:
369 :param repo_name:
370 """
370 """
371
371
372 repo_info = Repository.get_by_repo_name(repo_name)
372 repo_info = Repository.get_by_repo_name(repo_name)
373
373
374 if repo_info is None:
374 if repo_info is None:
375 return None
375 return None
376
376
377 defaults = repo_info.get_dict()
377 defaults = repo_info.get_dict()
378 defaults['repo_name'] = repo_info.just_name
378 defaults['repo_name'] = repo_info.just_name
379
379
380 groups = repo_info.groups_with_parents
380 groups = repo_info.groups_with_parents
381 parent_group = groups[-1] if groups else None
381 parent_group = groups[-1] if groups else None
382
382
383 # we use -1 as this is how in HTML, we mark an empty group
383 # we use -1 as this is how in HTML, we mark an empty group
384 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
384 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
385
385
386 keys_to_process = (
386 keys_to_process = (
387 {'k': 'repo_type', 'strip': False},
387 {'k': 'repo_type', 'strip': False},
388 {'k': 'repo_enable_downloads', 'strip': True},
388 {'k': 'repo_enable_downloads', 'strip': True},
389 {'k': 'repo_description', 'strip': True},
389 {'k': 'repo_description', 'strip': True},
390 {'k': 'repo_enable_locking', 'strip': True},
390 {'k': 'repo_enable_locking', 'strip': True},
391 {'k': 'repo_landing_rev', 'strip': True},
391 {'k': 'repo_landing_rev', 'strip': True},
392 {'k': 'clone_uri', 'strip': False},
392 {'k': 'clone_uri', 'strip': False},
393 {'k': 'push_uri', 'strip': False},
393 {'k': 'push_uri', 'strip': False},
394 {'k': 'repo_private', 'strip': True},
394 {'k': 'repo_private', 'strip': True},
395 {'k': 'repo_enable_statistics', 'strip': True}
395 {'k': 'repo_enable_statistics', 'strip': True}
396 )
396 )
397
397
398 for item in keys_to_process:
398 for item in keys_to_process:
399 attr = item['k']
399 attr = item['k']
400 if item['strip']:
400 if item['strip']:
401 attr = remove_prefix(item['k'], 'repo_')
401 attr = remove_prefix(item['k'], 'repo_')
402
402
403 val = defaults[attr]
403 val = defaults[attr]
404 if item['k'] == 'repo_landing_rev':
404 if item['k'] == 'repo_landing_rev':
405 val = ':'.join(defaults[attr])
405 val = ':'.join(defaults[attr])
406 defaults[item['k']] = val
406 defaults[item['k']] = val
407 if item['k'] == 'clone_uri':
407 if item['k'] == 'clone_uri':
408 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
408 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
409 if item['k'] == 'push_uri':
409 if item['k'] == 'push_uri':
410 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
410 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
411
411
412 # fill owner
412 # fill owner
413 if repo_info.user:
413 if repo_info.user:
414 defaults.update({'user': repo_info.user.username})
414 defaults.update({'user': repo_info.user.username})
415 else:
415 else:
416 replacement_user = User.get_first_super_admin().username
416 replacement_user = User.get_first_super_admin().username
417 defaults.update({'user': replacement_user})
417 defaults.update({'user': replacement_user})
418
418
419 return defaults
419 return defaults
420
420
421 def update(self, repo, **kwargs):
421 def update(self, repo, **kwargs):
422 try:
422 try:
423 cur_repo = self._get_repo(repo)
423 cur_repo = self._get_repo(repo)
424 source_repo_name = cur_repo.repo_name
424 source_repo_name = cur_repo.repo_name
425 if 'user' in kwargs:
425 if 'user' in kwargs:
426 cur_repo.user = User.get_by_username(kwargs['user'])
426 cur_repo.user = User.get_by_username(kwargs['user'])
427
427
428 if 'repo_group' in kwargs:
428 if 'repo_group' in kwargs:
429 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
429 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
430 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
430 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
431
431
432 update_keys = [
432 update_keys = [
433 (1, 'repo_description'),
433 (1, 'repo_description'),
434 (1, 'repo_landing_rev'),
434 (1, 'repo_landing_rev'),
435 (1, 'repo_private'),
435 (1, 'repo_private'),
436 (1, 'repo_enable_downloads'),
436 (1, 'repo_enable_downloads'),
437 (1, 'repo_enable_locking'),
437 (1, 'repo_enable_locking'),
438 (1, 'repo_enable_statistics'),
438 (1, 'repo_enable_statistics'),
439 (0, 'clone_uri'),
439 (0, 'clone_uri'),
440 (0, 'push_uri'),
440 (0, 'push_uri'),
441 (0, 'fork_id')
441 (0, 'fork_id')
442 ]
442 ]
443 for strip, k in update_keys:
443 for strip, k in update_keys:
444 if k in kwargs:
444 if k in kwargs:
445 val = kwargs[k]
445 val = kwargs[k]
446 if strip:
446 if strip:
447 k = remove_prefix(k, 'repo_')
447 k = remove_prefix(k, 'repo_')
448
448
449 setattr(cur_repo, k, val)
449 setattr(cur_repo, k, val)
450
450
451 new_name = cur_repo.get_new_name(kwargs['repo_name'])
451 new_name = cur_repo.get_new_name(kwargs['repo_name'])
452 cur_repo.repo_name = new_name
452 cur_repo.repo_name = new_name
453
453
454 # if private flag is set, reset default permission to NONE
454 # if private flag is set, reset default permission to NONE
455 if kwargs.get('repo_private'):
455 if kwargs.get('repo_private'):
456 EMPTY_PERM = 'repository.none'
456 EMPTY_PERM = 'repository.none'
457 RepoModel().grant_user_permission(
457 RepoModel().grant_user_permission(
458 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
458 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
459 )
459 )
460
460
461 # handle extra fields
461 # handle extra fields
462 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
462 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
463 k = RepositoryField.un_prefix_key(field)
463 k = RepositoryField.un_prefix_key(field)
464 ex_field = RepositoryField.get_by_key_name(
464 ex_field = RepositoryField.get_by_key_name(
465 key=k, repo=cur_repo)
465 key=k, repo=cur_repo)
466 if ex_field:
466 if ex_field:
467 ex_field.field_value = kwargs[field]
467 ex_field.field_value = kwargs[field]
468 self.sa.add(ex_field)
468 self.sa.add(ex_field)
469
469
470 self.sa.add(cur_repo)
470 self.sa.add(cur_repo)
471
471
472 if source_repo_name != new_name:
472 if source_repo_name != new_name:
473 # rename repository
473 # rename repository
474 self._rename_filesystem_repo(
474 self._rename_filesystem_repo(
475 old=source_repo_name, new=new_name)
475 old=source_repo_name, new=new_name)
476
476
477 return cur_repo
477 return cur_repo
478 except Exception:
478 except Exception:
479 log.error(traceback.format_exc())
479 log.error(traceback.format_exc())
480 raise
480 raise
481
481
482 def _create_repo(self, repo_name, repo_type, description, owner,
482 def _create_repo(self, repo_name, repo_type, description, owner,
483 private=False, clone_uri=None, repo_group=None,
483 private=False, clone_uri=None, repo_group=None,
484 landing_rev='rev:tip', fork_of=None,
484 landing_rev='rev:tip', fork_of=None,
485 copy_fork_permissions=False, enable_statistics=False,
485 copy_fork_permissions=False, enable_statistics=False,
486 enable_locking=False, enable_downloads=False,
486 enable_locking=False, enable_downloads=False,
487 copy_group_permissions=False,
487 copy_group_permissions=False,
488 state=Repository.STATE_PENDING):
488 state=Repository.STATE_PENDING):
489 """
489 """
490 Create repository inside database with PENDING state, this should be
490 Create repository inside database with PENDING state, this should be
491 only executed by create() repo. With exception of importing existing
491 only executed by create() repo. With exception of importing existing
492 repos
492 repos
493 """
493 """
494 from rhodecode.model.scm import ScmModel
494 from rhodecode.model.scm import ScmModel
495
495
496 owner = self._get_user(owner)
496 owner = self._get_user(owner)
497 fork_of = self._get_repo(fork_of)
497 fork_of = self._get_repo(fork_of)
498 repo_group = self._get_repo_group(safe_int(repo_group))
498 repo_group = self._get_repo_group(safe_int(repo_group))
499
499
500 try:
500 try:
501 repo_name = safe_unicode(repo_name)
501 repo_name = safe_unicode(repo_name)
502 description = safe_unicode(description)
502 description = safe_unicode(description)
503 # repo name is just a name of repository
503 # repo name is just a name of repository
504 # while repo_name_full is a full qualified name that is combined
504 # while repo_name_full is a full qualified name that is combined
505 # with name and path of group
505 # with name and path of group
506 repo_name_full = repo_name
506 repo_name_full = repo_name
507 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
507 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
508
508
509 new_repo = Repository()
509 new_repo = Repository()
510 new_repo.repo_state = state
510 new_repo.repo_state = state
511 new_repo.enable_statistics = False
511 new_repo.enable_statistics = False
512 new_repo.repo_name = repo_name_full
512 new_repo.repo_name = repo_name_full
513 new_repo.repo_type = repo_type
513 new_repo.repo_type = repo_type
514 new_repo.user = owner
514 new_repo.user = owner
515 new_repo.group = repo_group
515 new_repo.group = repo_group
516 new_repo.description = description or repo_name
516 new_repo.description = description or repo_name
517 new_repo.private = private
517 new_repo.private = private
518 new_repo.archived = False
518 new_repo.archived = False
519 new_repo.clone_uri = clone_uri
519 new_repo.clone_uri = clone_uri
520 new_repo.landing_rev = landing_rev
520 new_repo.landing_rev = landing_rev
521
521
522 new_repo.enable_statistics = enable_statistics
522 new_repo.enable_statistics = enable_statistics
523 new_repo.enable_locking = enable_locking
523 new_repo.enable_locking = enable_locking
524 new_repo.enable_downloads = enable_downloads
524 new_repo.enable_downloads = enable_downloads
525
525
526 if repo_group:
526 if repo_group:
527 new_repo.enable_locking = repo_group.enable_locking
527 new_repo.enable_locking = repo_group.enable_locking
528
528
529 if fork_of:
529 if fork_of:
530 parent_repo = fork_of
530 parent_repo = fork_of
531 new_repo.fork = parent_repo
531 new_repo.fork = parent_repo
532
532
533 events.trigger(events.RepoPreCreateEvent(new_repo))
533 events.trigger(events.RepoPreCreateEvent(new_repo))
534
534
535 self.sa.add(new_repo)
535 self.sa.add(new_repo)
536
536
537 EMPTY_PERM = 'repository.none'
537 EMPTY_PERM = 'repository.none'
538 if fork_of and copy_fork_permissions:
538 if fork_of and copy_fork_permissions:
539 repo = fork_of
539 repo = fork_of
540 user_perms = UserRepoToPerm.query() \
540 user_perms = UserRepoToPerm.query() \
541 .filter(UserRepoToPerm.repository == repo).all()
541 .filter(UserRepoToPerm.repository == repo).all()
542 group_perms = UserGroupRepoToPerm.query() \
542 group_perms = UserGroupRepoToPerm.query() \
543 .filter(UserGroupRepoToPerm.repository == repo).all()
543 .filter(UserGroupRepoToPerm.repository == repo).all()
544
544
545 for perm in user_perms:
545 for perm in user_perms:
546 UserRepoToPerm.create(
546 UserRepoToPerm.create(
547 perm.user, new_repo, perm.permission)
547 perm.user, new_repo, perm.permission)
548
548
549 for perm in group_perms:
549 for perm in group_perms:
550 UserGroupRepoToPerm.create(
550 UserGroupRepoToPerm.create(
551 perm.users_group, new_repo, perm.permission)
551 perm.users_group, new_repo, perm.permission)
552 # in case we copy permissions and also set this repo to private
552 # in case we copy permissions and also set this repo to private
553 # override the default user permission to make it a private repo
553 # override the default user permission to make it a private repo
554 if private:
554 if private:
555 RepoModel(self.sa).grant_user_permission(
555 RepoModel(self.sa).grant_user_permission(
556 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
556 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
557
557
558 elif repo_group and copy_group_permissions:
558 elif repo_group and copy_group_permissions:
559 user_perms = UserRepoGroupToPerm.query() \
559 user_perms = UserRepoGroupToPerm.query() \
560 .filter(UserRepoGroupToPerm.group == repo_group).all()
560 .filter(UserRepoGroupToPerm.group == repo_group).all()
561
561
562 group_perms = UserGroupRepoGroupToPerm.query() \
562 group_perms = UserGroupRepoGroupToPerm.query() \
563 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
563 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
564
564
565 for perm in user_perms:
565 for perm in user_perms:
566 perm_name = perm.permission.permission_name.replace(
566 perm_name = perm.permission.permission_name.replace(
567 'group.', 'repository.')
567 'group.', 'repository.')
568 perm_obj = Permission.get_by_key(perm_name)
568 perm_obj = Permission.get_by_key(perm_name)
569 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
569 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
570
570
571 for perm in group_perms:
571 for perm in group_perms:
572 perm_name = perm.permission.permission_name.replace(
572 perm_name = perm.permission.permission_name.replace(
573 'group.', 'repository.')
573 'group.', 'repository.')
574 perm_obj = Permission.get_by_key(perm_name)
574 perm_obj = Permission.get_by_key(perm_name)
575 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
575 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
576
576
577 if private:
577 if private:
578 RepoModel(self.sa).grant_user_permission(
578 RepoModel(self.sa).grant_user_permission(
579 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
579 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
580
580
581 else:
581 else:
582 perm_obj = self._create_default_perms(new_repo, private)
582 perm_obj = self._create_default_perms(new_repo, private)
583 self.sa.add(perm_obj)
583 self.sa.add(perm_obj)
584
584
585 # now automatically start following this repository as owner
585 # now automatically start following this repository as owner
586 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
586 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
587
587
588 # we need to flush here, in order to check if database won't
588 # we need to flush here, in order to check if database won't
589 # throw any exceptions, create filesystem dirs at the very end
589 # throw any exceptions, create filesystem dirs at the very end
590 self.sa.flush()
590 self.sa.flush()
591 events.trigger(events.RepoCreateEvent(new_repo))
591 events.trigger(events.RepoCreateEvent(new_repo))
592 return new_repo
592 return new_repo
593
593
594 except Exception:
594 except Exception:
595 log.error(traceback.format_exc())
595 log.error(traceback.format_exc())
596 raise
596 raise
597
597
598 def create(self, form_data, cur_user):
598 def create(self, form_data, cur_user):
599 """
599 """
600 Create repository using celery tasks
600 Create repository using celery tasks
601
601
602 :param form_data:
602 :param form_data:
603 :param cur_user:
603 :param cur_user:
604 """
604 """
605 from rhodecode.lib.celerylib import tasks, run_task
605 from rhodecode.lib.celerylib import tasks, run_task
606 return run_task(tasks.create_repo, form_data, cur_user)
606 return run_task(tasks.create_repo, form_data, cur_user)
607
607
608 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
608 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
609 perm_deletions=None, check_perms=True,
609 perm_deletions=None, check_perms=True,
610 cur_user=None):
610 cur_user=None):
611 if not perm_additions:
611 if not perm_additions:
612 perm_additions = []
612 perm_additions = []
613 if not perm_updates:
613 if not perm_updates:
614 perm_updates = []
614 perm_updates = []
615 if not perm_deletions:
615 if not perm_deletions:
616 perm_deletions = []
616 perm_deletions = []
617
617
618 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
618 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
619
619
620 changes = {
620 changes = {
621 'added': [],
621 'added': [],
622 'updated': [],
622 'updated': [],
623 'deleted': [],
623 'deleted': [],
624 'default_user_changed': None
624 'default_user_changed': None
625 }
625 }
626
626
627 repo = self._get_repo(repo)
627 repo = self._get_repo(repo)
628
628
629 # update permissions
629 # update permissions
630 for member_id, perm, member_type in perm_updates:
630 for member_id, perm, member_type in perm_updates:
631 member_id = int(member_id)
631 member_id = int(member_id)
632 if member_type == 'user':
632 if member_type == 'user':
633 member_name = User.get(member_id).username
633 member_name = User.get(member_id).username
634 if member_name == User.DEFAULT_USER:
634 if member_name == User.DEFAULT_USER:
635 # NOTE(dan): detect if we changed permissions for default user
635 # NOTE(dan): detect if we changed permissions for default user
636 perm_obj = self.sa.query(UserRepoToPerm) \
636 perm_obj = self.sa.query(UserRepoToPerm) \
637 .filter(UserRepoToPerm.user_id == member_id) \
637 .filter(UserRepoToPerm.user_id == member_id) \
638 .filter(UserRepoToPerm.repository == repo) \
638 .filter(UserRepoToPerm.repository == repo) \
639 .scalar()
639 .scalar()
640 if perm_obj and perm_obj.permission.permission_name != perm:
640 if perm_obj and perm_obj.permission.permission_name != perm:
641 changes['default_user_changed'] = True
641 changes['default_user_changed'] = True
642
642
643 # this updates also current one if found
643 # this updates also current one if found
644 self.grant_user_permission(
644 self.grant_user_permission(
645 repo=repo, user=member_id, perm=perm)
645 repo=repo, user=member_id, perm=perm)
646 elif member_type == 'user_group':
646 elif member_type == 'user_group':
647 # check if we have permissions to alter this usergroup
647 # check if we have permissions to alter this usergroup
648 member_name = UserGroup.get(member_id).users_group_name
648 member_name = UserGroup.get(member_id).users_group_name
649 if not check_perms or HasUserGroupPermissionAny(
649 if not check_perms or HasUserGroupPermissionAny(
650 *req_perms)(member_name, user=cur_user):
650 *req_perms)(member_name, user=cur_user):
651 self.grant_user_group_permission(
651 self.grant_user_group_permission(
652 repo=repo, group_name=member_id, perm=perm)
652 repo=repo, group_name=member_id, perm=perm)
653 else:
653 else:
654 raise ValueError("member_type must be 'user' or 'user_group' "
654 raise ValueError("member_type must be 'user' or 'user_group' "
655 "got {} instead".format(member_type))
655 "got {} instead".format(member_type))
656 changes['updated'].append({'type': member_type, 'id': member_id,
656 changes['updated'].append({'type': member_type, 'id': member_id,
657 'name': member_name, 'new_perm': perm})
657 'name': member_name, 'new_perm': perm})
658
658
659 # set new permissions
659 # set new permissions
660 for member_id, perm, member_type in perm_additions:
660 for member_id, perm, member_type in perm_additions:
661 member_id = int(member_id)
661 member_id = int(member_id)
662 if member_type == 'user':
662 if member_type == 'user':
663 member_name = User.get(member_id).username
663 member_name = User.get(member_id).username
664 self.grant_user_permission(
664 self.grant_user_permission(
665 repo=repo, user=member_id, perm=perm)
665 repo=repo, user=member_id, perm=perm)
666 elif member_type == 'user_group':
666 elif member_type == 'user_group':
667 # check if we have permissions to alter this usergroup
667 # check if we have permissions to alter this usergroup
668 member_name = UserGroup.get(member_id).users_group_name
668 member_name = UserGroup.get(member_id).users_group_name
669 if not check_perms or HasUserGroupPermissionAny(
669 if not check_perms or HasUserGroupPermissionAny(
670 *req_perms)(member_name, user=cur_user):
670 *req_perms)(member_name, user=cur_user):
671 self.grant_user_group_permission(
671 self.grant_user_group_permission(
672 repo=repo, group_name=member_id, perm=perm)
672 repo=repo, group_name=member_id, perm=perm)
673 else:
673 else:
674 raise ValueError("member_type must be 'user' or 'user_group' "
674 raise ValueError("member_type must be 'user' or 'user_group' "
675 "got {} instead".format(member_type))
675 "got {} instead".format(member_type))
676
676
677 changes['added'].append({'type': member_type, 'id': member_id,
677 changes['added'].append({'type': member_type, 'id': member_id,
678 'name': member_name, 'new_perm': perm})
678 'name': member_name, 'new_perm': perm})
679 # delete permissions
679 # delete permissions
680 for member_id, perm, member_type in perm_deletions:
680 for member_id, perm, member_type in perm_deletions:
681 member_id = int(member_id)
681 member_id = int(member_id)
682 if member_type == 'user':
682 if member_type == 'user':
683 member_name = User.get(member_id).username
683 member_name = User.get(member_id).username
684 self.revoke_user_permission(repo=repo, user=member_id)
684 self.revoke_user_permission(repo=repo, user=member_id)
685 elif member_type == 'user_group':
685 elif member_type == 'user_group':
686 # check if we have permissions to alter this usergroup
686 # check if we have permissions to alter this usergroup
687 member_name = UserGroup.get(member_id).users_group_name
687 member_name = UserGroup.get(member_id).users_group_name
688 if not check_perms or HasUserGroupPermissionAny(
688 if not check_perms or HasUserGroupPermissionAny(
689 *req_perms)(member_name, user=cur_user):
689 *req_perms)(member_name, user=cur_user):
690 self.revoke_user_group_permission(
690 self.revoke_user_group_permission(
691 repo=repo, group_name=member_id)
691 repo=repo, group_name=member_id)
692 else:
692 else:
693 raise ValueError("member_type must be 'user' or 'user_group' "
693 raise ValueError("member_type must be 'user' or 'user_group' "
694 "got {} instead".format(member_type))
694 "got {} instead".format(member_type))
695
695
696 changes['deleted'].append({'type': member_type, 'id': member_id,
696 changes['deleted'].append({'type': member_type, 'id': member_id,
697 'name': member_name, 'new_perm': perm})
697 'name': member_name, 'new_perm': perm})
698 return changes
698 return changes
699
699
700 def create_fork(self, form_data, cur_user):
700 def create_fork(self, form_data, cur_user):
701 """
701 """
702 Simple wrapper into executing celery task for fork creation
702 Simple wrapper into executing celery task for fork creation
703
703
704 :param form_data:
704 :param form_data:
705 :param cur_user:
705 :param cur_user:
706 """
706 """
707 from rhodecode.lib.celerylib import tasks, run_task
707 from rhodecode.lib.celerylib import tasks, run_task
708 return run_task(tasks.create_repo_fork, form_data, cur_user)
708 return run_task(tasks.create_repo_fork, form_data, cur_user)
709
709
710 def archive(self, repo):
710 def archive(self, repo):
711 """
711 """
712 Archive given repository. Set archive flag.
712 Archive given repository. Set archive flag.
713
713
714 :param repo:
714 :param repo:
715 """
715 """
716 repo = self._get_repo(repo)
716 repo = self._get_repo(repo)
717 if repo:
717 if repo:
718
718
719 try:
719 try:
720 repo.archived = True
720 repo.archived = True
721 self.sa.add(repo)
721 self.sa.add(repo)
722 self.sa.commit()
722 self.sa.commit()
723 except Exception:
723 except Exception:
724 log.error(traceback.format_exc())
724 log.error(traceback.format_exc())
725 raise
725 raise
726
726
727 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
727 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
728 """
728 """
729 Delete given repository, forks parameter defines what do do with
729 Delete given repository, forks parameter defines what do do with
730 attached forks. Throws AttachedForksError if deleted repo has attached
730 attached forks. Throws AttachedForksError if deleted repo has attached
731 forks
731 forks
732
732
733 :param repo:
733 :param repo:
734 :param forks: str 'delete' or 'detach'
734 :param forks: str 'delete' or 'detach'
735 :param pull_requests: str 'delete' or None
735 :param pull_requests: str 'delete' or None
736 :param fs_remove: remove(archive) repo from filesystem
736 :param fs_remove: remove(archive) repo from filesystem
737 """
737 """
738 if not cur_user:
738 if not cur_user:
739 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
739 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
740 repo = self._get_repo(repo)
740 repo = self._get_repo(repo)
741 if repo:
741 if repo:
742 if forks == 'detach':
742 if forks == 'detach':
743 for r in repo.forks:
743 for r in repo.forks:
744 r.fork = None
744 r.fork = None
745 self.sa.add(r)
745 self.sa.add(r)
746 elif forks == 'delete':
746 elif forks == 'delete':
747 for r in repo.forks:
747 for r in repo.forks:
748 self.delete(r, forks='delete')
748 self.delete(r, forks='delete')
749 elif [f for f in repo.forks]:
749 elif [f for f in repo.forks]:
750 raise AttachedForksError()
750 raise AttachedForksError()
751
751
752 # check for pull requests
752 # check for pull requests
753 pr_sources = repo.pull_requests_source
753 pr_sources = repo.pull_requests_source
754 pr_targets = repo.pull_requests_target
754 pr_targets = repo.pull_requests_target
755 if pull_requests != 'delete' and (pr_sources or pr_targets):
755 if pull_requests != 'delete' and (pr_sources or pr_targets):
756 raise AttachedPullRequestsError()
756 raise AttachedPullRequestsError()
757
757
758 old_repo_dict = repo.get_dict()
758 old_repo_dict = repo.get_dict()
759 events.trigger(events.RepoPreDeleteEvent(repo))
759 events.trigger(events.RepoPreDeleteEvent(repo))
760 try:
760 try:
761 self.sa.delete(repo)
761 self.sa.delete(repo)
762 if fs_remove:
762 if fs_remove:
763 self._delete_filesystem_repo(repo)
763 self._delete_filesystem_repo(repo)
764 else:
764 else:
765 log.debug('skipping removal from filesystem')
765 log.debug('skipping removal from filesystem')
766 old_repo_dict.update({
766 old_repo_dict.update({
767 'deleted_by': cur_user,
767 'deleted_by': cur_user,
768 'deleted_on': time.time(),
768 'deleted_on': time.time(),
769 })
769 })
770 log_delete_repository(**old_repo_dict)
770 hooks_base.delete_repository(**old_repo_dict)
771 events.trigger(events.RepoDeleteEvent(repo))
771 events.trigger(events.RepoDeleteEvent(repo))
772 except Exception:
772 except Exception:
773 log.error(traceback.format_exc())
773 log.error(traceback.format_exc())
774 raise
774 raise
775
775
776 def grant_user_permission(self, repo, user, perm):
776 def grant_user_permission(self, repo, user, perm):
777 """
777 """
778 Grant permission for user on given repository, or update existing one
778 Grant permission for user on given repository, or update existing one
779 if found
779 if found
780
780
781 :param repo: Instance of Repository, repository_id, or repository name
781 :param repo: Instance of Repository, repository_id, or repository name
782 :param user: Instance of User, user_id or username
782 :param user: Instance of User, user_id or username
783 :param perm: Instance of Permission, or permission_name
783 :param perm: Instance of Permission, or permission_name
784 """
784 """
785 user = self._get_user(user)
785 user = self._get_user(user)
786 repo = self._get_repo(repo)
786 repo = self._get_repo(repo)
787 permission = self._get_perm(perm)
787 permission = self._get_perm(perm)
788
788
789 # check if we have that permission already
789 # check if we have that permission already
790 obj = self.sa.query(UserRepoToPerm) \
790 obj = self.sa.query(UserRepoToPerm) \
791 .filter(UserRepoToPerm.user == user) \
791 .filter(UserRepoToPerm.user == user) \
792 .filter(UserRepoToPerm.repository == repo) \
792 .filter(UserRepoToPerm.repository == repo) \
793 .scalar()
793 .scalar()
794 if obj is None:
794 if obj is None:
795 # create new !
795 # create new !
796 obj = UserRepoToPerm()
796 obj = UserRepoToPerm()
797 obj.repository = repo
797 obj.repository = repo
798 obj.user = user
798 obj.user = user
799 obj.permission = permission
799 obj.permission = permission
800 self.sa.add(obj)
800 self.sa.add(obj)
801 log.debug('Granted perm %s to %s on %s', perm, user, repo)
801 log.debug('Granted perm %s to %s on %s', perm, user, repo)
802 action_logger_generic(
802 action_logger_generic(
803 'granted permission: {} to user: {} on repo: {}'.format(
803 'granted permission: {} to user: {} on repo: {}'.format(
804 perm, user, repo), namespace='security.repo')
804 perm, user, repo), namespace='security.repo')
805 return obj
805 return obj
806
806
807 def revoke_user_permission(self, repo, user):
807 def revoke_user_permission(self, repo, user):
808 """
808 """
809 Revoke permission for user on given repository
809 Revoke permission for user on given repository
810
810
811 :param repo: Instance of Repository, repository_id, or repository name
811 :param repo: Instance of Repository, repository_id, or repository name
812 :param user: Instance of User, user_id or username
812 :param user: Instance of User, user_id or username
813 """
813 """
814
814
815 user = self._get_user(user)
815 user = self._get_user(user)
816 repo = self._get_repo(repo)
816 repo = self._get_repo(repo)
817
817
818 obj = self.sa.query(UserRepoToPerm) \
818 obj = self.sa.query(UserRepoToPerm) \
819 .filter(UserRepoToPerm.repository == repo) \
819 .filter(UserRepoToPerm.repository == repo) \
820 .filter(UserRepoToPerm.user == user) \
820 .filter(UserRepoToPerm.user == user) \
821 .scalar()
821 .scalar()
822 if obj:
822 if obj:
823 self.sa.delete(obj)
823 self.sa.delete(obj)
824 log.debug('Revoked perm on %s on %s', repo, user)
824 log.debug('Revoked perm on %s on %s', repo, user)
825 action_logger_generic(
825 action_logger_generic(
826 'revoked permission from user: {} on repo: {}'.format(
826 'revoked permission from user: {} on repo: {}'.format(
827 user, repo), namespace='security.repo')
827 user, repo), namespace='security.repo')
828
828
829 def grant_user_group_permission(self, repo, group_name, perm):
829 def grant_user_group_permission(self, repo, group_name, perm):
830 """
830 """
831 Grant permission for user group on given repository, or update
831 Grant permission for user group on given repository, or update
832 existing one if found
832 existing one if found
833
833
834 :param repo: Instance of Repository, repository_id, or repository name
834 :param repo: Instance of Repository, repository_id, or repository name
835 :param group_name: Instance of UserGroup, users_group_id,
835 :param group_name: Instance of UserGroup, users_group_id,
836 or user group name
836 or user group name
837 :param perm: Instance of Permission, or permission_name
837 :param perm: Instance of Permission, or permission_name
838 """
838 """
839 repo = self._get_repo(repo)
839 repo = self._get_repo(repo)
840 group_name = self._get_user_group(group_name)
840 group_name = self._get_user_group(group_name)
841 permission = self._get_perm(perm)
841 permission = self._get_perm(perm)
842
842
843 # check if we have that permission already
843 # check if we have that permission already
844 obj = self.sa.query(UserGroupRepoToPerm) \
844 obj = self.sa.query(UserGroupRepoToPerm) \
845 .filter(UserGroupRepoToPerm.users_group == group_name) \
845 .filter(UserGroupRepoToPerm.users_group == group_name) \
846 .filter(UserGroupRepoToPerm.repository == repo) \
846 .filter(UserGroupRepoToPerm.repository == repo) \
847 .scalar()
847 .scalar()
848
848
849 if obj is None:
849 if obj is None:
850 # create new
850 # create new
851 obj = UserGroupRepoToPerm()
851 obj = UserGroupRepoToPerm()
852
852
853 obj.repository = repo
853 obj.repository = repo
854 obj.users_group = group_name
854 obj.users_group = group_name
855 obj.permission = permission
855 obj.permission = permission
856 self.sa.add(obj)
856 self.sa.add(obj)
857 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
857 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
858 action_logger_generic(
858 action_logger_generic(
859 'granted permission: {} to usergroup: {} on repo: {}'.format(
859 'granted permission: {} to usergroup: {} on repo: {}'.format(
860 perm, group_name, repo), namespace='security.repo')
860 perm, group_name, repo), namespace='security.repo')
861
861
862 return obj
862 return obj
863
863
864 def revoke_user_group_permission(self, repo, group_name):
864 def revoke_user_group_permission(self, repo, group_name):
865 """
865 """
866 Revoke permission for user group on given repository
866 Revoke permission for user group on given repository
867
867
868 :param repo: Instance of Repository, repository_id, or repository name
868 :param repo: Instance of Repository, repository_id, or repository name
869 :param group_name: Instance of UserGroup, users_group_id,
869 :param group_name: Instance of UserGroup, users_group_id,
870 or user group name
870 or user group name
871 """
871 """
872 repo = self._get_repo(repo)
872 repo = self._get_repo(repo)
873 group_name = self._get_user_group(group_name)
873 group_name = self._get_user_group(group_name)
874
874
875 obj = self.sa.query(UserGroupRepoToPerm) \
875 obj = self.sa.query(UserGroupRepoToPerm) \
876 .filter(UserGroupRepoToPerm.repository == repo) \
876 .filter(UserGroupRepoToPerm.repository == repo) \
877 .filter(UserGroupRepoToPerm.users_group == group_name) \
877 .filter(UserGroupRepoToPerm.users_group == group_name) \
878 .scalar()
878 .scalar()
879 if obj:
879 if obj:
880 self.sa.delete(obj)
880 self.sa.delete(obj)
881 log.debug('Revoked perm to %s on %s', repo, group_name)
881 log.debug('Revoked perm to %s on %s', repo, group_name)
882 action_logger_generic(
882 action_logger_generic(
883 'revoked permission from usergroup: {} on repo: {}'.format(
883 'revoked permission from usergroup: {} on repo: {}'.format(
884 group_name, repo), namespace='security.repo')
884 group_name, repo), namespace='security.repo')
885
885
886 def delete_stats(self, repo_name):
886 def delete_stats(self, repo_name):
887 """
887 """
888 removes stats for given repo
888 removes stats for given repo
889
889
890 :param repo_name:
890 :param repo_name:
891 """
891 """
892 repo = self._get_repo(repo_name)
892 repo = self._get_repo(repo_name)
893 try:
893 try:
894 obj = self.sa.query(Statistics) \
894 obj = self.sa.query(Statistics) \
895 .filter(Statistics.repository == repo).scalar()
895 .filter(Statistics.repository == repo).scalar()
896 if obj:
896 if obj:
897 self.sa.delete(obj)
897 self.sa.delete(obj)
898 except Exception:
898 except Exception:
899 log.error(traceback.format_exc())
899 log.error(traceback.format_exc())
900 raise
900 raise
901
901
902 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
902 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
903 field_type='str', field_desc=''):
903 field_type='str', field_desc=''):
904
904
905 repo = self._get_repo(repo_name)
905 repo = self._get_repo(repo_name)
906
906
907 new_field = RepositoryField()
907 new_field = RepositoryField()
908 new_field.repository = repo
908 new_field.repository = repo
909 new_field.field_key = field_key
909 new_field.field_key = field_key
910 new_field.field_type = field_type # python type
910 new_field.field_type = field_type # python type
911 new_field.field_value = field_value
911 new_field.field_value = field_value
912 new_field.field_desc = field_desc
912 new_field.field_desc = field_desc
913 new_field.field_label = field_label
913 new_field.field_label = field_label
914 self.sa.add(new_field)
914 self.sa.add(new_field)
915 return new_field
915 return new_field
916
916
917 def delete_repo_field(self, repo_name, field_key):
917 def delete_repo_field(self, repo_name, field_key):
918 repo = self._get_repo(repo_name)
918 repo = self._get_repo(repo_name)
919 field = RepositoryField.get_by_key_name(field_key, repo)
919 field = RepositoryField.get_by_key_name(field_key, repo)
920 if field:
920 if field:
921 self.sa.delete(field)
921 self.sa.delete(field)
922
922
923 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
923 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
924 clone_uri=None, repo_store_location=None,
924 clone_uri=None, repo_store_location=None,
925 use_global_config=False, install_hooks=True):
925 use_global_config=False, install_hooks=True):
926 """
926 """
927 makes repository on filesystem. It's group aware means it'll create
927 makes repository on filesystem. It's group aware means it'll create
928 a repository within a group, and alter the paths accordingly of
928 a repository within a group, and alter the paths accordingly of
929 group location
929 group location
930
930
931 :param repo_name:
931 :param repo_name:
932 :param alias:
932 :param alias:
933 :param parent:
933 :param parent:
934 :param clone_uri:
934 :param clone_uri:
935 :param repo_store_location:
935 :param repo_store_location:
936 """
936 """
937 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
937 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
938 from rhodecode.model.scm import ScmModel
938 from rhodecode.model.scm import ScmModel
939
939
940 if Repository.NAME_SEP in repo_name:
940 if Repository.NAME_SEP in repo_name:
941 raise ValueError(
941 raise ValueError(
942 'repo_name must not contain groups got `%s`' % repo_name)
942 'repo_name must not contain groups got `%s`' % repo_name)
943
943
944 if isinstance(repo_group, RepoGroup):
944 if isinstance(repo_group, RepoGroup):
945 new_parent_path = os.sep.join(repo_group.full_path_splitted)
945 new_parent_path = os.sep.join(repo_group.full_path_splitted)
946 else:
946 else:
947 new_parent_path = repo_group or ''
947 new_parent_path = repo_group or ''
948
948
949 if repo_store_location:
949 if repo_store_location:
950 _paths = [repo_store_location]
950 _paths = [repo_store_location]
951 else:
951 else:
952 _paths = [self.repos_path, new_parent_path, repo_name]
952 _paths = [self.repos_path, new_parent_path, repo_name]
953 # we need to make it str for mercurial
953 # we need to make it str for mercurial
954 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
954 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
955
955
956 # check if this path is not a repository
956 # check if this path is not a repository
957 if is_valid_repo(repo_path, self.repos_path):
957 if is_valid_repo(repo_path, self.repos_path):
958 raise Exception('This path %s is a valid repository' % repo_path)
958 raise Exception('This path %s is a valid repository' % repo_path)
959
959
960 # check if this path is a group
960 # check if this path is a group
961 if is_valid_repo_group(repo_path, self.repos_path):
961 if is_valid_repo_group(repo_path, self.repos_path):
962 raise Exception('This path %s is a valid group' % repo_path)
962 raise Exception('This path %s is a valid group' % repo_path)
963
963
964 log.info('creating repo %s in %s from url: `%s`',
964 log.info('creating repo %s in %s from url: `%s`',
965 repo_name, safe_unicode(repo_path),
965 repo_name, safe_unicode(repo_path),
966 obfuscate_url_pw(clone_uri))
966 obfuscate_url_pw(clone_uri))
967
967
968 backend = get_backend(repo_type)
968 backend = get_backend(repo_type)
969
969
970 config_repo = None if use_global_config else repo_name
970 config_repo = None if use_global_config else repo_name
971 if config_repo and new_parent_path:
971 if config_repo and new_parent_path:
972 config_repo = Repository.NAME_SEP.join(
972 config_repo = Repository.NAME_SEP.join(
973 (new_parent_path, config_repo))
973 (new_parent_path, config_repo))
974 config = make_db_config(clear_session=False, repo=config_repo)
974 config = make_db_config(clear_session=False, repo=config_repo)
975 config.set('extensions', 'largefiles', '')
975 config.set('extensions', 'largefiles', '')
976
976
977 # patch and reset hooks section of UI config to not run any
977 # patch and reset hooks section of UI config to not run any
978 # hooks on creating remote repo
978 # hooks on creating remote repo
979 config.clear_section('hooks')
979 config.clear_section('hooks')
980
980
981 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
981 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
982 if repo_type == 'git':
982 if repo_type == 'git':
983 repo = backend(
983 repo = backend(
984 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
984 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
985 with_wire={"cache": False})
985 with_wire={"cache": False})
986 else:
986 else:
987 repo = backend(
987 repo = backend(
988 repo_path, config=config, create=True, src_url=clone_uri,
988 repo_path, config=config, create=True, src_url=clone_uri,
989 with_wire={"cache": False})
989 with_wire={"cache": False})
990
990
991 if install_hooks:
991 if install_hooks:
992 repo.install_hooks()
992 repo.install_hooks()
993
993
994 log.debug('Created repo %s with %s backend',
994 log.debug('Created repo %s with %s backend',
995 safe_unicode(repo_name), safe_unicode(repo_type))
995 safe_unicode(repo_name), safe_unicode(repo_type))
996 return repo
996 return repo
997
997
998 def _rename_filesystem_repo(self, old, new):
998 def _rename_filesystem_repo(self, old, new):
999 """
999 """
1000 renames repository on filesystem
1000 renames repository on filesystem
1001
1001
1002 :param old: old name
1002 :param old: old name
1003 :param new: new name
1003 :param new: new name
1004 """
1004 """
1005 log.info('renaming repo from %s to %s', old, new)
1005 log.info('renaming repo from %s to %s', old, new)
1006
1006
1007 old_path = os.path.join(self.repos_path, old)
1007 old_path = os.path.join(self.repos_path, old)
1008 new_path = os.path.join(self.repos_path, new)
1008 new_path = os.path.join(self.repos_path, new)
1009 if os.path.isdir(new_path):
1009 if os.path.isdir(new_path):
1010 raise Exception(
1010 raise Exception(
1011 'Was trying to rename to already existing dir %s' % new_path
1011 'Was trying to rename to already existing dir %s' % new_path
1012 )
1012 )
1013 shutil.move(old_path, new_path)
1013 shutil.move(old_path, new_path)
1014
1014
1015 def _delete_filesystem_repo(self, repo):
1015 def _delete_filesystem_repo(self, repo):
1016 """
1016 """
1017 removes repo from filesystem, the removal is acctually made by
1017 removes repo from filesystem, the removal is acctually made by
1018 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1018 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1019 repository is no longer valid for rhodecode, can be undeleted later on
1019 repository is no longer valid for rhodecode, can be undeleted later on
1020 by reverting the renames on this repository
1020 by reverting the renames on this repository
1021
1021
1022 :param repo: repo object
1022 :param repo: repo object
1023 """
1023 """
1024 rm_path = os.path.join(self.repos_path, repo.repo_name)
1024 rm_path = os.path.join(self.repos_path, repo.repo_name)
1025 repo_group = repo.group
1025 repo_group = repo.group
1026 log.info("Removing repository %s", rm_path)
1026 log.info("Removing repository %s", rm_path)
1027 # disable hg/git internal that it doesn't get detected as repo
1027 # disable hg/git internal that it doesn't get detected as repo
1028 alias = repo.repo_type
1028 alias = repo.repo_type
1029
1029
1030 config = make_db_config(clear_session=False)
1030 config = make_db_config(clear_session=False)
1031 config.set('extensions', 'largefiles', '')
1031 config.set('extensions', 'largefiles', '')
1032 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1032 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1033
1033
1034 # skip this for bare git repos
1034 # skip this for bare git repos
1035 if not bare:
1035 if not bare:
1036 # disable VCS repo
1036 # disable VCS repo
1037 vcs_path = os.path.join(rm_path, '.%s' % alias)
1037 vcs_path = os.path.join(rm_path, '.%s' % alias)
1038 if os.path.exists(vcs_path):
1038 if os.path.exists(vcs_path):
1039 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1039 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1040
1040
1041 _now = datetime.datetime.now()
1041 _now = datetime.datetime.now()
1042 _ms = str(_now.microsecond).rjust(6, '0')
1042 _ms = str(_now.microsecond).rjust(6, '0')
1043 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1043 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1044 repo.just_name)
1044 repo.just_name)
1045 if repo_group:
1045 if repo_group:
1046 # if repository is in group, prefix the removal path with the group
1046 # if repository is in group, prefix the removal path with the group
1047 args = repo_group.full_path_splitted + [_d]
1047 args = repo_group.full_path_splitted + [_d]
1048 _d = os.path.join(*args)
1048 _d = os.path.join(*args)
1049
1049
1050 if os.path.isdir(rm_path):
1050 if os.path.isdir(rm_path):
1051 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1051 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1052
1052
1053 # finally cleanup diff-cache if it exists
1053 # finally cleanup diff-cache if it exists
1054 cached_diffs_dir = repo.cached_diffs_dir
1054 cached_diffs_dir = repo.cached_diffs_dir
1055 if os.path.isdir(cached_diffs_dir):
1055 if os.path.isdir(cached_diffs_dir):
1056 shutil.rmtree(cached_diffs_dir)
1056 shutil.rmtree(cached_diffs_dir)
1057
1057
1058
1058
1059 class ReadmeFinder:
1059 class ReadmeFinder:
1060 """
1060 """
1061 Utility which knows how to find a readme for a specific commit.
1061 Utility which knows how to find a readme for a specific commit.
1062
1062
1063 The main idea is that this is a configurable algorithm. When creating an
1063 The main idea is that this is a configurable algorithm. When creating an
1064 instance you can define parameters, currently only the `default_renderer`.
1064 instance you can define parameters, currently only the `default_renderer`.
1065 Based on this configuration the method :meth:`search` behaves slightly
1065 Based on this configuration the method :meth:`search` behaves slightly
1066 different.
1066 different.
1067 """
1067 """
1068
1068
1069 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1069 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1070 path_re = re.compile(r'^docs?', re.IGNORECASE)
1070 path_re = re.compile(r'^docs?', re.IGNORECASE)
1071
1071
1072 default_priorities = {
1072 default_priorities = {
1073 None: 0,
1073 None: 0,
1074 '.text': 2,
1074 '.text': 2,
1075 '.txt': 3,
1075 '.txt': 3,
1076 '.rst': 1,
1076 '.rst': 1,
1077 '.rest': 2,
1077 '.rest': 2,
1078 '.md': 1,
1078 '.md': 1,
1079 '.mkdn': 2,
1079 '.mkdn': 2,
1080 '.mdown': 3,
1080 '.mdown': 3,
1081 '.markdown': 4,
1081 '.markdown': 4,
1082 }
1082 }
1083
1083
1084 path_priority = {
1084 path_priority = {
1085 'doc': 0,
1085 'doc': 0,
1086 'docs': 1,
1086 'docs': 1,
1087 }
1087 }
1088
1088
1089 FALLBACK_PRIORITY = 99
1089 FALLBACK_PRIORITY = 99
1090
1090
1091 RENDERER_TO_EXTENSION = {
1091 RENDERER_TO_EXTENSION = {
1092 'rst': ['.rst', '.rest'],
1092 'rst': ['.rst', '.rest'],
1093 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1093 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1094 }
1094 }
1095
1095
1096 def __init__(self, default_renderer=None):
1096 def __init__(self, default_renderer=None):
1097 self._default_renderer = default_renderer
1097 self._default_renderer = default_renderer
1098 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1098 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1099 default_renderer, [])
1099 default_renderer, [])
1100
1100
1101 def search(self, commit, path=u'/'):
1101 def search(self, commit, path=u'/'):
1102 """
1102 """
1103 Find a readme in the given `commit`.
1103 Find a readme in the given `commit`.
1104 """
1104 """
1105 nodes = commit.get_nodes(path)
1105 nodes = commit.get_nodes(path)
1106 matches = self._match_readmes(nodes)
1106 matches = self._match_readmes(nodes)
1107 matches = self._sort_according_to_priority(matches)
1107 matches = self._sort_according_to_priority(matches)
1108 if matches:
1108 if matches:
1109 return matches[0].node
1109 return matches[0].node
1110
1110
1111 paths = self._match_paths(nodes)
1111 paths = self._match_paths(nodes)
1112 paths = self._sort_paths_according_to_priority(paths)
1112 paths = self._sort_paths_according_to_priority(paths)
1113 for path in paths:
1113 for path in paths:
1114 match = self.search(commit, path=path)
1114 match = self.search(commit, path=path)
1115 if match:
1115 if match:
1116 return match
1116 return match
1117
1117
1118 return None
1118 return None
1119
1119
1120 def _match_readmes(self, nodes):
1120 def _match_readmes(self, nodes):
1121 for node in nodes:
1121 for node in nodes:
1122 if not node.is_file():
1122 if not node.is_file():
1123 continue
1123 continue
1124 path = node.path.rsplit('/', 1)[-1]
1124 path = node.path.rsplit('/', 1)[-1]
1125 match = self.readme_re.match(path)
1125 match = self.readme_re.match(path)
1126 if match:
1126 if match:
1127 extension = match.group(1)
1127 extension = match.group(1)
1128 yield ReadmeMatch(node, match, self._priority(extension))
1128 yield ReadmeMatch(node, match, self._priority(extension))
1129
1129
1130 def _match_paths(self, nodes):
1130 def _match_paths(self, nodes):
1131 for node in nodes:
1131 for node in nodes:
1132 if not node.is_dir():
1132 if not node.is_dir():
1133 continue
1133 continue
1134 match = self.path_re.match(node.path)
1134 match = self.path_re.match(node.path)
1135 if match:
1135 if match:
1136 yield node.path
1136 yield node.path
1137
1137
1138 def _priority(self, extension):
1138 def _priority(self, extension):
1139 renderer_priority = (
1139 renderer_priority = (
1140 0 if extension in self._renderer_extensions else 1)
1140 0 if extension in self._renderer_extensions else 1)
1141 extension_priority = self.default_priorities.get(
1141 extension_priority = self.default_priorities.get(
1142 extension, self.FALLBACK_PRIORITY)
1142 extension, self.FALLBACK_PRIORITY)
1143 return (renderer_priority, extension_priority)
1143 return (renderer_priority, extension_priority)
1144
1144
1145 def _sort_according_to_priority(self, matches):
1145 def _sort_according_to_priority(self, matches):
1146
1146
1147 def priority_and_path(match):
1147 def priority_and_path(match):
1148 return (match.priority, match.path)
1148 return (match.priority, match.path)
1149
1149
1150 return sorted(matches, key=priority_and_path)
1150 return sorted(matches, key=priority_and_path)
1151
1151
1152 def _sort_paths_according_to_priority(self, paths):
1152 def _sort_paths_according_to_priority(self, paths):
1153
1153
1154 def priority_and_path(path):
1154 def priority_and_path(path):
1155 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1155 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1156
1156
1157 return sorted(paths, key=priority_and_path)
1157 return sorted(paths, key=priority_and_path)
1158
1158
1159
1159
1160 class ReadmeMatch:
1160 class ReadmeMatch:
1161
1161
1162 def __init__(self, node, match, priority):
1162 def __init__(self, node, match, priority):
1163 self.node = node
1163 self.node = node
1164 self._match = match
1164 self._match = match
1165 self.priority = priority
1165 self.priority = priority
1166
1166
1167 @property
1167 @property
1168 def path(self):
1168 def path(self):
1169 return self.node.path
1169 return self.node.path
1170
1170
1171 def __repr__(self):
1171 def __repr__(self):
1172 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1172 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,884 +1,884 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 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import time
31 import time
32 import traceback
32 import traceback
33 import string
33 import string
34
34
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 UserGroup, Repository)
41 UserGroup, Repository)
42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.utils2 import action_logger_generic
44 from rhodecode.lib.utils2 import action_logger_generic
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class RepoGroupModel(BaseModel):
49 class RepoGroupModel(BaseModel):
50
50
51 cls = RepoGroup
51 cls = RepoGroup
52 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
53 PERSONAL_GROUP_PATTERN = '${username}' # default
53 PERSONAL_GROUP_PATTERN = '${username}' # default
54
54
55 def _get_user_group(self, users_group):
55 def _get_user_group(self, users_group):
56 return self._get_instance(UserGroup, users_group,
56 return self._get_instance(UserGroup, users_group,
57 callback=UserGroup.get_by_group_name)
57 callback=UserGroup.get_by_group_name)
58
58
59 def _get_repo_group(self, repo_group):
59 def _get_repo_group(self, repo_group):
60 return self._get_instance(RepoGroup, repo_group,
60 return self._get_instance(RepoGroup, repo_group,
61 callback=RepoGroup.get_by_group_name)
61 callback=RepoGroup.get_by_group_name)
62
62
63 @LazyProperty
63 @LazyProperty
64 def repos_path(self):
64 def repos_path(self):
65 """
65 """
66 Gets the repositories root path from database
66 Gets the repositories root path from database
67 """
67 """
68
68
69 settings_model = VcsSettingsModel(sa=self.sa)
69 settings_model = VcsSettingsModel(sa=self.sa)
70 return settings_model.get_repos_location()
70 return settings_model.get_repos_location()
71
71
72 def get_by_group_name(self, repo_group_name, cache=None):
72 def get_by_group_name(self, repo_group_name, cache=None):
73 repo = self.sa.query(RepoGroup) \
73 repo = self.sa.query(RepoGroup) \
74 .filter(RepoGroup.group_name == repo_group_name)
74 .filter(RepoGroup.group_name == repo_group_name)
75
75
76 if cache:
76 if cache:
77 name_key = _hash_key(repo_group_name)
77 name_key = _hash_key(repo_group_name)
78 repo = repo.options(
78 repo = repo.options(
79 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
80 return repo.scalar()
80 return repo.scalar()
81
81
82 def get_default_create_personal_repo_group(self):
82 def get_default_create_personal_repo_group(self):
83 value = SettingsModel().get_setting_by_name(
83 value = SettingsModel().get_setting_by_name(
84 'create_personal_repo_group')
84 'create_personal_repo_group')
85 return value.app_settings_value if value else None or False
85 return value.app_settings_value if value else None or False
86
86
87 def get_personal_group_name_pattern(self):
87 def get_personal_group_name_pattern(self):
88 value = SettingsModel().get_setting_by_name(
88 value = SettingsModel().get_setting_by_name(
89 'personal_repo_group_pattern')
89 'personal_repo_group_pattern')
90 val = value.app_settings_value if value else None
90 val = value.app_settings_value if value else None
91 group_template = val or self.PERSONAL_GROUP_PATTERN
91 group_template = val or self.PERSONAL_GROUP_PATTERN
92
92
93 group_template = group_template.lstrip('/')
93 group_template = group_template.lstrip('/')
94 return group_template
94 return group_template
95
95
96 def get_personal_group_name(self, user):
96 def get_personal_group_name(self, user):
97 template = self.get_personal_group_name_pattern()
97 template = self.get_personal_group_name_pattern()
98 return string.Template(template).safe_substitute(
98 return string.Template(template).safe_substitute(
99 username=user.username,
99 username=user.username,
100 user_id=user.user_id,
100 user_id=user.user_id,
101 first_name=user.first_name,
101 first_name=user.first_name,
102 last_name=user.last_name,
102 last_name=user.last_name,
103 )
103 )
104
104
105 def create_personal_repo_group(self, user, commit_early=True):
105 def create_personal_repo_group(self, user, commit_early=True):
106 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
106 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
107 personal_repo_group_name = self.get_personal_group_name(user)
107 personal_repo_group_name = self.get_personal_group_name(user)
108
108
109 # create a new one
109 # create a new one
110 RepoGroupModel().create(
110 RepoGroupModel().create(
111 group_name=personal_repo_group_name,
111 group_name=personal_repo_group_name,
112 group_description=desc,
112 group_description=desc,
113 owner=user.username,
113 owner=user.username,
114 personal=True,
114 personal=True,
115 commit_early=commit_early)
115 commit_early=commit_early)
116
116
117 def _create_default_perms(self, new_group):
117 def _create_default_perms(self, new_group):
118 # create default permission
118 # create default permission
119 default_perm = 'group.read'
119 default_perm = 'group.read'
120 def_user = User.get_default_user()
120 def_user = User.get_default_user()
121 for p in def_user.user_perms:
121 for p in def_user.user_perms:
122 if p.permission.permission_name.startswith('group.'):
122 if p.permission.permission_name.startswith('group.'):
123 default_perm = p.permission.permission_name
123 default_perm = p.permission.permission_name
124 break
124 break
125
125
126 repo_group_to_perm = UserRepoGroupToPerm()
126 repo_group_to_perm = UserRepoGroupToPerm()
127 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
127 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
128
128
129 repo_group_to_perm.group = new_group
129 repo_group_to_perm.group = new_group
130 repo_group_to_perm.user_id = def_user.user_id
130 repo_group_to_perm.user_id = def_user.user_id
131 return repo_group_to_perm
131 return repo_group_to_perm
132
132
133 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
133 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
134 get_object=False):
134 get_object=False):
135 """
135 """
136 Get's the group name and a parent group name from given group name.
136 Get's the group name and a parent group name from given group name.
137 If repo_in_path is set to truth, we asume the full path also includes
137 If repo_in_path is set to truth, we asume the full path also includes
138 repo name, in such case we clean the last element.
138 repo name, in such case we clean the last element.
139
139
140 :param group_name_full:
140 :param group_name_full:
141 """
141 """
142 split_paths = 1
142 split_paths = 1
143 if repo_in_path:
143 if repo_in_path:
144 split_paths = 2
144 split_paths = 2
145 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
145 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
146
146
147 if repo_in_path and len(_parts) > 1:
147 if repo_in_path and len(_parts) > 1:
148 # such case last element is the repo_name
148 # such case last element is the repo_name
149 _parts.pop(-1)
149 _parts.pop(-1)
150 group_name_cleaned = _parts[-1] # just the group name
150 group_name_cleaned = _parts[-1] # just the group name
151 parent_repo_group_name = None
151 parent_repo_group_name = None
152
152
153 if len(_parts) > 1:
153 if len(_parts) > 1:
154 parent_repo_group_name = _parts[0]
154 parent_repo_group_name = _parts[0]
155
155
156 parent_group = None
156 parent_group = None
157 if parent_repo_group_name:
157 if parent_repo_group_name:
158 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
158 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
159
159
160 if get_object:
160 if get_object:
161 return group_name_cleaned, parent_repo_group_name, parent_group
161 return group_name_cleaned, parent_repo_group_name, parent_group
162
162
163 return group_name_cleaned, parent_repo_group_name
163 return group_name_cleaned, parent_repo_group_name
164
164
165 def check_exist_filesystem(self, group_name, exc_on_failure=True):
165 def check_exist_filesystem(self, group_name, exc_on_failure=True):
166 create_path = os.path.join(self.repos_path, group_name)
166 create_path = os.path.join(self.repos_path, group_name)
167 log.debug('creating new group in %s', create_path)
167 log.debug('creating new group in %s', create_path)
168
168
169 if os.path.isdir(create_path):
169 if os.path.isdir(create_path):
170 if exc_on_failure:
170 if exc_on_failure:
171 abs_create_path = os.path.abspath(create_path)
171 abs_create_path = os.path.abspath(create_path)
172 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
172 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
173 return False
173 return False
174 return True
174 return True
175
175
176 def _create_group(self, group_name):
176 def _create_group(self, group_name):
177 """
177 """
178 makes repository group on filesystem
178 makes repository group on filesystem
179
179
180 :param repo_name:
180 :param repo_name:
181 :param parent_id:
181 :param parent_id:
182 """
182 """
183
183
184 self.check_exist_filesystem(group_name)
184 self.check_exist_filesystem(group_name)
185 create_path = os.path.join(self.repos_path, group_name)
185 create_path = os.path.join(self.repos_path, group_name)
186 log.debug('creating new group in %s', create_path)
186 log.debug('creating new group in %s', create_path)
187 os.makedirs(create_path, mode=0o755)
187 os.makedirs(create_path, mode=0o755)
188 log.debug('created group in %s', create_path)
188 log.debug('created group in %s', create_path)
189
189
190 def _rename_group(self, old, new):
190 def _rename_group(self, old, new):
191 """
191 """
192 Renames a group on filesystem
192 Renames a group on filesystem
193
193
194 :param group_name:
194 :param group_name:
195 """
195 """
196
196
197 if old == new:
197 if old == new:
198 log.debug('skipping group rename')
198 log.debug('skipping group rename')
199 return
199 return
200
200
201 log.debug('renaming repository group from %s to %s', old, new)
201 log.debug('renaming repository group from %s to %s', old, new)
202
202
203 old_path = os.path.join(self.repos_path, old)
203 old_path = os.path.join(self.repos_path, old)
204 new_path = os.path.join(self.repos_path, new)
204 new_path = os.path.join(self.repos_path, new)
205
205
206 log.debug('renaming repos paths from %s to %s', old_path, new_path)
206 log.debug('renaming repos paths from %s to %s', old_path, new_path)
207
207
208 if os.path.isdir(new_path):
208 if os.path.isdir(new_path):
209 raise Exception('Was trying to rename to already '
209 raise Exception('Was trying to rename to already '
210 'existing dir %s' % new_path)
210 'existing dir %s' % new_path)
211 shutil.move(old_path, new_path)
211 shutil.move(old_path, new_path)
212
212
213 def _delete_filesystem_group(self, group, force_delete=False):
213 def _delete_filesystem_group(self, group, force_delete=False):
214 """
214 """
215 Deletes a group from a filesystem
215 Deletes a group from a filesystem
216
216
217 :param group: instance of group from database
217 :param group: instance of group from database
218 :param force_delete: use shutil rmtree to remove all objects
218 :param force_delete: use shutil rmtree to remove all objects
219 """
219 """
220 paths = group.full_path.split(RepoGroup.url_sep())
220 paths = group.full_path.split(RepoGroup.url_sep())
221 paths = os.sep.join(paths)
221 paths = os.sep.join(paths)
222
222
223 rm_path = os.path.join(self.repos_path, paths)
223 rm_path = os.path.join(self.repos_path, paths)
224 log.info("Removing group %s", rm_path)
224 log.info("Removing group %s", rm_path)
225 # delete only if that path really exists
225 # delete only if that path really exists
226 if os.path.isdir(rm_path):
226 if os.path.isdir(rm_path):
227 if force_delete:
227 if force_delete:
228 shutil.rmtree(rm_path)
228 shutil.rmtree(rm_path)
229 else:
229 else:
230 # archive that group`
230 # archive that group`
231 _now = datetime.datetime.now()
231 _now = datetime.datetime.now()
232 _ms = str(_now.microsecond).rjust(6, '0')
232 _ms = str(_now.microsecond).rjust(6, '0')
233 _d = 'rm__%s_GROUP_%s' % (
233 _d = 'rm__%s_GROUP_%s' % (
234 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
234 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
235 shutil.move(rm_path, os.path.join(self.repos_path, _d))
235 shutil.move(rm_path, os.path.join(self.repos_path, _d))
236
236
237 def create(self, group_name, group_description, owner, just_db=False,
237 def create(self, group_name, group_description, owner, just_db=False,
238 copy_permissions=False, personal=None, commit_early=True):
238 copy_permissions=False, personal=None, commit_early=True):
239
239
240 (group_name_cleaned,
240 (group_name_cleaned,
241 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
241 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
242
242
243 parent_group = None
243 parent_group = None
244 if parent_group_name:
244 if parent_group_name:
245 parent_group = self._get_repo_group(parent_group_name)
245 parent_group = self._get_repo_group(parent_group_name)
246 if not parent_group:
246 if not parent_group:
247 # we tried to create a nested group, but the parent is not
247 # we tried to create a nested group, but the parent is not
248 # existing
248 # existing
249 raise ValueError(
249 raise ValueError(
250 'Parent group `%s` given in `%s` group name '
250 'Parent group `%s` given in `%s` group name '
251 'is not yet existing.' % (parent_group_name, group_name))
251 'is not yet existing.' % (parent_group_name, group_name))
252
252
253 # because we are doing a cleanup, we need to check if such directory
253 # because we are doing a cleanup, we need to check if such directory
254 # already exists. If we don't do that we can accidentally delete
254 # already exists. If we don't do that we can accidentally delete
255 # existing directory via cleanup that can cause data issues, since
255 # existing directory via cleanup that can cause data issues, since
256 # delete does a folder rename to special syntax later cleanup
256 # delete does a folder rename to special syntax later cleanup
257 # functions can delete this
257 # functions can delete this
258 cleanup_group = self.check_exist_filesystem(group_name,
258 cleanup_group = self.check_exist_filesystem(group_name,
259 exc_on_failure=False)
259 exc_on_failure=False)
260 user = self._get_user(owner)
260 user = self._get_user(owner)
261 if not user:
261 if not user:
262 raise ValueError('Owner %s not found as rhodecode user', owner)
262 raise ValueError('Owner %s not found as rhodecode user', owner)
263
263
264 try:
264 try:
265 new_repo_group = RepoGroup()
265 new_repo_group = RepoGroup()
266 new_repo_group.user = user
266 new_repo_group.user = user
267 new_repo_group.group_description = group_description or group_name
267 new_repo_group.group_description = group_description or group_name
268 new_repo_group.parent_group = parent_group
268 new_repo_group.parent_group = parent_group
269 new_repo_group.group_name = group_name
269 new_repo_group.group_name = group_name
270 new_repo_group.personal = personal
270 new_repo_group.personal = personal
271
271
272 self.sa.add(new_repo_group)
272 self.sa.add(new_repo_group)
273
273
274 # create an ADMIN permission for owner except if we're super admin,
274 # create an ADMIN permission for owner except if we're super admin,
275 # later owner should go into the owner field of groups
275 # later owner should go into the owner field of groups
276 if not user.is_admin:
276 if not user.is_admin:
277 self.grant_user_permission(repo_group=new_repo_group,
277 self.grant_user_permission(repo_group=new_repo_group,
278 user=owner, perm='group.admin')
278 user=owner, perm='group.admin')
279
279
280 if parent_group and copy_permissions:
280 if parent_group and copy_permissions:
281 # copy permissions from parent
281 # copy permissions from parent
282 user_perms = UserRepoGroupToPerm.query() \
282 user_perms = UserRepoGroupToPerm.query() \
283 .filter(UserRepoGroupToPerm.group == parent_group).all()
283 .filter(UserRepoGroupToPerm.group == parent_group).all()
284
284
285 group_perms = UserGroupRepoGroupToPerm.query() \
285 group_perms = UserGroupRepoGroupToPerm.query() \
286 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
286 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
287
287
288 for perm in user_perms:
288 for perm in user_perms:
289 # don't copy over the permission for user who is creating
289 # don't copy over the permission for user who is creating
290 # this group, if he is not super admin he get's admin
290 # this group, if he is not super admin he get's admin
291 # permission set above
291 # permission set above
292 if perm.user != user or user.is_admin:
292 if perm.user != user or user.is_admin:
293 UserRepoGroupToPerm.create(
293 UserRepoGroupToPerm.create(
294 perm.user, new_repo_group, perm.permission)
294 perm.user, new_repo_group, perm.permission)
295
295
296 for perm in group_perms:
296 for perm in group_perms:
297 UserGroupRepoGroupToPerm.create(
297 UserGroupRepoGroupToPerm.create(
298 perm.users_group, new_repo_group, perm.permission)
298 perm.users_group, new_repo_group, perm.permission)
299 else:
299 else:
300 perm_obj = self._create_default_perms(new_repo_group)
300 perm_obj = self._create_default_perms(new_repo_group)
301 self.sa.add(perm_obj)
301 self.sa.add(perm_obj)
302
302
303 # now commit the changes, earlier so we are sure everything is in
303 # now commit the changes, earlier so we are sure everything is in
304 # the database.
304 # the database.
305 if commit_early:
305 if commit_early:
306 self.sa.commit()
306 self.sa.commit()
307 if not just_db:
307 if not just_db:
308 self._create_group(new_repo_group.group_name)
308 self._create_group(new_repo_group.group_name)
309
309
310 # trigger the post hook
310 # trigger the post hook
311 from rhodecode.lib.hooks_base import log_create_repository_group
311 from rhodecode.lib import hooks_base
312 repo_group = RepoGroup.get_by_group_name(group_name)
312 repo_group = RepoGroup.get_by_group_name(group_name)
313
313
314 # update repo group commit caches initially
314 # update repo group commit caches initially
315 repo_group.update_commit_cache()
315 repo_group.update_commit_cache()
316
316
317 log_create_repository_group(
317 hooks_base.create_repository_group(
318 created_by=user.username, **repo_group.get_dict())
318 created_by=user.username, **repo_group.get_dict())
319
319
320 # Trigger create event.
320 # Trigger create event.
321 events.trigger(events.RepoGroupCreateEvent(repo_group))
321 events.trigger(events.RepoGroupCreateEvent(repo_group))
322
322
323 return new_repo_group
323 return new_repo_group
324 except Exception:
324 except Exception:
325 self.sa.rollback()
325 self.sa.rollback()
326 log.exception('Exception occurred when creating repository group, '
326 log.exception('Exception occurred when creating repository group, '
327 'doing cleanup...')
327 'doing cleanup...')
328 # rollback things manually !
328 # rollback things manually !
329 repo_group = RepoGroup.get_by_group_name(group_name)
329 repo_group = RepoGroup.get_by_group_name(group_name)
330 if repo_group:
330 if repo_group:
331 RepoGroup.delete(repo_group.group_id)
331 RepoGroup.delete(repo_group.group_id)
332 self.sa.commit()
332 self.sa.commit()
333 if cleanup_group:
333 if cleanup_group:
334 RepoGroupModel()._delete_filesystem_group(repo_group)
334 RepoGroupModel()._delete_filesystem_group(repo_group)
335 raise
335 raise
336
336
337 def update_permissions(
337 def update_permissions(
338 self, repo_group, perm_additions=None, perm_updates=None,
338 self, repo_group, perm_additions=None, perm_updates=None,
339 perm_deletions=None, recursive=None, check_perms=True,
339 perm_deletions=None, recursive=None, check_perms=True,
340 cur_user=None):
340 cur_user=None):
341 from rhodecode.model.repo import RepoModel
341 from rhodecode.model.repo import RepoModel
342 from rhodecode.lib.auth import HasUserGroupPermissionAny
342 from rhodecode.lib.auth import HasUserGroupPermissionAny
343
343
344 if not perm_additions:
344 if not perm_additions:
345 perm_additions = []
345 perm_additions = []
346 if not perm_updates:
346 if not perm_updates:
347 perm_updates = []
347 perm_updates = []
348 if not perm_deletions:
348 if not perm_deletions:
349 perm_deletions = []
349 perm_deletions = []
350
350
351 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
351 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
352
352
353 changes = {
353 changes = {
354 'added': [],
354 'added': [],
355 'updated': [],
355 'updated': [],
356 'deleted': [],
356 'deleted': [],
357 'default_user_changed': None
357 'default_user_changed': None
358 }
358 }
359
359
360 def _set_perm_user(obj, user, perm):
360 def _set_perm_user(obj, user, perm):
361 if isinstance(obj, RepoGroup):
361 if isinstance(obj, RepoGroup):
362 self.grant_user_permission(
362 self.grant_user_permission(
363 repo_group=obj, user=user, perm=perm)
363 repo_group=obj, user=user, perm=perm)
364 elif isinstance(obj, Repository):
364 elif isinstance(obj, Repository):
365 # private repos will not allow to change the default
365 # private repos will not allow to change the default
366 # permissions using recursive mode
366 # permissions using recursive mode
367 if obj.private and user == User.DEFAULT_USER:
367 if obj.private and user == User.DEFAULT_USER:
368 return
368 return
369
369
370 # we set group permission but we have to switch to repo
370 # we set group permission but we have to switch to repo
371 # permission
371 # permission
372 perm = perm.replace('group.', 'repository.')
372 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_permission(
373 RepoModel().grant_user_permission(
374 repo=obj, user=user, perm=perm)
374 repo=obj, user=user, perm=perm)
375
375
376 def _set_perm_group(obj, users_group, perm):
376 def _set_perm_group(obj, users_group, perm):
377 if isinstance(obj, RepoGroup):
377 if isinstance(obj, RepoGroup):
378 self.grant_user_group_permission(
378 self.grant_user_group_permission(
379 repo_group=obj, group_name=users_group, perm=perm)
379 repo_group=obj, group_name=users_group, perm=perm)
380 elif isinstance(obj, Repository):
380 elif isinstance(obj, Repository):
381 # we set group permission but we have to switch to repo
381 # we set group permission but we have to switch to repo
382 # permission
382 # permission
383 perm = perm.replace('group.', 'repository.')
383 perm = perm.replace('group.', 'repository.')
384 RepoModel().grant_user_group_permission(
384 RepoModel().grant_user_group_permission(
385 repo=obj, group_name=users_group, perm=perm)
385 repo=obj, group_name=users_group, perm=perm)
386
386
387 def _revoke_perm_user(obj, user):
387 def _revoke_perm_user(obj, user):
388 if isinstance(obj, RepoGroup):
388 if isinstance(obj, RepoGroup):
389 self.revoke_user_permission(repo_group=obj, user=user)
389 self.revoke_user_permission(repo_group=obj, user=user)
390 elif isinstance(obj, Repository):
390 elif isinstance(obj, Repository):
391 RepoModel().revoke_user_permission(repo=obj, user=user)
391 RepoModel().revoke_user_permission(repo=obj, user=user)
392
392
393 def _revoke_perm_group(obj, user_group):
393 def _revoke_perm_group(obj, user_group):
394 if isinstance(obj, RepoGroup):
394 if isinstance(obj, RepoGroup):
395 self.revoke_user_group_permission(
395 self.revoke_user_group_permission(
396 repo_group=obj, group_name=user_group)
396 repo_group=obj, group_name=user_group)
397 elif isinstance(obj, Repository):
397 elif isinstance(obj, Repository):
398 RepoModel().revoke_user_group_permission(
398 RepoModel().revoke_user_group_permission(
399 repo=obj, group_name=user_group)
399 repo=obj, group_name=user_group)
400
400
401 # start updates
401 # start updates
402 log.debug('Now updating permissions for %s in recursive mode:%s',
402 log.debug('Now updating permissions for %s in recursive mode:%s',
403 repo_group, recursive)
403 repo_group, recursive)
404
404
405 # initialize check function, we'll call that multiple times
405 # initialize check function, we'll call that multiple times
406 has_group_perm = HasUserGroupPermissionAny(*req_perms)
406 has_group_perm = HasUserGroupPermissionAny(*req_perms)
407
407
408 for obj in repo_group.recursive_groups_and_repos():
408 for obj in repo_group.recursive_groups_and_repos():
409 # iterated obj is an instance of a repos group or repository in
409 # iterated obj is an instance of a repos group or repository in
410 # that group, recursive option can be: none, repos, groups, all
410 # that group, recursive option can be: none, repos, groups, all
411 if recursive == 'all':
411 if recursive == 'all':
412 obj = obj
412 obj = obj
413 elif recursive == 'repos':
413 elif recursive == 'repos':
414 # skip groups, other than this one
414 # skip groups, other than this one
415 if isinstance(obj, RepoGroup) and not obj == repo_group:
415 if isinstance(obj, RepoGroup) and not obj == repo_group:
416 continue
416 continue
417 elif recursive == 'groups':
417 elif recursive == 'groups':
418 # skip repos
418 # skip repos
419 if isinstance(obj, Repository):
419 if isinstance(obj, Repository):
420 continue
420 continue
421 else: # recursive == 'none':
421 else: # recursive == 'none':
422 # DEFAULT option - don't apply to iterated objects
422 # DEFAULT option - don't apply to iterated objects
423 # also we do a break at the end of this loop. if we are not
423 # also we do a break at the end of this loop. if we are not
424 # in recursive mode
424 # in recursive mode
425 obj = repo_group
425 obj = repo_group
426
426
427 change_obj = obj.get_api_data()
427 change_obj = obj.get_api_data()
428
428
429 # update permissions
429 # update permissions
430 for member_id, perm, member_type in perm_updates:
430 for member_id, perm, member_type in perm_updates:
431 member_id = int(member_id)
431 member_id = int(member_id)
432 if member_type == 'user':
432 if member_type == 'user':
433 member_name = User.get(member_id).username
433 member_name = User.get(member_id).username
434 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
434 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
435 # NOTE(dan): detect if we changed permissions for default user
435 # NOTE(dan): detect if we changed permissions for default user
436 perm_obj = self.sa.query(UserRepoGroupToPerm) \
436 perm_obj = self.sa.query(UserRepoGroupToPerm) \
437 .filter(UserRepoGroupToPerm.user_id == member_id) \
437 .filter(UserRepoGroupToPerm.user_id == member_id) \
438 .filter(UserRepoGroupToPerm.group == repo_group) \
438 .filter(UserRepoGroupToPerm.group == repo_group) \
439 .scalar()
439 .scalar()
440 if perm_obj and perm_obj.permission.permission_name != perm:
440 if perm_obj and perm_obj.permission.permission_name != perm:
441 changes['default_user_changed'] = True
441 changes['default_user_changed'] = True
442
442
443 # this updates also current one if found
443 # this updates also current one if found
444 _set_perm_user(obj, user=member_id, perm=perm)
444 _set_perm_user(obj, user=member_id, perm=perm)
445 elif member_type == 'user_group':
445 elif member_type == 'user_group':
446 member_name = UserGroup.get(member_id).users_group_name
446 member_name = UserGroup.get(member_id).users_group_name
447 if not check_perms or has_group_perm(member_name,
447 if not check_perms or has_group_perm(member_name,
448 user=cur_user):
448 user=cur_user):
449 _set_perm_group(obj, users_group=member_id, perm=perm)
449 _set_perm_group(obj, users_group=member_id, perm=perm)
450 else:
450 else:
451 raise ValueError("member_type must be 'user' or 'user_group' "
451 raise ValueError("member_type must be 'user' or 'user_group' "
452 "got {} instead".format(member_type))
452 "got {} instead".format(member_type))
453
453
454 changes['updated'].append(
454 changes['updated'].append(
455 {'change_obj': change_obj, 'type': member_type,
455 {'change_obj': change_obj, 'type': member_type,
456 'id': member_id, 'name': member_name, 'new_perm': perm})
456 'id': member_id, 'name': member_name, 'new_perm': perm})
457
457
458 # set new permissions
458 # set new permissions
459 for member_id, perm, member_type in perm_additions:
459 for member_id, perm, member_type in perm_additions:
460 member_id = int(member_id)
460 member_id = int(member_id)
461 if member_type == 'user':
461 if member_type == 'user':
462 member_name = User.get(member_id).username
462 member_name = User.get(member_id).username
463 _set_perm_user(obj, user=member_id, perm=perm)
463 _set_perm_user(obj, user=member_id, perm=perm)
464 elif member_type == 'user_group':
464 elif member_type == 'user_group':
465 # check if we have permissions to alter this usergroup
465 # check if we have permissions to alter this usergroup
466 member_name = UserGroup.get(member_id).users_group_name
466 member_name = UserGroup.get(member_id).users_group_name
467 if not check_perms or has_group_perm(member_name,
467 if not check_perms or has_group_perm(member_name,
468 user=cur_user):
468 user=cur_user):
469 _set_perm_group(obj, users_group=member_id, perm=perm)
469 _set_perm_group(obj, users_group=member_id, perm=perm)
470 else:
470 else:
471 raise ValueError("member_type must be 'user' or 'user_group' "
471 raise ValueError("member_type must be 'user' or 'user_group' "
472 "got {} instead".format(member_type))
472 "got {} instead".format(member_type))
473
473
474 changes['added'].append(
474 changes['added'].append(
475 {'change_obj': change_obj, 'type': member_type,
475 {'change_obj': change_obj, 'type': member_type,
476 'id': member_id, 'name': member_name, 'new_perm': perm})
476 'id': member_id, 'name': member_name, 'new_perm': perm})
477
477
478 # delete permissions
478 # delete permissions
479 for member_id, perm, member_type in perm_deletions:
479 for member_id, perm, member_type in perm_deletions:
480 member_id = int(member_id)
480 member_id = int(member_id)
481 if member_type == 'user':
481 if member_type == 'user':
482 member_name = User.get(member_id).username
482 member_name = User.get(member_id).username
483 _revoke_perm_user(obj, user=member_id)
483 _revoke_perm_user(obj, user=member_id)
484 elif member_type == 'user_group':
484 elif member_type == 'user_group':
485 # check if we have permissions to alter this usergroup
485 # check if we have permissions to alter this usergroup
486 member_name = UserGroup.get(member_id).users_group_name
486 member_name = UserGroup.get(member_id).users_group_name
487 if not check_perms or has_group_perm(member_name,
487 if not check_perms or has_group_perm(member_name,
488 user=cur_user):
488 user=cur_user):
489 _revoke_perm_group(obj, user_group=member_id)
489 _revoke_perm_group(obj, user_group=member_id)
490 else:
490 else:
491 raise ValueError("member_type must be 'user' or 'user_group' "
491 raise ValueError("member_type must be 'user' or 'user_group' "
492 "got {} instead".format(member_type))
492 "got {} instead".format(member_type))
493
493
494 changes['deleted'].append(
494 changes['deleted'].append(
495 {'change_obj': change_obj, 'type': member_type,
495 {'change_obj': change_obj, 'type': member_type,
496 'id': member_id, 'name': member_name, 'new_perm': perm})
496 'id': member_id, 'name': member_name, 'new_perm': perm})
497
497
498 # if it's not recursive call for all,repos,groups
498 # if it's not recursive call for all,repos,groups
499 # break the loop and don't proceed with other changes
499 # break the loop and don't proceed with other changes
500 if recursive not in ['all', 'repos', 'groups']:
500 if recursive not in ['all', 'repos', 'groups']:
501 break
501 break
502
502
503 return changes
503 return changes
504
504
505 def update(self, repo_group, form_data):
505 def update(self, repo_group, form_data):
506 try:
506 try:
507 repo_group = self._get_repo_group(repo_group)
507 repo_group = self._get_repo_group(repo_group)
508 old_path = repo_group.full_path
508 old_path = repo_group.full_path
509
509
510 # change properties
510 # change properties
511 if 'group_description' in form_data:
511 if 'group_description' in form_data:
512 repo_group.group_description = form_data['group_description']
512 repo_group.group_description = form_data['group_description']
513
513
514 if 'enable_locking' in form_data:
514 if 'enable_locking' in form_data:
515 repo_group.enable_locking = form_data['enable_locking']
515 repo_group.enable_locking = form_data['enable_locking']
516
516
517 if 'group_parent_id' in form_data:
517 if 'group_parent_id' in form_data:
518 parent_group = (
518 parent_group = (
519 self._get_repo_group(form_data['group_parent_id']))
519 self._get_repo_group(form_data['group_parent_id']))
520 repo_group.group_parent_id = (
520 repo_group.group_parent_id = (
521 parent_group.group_id if parent_group else None)
521 parent_group.group_id if parent_group else None)
522 repo_group.parent_group = parent_group
522 repo_group.parent_group = parent_group
523
523
524 # mikhail: to update the full_path, we have to explicitly
524 # mikhail: to update the full_path, we have to explicitly
525 # update group_name
525 # update group_name
526 group_name = form_data.get('group_name', repo_group.name)
526 group_name = form_data.get('group_name', repo_group.name)
527 repo_group.group_name = repo_group.get_new_name(group_name)
527 repo_group.group_name = repo_group.get_new_name(group_name)
528
528
529 new_path = repo_group.full_path
529 new_path = repo_group.full_path
530
530
531 if 'user' in form_data:
531 if 'user' in form_data:
532 repo_group.user = User.get_by_username(form_data['user'])
532 repo_group.user = User.get_by_username(form_data['user'])
533
533
534 self.sa.add(repo_group)
534 self.sa.add(repo_group)
535
535
536 # iterate over all members of this groups and do fixes
536 # iterate over all members of this groups and do fixes
537 # set locking if given
537 # set locking if given
538 # if obj is a repoGroup also fix the name of the group according
538 # if obj is a repoGroup also fix the name of the group according
539 # to the parent
539 # to the parent
540 # if obj is a Repo fix it's name
540 # if obj is a Repo fix it's name
541 # this can be potentially heavy operation
541 # this can be potentially heavy operation
542 for obj in repo_group.recursive_groups_and_repos():
542 for obj in repo_group.recursive_groups_and_repos():
543 # set the value from it's parent
543 # set the value from it's parent
544 obj.enable_locking = repo_group.enable_locking
544 obj.enable_locking = repo_group.enable_locking
545 if isinstance(obj, RepoGroup):
545 if isinstance(obj, RepoGroup):
546 new_name = obj.get_new_name(obj.name)
546 new_name = obj.get_new_name(obj.name)
547 log.debug('Fixing group %s to new name %s',
547 log.debug('Fixing group %s to new name %s',
548 obj.group_name, new_name)
548 obj.group_name, new_name)
549 obj.group_name = new_name
549 obj.group_name = new_name
550
550
551 elif isinstance(obj, Repository):
551 elif isinstance(obj, Repository):
552 # we need to get all repositories from this new group and
552 # we need to get all repositories from this new group and
553 # rename them accordingly to new group path
553 # rename them accordingly to new group path
554 new_name = obj.get_new_name(obj.just_name)
554 new_name = obj.get_new_name(obj.just_name)
555 log.debug('Fixing repo %s to new name %s',
555 log.debug('Fixing repo %s to new name %s',
556 obj.repo_name, new_name)
556 obj.repo_name, new_name)
557 obj.repo_name = new_name
557 obj.repo_name = new_name
558
558
559 self.sa.add(obj)
559 self.sa.add(obj)
560
560
561 self._rename_group(old_path, new_path)
561 self._rename_group(old_path, new_path)
562
562
563 # Trigger update event.
563 # Trigger update event.
564 events.trigger(events.RepoGroupUpdateEvent(repo_group))
564 events.trigger(events.RepoGroupUpdateEvent(repo_group))
565
565
566 return repo_group
566 return repo_group
567 except Exception:
567 except Exception:
568 log.error(traceback.format_exc())
568 log.error(traceback.format_exc())
569 raise
569 raise
570
570
571 def delete(self, repo_group, force_delete=False, fs_remove=True):
571 def delete(self, repo_group, force_delete=False, fs_remove=True):
572 repo_group = self._get_repo_group(repo_group)
572 repo_group = self._get_repo_group(repo_group)
573 if not repo_group:
573 if not repo_group:
574 return False
574 return False
575 try:
575 try:
576 self.sa.delete(repo_group)
576 self.sa.delete(repo_group)
577 if fs_remove:
577 if fs_remove:
578 self._delete_filesystem_group(repo_group, force_delete)
578 self._delete_filesystem_group(repo_group, force_delete)
579 else:
579 else:
580 log.debug('skipping removal from filesystem')
580 log.debug('skipping removal from filesystem')
581
581
582 # Trigger delete event.
582 # Trigger delete event.
583 events.trigger(events.RepoGroupDeleteEvent(repo_group))
583 events.trigger(events.RepoGroupDeleteEvent(repo_group))
584 return True
584 return True
585
585
586 except Exception:
586 except Exception:
587 log.error('Error removing repo_group %s', repo_group)
587 log.error('Error removing repo_group %s', repo_group)
588 raise
588 raise
589
589
590 def grant_user_permission(self, repo_group, user, perm):
590 def grant_user_permission(self, repo_group, user, perm):
591 """
591 """
592 Grant permission for user on given repository group, or update
592 Grant permission for user on given repository group, or update
593 existing one if found
593 existing one if found
594
594
595 :param repo_group: Instance of RepoGroup, repositories_group_id,
595 :param repo_group: Instance of RepoGroup, repositories_group_id,
596 or repositories_group name
596 or repositories_group name
597 :param user: Instance of User, user_id or username
597 :param user: Instance of User, user_id or username
598 :param perm: Instance of Permission, or permission_name
598 :param perm: Instance of Permission, or permission_name
599 """
599 """
600
600
601 repo_group = self._get_repo_group(repo_group)
601 repo_group = self._get_repo_group(repo_group)
602 user = self._get_user(user)
602 user = self._get_user(user)
603 permission = self._get_perm(perm)
603 permission = self._get_perm(perm)
604
604
605 # check if we have that permission already
605 # check if we have that permission already
606 obj = self.sa.query(UserRepoGroupToPerm)\
606 obj = self.sa.query(UserRepoGroupToPerm)\
607 .filter(UserRepoGroupToPerm.user == user)\
607 .filter(UserRepoGroupToPerm.user == user)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
609 .scalar()
609 .scalar()
610 if obj is None:
610 if obj is None:
611 # create new !
611 # create new !
612 obj = UserRepoGroupToPerm()
612 obj = UserRepoGroupToPerm()
613 obj.group = repo_group
613 obj.group = repo_group
614 obj.user = user
614 obj.user = user
615 obj.permission = permission
615 obj.permission = permission
616 self.sa.add(obj)
616 self.sa.add(obj)
617 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
617 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
618 action_logger_generic(
618 action_logger_generic(
619 'granted permission: {} to user: {} on repogroup: {}'.format(
619 'granted permission: {} to user: {} on repogroup: {}'.format(
620 perm, user, repo_group), namespace='security.repogroup')
620 perm, user, repo_group), namespace='security.repogroup')
621 return obj
621 return obj
622
622
623 def revoke_user_permission(self, repo_group, user):
623 def revoke_user_permission(self, repo_group, user):
624 """
624 """
625 Revoke permission for user on given repository group
625 Revoke permission for user on given repository group
626
626
627 :param repo_group: Instance of RepoGroup, repositories_group_id,
627 :param repo_group: Instance of RepoGroup, repositories_group_id,
628 or repositories_group name
628 or repositories_group name
629 :param user: Instance of User, user_id or username
629 :param user: Instance of User, user_id or username
630 """
630 """
631
631
632 repo_group = self._get_repo_group(repo_group)
632 repo_group = self._get_repo_group(repo_group)
633 user = self._get_user(user)
633 user = self._get_user(user)
634
634
635 obj = self.sa.query(UserRepoGroupToPerm)\
635 obj = self.sa.query(UserRepoGroupToPerm)\
636 .filter(UserRepoGroupToPerm.user == user)\
636 .filter(UserRepoGroupToPerm.user == user)\
637 .filter(UserRepoGroupToPerm.group == repo_group)\
637 .filter(UserRepoGroupToPerm.group == repo_group)\
638 .scalar()
638 .scalar()
639 if obj:
639 if obj:
640 self.sa.delete(obj)
640 self.sa.delete(obj)
641 log.debug('Revoked perm on %s on %s', repo_group, user)
641 log.debug('Revoked perm on %s on %s', repo_group, user)
642 action_logger_generic(
642 action_logger_generic(
643 'revoked permission from user: {} on repogroup: {}'.format(
643 'revoked permission from user: {} on repogroup: {}'.format(
644 user, repo_group), namespace='security.repogroup')
644 user, repo_group), namespace='security.repogroup')
645
645
646 def grant_user_group_permission(self, repo_group, group_name, perm):
646 def grant_user_group_permission(self, repo_group, group_name, perm):
647 """
647 """
648 Grant permission for user group on given repository group, or update
648 Grant permission for user group on given repository group, or update
649 existing one if found
649 existing one if found
650
650
651 :param repo_group: Instance of RepoGroup, repositories_group_id,
651 :param repo_group: Instance of RepoGroup, repositories_group_id,
652 or repositories_group name
652 or repositories_group name
653 :param group_name: Instance of UserGroup, users_group_id,
653 :param group_name: Instance of UserGroup, users_group_id,
654 or user group name
654 or user group name
655 :param perm: Instance of Permission, or permission_name
655 :param perm: Instance of Permission, or permission_name
656 """
656 """
657 repo_group = self._get_repo_group(repo_group)
657 repo_group = self._get_repo_group(repo_group)
658 group_name = self._get_user_group(group_name)
658 group_name = self._get_user_group(group_name)
659 permission = self._get_perm(perm)
659 permission = self._get_perm(perm)
660
660
661 # check if we have that permission already
661 # check if we have that permission already
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
665 .scalar()
665 .scalar()
666
666
667 if obj is None:
667 if obj is None:
668 # create new
668 # create new
669 obj = UserGroupRepoGroupToPerm()
669 obj = UserGroupRepoGroupToPerm()
670
670
671 obj.group = repo_group
671 obj.group = repo_group
672 obj.users_group = group_name
672 obj.users_group = group_name
673 obj.permission = permission
673 obj.permission = permission
674 self.sa.add(obj)
674 self.sa.add(obj)
675 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
675 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
676 action_logger_generic(
676 action_logger_generic(
677 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
677 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
678 perm, group_name, repo_group), namespace='security.repogroup')
678 perm, group_name, repo_group), namespace='security.repogroup')
679 return obj
679 return obj
680
680
681 def revoke_user_group_permission(self, repo_group, group_name):
681 def revoke_user_group_permission(self, repo_group, group_name):
682 """
682 """
683 Revoke permission for user group on given repository group
683 Revoke permission for user group on given repository group
684
684
685 :param repo_group: Instance of RepoGroup, repositories_group_id,
685 :param repo_group: Instance of RepoGroup, repositories_group_id,
686 or repositories_group name
686 or repositories_group name
687 :param group_name: Instance of UserGroup, users_group_id,
687 :param group_name: Instance of UserGroup, users_group_id,
688 or user group name
688 or user group name
689 """
689 """
690 repo_group = self._get_repo_group(repo_group)
690 repo_group = self._get_repo_group(repo_group)
691 group_name = self._get_user_group(group_name)
691 group_name = self._get_user_group(group_name)
692
692
693 obj = self.sa.query(UserGroupRepoGroupToPerm)\
693 obj = self.sa.query(UserGroupRepoGroupToPerm)\
694 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
694 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
695 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
695 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
696 .scalar()
696 .scalar()
697 if obj:
697 if obj:
698 self.sa.delete(obj)
698 self.sa.delete(obj)
699 log.debug('Revoked perm to %s on %s', repo_group, group_name)
699 log.debug('Revoked perm to %s on %s', repo_group, group_name)
700 action_logger_generic(
700 action_logger_generic(
701 'revoked permission from usergroup: {} on repogroup: {}'.format(
701 'revoked permission from usergroup: {} on repogroup: {}'.format(
702 group_name, repo_group), namespace='security.repogroup')
702 group_name, repo_group), namespace='security.repogroup')
703
703
704 @classmethod
704 @classmethod
705 def update_commit_cache(cls, repo_groups=None):
705 def update_commit_cache(cls, repo_groups=None):
706 if not repo_groups:
706 if not repo_groups:
707 repo_groups = RepoGroup.getAll()
707 repo_groups = RepoGroup.getAll()
708 for repo_group in repo_groups:
708 for repo_group in repo_groups:
709 repo_group.update_commit_cache()
709 repo_group.update_commit_cache()
710
710
711 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
711 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
712 super_user_actions=False):
712 super_user_actions=False):
713
713
714 from pyramid.threadlocal import get_current_request
714 from pyramid.threadlocal import get_current_request
715 _render = get_current_request().get_partial_renderer(
715 _render = get_current_request().get_partial_renderer(
716 'rhodecode:templates/data_table/_dt_elements.mako')
716 'rhodecode:templates/data_table/_dt_elements.mako')
717 c = _render.get_call_context()
717 c = _render.get_call_context()
718 h = _render.get_helpers()
718 h = _render.get_helpers()
719
719
720 def quick_menu(repo_group_name):
720 def quick_menu(repo_group_name):
721 return _render('quick_repo_group_menu', repo_group_name)
721 return _render('quick_repo_group_menu', repo_group_name)
722
722
723 def repo_group_lnk(repo_group_name):
723 def repo_group_lnk(repo_group_name):
724 return _render('repo_group_name', repo_group_name)
724 return _render('repo_group_name', repo_group_name)
725
725
726 def last_change(last_change):
726 def last_change(last_change):
727 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
727 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
728 ts = time.time()
728 ts = time.time()
729 utc_offset = (datetime.datetime.fromtimestamp(ts)
729 utc_offset = (datetime.datetime.fromtimestamp(ts)
730 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
730 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
731 last_change = last_change + datetime.timedelta(seconds=utc_offset)
731 last_change = last_change + datetime.timedelta(seconds=utc_offset)
732 return _render("last_change", last_change)
732 return _render("last_change", last_change)
733
733
734 def desc(desc, personal):
734 def desc(desc, personal):
735 return _render(
735 return _render(
736 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
736 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
737
737
738 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
738 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
739 return _render(
739 return _render(
740 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
740 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
741
741
742 def repo_group_name(repo_group_name, children_groups):
742 def repo_group_name(repo_group_name, children_groups):
743 return _render("repo_group_name", repo_group_name, children_groups)
743 return _render("repo_group_name", repo_group_name, children_groups)
744
744
745 def user_profile(username):
745 def user_profile(username):
746 return _render('user_profile', username)
746 return _render('user_profile', username)
747
747
748 repo_group_data = []
748 repo_group_data = []
749 for group in repo_group_list:
749 for group in repo_group_list:
750 # NOTE(marcink): because we use only raw column we need to load it like that
750 # NOTE(marcink): because we use only raw column we need to load it like that
751 changeset_cache = RepoGroup._load_changeset_cache(
751 changeset_cache = RepoGroup._load_changeset_cache(
752 '', group._changeset_cache)
752 '', group._changeset_cache)
753 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
753 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
754 row = {
754 row = {
755 "menu": quick_menu(group.group_name),
755 "menu": quick_menu(group.group_name),
756 "name": repo_group_lnk(group.group_name),
756 "name": repo_group_lnk(group.group_name),
757 "name_raw": group.group_name,
757 "name_raw": group.group_name,
758
758
759 "last_change": last_change(last_commit_change),
759 "last_change": last_change(last_commit_change),
760
760
761 "last_changeset": "",
761 "last_changeset": "",
762 "last_changeset_raw": "",
762 "last_changeset_raw": "",
763
763
764 "desc": desc(h.escape(group.group_description), group.personal),
764 "desc": desc(h.escape(group.group_description), group.personal),
765 "top_level_repos": 0,
765 "top_level_repos": 0,
766 "owner": user_profile(group.User.username)
766 "owner": user_profile(group.User.username)
767 }
767 }
768 if admin:
768 if admin:
769 repo_count = group.repositories.count()
769 repo_count = group.repositories.count()
770 children_groups = map(
770 children_groups = map(
771 h.safe_unicode,
771 h.safe_unicode,
772 itertools.chain((g.name for g in group.parents),
772 itertools.chain((g.name for g in group.parents),
773 (x.name for x in [group])))
773 (x.name for x in [group])))
774 row.update({
774 row.update({
775 "action": repo_group_actions(
775 "action": repo_group_actions(
776 group.group_id, group.group_name, repo_count),
776 group.group_id, group.group_name, repo_count),
777 "top_level_repos": repo_count,
777 "top_level_repos": repo_count,
778 "name": repo_group_name(group.group_name, children_groups),
778 "name": repo_group_name(group.group_name, children_groups),
779
779
780 })
780 })
781 repo_group_data.append(row)
781 repo_group_data.append(row)
782
782
783 return repo_group_data
783 return repo_group_data
784
784
785 def get_repo_groups_data_table(
785 def get_repo_groups_data_table(
786 self, draw, start, limit,
786 self, draw, start, limit,
787 search_q, order_by, order_dir,
787 search_q, order_by, order_dir,
788 auth_user, repo_group_id):
788 auth_user, repo_group_id):
789 from rhodecode.model.scm import RepoGroupList
789 from rhodecode.model.scm import RepoGroupList
790
790
791 _perms = ['group.read', 'group.write', 'group.admin']
791 _perms = ['group.read', 'group.write', 'group.admin']
792 repo_groups = RepoGroup.query() \
792 repo_groups = RepoGroup.query() \
793 .filter(RepoGroup.group_parent_id == repo_group_id) \
793 .filter(RepoGroup.group_parent_id == repo_group_id) \
794 .all()
794 .all()
795 auth_repo_group_list = RepoGroupList(
795 auth_repo_group_list = RepoGroupList(
796 repo_groups, perm_set=_perms,
796 repo_groups, perm_set=_perms,
797 extra_kwargs=dict(user=auth_user))
797 extra_kwargs=dict(user=auth_user))
798
798
799 allowed_ids = [-1]
799 allowed_ids = [-1]
800 for repo_group in auth_repo_group_list:
800 for repo_group in auth_repo_group_list:
801 allowed_ids.append(repo_group.group_id)
801 allowed_ids.append(repo_group.group_id)
802
802
803 repo_groups_data_total_count = RepoGroup.query() \
803 repo_groups_data_total_count = RepoGroup.query() \
804 .filter(RepoGroup.group_parent_id == repo_group_id) \
804 .filter(RepoGroup.group_parent_id == repo_group_id) \
805 .filter(or_(
805 .filter(or_(
806 # generate multiple IN to fix limitation problems
806 # generate multiple IN to fix limitation problems
807 *in_filter_generator(RepoGroup.group_id, allowed_ids))
807 *in_filter_generator(RepoGroup.group_id, allowed_ids))
808 ) \
808 ) \
809 .count()
809 .count()
810
810
811 base_q = Session.query(
811 base_q = Session.query(
812 RepoGroup.group_name,
812 RepoGroup.group_name,
813 RepoGroup.group_name_hash,
813 RepoGroup.group_name_hash,
814 RepoGroup.group_description,
814 RepoGroup.group_description,
815 RepoGroup.group_id,
815 RepoGroup.group_id,
816 RepoGroup.personal,
816 RepoGroup.personal,
817 RepoGroup.updated_on,
817 RepoGroup.updated_on,
818 RepoGroup._changeset_cache,
818 RepoGroup._changeset_cache,
819 User,
819 User,
820 ) \
820 ) \
821 .filter(RepoGroup.group_parent_id == repo_group_id) \
821 .filter(RepoGroup.group_parent_id == repo_group_id) \
822 .filter(or_(
822 .filter(or_(
823 # generate multiple IN to fix limitation problems
823 # generate multiple IN to fix limitation problems
824 *in_filter_generator(RepoGroup.group_id, allowed_ids))
824 *in_filter_generator(RepoGroup.group_id, allowed_ids))
825 ) \
825 ) \
826 .join(User, User.user_id == RepoGroup.user_id) \
826 .join(User, User.user_id == RepoGroup.user_id) \
827 .group_by(RepoGroup, User)
827 .group_by(RepoGroup, User)
828
828
829 repo_groups_data_total_filtered_count = base_q.count()
829 repo_groups_data_total_filtered_count = base_q.count()
830
830
831 sort_defined = False
831 sort_defined = False
832
832
833 if order_by == 'group_name':
833 if order_by == 'group_name':
834 sort_col = func.lower(RepoGroup.group_name)
834 sort_col = func.lower(RepoGroup.group_name)
835 sort_defined = True
835 sort_defined = True
836 elif order_by == 'user_username':
836 elif order_by == 'user_username':
837 sort_col = User.username
837 sort_col = User.username
838 else:
838 else:
839 sort_col = getattr(RepoGroup, order_by, None)
839 sort_col = getattr(RepoGroup, order_by, None)
840
840
841 if sort_defined or sort_col:
841 if sort_defined or sort_col:
842 if order_dir == 'asc':
842 if order_dir == 'asc':
843 sort_col = sort_col.asc()
843 sort_col = sort_col.asc()
844 else:
844 else:
845 sort_col = sort_col.desc()
845 sort_col = sort_col.desc()
846
846
847 base_q = base_q.order_by(sort_col)
847 base_q = base_q.order_by(sort_col)
848 base_q = base_q.offset(start).limit(limit)
848 base_q = base_q.offset(start).limit(limit)
849
849
850 repo_group_list = base_q.all()
850 repo_group_list = base_q.all()
851
851
852 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
852 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
853 repo_group_list=repo_group_list, admin=False)
853 repo_group_list=repo_group_list, admin=False)
854
854
855 data = ({
855 data = ({
856 'draw': draw,
856 'draw': draw,
857 'data': repo_groups_data,
857 'data': repo_groups_data,
858 'recordsTotal': repo_groups_data_total_count,
858 'recordsTotal': repo_groups_data_total_count,
859 'recordsFiltered': repo_groups_data_total_filtered_count,
859 'recordsFiltered': repo_groups_data_total_filtered_count,
860 })
860 })
861 return data
861 return data
862
862
863 def _get_defaults(self, repo_group_name):
863 def _get_defaults(self, repo_group_name):
864 repo_group = RepoGroup.get_by_group_name(repo_group_name)
864 repo_group = RepoGroup.get_by_group_name(repo_group_name)
865
865
866 if repo_group is None:
866 if repo_group is None:
867 return None
867 return None
868
868
869 defaults = repo_group.get_dict()
869 defaults = repo_group.get_dict()
870 defaults['repo_group_name'] = repo_group.name
870 defaults['repo_group_name'] = repo_group.name
871 defaults['repo_group_description'] = repo_group.group_description
871 defaults['repo_group_description'] = repo_group.group_description
872 defaults['repo_group_enable_locking'] = repo_group.enable_locking
872 defaults['repo_group_enable_locking'] = repo_group.enable_locking
873
873
874 # we use -1 as this is how in HTML, we mark an empty group
874 # we use -1 as this is how in HTML, we mark an empty group
875 defaults['repo_group'] = defaults['group_parent_id'] or -1
875 defaults['repo_group'] = defaults['group_parent_id'] or -1
876
876
877 # fill owner
877 # fill owner
878 if repo_group.user:
878 if repo_group.user:
879 defaults.update({'user': repo_group.user.username})
879 defaults.update({'user': repo_group.user.username})
880 else:
880 else:
881 replacement_user = User.get_first_super_admin().username
881 replacement_user = User.get_first_super_admin().username
882 defaults.update({'user': replacement_user})
882 defaults.update({'user': replacement_user})
883
883
884 return defaults
884 return defaults
@@ -1,1051 +1,1050 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-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 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError,
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
41 UserOwnsPullRequestsException, UserOwnsArtifactsException)
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'description': user.description,
182 'description': user.description,
183 'active': user.active,
183 'active': user.active,
184 'admin': user.admin,
184 'admin': user.admin,
185 'extern_name': user.extern_name,
185 'extern_name': user.extern_name,
186 'extern_type': user.extern_type,
186 'extern_type': user.extern_type,
187 'language': user.user_data.get('language')
187 'language': user.user_data.get('language')
188 }
188 }
189
189
190 # in case there's new_password, that comes from form, use it to
190 # in case there's new_password, that comes from form, use it to
191 # store password
191 # store password
192 if kwargs.get('new_password'):
192 if kwargs.get('new_password'):
193 kwargs['password'] = kwargs['new_password']
193 kwargs['password'] = kwargs['new_password']
194
194
195 # cleanups, my_account password change form
195 # cleanups, my_account password change form
196 kwargs.pop('current_password', None)
196 kwargs.pop('current_password', None)
197 kwargs.pop('new_password', None)
197 kwargs.pop('new_password', None)
198
198
199 # cleanups, user edit password change form
199 # cleanups, user edit password change form
200 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_confirmation', None)
201 kwargs.pop('password_change', None)
201 kwargs.pop('password_change', None)
202
202
203 # create repo group on user creation
203 # create repo group on user creation
204 kwargs.pop('create_repo_group', None)
204 kwargs.pop('create_repo_group', None)
205
205
206 # legacy forms send name, which is the firstname
206 # legacy forms send name, which is the firstname
207 firstname = kwargs.pop('name', None)
207 firstname = kwargs.pop('name', None)
208 if firstname:
208 if firstname:
209 kwargs['firstname'] = firstname
209 kwargs['firstname'] = firstname
210
210
211 for k, v in kwargs.items():
211 for k, v in kwargs.items():
212 # skip if we don't want to update this
212 # skip if we don't want to update this
213 if skip_attrs and k in skip_attrs:
213 if skip_attrs and k in skip_attrs:
214 continue
214 continue
215
215
216 user_attrs[k] = v
216 user_attrs[k] = v
217
217
218 try:
218 try:
219 return self.create_or_update(**user_attrs)
219 return self.create_or_update(**user_attrs)
220 except Exception:
220 except Exception:
221 log.error(traceback.format_exc())
221 log.error(traceback.format_exc())
222 raise
222 raise
223
223
224 def create_or_update(
224 def create_or_update(
225 self, username, password, email, firstname='', lastname='',
225 self, username, password, email, firstname='', lastname='',
226 active=True, admin=False, extern_type=None, extern_name=None,
226 active=True, admin=False, extern_type=None, extern_name=None,
227 cur_user=None, plugin=None, force_password_change=False,
227 cur_user=None, plugin=None, force_password_change=False,
228 allow_to_create_user=True, create_repo_group=None,
228 allow_to_create_user=True, create_repo_group=None,
229 updating_user_id=None, language=None, description='',
229 updating_user_id=None, language=None, description='',
230 strict_creation_check=True):
230 strict_creation_check=True):
231 """
231 """
232 Creates a new instance if not found, or updates current one
232 Creates a new instance if not found, or updates current one
233
233
234 :param username:
234 :param username:
235 :param password:
235 :param password:
236 :param email:
236 :param email:
237 :param firstname:
237 :param firstname:
238 :param lastname:
238 :param lastname:
239 :param active:
239 :param active:
240 :param admin:
240 :param admin:
241 :param extern_type:
241 :param extern_type:
242 :param extern_name:
242 :param extern_name:
243 :param cur_user:
243 :param cur_user:
244 :param plugin: optional plugin this method was called from
244 :param plugin: optional plugin this method was called from
245 :param force_password_change: toggles new or existing user flag
245 :param force_password_change: toggles new or existing user flag
246 for password change
246 for password change
247 :param allow_to_create_user: Defines if the method can actually create
247 :param allow_to_create_user: Defines if the method can actually create
248 new users
248 new users
249 :param create_repo_group: Defines if the method should also
249 :param create_repo_group: Defines if the method should also
250 create an repo group with user name, and owner
250 create an repo group with user name, and owner
251 :param updating_user_id: if we set it up this is the user we want to
251 :param updating_user_id: if we set it up this is the user we want to
252 update this allows to editing username.
252 update this allows to editing username.
253 :param language: language of user from interface.
253 :param language: language of user from interface.
254 :param description: user description
254 :param description: user description
255 :param strict_creation_check: checks for allowed creation license wise etc.
255 :param strict_creation_check: checks for allowed creation license wise etc.
256
256
257 :returns: new User object with injected `is_new_user` attribute.
257 :returns: new User object with injected `is_new_user` attribute.
258 """
258 """
259
259
260 if not cur_user:
260 if not cur_user:
261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
261 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
262
262
263 from rhodecode.lib.auth import (
263 from rhodecode.lib.auth import (
264 get_crypt_password, check_password)
264 get_crypt_password, check_password)
265 from rhodecode.lib.hooks_base import (
265 from rhodecode.lib import hooks_base
266 log_create_user, check_allowed_create_user)
267
266
268 def _password_change(new_user, password):
267 def _password_change(new_user, password):
269 old_password = new_user.password or ''
268 old_password = new_user.password or ''
270 # empty password
269 # empty password
271 if not old_password:
270 if not old_password:
272 return False
271 return False
273
272
274 # password check is only needed for RhodeCode internal auth calls
273 # password check is only needed for RhodeCode internal auth calls
275 # in case it's a plugin we don't care
274 # in case it's a plugin we don't care
276 if not plugin:
275 if not plugin:
277
276
278 # first check if we gave crypted password back, and if it
277 # first check if we gave crypted password back, and if it
279 # matches it's not password change
278 # matches it's not password change
280 if new_user.password == password:
279 if new_user.password == password:
281 return False
280 return False
282
281
283 password_match = check_password(password, old_password)
282 password_match = check_password(password, old_password)
284 if not password_match:
283 if not password_match:
285 return True
284 return True
286
285
287 return False
286 return False
288
287
289 # read settings on default personal repo group creation
288 # read settings on default personal repo group creation
290 if create_repo_group is None:
289 if create_repo_group is None:
291 default_create_repo_group = RepoGroupModel()\
290 default_create_repo_group = RepoGroupModel()\
292 .get_default_create_personal_repo_group()
291 .get_default_create_personal_repo_group()
293 create_repo_group = default_create_repo_group
292 create_repo_group = default_create_repo_group
294
293
295 user_data = {
294 user_data = {
296 'username': username,
295 'username': username,
297 'password': password,
296 'password': password,
298 'email': email,
297 'email': email,
299 'firstname': firstname,
298 'firstname': firstname,
300 'lastname': lastname,
299 'lastname': lastname,
301 'active': active,
300 'active': active,
302 'admin': admin
301 'admin': admin
303 }
302 }
304
303
305 if updating_user_id:
304 if updating_user_id:
306 log.debug('Checking for existing account in RhodeCode '
305 log.debug('Checking for existing account in RhodeCode '
307 'database with user_id `%s` ', updating_user_id)
306 'database with user_id `%s` ', updating_user_id)
308 user = User.get(updating_user_id)
307 user = User.get(updating_user_id)
309 else:
308 else:
310 log.debug('Checking for existing account in RhodeCode '
309 log.debug('Checking for existing account in RhodeCode '
311 'database with username `%s` ', username)
310 'database with username `%s` ', username)
312 user = User.get_by_username(username, case_insensitive=True)
311 user = User.get_by_username(username, case_insensitive=True)
313
312
314 if user is None:
313 if user is None:
315 # we check internal flag if this method is actually allowed to
314 # we check internal flag if this method is actually allowed to
316 # create new user
315 # create new user
317 if not allow_to_create_user:
316 if not allow_to_create_user:
318 msg = ('Method wants to create new user, but it is not '
317 msg = ('Method wants to create new user, but it is not '
319 'allowed to do so')
318 'allowed to do so')
320 log.warning(msg)
319 log.warning(msg)
321 raise NotAllowedToCreateUserError(msg)
320 raise NotAllowedToCreateUserError(msg)
322
321
323 log.debug('Creating new user %s', username)
322 log.debug('Creating new user %s', username)
324
323
325 # only if we create user that is active
324 # only if we create user that is active
326 new_active_user = active
325 new_active_user = active
327 if new_active_user and strict_creation_check:
326 if new_active_user and strict_creation_check:
328 # raises UserCreationError if it's not allowed for any reason to
327 # raises UserCreationError if it's not allowed for any reason to
329 # create new active user, this also executes pre-create hooks
328 # create new active user, this also executes pre-create hooks
330 check_allowed_create_user(user_data, cur_user, strict_check=True)
329 hooks_base.check_allowed_create_user(user_data, cur_user, strict_check=True)
331 events.trigger(events.UserPreCreate(user_data))
330 events.trigger(events.UserPreCreate(user_data))
332 new_user = User()
331 new_user = User()
333 edit = False
332 edit = False
334 else:
333 else:
335 log.debug('updating user `%s`', username)
334 log.debug('updating user `%s`', username)
336 events.trigger(events.UserPreUpdate(user, user_data))
335 events.trigger(events.UserPreUpdate(user, user_data))
337 new_user = user
336 new_user = user
338 edit = True
337 edit = True
339
338
340 # we're not allowed to edit default user
339 # we're not allowed to edit default user
341 if user.username == User.DEFAULT_USER:
340 if user.username == User.DEFAULT_USER:
342 raise DefaultUserException(
341 raise DefaultUserException(
343 "You can't edit this user (`%(username)s`) since it's "
342 "You can't edit this user (`%(username)s`) since it's "
344 "crucial for entire application"
343 "crucial for entire application"
345 % {'username': user.username})
344 % {'username': user.username})
346
345
347 # inject special attribute that will tell us if User is new or old
346 # inject special attribute that will tell us if User is new or old
348 new_user.is_new_user = not edit
347 new_user.is_new_user = not edit
349 # for users that didn's specify auth type, we use RhodeCode built in
348 # for users that didn's specify auth type, we use RhodeCode built in
350 from rhodecode.authentication.plugins import auth_rhodecode
349 from rhodecode.authentication.plugins import auth_rhodecode
351 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
350 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
352 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
351 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
353
352
354 try:
353 try:
355 new_user.username = username
354 new_user.username = username
356 new_user.admin = admin
355 new_user.admin = admin
357 new_user.email = email
356 new_user.email = email
358 new_user.active = active
357 new_user.active = active
359 new_user.extern_name = safe_unicode(extern_name)
358 new_user.extern_name = safe_unicode(extern_name)
360 new_user.extern_type = safe_unicode(extern_type)
359 new_user.extern_type = safe_unicode(extern_type)
361 new_user.name = firstname
360 new_user.name = firstname
362 new_user.lastname = lastname
361 new_user.lastname = lastname
363 new_user.description = description
362 new_user.description = description
364
363
365 # set password only if creating an user or password is changed
364 # set password only if creating an user or password is changed
366 if not edit or _password_change(new_user, password):
365 if not edit or _password_change(new_user, password):
367 reason = 'new password' if edit else 'new user'
366 reason = 'new password' if edit else 'new user'
368 log.debug('Updating password reason=>%s', reason)
367 log.debug('Updating password reason=>%s', reason)
369 new_user.password = get_crypt_password(password) if password else None
368 new_user.password = get_crypt_password(password) if password else None
370
369
371 if force_password_change:
370 if force_password_change:
372 new_user.update_userdata(force_password_change=True)
371 new_user.update_userdata(force_password_change=True)
373 if language:
372 if language:
374 new_user.update_userdata(language=language)
373 new_user.update_userdata(language=language)
375 new_user.update_userdata(notification_status=True)
374 new_user.update_userdata(notification_status=True)
376
375
377 self.sa.add(new_user)
376 self.sa.add(new_user)
378
377
379 if not edit and create_repo_group:
378 if not edit and create_repo_group:
380 RepoGroupModel().create_personal_repo_group(
379 RepoGroupModel().create_personal_repo_group(
381 new_user, commit_early=False)
380 new_user, commit_early=False)
382
381
383 if not edit:
382 if not edit:
384 # add the RSS token
383 # add the RSS token
385 self.add_auth_token(
384 self.add_auth_token(
386 user=username, lifetime_minutes=-1,
385 user=username, lifetime_minutes=-1,
387 role=self.auth_token_role.ROLE_FEED,
386 role=self.auth_token_role.ROLE_FEED,
388 description=u'Generated feed token')
387 description=u'Generated feed token')
389
388
390 kwargs = new_user.get_dict()
389 kwargs = new_user.get_dict()
391 # backward compat, require api_keys present
390 # backward compat, require api_keys present
392 kwargs['api_keys'] = kwargs['auth_tokens']
391 kwargs['api_keys'] = kwargs['auth_tokens']
393 log_create_user(created_by=cur_user, **kwargs)
392 hooks_base.create_user(created_by=cur_user, **kwargs)
394 events.trigger(events.UserPostCreate(user_data))
393 events.trigger(events.UserPostCreate(user_data))
395 return new_user
394 return new_user
396 except (DatabaseError,):
395 except (DatabaseError,):
397 log.error(traceback.format_exc())
396 log.error(traceback.format_exc())
398 raise
397 raise
399
398
400 def create_registration(self, form_data,
399 def create_registration(self, form_data,
401 extern_name='rhodecode', extern_type='rhodecode'):
400 extern_name='rhodecode', extern_type='rhodecode'):
402 from rhodecode.model.notification import NotificationModel
401 from rhodecode.model.notification import NotificationModel
403 from rhodecode.model.notification import EmailNotificationModel
402 from rhodecode.model.notification import EmailNotificationModel
404
403
405 try:
404 try:
406 form_data['admin'] = False
405 form_data['admin'] = False
407 form_data['extern_name'] = extern_name
406 form_data['extern_name'] = extern_name
408 form_data['extern_type'] = extern_type
407 form_data['extern_type'] = extern_type
409 new_user = self.create(form_data)
408 new_user = self.create(form_data)
410
409
411 self.sa.add(new_user)
410 self.sa.add(new_user)
412 self.sa.flush()
411 self.sa.flush()
413
412
414 user_data = new_user.get_dict()
413 user_data = new_user.get_dict()
415 user_data.update({
414 user_data.update({
416 'first_name': user_data.get('firstname'),
415 'first_name': user_data.get('firstname'),
417 'last_name': user_data.get('lastname'),
416 'last_name': user_data.get('lastname'),
418 })
417 })
419 kwargs = {
418 kwargs = {
420 # use SQLALCHEMY safe dump of user data
419 # use SQLALCHEMY safe dump of user data
421 'user': AttributeDict(user_data),
420 'user': AttributeDict(user_data),
422 'date': datetime.datetime.now()
421 'date': datetime.datetime.now()
423 }
422 }
424 notification_type = EmailNotificationModel.TYPE_REGISTRATION
423 notification_type = EmailNotificationModel.TYPE_REGISTRATION
425 # pre-generate the subject for notification itself
424 # pre-generate the subject for notification itself
426 (subject,
425 (subject,
427 _h, _e, # we don't care about those
426 _h, _e, # we don't care about those
428 body_plaintext) = EmailNotificationModel().render_email(
427 body_plaintext) = EmailNotificationModel().render_email(
429 notification_type, **kwargs)
428 notification_type, **kwargs)
430
429
431 # create notification objects, and emails
430 # create notification objects, and emails
432 NotificationModel().create(
431 NotificationModel().create(
433 created_by=new_user,
432 created_by=new_user,
434 notification_subject=subject,
433 notification_subject=subject,
435 notification_body=body_plaintext,
434 notification_body=body_plaintext,
436 notification_type=notification_type,
435 notification_type=notification_type,
437 recipients=None, # all admins
436 recipients=None, # all admins
438 email_kwargs=kwargs,
437 email_kwargs=kwargs,
439 )
438 )
440
439
441 return new_user
440 return new_user
442 except Exception:
441 except Exception:
443 log.error(traceback.format_exc())
442 log.error(traceback.format_exc())
444 raise
443 raise
445
444
446 def _handle_user_repos(self, username, repositories, handle_user,
445 def _handle_user_repos(self, username, repositories, handle_user,
447 handle_mode=None):
446 handle_mode=None):
448
447
449 left_overs = True
448 left_overs = True
450
449
451 from rhodecode.model.repo import RepoModel
450 from rhodecode.model.repo import RepoModel
452
451
453 if handle_mode == 'detach':
452 if handle_mode == 'detach':
454 for obj in repositories:
453 for obj in repositories:
455 obj.user = handle_user
454 obj.user = handle_user
456 # set description we know why we super admin now owns
455 # set description we know why we super admin now owns
457 # additional repositories that were orphaned !
456 # additional repositories that were orphaned !
458 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
457 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
459 self.sa.add(obj)
458 self.sa.add(obj)
460 left_overs = False
459 left_overs = False
461 elif handle_mode == 'delete':
460 elif handle_mode == 'delete':
462 for obj in repositories:
461 for obj in repositories:
463 RepoModel().delete(obj, forks='detach')
462 RepoModel().delete(obj, forks='detach')
464 left_overs = False
463 left_overs = False
465
464
466 # if nothing is done we have left overs left
465 # if nothing is done we have left overs left
467 return left_overs
466 return left_overs
468
467
469 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
468 def _handle_user_repo_groups(self, username, repository_groups, handle_user,
470 handle_mode=None):
469 handle_mode=None):
471
470
472 left_overs = True
471 left_overs = True
473
472
474 from rhodecode.model.repo_group import RepoGroupModel
473 from rhodecode.model.repo_group import RepoGroupModel
475
474
476 if handle_mode == 'detach':
475 if handle_mode == 'detach':
477 for r in repository_groups:
476 for r in repository_groups:
478 r.user = handle_user
477 r.user = handle_user
479 # set description we know why we super admin now owns
478 # set description we know why we super admin now owns
480 # additional repositories that were orphaned !
479 # additional repositories that were orphaned !
481 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
480 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
482 r.personal = False
481 r.personal = False
483 self.sa.add(r)
482 self.sa.add(r)
484 left_overs = False
483 left_overs = False
485 elif handle_mode == 'delete':
484 elif handle_mode == 'delete':
486 for r in repository_groups:
485 for r in repository_groups:
487 RepoGroupModel().delete(r)
486 RepoGroupModel().delete(r)
488 left_overs = False
487 left_overs = False
489
488
490 # if nothing is done we have left overs left
489 # if nothing is done we have left overs left
491 return left_overs
490 return left_overs
492
491
493 def _handle_user_user_groups(self, username, user_groups, handle_user,
492 def _handle_user_user_groups(self, username, user_groups, handle_user,
494 handle_mode=None):
493 handle_mode=None):
495
494
496 left_overs = True
495 left_overs = True
497
496
498 from rhodecode.model.user_group import UserGroupModel
497 from rhodecode.model.user_group import UserGroupModel
499
498
500 if handle_mode == 'detach':
499 if handle_mode == 'detach':
501 for r in user_groups:
500 for r in user_groups:
502 for user_user_group_to_perm in r.user_user_group_to_perm:
501 for user_user_group_to_perm in r.user_user_group_to_perm:
503 if user_user_group_to_perm.user.username == username:
502 if user_user_group_to_perm.user.username == username:
504 user_user_group_to_perm.user = handle_user
503 user_user_group_to_perm.user = handle_user
505 r.user = handle_user
504 r.user = handle_user
506 # set description we know why we super admin now owns
505 # set description we know why we super admin now owns
507 # additional repositories that were orphaned !
506 # additional repositories that were orphaned !
508 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
507 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
509 self.sa.add(r)
508 self.sa.add(r)
510 left_overs = False
509 left_overs = False
511 elif handle_mode == 'delete':
510 elif handle_mode == 'delete':
512 for r in user_groups:
511 for r in user_groups:
513 UserGroupModel().delete(r)
512 UserGroupModel().delete(r)
514 left_overs = False
513 left_overs = False
515
514
516 # if nothing is done we have left overs left
515 # if nothing is done we have left overs left
517 return left_overs
516 return left_overs
518
517
519 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
518 def _handle_user_pull_requests(self, username, pull_requests, handle_user,
520 handle_mode=None):
519 handle_mode=None):
521 left_overs = True
520 left_overs = True
522
521
523 from rhodecode.model.pull_request import PullRequestModel
522 from rhodecode.model.pull_request import PullRequestModel
524
523
525 if handle_mode == 'detach':
524 if handle_mode == 'detach':
526 for pr in pull_requests:
525 for pr in pull_requests:
527 pr.user_id = handle_user.user_id
526 pr.user_id = handle_user.user_id
528 # set description we know why we super admin now owns
527 # set description we know why we super admin now owns
529 # additional repositories that were orphaned !
528 # additional repositories that were orphaned !
530 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
529 pr.description += ' \n::detached pull requests from deleted user: %s' % (username,)
531 self.sa.add(pr)
530 self.sa.add(pr)
532 left_overs = False
531 left_overs = False
533 elif handle_mode == 'delete':
532 elif handle_mode == 'delete':
534 for pr in pull_requests:
533 for pr in pull_requests:
535 PullRequestModel().delete(pr)
534 PullRequestModel().delete(pr)
536
535
537 left_overs = False
536 left_overs = False
538
537
539 # if nothing is done we have left overs left
538 # if nothing is done we have left overs left
540 return left_overs
539 return left_overs
541
540
542 def _handle_user_artifacts(self, username, artifacts, handle_user,
541 def _handle_user_artifacts(self, username, artifacts, handle_user,
543 handle_mode=None):
542 handle_mode=None):
544
543
545 left_overs = True
544 left_overs = True
546
545
547 if handle_mode == 'detach':
546 if handle_mode == 'detach':
548 for a in artifacts:
547 for a in artifacts:
549 a.upload_user = handle_user
548 a.upload_user = handle_user
550 # set description we know why we super admin now owns
549 # set description we know why we super admin now owns
551 # additional artifacts that were orphaned !
550 # additional artifacts that were orphaned !
552 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
551 a.file_description += ' \n::detached artifact from deleted user: %s' % (username,)
553 self.sa.add(a)
552 self.sa.add(a)
554 left_overs = False
553 left_overs = False
555 elif handle_mode == 'delete':
554 elif handle_mode == 'delete':
556 from rhodecode.apps.file_store import utils as store_utils
555 from rhodecode.apps.file_store import utils as store_utils
557 request = get_current_request()
556 request = get_current_request()
558 storage = store_utils.get_file_storage(request.registry.settings)
557 storage = store_utils.get_file_storage(request.registry.settings)
559 for a in artifacts:
558 for a in artifacts:
560 file_uid = a.file_uid
559 file_uid = a.file_uid
561 storage.delete(file_uid)
560 storage.delete(file_uid)
562 self.sa.delete(a)
561 self.sa.delete(a)
563
562
564 left_overs = False
563 left_overs = False
565
564
566 # if nothing is done we have left overs left
565 # if nothing is done we have left overs left
567 return left_overs
566 return left_overs
568
567
569 def delete(self, user, cur_user=None, handle_repos=None,
568 def delete(self, user, cur_user=None, handle_repos=None,
570 handle_repo_groups=None, handle_user_groups=None,
569 handle_repo_groups=None, handle_user_groups=None,
571 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
570 handle_pull_requests=None, handle_artifacts=None, handle_new_owner=None):
572 from rhodecode.lib.hooks_base import log_delete_user
571 from rhodecode.lib import hooks_base
573
572
574 if not cur_user:
573 if not cur_user:
575 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
574 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
576
575
577 user = self._get_user(user)
576 user = self._get_user(user)
578
577
579 try:
578 try:
580 if user.username == User.DEFAULT_USER:
579 if user.username == User.DEFAULT_USER:
581 raise DefaultUserException(
580 raise DefaultUserException(
582 u"You can't remove this user since it's"
581 u"You can't remove this user since it's"
583 u" crucial for entire application")
582 u" crucial for entire application")
584 handle_user = handle_new_owner or self.cls.get_first_super_admin()
583 handle_user = handle_new_owner or self.cls.get_first_super_admin()
585 log.debug('New detached objects owner %s', handle_user)
584 log.debug('New detached objects owner %s', handle_user)
586
585
587 left_overs = self._handle_user_repos(
586 left_overs = self._handle_user_repos(
588 user.username, user.repositories, handle_user, handle_repos)
587 user.username, user.repositories, handle_user, handle_repos)
589 if left_overs and user.repositories:
588 if left_overs and user.repositories:
590 repos = [x.repo_name for x in user.repositories]
589 repos = [x.repo_name for x in user.repositories]
591 raise UserOwnsReposException(
590 raise UserOwnsReposException(
592 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
591 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
593 u'removed. Switch owners or remove those repositories:%(list_repos)s'
592 u'removed. Switch owners or remove those repositories:%(list_repos)s'
594 % {'username': user.username, 'len_repos': len(repos),
593 % {'username': user.username, 'len_repos': len(repos),
595 'list_repos': ', '.join(repos)})
594 'list_repos': ', '.join(repos)})
596
595
597 left_overs = self._handle_user_repo_groups(
596 left_overs = self._handle_user_repo_groups(
598 user.username, user.repository_groups, handle_user, handle_repo_groups)
597 user.username, user.repository_groups, handle_user, handle_repo_groups)
599 if left_overs and user.repository_groups:
598 if left_overs and user.repository_groups:
600 repo_groups = [x.group_name for x in user.repository_groups]
599 repo_groups = [x.group_name for x in user.repository_groups]
601 raise UserOwnsRepoGroupsException(
600 raise UserOwnsRepoGroupsException(
602 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
601 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
603 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
602 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
604 % {'username': user.username, 'len_repo_groups': len(repo_groups),
603 % {'username': user.username, 'len_repo_groups': len(repo_groups),
605 'list_repo_groups': ', '.join(repo_groups)})
604 'list_repo_groups': ', '.join(repo_groups)})
606
605
607 left_overs = self._handle_user_user_groups(
606 left_overs = self._handle_user_user_groups(
608 user.username, user.user_groups, handle_user, handle_user_groups)
607 user.username, user.user_groups, handle_user, handle_user_groups)
609 if left_overs and user.user_groups:
608 if left_overs and user.user_groups:
610 user_groups = [x.users_group_name for x in user.user_groups]
609 user_groups = [x.users_group_name for x in user.user_groups]
611 raise UserOwnsUserGroupsException(
610 raise UserOwnsUserGroupsException(
612 u'user "%s" still owns %s user groups and cannot be '
611 u'user "%s" still owns %s user groups and cannot be '
613 u'removed. Switch owners or remove those user groups:%s'
612 u'removed. Switch owners or remove those user groups:%s'
614 % (user.username, len(user_groups), ', '.join(user_groups)))
613 % (user.username, len(user_groups), ', '.join(user_groups)))
615
614
616 left_overs = self._handle_user_pull_requests(
615 left_overs = self._handle_user_pull_requests(
617 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
616 user.username, user.user_pull_requests, handle_user, handle_pull_requests)
618 if left_overs and user.user_pull_requests:
617 if left_overs and user.user_pull_requests:
619 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
618 pull_requests = ['!{}'.format(x.pull_request_id) for x in user.user_pull_requests]
620 raise UserOwnsPullRequestsException(
619 raise UserOwnsPullRequestsException(
621 u'user "%s" still owns %s pull requests and cannot be '
620 u'user "%s" still owns %s pull requests and cannot be '
622 u'removed. Switch owners or remove those pull requests:%s'
621 u'removed. Switch owners or remove those pull requests:%s'
623 % (user.username, len(pull_requests), ', '.join(pull_requests)))
622 % (user.username, len(pull_requests), ', '.join(pull_requests)))
624
623
625 left_overs = self._handle_user_artifacts(
624 left_overs = self._handle_user_artifacts(
626 user.username, user.artifacts, handle_user, handle_artifacts)
625 user.username, user.artifacts, handle_user, handle_artifacts)
627 if left_overs and user.artifacts:
626 if left_overs and user.artifacts:
628 artifacts = [x.file_uid for x in user.artifacts]
627 artifacts = [x.file_uid for x in user.artifacts]
629 raise UserOwnsArtifactsException(
628 raise UserOwnsArtifactsException(
630 u'user "%s" still owns %s artifacts and cannot be '
629 u'user "%s" still owns %s artifacts and cannot be '
631 u'removed. Switch owners or remove those artifacts:%s'
630 u'removed. Switch owners or remove those artifacts:%s'
632 % (user.username, len(artifacts), ', '.join(artifacts)))
631 % (user.username, len(artifacts), ', '.join(artifacts)))
633
632
634 user_data = user.get_dict() # fetch user data before expire
633 user_data = user.get_dict() # fetch user data before expire
635
634
636 # we might change the user data with detach/delete, make sure
635 # we might change the user data with detach/delete, make sure
637 # the object is marked as expired before actually deleting !
636 # the object is marked as expired before actually deleting !
638 self.sa.expire(user)
637 self.sa.expire(user)
639 self.sa.delete(user)
638 self.sa.delete(user)
640
639
641 log_delete_user(deleted_by=cur_user, **user_data)
640 hooks_base.delete_user(deleted_by=cur_user, **user_data)
642 except Exception:
641 except Exception:
643 log.error(traceback.format_exc())
642 log.error(traceback.format_exc())
644 raise
643 raise
645
644
646 def reset_password_link(self, data, pwd_reset_url):
645 def reset_password_link(self, data, pwd_reset_url):
647 from rhodecode.lib.celerylib import tasks, run_task
646 from rhodecode.lib.celerylib import tasks, run_task
648 from rhodecode.model.notification import EmailNotificationModel
647 from rhodecode.model.notification import EmailNotificationModel
649 user_email = data['email']
648 user_email = data['email']
650 try:
649 try:
651 user = User.get_by_email(user_email)
650 user = User.get_by_email(user_email)
652 if user:
651 if user:
653 log.debug('password reset user found %s', user)
652 log.debug('password reset user found %s', user)
654
653
655 email_kwargs = {
654 email_kwargs = {
656 'password_reset_url': pwd_reset_url,
655 'password_reset_url': pwd_reset_url,
657 'user': user,
656 'user': user,
658 'email': user_email,
657 'email': user_email,
659 'date': datetime.datetime.now(),
658 'date': datetime.datetime.now(),
660 'first_admin_email': User.get_first_super_admin().email
659 'first_admin_email': User.get_first_super_admin().email
661 }
660 }
662
661
663 (subject, headers, email_body,
662 (subject, headers, email_body,
664 email_body_plaintext) = EmailNotificationModel().render_email(
663 email_body_plaintext) = EmailNotificationModel().render_email(
665 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
664 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
666
665
667 recipients = [user_email]
666 recipients = [user_email]
668
667
669 action_logger_generic(
668 action_logger_generic(
670 'sending password reset email to user: {}'.format(
669 'sending password reset email to user: {}'.format(
671 user), namespace='security.password_reset')
670 user), namespace='security.password_reset')
672
671
673 run_task(tasks.send_email, recipients, subject,
672 run_task(tasks.send_email, recipients, subject,
674 email_body_plaintext, email_body)
673 email_body_plaintext, email_body)
675
674
676 else:
675 else:
677 log.debug("password reset email %s not found", user_email)
676 log.debug("password reset email %s not found", user_email)
678 except Exception:
677 except Exception:
679 log.error(traceback.format_exc())
678 log.error(traceback.format_exc())
680 return False
679 return False
681
680
682 return True
681 return True
683
682
684 def reset_password(self, data):
683 def reset_password(self, data):
685 from rhodecode.lib.celerylib import tasks, run_task
684 from rhodecode.lib.celerylib import tasks, run_task
686 from rhodecode.model.notification import EmailNotificationModel
685 from rhodecode.model.notification import EmailNotificationModel
687 from rhodecode.lib import auth
686 from rhodecode.lib import auth
688 user_email = data['email']
687 user_email = data['email']
689 pre_db = True
688 pre_db = True
690 try:
689 try:
691 user = User.get_by_email(user_email)
690 user = User.get_by_email(user_email)
692 new_passwd = auth.PasswordGenerator().gen_password(
691 new_passwd = auth.PasswordGenerator().gen_password(
693 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
692 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
694 if user:
693 if user:
695 user.password = auth.get_crypt_password(new_passwd)
694 user.password = auth.get_crypt_password(new_passwd)
696 # also force this user to reset his password !
695 # also force this user to reset his password !
697 user.update_userdata(force_password_change=True)
696 user.update_userdata(force_password_change=True)
698
697
699 Session().add(user)
698 Session().add(user)
700
699
701 # now delete the token in question
700 # now delete the token in question
702 UserApiKeys = AuthTokenModel.cls
701 UserApiKeys = AuthTokenModel.cls
703 UserApiKeys().query().filter(
702 UserApiKeys().query().filter(
704 UserApiKeys.api_key == data['token']).delete()
703 UserApiKeys.api_key == data['token']).delete()
705
704
706 Session().commit()
705 Session().commit()
707 log.info('successfully reset password for `%s`', user_email)
706 log.info('successfully reset password for `%s`', user_email)
708
707
709 if new_passwd is None:
708 if new_passwd is None:
710 raise Exception('unable to generate new password')
709 raise Exception('unable to generate new password')
711
710
712 pre_db = False
711 pre_db = False
713
712
714 email_kwargs = {
713 email_kwargs = {
715 'new_password': new_passwd,
714 'new_password': new_passwd,
716 'user': user,
715 'user': user,
717 'email': user_email,
716 'email': user_email,
718 'date': datetime.datetime.now(),
717 'date': datetime.datetime.now(),
719 'first_admin_email': User.get_first_super_admin().email
718 'first_admin_email': User.get_first_super_admin().email
720 }
719 }
721
720
722 (subject, headers, email_body,
721 (subject, headers, email_body,
723 email_body_plaintext) = EmailNotificationModel().render_email(
722 email_body_plaintext) = EmailNotificationModel().render_email(
724 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
723 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
725 **email_kwargs)
724 **email_kwargs)
726
725
727 recipients = [user_email]
726 recipients = [user_email]
728
727
729 action_logger_generic(
728 action_logger_generic(
730 'sent new password to user: {} with email: {}'.format(
729 'sent new password to user: {} with email: {}'.format(
731 user, user_email), namespace='security.password_reset')
730 user, user_email), namespace='security.password_reset')
732
731
733 run_task(tasks.send_email, recipients, subject,
732 run_task(tasks.send_email, recipients, subject,
734 email_body_plaintext, email_body)
733 email_body_plaintext, email_body)
735
734
736 except Exception:
735 except Exception:
737 log.error('Failed to update user password')
736 log.error('Failed to update user password')
738 log.error(traceback.format_exc())
737 log.error(traceback.format_exc())
739 if pre_db:
738 if pre_db:
740 # we rollback only if local db stuff fails. If it goes into
739 # we rollback only if local db stuff fails. If it goes into
741 # run_task, we're pass rollback state this wouldn't work then
740 # run_task, we're pass rollback state this wouldn't work then
742 Session().rollback()
741 Session().rollback()
743
742
744 return True
743 return True
745
744
746 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
745 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
747 """
746 """
748 Fetches auth_user by user_id,or api_key if present.
747 Fetches auth_user by user_id,or api_key if present.
749 Fills auth_user attributes with those taken from database.
748 Fills auth_user attributes with those taken from database.
750 Additionally set's is_authenitated if lookup fails
749 Additionally set's is_authenitated if lookup fails
751 present in database
750 present in database
752
751
753 :param auth_user: instance of user to set attributes
752 :param auth_user: instance of user to set attributes
754 :param user_id: user id to fetch by
753 :param user_id: user id to fetch by
755 :param api_key: api key to fetch by
754 :param api_key: api key to fetch by
756 :param username: username to fetch by
755 :param username: username to fetch by
757 """
756 """
758 def token_obfuscate(token):
757 def token_obfuscate(token):
759 if token:
758 if token:
760 return token[:4] + "****"
759 return token[:4] + "****"
761
760
762 if user_id is None and api_key is None and username is None:
761 if user_id is None and api_key is None and username is None:
763 raise Exception('You need to pass user_id, api_key or username')
762 raise Exception('You need to pass user_id, api_key or username')
764
763
765 log.debug(
764 log.debug(
766 'AuthUser: fill data execution based on: '
765 'AuthUser: fill data execution based on: '
767 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
766 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
768 try:
767 try:
769 dbuser = None
768 dbuser = None
770 if user_id:
769 if user_id:
771 dbuser = self.get(user_id)
770 dbuser = self.get(user_id)
772 elif api_key:
771 elif api_key:
773 dbuser = self.get_by_auth_token(api_key)
772 dbuser = self.get_by_auth_token(api_key)
774 elif username:
773 elif username:
775 dbuser = self.get_by_username(username)
774 dbuser = self.get_by_username(username)
776
775
777 if not dbuser:
776 if not dbuser:
778 log.warning(
777 log.warning(
779 'Unable to lookup user by id:%s api_key:%s username:%s',
778 'Unable to lookup user by id:%s api_key:%s username:%s',
780 user_id, token_obfuscate(api_key), username)
779 user_id, token_obfuscate(api_key), username)
781 return False
780 return False
782 if not dbuser.active:
781 if not dbuser.active:
783 log.debug('User `%s:%s` is inactive, skipping fill data',
782 log.debug('User `%s:%s` is inactive, skipping fill data',
784 username, user_id)
783 username, user_id)
785 return False
784 return False
786
785
787 log.debug('AuthUser: filling found user:%s data', dbuser)
786 log.debug('AuthUser: filling found user:%s data', dbuser)
788
787
789 attrs = {
788 attrs = {
790 'user_id': dbuser.user_id,
789 'user_id': dbuser.user_id,
791 'username': dbuser.username,
790 'username': dbuser.username,
792 'name': dbuser.name,
791 'name': dbuser.name,
793 'first_name': dbuser.first_name,
792 'first_name': dbuser.first_name,
794 'firstname': dbuser.firstname,
793 'firstname': dbuser.firstname,
795 'last_name': dbuser.last_name,
794 'last_name': dbuser.last_name,
796 'lastname': dbuser.lastname,
795 'lastname': dbuser.lastname,
797 'admin': dbuser.admin,
796 'admin': dbuser.admin,
798 'active': dbuser.active,
797 'active': dbuser.active,
799
798
800 'email': dbuser.email,
799 'email': dbuser.email,
801 'emails': dbuser.emails_cached(),
800 'emails': dbuser.emails_cached(),
802 'short_contact': dbuser.short_contact,
801 'short_contact': dbuser.short_contact,
803 'full_contact': dbuser.full_contact,
802 'full_contact': dbuser.full_contact,
804 'full_name': dbuser.full_name,
803 'full_name': dbuser.full_name,
805 'full_name_or_username': dbuser.full_name_or_username,
804 'full_name_or_username': dbuser.full_name_or_username,
806
805
807 '_api_key': dbuser._api_key,
806 '_api_key': dbuser._api_key,
808 '_user_data': dbuser._user_data,
807 '_user_data': dbuser._user_data,
809
808
810 'created_on': dbuser.created_on,
809 'created_on': dbuser.created_on,
811 'extern_name': dbuser.extern_name,
810 'extern_name': dbuser.extern_name,
812 'extern_type': dbuser.extern_type,
811 'extern_type': dbuser.extern_type,
813
812
814 'inherit_default_permissions': dbuser.inherit_default_permissions,
813 'inherit_default_permissions': dbuser.inherit_default_permissions,
815
814
816 'language': dbuser.language,
815 'language': dbuser.language,
817 'last_activity': dbuser.last_activity,
816 'last_activity': dbuser.last_activity,
818 'last_login': dbuser.last_login,
817 'last_login': dbuser.last_login,
819 'password': dbuser.password,
818 'password': dbuser.password,
820 }
819 }
821 auth_user.__dict__.update(attrs)
820 auth_user.__dict__.update(attrs)
822 except Exception:
821 except Exception:
823 log.error(traceback.format_exc())
822 log.error(traceback.format_exc())
824 auth_user.is_authenticated = False
823 auth_user.is_authenticated = False
825 return False
824 return False
826
825
827 return True
826 return True
828
827
829 def has_perm(self, user, perm):
828 def has_perm(self, user, perm):
830 perm = self._get_perm(perm)
829 perm = self._get_perm(perm)
831 user = self._get_user(user)
830 user = self._get_user(user)
832
831
833 return UserToPerm.query().filter(UserToPerm.user == user)\
832 return UserToPerm.query().filter(UserToPerm.user == user)\
834 .filter(UserToPerm.permission == perm).scalar() is not None
833 .filter(UserToPerm.permission == perm).scalar() is not None
835
834
836 def grant_perm(self, user, perm):
835 def grant_perm(self, user, perm):
837 """
836 """
838 Grant user global permissions
837 Grant user global permissions
839
838
840 :param user:
839 :param user:
841 :param perm:
840 :param perm:
842 """
841 """
843 user = self._get_user(user)
842 user = self._get_user(user)
844 perm = self._get_perm(perm)
843 perm = self._get_perm(perm)
845 # if this permission is already granted skip it
844 # if this permission is already granted skip it
846 _perm = UserToPerm.query()\
845 _perm = UserToPerm.query()\
847 .filter(UserToPerm.user == user)\
846 .filter(UserToPerm.user == user)\
848 .filter(UserToPerm.permission == perm)\
847 .filter(UserToPerm.permission == perm)\
849 .scalar()
848 .scalar()
850 if _perm:
849 if _perm:
851 return
850 return
852 new = UserToPerm()
851 new = UserToPerm()
853 new.user = user
852 new.user = user
854 new.permission = perm
853 new.permission = perm
855 self.sa.add(new)
854 self.sa.add(new)
856 return new
855 return new
857
856
858 def revoke_perm(self, user, perm):
857 def revoke_perm(self, user, perm):
859 """
858 """
860 Revoke users global permissions
859 Revoke users global permissions
861
860
862 :param user:
861 :param user:
863 :param perm:
862 :param perm:
864 """
863 """
865 user = self._get_user(user)
864 user = self._get_user(user)
866 perm = self._get_perm(perm)
865 perm = self._get_perm(perm)
867
866
868 obj = UserToPerm.query()\
867 obj = UserToPerm.query()\
869 .filter(UserToPerm.user == user)\
868 .filter(UserToPerm.user == user)\
870 .filter(UserToPerm.permission == perm)\
869 .filter(UserToPerm.permission == perm)\
871 .scalar()
870 .scalar()
872 if obj:
871 if obj:
873 self.sa.delete(obj)
872 self.sa.delete(obj)
874
873
875 def add_extra_email(self, user, email):
874 def add_extra_email(self, user, email):
876 """
875 """
877 Adds email address to UserEmailMap
876 Adds email address to UserEmailMap
878
877
879 :param user:
878 :param user:
880 :param email:
879 :param email:
881 """
880 """
882
881
883 user = self._get_user(user)
882 user = self._get_user(user)
884
883
885 obj = UserEmailMap()
884 obj = UserEmailMap()
886 obj.user = user
885 obj.user = user
887 obj.email = email
886 obj.email = email
888 self.sa.add(obj)
887 self.sa.add(obj)
889 return obj
888 return obj
890
889
891 def delete_extra_email(self, user, email_id):
890 def delete_extra_email(self, user, email_id):
892 """
891 """
893 Removes email address from UserEmailMap
892 Removes email address from UserEmailMap
894
893
895 :param user:
894 :param user:
896 :param email_id:
895 :param email_id:
897 """
896 """
898 user = self._get_user(user)
897 user = self._get_user(user)
899 obj = UserEmailMap.query().get(email_id)
898 obj = UserEmailMap.query().get(email_id)
900 if obj and obj.user_id == user.user_id:
899 if obj and obj.user_id == user.user_id:
901 self.sa.delete(obj)
900 self.sa.delete(obj)
902
901
903 def parse_ip_range(self, ip_range):
902 def parse_ip_range(self, ip_range):
904 ip_list = []
903 ip_list = []
905
904
906 def make_unique(value):
905 def make_unique(value):
907 seen = []
906 seen = []
908 return [c for c in value if not (c in seen or seen.append(c))]
907 return [c for c in value if not (c in seen or seen.append(c))]
909
908
910 # firsts split by commas
909 # firsts split by commas
911 for ip_range in ip_range.split(','):
910 for ip_range in ip_range.split(','):
912 if not ip_range:
911 if not ip_range:
913 continue
912 continue
914 ip_range = ip_range.strip()
913 ip_range = ip_range.strip()
915 if '-' in ip_range:
914 if '-' in ip_range:
916 start_ip, end_ip = ip_range.split('-', 1)
915 start_ip, end_ip = ip_range.split('-', 1)
917 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
916 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
918 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
917 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
919 parsed_ip_range = []
918 parsed_ip_range = []
920
919
921 for index in range(int(start_ip), int(end_ip) + 1):
920 for index in range(int(start_ip), int(end_ip) + 1):
922 new_ip = ipaddress.ip_address(index)
921 new_ip = ipaddress.ip_address(index)
923 parsed_ip_range.append(str(new_ip))
922 parsed_ip_range.append(str(new_ip))
924 ip_list.extend(parsed_ip_range)
923 ip_list.extend(parsed_ip_range)
925 else:
924 else:
926 ip_list.append(ip_range)
925 ip_list.append(ip_range)
927
926
928 return make_unique(ip_list)
927 return make_unique(ip_list)
929
928
930 def add_extra_ip(self, user, ip, description=None):
929 def add_extra_ip(self, user, ip, description=None):
931 """
930 """
932 Adds ip address to UserIpMap
931 Adds ip address to UserIpMap
933
932
934 :param user:
933 :param user:
935 :param ip:
934 :param ip:
936 """
935 """
937
936
938 user = self._get_user(user)
937 user = self._get_user(user)
939 obj = UserIpMap()
938 obj = UserIpMap()
940 obj.user = user
939 obj.user = user
941 obj.ip_addr = ip
940 obj.ip_addr = ip
942 obj.description = description
941 obj.description = description
943 self.sa.add(obj)
942 self.sa.add(obj)
944 return obj
943 return obj
945
944
946 auth_token_role = AuthTokenModel.cls
945 auth_token_role = AuthTokenModel.cls
947
946
948 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
947 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
949 scope_callback=None):
948 scope_callback=None):
950 """
949 """
951 Add AuthToken for user.
950 Add AuthToken for user.
952
951
953 :param user: username/user_id
952 :param user: username/user_id
954 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
953 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
955 :param role: one of AuthTokenModel.cls.ROLE_*
954 :param role: one of AuthTokenModel.cls.ROLE_*
956 :param description: optional string description
955 :param description: optional string description
957 """
956 """
958
957
959 token = AuthTokenModel().create(
958 token = AuthTokenModel().create(
960 user, description, lifetime_minutes, role)
959 user, description, lifetime_minutes, role)
961 if scope_callback and callable(scope_callback):
960 if scope_callback and callable(scope_callback):
962 # call the callback if we provide, used to attach scope for EE edition
961 # call the callback if we provide, used to attach scope for EE edition
963 scope_callback(token)
962 scope_callback(token)
964 return token
963 return token
965
964
966 def delete_extra_ip(self, user, ip_id):
965 def delete_extra_ip(self, user, ip_id):
967 """
966 """
968 Removes ip address from UserIpMap
967 Removes ip address from UserIpMap
969
968
970 :param user:
969 :param user:
971 :param ip_id:
970 :param ip_id:
972 """
971 """
973 user = self._get_user(user)
972 user = self._get_user(user)
974 obj = UserIpMap.query().get(ip_id)
973 obj = UserIpMap.query().get(ip_id)
975 if obj and obj.user_id == user.user_id:
974 if obj and obj.user_id == user.user_id:
976 self.sa.delete(obj)
975 self.sa.delete(obj)
977
976
978 def get_accounts_in_creation_order(self, current_user=None):
977 def get_accounts_in_creation_order(self, current_user=None):
979 """
978 """
980 Get accounts in order of creation for deactivation for license limits
979 Get accounts in order of creation for deactivation for license limits
981
980
982 pick currently logged in user, and append to the list in position 0
981 pick currently logged in user, and append to the list in position 0
983 pick all super-admins in order of creation date and add it to the list
982 pick all super-admins in order of creation date and add it to the list
984 pick all other accounts in order of creation and add it to the list.
983 pick all other accounts in order of creation and add it to the list.
985
984
986 Based on that list, the last accounts can be disabled as they are
985 Based on that list, the last accounts can be disabled as they are
987 created at the end and don't include any of the super admins as well
986 created at the end and don't include any of the super admins as well
988 as the current user.
987 as the current user.
989
988
990 :param current_user: optionally current user running this operation
989 :param current_user: optionally current user running this operation
991 """
990 """
992
991
993 if not current_user:
992 if not current_user:
994 current_user = get_current_rhodecode_user()
993 current_user = get_current_rhodecode_user()
995 active_super_admins = [
994 active_super_admins = [
996 x.user_id for x in User.query()
995 x.user_id for x in User.query()
997 .filter(User.user_id != current_user.user_id)
996 .filter(User.user_id != current_user.user_id)
998 .filter(User.active == true())
997 .filter(User.active == true())
999 .filter(User.admin == true())
998 .filter(User.admin == true())
1000 .order_by(User.created_on.asc())]
999 .order_by(User.created_on.asc())]
1001
1000
1002 active_regular_users = [
1001 active_regular_users = [
1003 x.user_id for x in User.query()
1002 x.user_id for x in User.query()
1004 .filter(User.user_id != current_user.user_id)
1003 .filter(User.user_id != current_user.user_id)
1005 .filter(User.active == true())
1004 .filter(User.active == true())
1006 .filter(User.admin == false())
1005 .filter(User.admin == false())
1007 .order_by(User.created_on.asc())]
1006 .order_by(User.created_on.asc())]
1008
1007
1009 list_of_accounts = [current_user.user_id]
1008 list_of_accounts = [current_user.user_id]
1010 list_of_accounts += active_super_admins
1009 list_of_accounts += active_super_admins
1011 list_of_accounts += active_regular_users
1010 list_of_accounts += active_regular_users
1012
1011
1013 return list_of_accounts
1012 return list_of_accounts
1014
1013
1015 def deactivate_last_users(self, expected_users, current_user=None):
1014 def deactivate_last_users(self, expected_users, current_user=None):
1016 """
1015 """
1017 Deactivate accounts that are over the license limits.
1016 Deactivate accounts that are over the license limits.
1018 Algorithm of which accounts to disabled is based on the formula:
1017 Algorithm of which accounts to disabled is based on the formula:
1019
1018
1020 Get current user, then super admins in creation order, then regular
1019 Get current user, then super admins in creation order, then regular
1021 active users in creation order.
1020 active users in creation order.
1022
1021
1023 Using that list we mark all accounts from the end of it as inactive.
1022 Using that list we mark all accounts from the end of it as inactive.
1024 This way we block only latest created accounts.
1023 This way we block only latest created accounts.
1025
1024
1026 :param expected_users: list of users in special order, we deactivate
1025 :param expected_users: list of users in special order, we deactivate
1027 the end N amount of users from that list
1026 the end N amount of users from that list
1028 """
1027 """
1029
1028
1030 list_of_accounts = self.get_accounts_in_creation_order(
1029 list_of_accounts = self.get_accounts_in_creation_order(
1031 current_user=current_user)
1030 current_user=current_user)
1032
1031
1033 for acc_id in list_of_accounts[expected_users + 1:]:
1032 for acc_id in list_of_accounts[expected_users + 1:]:
1034 user = User.get(acc_id)
1033 user = User.get(acc_id)
1035 log.info('Deactivating account %s for license unlock', user)
1034 log.info('Deactivating account %s for license unlock', user)
1036 user.active = False
1035 user.active = False
1037 Session().add(user)
1036 Session().add(user)
1038 Session().commit()
1037 Session().commit()
1039
1038
1040 return
1039 return
1041
1040
1042 def get_user_log(self, user, filter_term):
1041 def get_user_log(self, user, filter_term):
1043 user_log = UserLog.query()\
1042 user_log = UserLog.query()\
1044 .filter(or_(UserLog.user_id == user.user_id,
1043 .filter(or_(UserLog.user_id == user.user_id,
1045 UserLog.username == user.username))\
1044 UserLog.username == user.username))\
1046 .options(joinedload(UserLog.user))\
1045 .options(joinedload(UserLog.user))\
1047 .options(joinedload(UserLog.repository))\
1046 .options(joinedload(UserLog.repository))\
1048 .order_by(UserLog.action_date.desc())
1047 .order_by(UserLog.action_date.desc())
1049
1048
1050 user_log = user_log_filter(user_log, filter_term)
1049 user_log = user_log_filter(user_log, filter_term)
1051 return user_log
1050 return user_log
General Comments 0
You need to be logged in to leave comments. Login now