##// END OF EJS Templates
admin: made all grids use same partial loading logic...
marcink -
r4146:7a71b271 default
parent child Browse files
Show More
@@ -1,454 +1,457 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def admin_routes(config):
25 def admin_routes(config):
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29
29
30 config.add_route(
30 config.add_route(
31 name='admin_audit_logs',
31 name='admin_audit_logs',
32 pattern='/audit_logs')
32 pattern='/audit_logs')
33
33
34 config.add_route(
34 config.add_route(
35 name='admin_audit_log_entry',
35 name='admin_audit_log_entry',
36 pattern='/audit_logs/{audit_log_id}')
36 pattern='/audit_logs/{audit_log_id}')
37
37
38 config.add_route(
38 config.add_route(
39 name='pull_requests_global_0', # backward compat
39 name='pull_requests_global_0', # backward compat
40 pattern='/pull_requests/{pull_request_id:\d+}')
40 pattern='/pull_requests/{pull_request_id:\d+}')
41 config.add_route(
41 config.add_route(
42 name='pull_requests_global_1', # backward compat
42 name='pull_requests_global_1', # backward compat
43 pattern='/pull-requests/{pull_request_id:\d+}')
43 pattern='/pull-requests/{pull_request_id:\d+}')
44 config.add_route(
44 config.add_route(
45 name='pull_requests_global',
45 name='pull_requests_global',
46 pattern='/pull-request/{pull_request_id:\d+}')
46 pattern='/pull-request/{pull_request_id:\d+}')
47
47
48 config.add_route(
48 config.add_route(
49 name='admin_settings_open_source',
49 name='admin_settings_open_source',
50 pattern='/settings/open_source')
50 pattern='/settings/open_source')
51 config.add_route(
51 config.add_route(
52 name='admin_settings_vcs_svn_generate_cfg',
52 name='admin_settings_vcs_svn_generate_cfg',
53 pattern='/settings/vcs/svn_generate_cfg')
53 pattern='/settings/vcs/svn_generate_cfg')
54
54
55 config.add_route(
55 config.add_route(
56 name='admin_settings_system',
56 name='admin_settings_system',
57 pattern='/settings/system')
57 pattern='/settings/system')
58 config.add_route(
58 config.add_route(
59 name='admin_settings_system_update',
59 name='admin_settings_system_update',
60 pattern='/settings/system/updates')
60 pattern='/settings/system/updates')
61
61
62 config.add_route(
62 config.add_route(
63 name='admin_settings_exception_tracker',
63 name='admin_settings_exception_tracker',
64 pattern='/settings/exceptions')
64 pattern='/settings/exceptions')
65 config.add_route(
65 config.add_route(
66 name='admin_settings_exception_tracker_delete_all',
66 name='admin_settings_exception_tracker_delete_all',
67 pattern='/settings/exceptions/delete')
67 pattern='/settings/exceptions/delete')
68 config.add_route(
68 config.add_route(
69 name='admin_settings_exception_tracker_show',
69 name='admin_settings_exception_tracker_show',
70 pattern='/settings/exceptions/{exception_id}')
70 pattern='/settings/exceptions/{exception_id}')
71 config.add_route(
71 config.add_route(
72 name='admin_settings_exception_tracker_delete',
72 name='admin_settings_exception_tracker_delete',
73 pattern='/settings/exceptions/{exception_id}/delete')
73 pattern='/settings/exceptions/{exception_id}/delete')
74
74
75 config.add_route(
75 config.add_route(
76 name='admin_settings_sessions',
76 name='admin_settings_sessions',
77 pattern='/settings/sessions')
77 pattern='/settings/sessions')
78 config.add_route(
78 config.add_route(
79 name='admin_settings_sessions_cleanup',
79 name='admin_settings_sessions_cleanup',
80 pattern='/settings/sessions/cleanup')
80 pattern='/settings/sessions/cleanup')
81
81
82 config.add_route(
82 config.add_route(
83 name='admin_settings_process_management',
83 name='admin_settings_process_management',
84 pattern='/settings/process_management')
84 pattern='/settings/process_management')
85 config.add_route(
85 config.add_route(
86 name='admin_settings_process_management_data',
86 name='admin_settings_process_management_data',
87 pattern='/settings/process_management/data')
87 pattern='/settings/process_management/data')
88 config.add_route(
88 config.add_route(
89 name='admin_settings_process_management_signal',
89 name='admin_settings_process_management_signal',
90 pattern='/settings/process_management/signal')
90 pattern='/settings/process_management/signal')
91 config.add_route(
91 config.add_route(
92 name='admin_settings_process_management_master_signal',
92 name='admin_settings_process_management_master_signal',
93 pattern='/settings/process_management/master_signal')
93 pattern='/settings/process_management/master_signal')
94
94
95 # default settings
95 # default settings
96 config.add_route(
96 config.add_route(
97 name='admin_defaults_repositories',
97 name='admin_defaults_repositories',
98 pattern='/defaults/repositories')
98 pattern='/defaults/repositories')
99 config.add_route(
99 config.add_route(
100 name='admin_defaults_repositories_update',
100 name='admin_defaults_repositories_update',
101 pattern='/defaults/repositories/update')
101 pattern='/defaults/repositories/update')
102
102
103 # admin settings
103 # admin settings
104
104
105 config.add_route(
105 config.add_route(
106 name='admin_settings',
106 name='admin_settings',
107 pattern='/settings')
107 pattern='/settings')
108 config.add_route(
108 config.add_route(
109 name='admin_settings_update',
109 name='admin_settings_update',
110 pattern='/settings/update')
110 pattern='/settings/update')
111
111
112 config.add_route(
112 config.add_route(
113 name='admin_settings_global',
113 name='admin_settings_global',
114 pattern='/settings/global')
114 pattern='/settings/global')
115 config.add_route(
115 config.add_route(
116 name='admin_settings_global_update',
116 name='admin_settings_global_update',
117 pattern='/settings/global/update')
117 pattern='/settings/global/update')
118
118
119 config.add_route(
119 config.add_route(
120 name='admin_settings_vcs',
120 name='admin_settings_vcs',
121 pattern='/settings/vcs')
121 pattern='/settings/vcs')
122 config.add_route(
122 config.add_route(
123 name='admin_settings_vcs_update',
123 name='admin_settings_vcs_update',
124 pattern='/settings/vcs/update')
124 pattern='/settings/vcs/update')
125 config.add_route(
125 config.add_route(
126 name='admin_settings_vcs_svn_pattern_delete',
126 name='admin_settings_vcs_svn_pattern_delete',
127 pattern='/settings/vcs/svn_pattern_delete')
127 pattern='/settings/vcs/svn_pattern_delete')
128
128
129 config.add_route(
129 config.add_route(
130 name='admin_settings_mapping',
130 name='admin_settings_mapping',
131 pattern='/settings/mapping')
131 pattern='/settings/mapping')
132 config.add_route(
132 config.add_route(
133 name='admin_settings_mapping_update',
133 name='admin_settings_mapping_update',
134 pattern='/settings/mapping/update')
134 pattern='/settings/mapping/update')
135
135
136 config.add_route(
136 config.add_route(
137 name='admin_settings_visual',
137 name='admin_settings_visual',
138 pattern='/settings/visual')
138 pattern='/settings/visual')
139 config.add_route(
139 config.add_route(
140 name='admin_settings_visual_update',
140 name='admin_settings_visual_update',
141 pattern='/settings/visual/update')
141 pattern='/settings/visual/update')
142
142
143
144 config.add_route(
143 config.add_route(
145 name='admin_settings_issuetracker',
144 name='admin_settings_issuetracker',
146 pattern='/settings/issue-tracker')
145 pattern='/settings/issue-tracker')
147 config.add_route(
146 config.add_route(
148 name='admin_settings_issuetracker_update',
147 name='admin_settings_issuetracker_update',
149 pattern='/settings/issue-tracker/update')
148 pattern='/settings/issue-tracker/update')
150 config.add_route(
149 config.add_route(
151 name='admin_settings_issuetracker_test',
150 name='admin_settings_issuetracker_test',
152 pattern='/settings/issue-tracker/test')
151 pattern='/settings/issue-tracker/test')
153 config.add_route(
152 config.add_route(
154 name='admin_settings_issuetracker_delete',
153 name='admin_settings_issuetracker_delete',
155 pattern='/settings/issue-tracker/delete')
154 pattern='/settings/issue-tracker/delete')
156
155
157 config.add_route(
156 config.add_route(
158 name='admin_settings_email',
157 name='admin_settings_email',
159 pattern='/settings/email')
158 pattern='/settings/email')
160 config.add_route(
159 config.add_route(
161 name='admin_settings_email_update',
160 name='admin_settings_email_update',
162 pattern='/settings/email/update')
161 pattern='/settings/email/update')
163
162
164 config.add_route(
163 config.add_route(
165 name='admin_settings_hooks',
164 name='admin_settings_hooks',
166 pattern='/settings/hooks')
165 pattern='/settings/hooks')
167 config.add_route(
166 config.add_route(
168 name='admin_settings_hooks_update',
167 name='admin_settings_hooks_update',
169 pattern='/settings/hooks/update')
168 pattern='/settings/hooks/update')
170 config.add_route(
169 config.add_route(
171 name='admin_settings_hooks_delete',
170 name='admin_settings_hooks_delete',
172 pattern='/settings/hooks/delete')
171 pattern='/settings/hooks/delete')
173
172
174 config.add_route(
173 config.add_route(
175 name='admin_settings_search',
174 name='admin_settings_search',
176 pattern='/settings/search')
175 pattern='/settings/search')
177
176
178 config.add_route(
177 config.add_route(
179 name='admin_settings_labs',
178 name='admin_settings_labs',
180 pattern='/settings/labs')
179 pattern='/settings/labs')
181 config.add_route(
180 config.add_route(
182 name='admin_settings_labs_update',
181 name='admin_settings_labs_update',
183 pattern='/settings/labs/update')
182 pattern='/settings/labs/update')
184
183
185 # Automation EE feature
184 # Automation EE feature
186 config.add_route(
185 config.add_route(
187 'admin_settings_automation',
186 'admin_settings_automation',
188 pattern=ADMIN_PREFIX + '/settings/automation')
187 pattern=ADMIN_PREFIX + '/settings/automation')
189
188
190 # global permissions
189 # global permissions
191
190
192 config.add_route(
191 config.add_route(
193 name='admin_permissions_application',
192 name='admin_permissions_application',
194 pattern='/permissions/application')
193 pattern='/permissions/application')
195 config.add_route(
194 config.add_route(
196 name='admin_permissions_application_update',
195 name='admin_permissions_application_update',
197 pattern='/permissions/application/update')
196 pattern='/permissions/application/update')
198
197
199 config.add_route(
198 config.add_route(
200 name='admin_permissions_global',
199 name='admin_permissions_global',
201 pattern='/permissions/global')
200 pattern='/permissions/global')
202 config.add_route(
201 config.add_route(
203 name='admin_permissions_global_update',
202 name='admin_permissions_global_update',
204 pattern='/permissions/global/update')
203 pattern='/permissions/global/update')
205
204
206 config.add_route(
205 config.add_route(
207 name='admin_permissions_object',
206 name='admin_permissions_object',
208 pattern='/permissions/object')
207 pattern='/permissions/object')
209 config.add_route(
208 config.add_route(
210 name='admin_permissions_object_update',
209 name='admin_permissions_object_update',
211 pattern='/permissions/object/update')
210 pattern='/permissions/object/update')
212
211
213 # Branch perms EE feature
212 # Branch perms EE feature
214 config.add_route(
213 config.add_route(
215 name='admin_permissions_branch',
214 name='admin_permissions_branch',
216 pattern='/permissions/branch')
215 pattern='/permissions/branch')
217
216
218 config.add_route(
217 config.add_route(
219 name='admin_permissions_ips',
218 name='admin_permissions_ips',
220 pattern='/permissions/ips')
219 pattern='/permissions/ips')
221
220
222 config.add_route(
221 config.add_route(
223 name='admin_permissions_overview',
222 name='admin_permissions_overview',
224 pattern='/permissions/overview')
223 pattern='/permissions/overview')
225
224
226 config.add_route(
225 config.add_route(
227 name='admin_permissions_auth_token_access',
226 name='admin_permissions_auth_token_access',
228 pattern='/permissions/auth_token_access')
227 pattern='/permissions/auth_token_access')
229
228
230 config.add_route(
229 config.add_route(
231 name='admin_permissions_ssh_keys',
230 name='admin_permissions_ssh_keys',
232 pattern='/permissions/ssh_keys')
231 pattern='/permissions/ssh_keys')
233 config.add_route(
232 config.add_route(
234 name='admin_permissions_ssh_keys_data',
233 name='admin_permissions_ssh_keys_data',
235 pattern='/permissions/ssh_keys/data')
234 pattern='/permissions/ssh_keys/data')
236 config.add_route(
235 config.add_route(
237 name='admin_permissions_ssh_keys_update',
236 name='admin_permissions_ssh_keys_update',
238 pattern='/permissions/ssh_keys/update')
237 pattern='/permissions/ssh_keys/update')
239
238
240 # users admin
239 # users admin
241 config.add_route(
240 config.add_route(
242 name='users',
241 name='users',
243 pattern='/users')
242 pattern='/users')
244
243
245 config.add_route(
244 config.add_route(
246 name='users_data',
245 name='users_data',
247 pattern='/users_data')
246 pattern='/users_data')
248
247
249 config.add_route(
248 config.add_route(
250 name='users_create',
249 name='users_create',
251 pattern='/users/create')
250 pattern='/users/create')
252
251
253 config.add_route(
252 config.add_route(
254 name='users_new',
253 name='users_new',
255 pattern='/users/new')
254 pattern='/users/new')
256
255
257 # user management
256 # user management
258 config.add_route(
257 config.add_route(
259 name='user_edit',
258 name='user_edit',
260 pattern='/users/{user_id:\d+}/edit',
259 pattern='/users/{user_id:\d+}/edit',
261 user_route=True)
260 user_route=True)
262 config.add_route(
261 config.add_route(
263 name='user_edit_advanced',
262 name='user_edit_advanced',
264 pattern='/users/{user_id:\d+}/edit/advanced',
263 pattern='/users/{user_id:\d+}/edit/advanced',
265 user_route=True)
264 user_route=True)
266 config.add_route(
265 config.add_route(
267 name='user_edit_global_perms',
266 name='user_edit_global_perms',
268 pattern='/users/{user_id:\d+}/edit/global_permissions',
267 pattern='/users/{user_id:\d+}/edit/global_permissions',
269 user_route=True)
268 user_route=True)
270 config.add_route(
269 config.add_route(
271 name='user_edit_global_perms_update',
270 name='user_edit_global_perms_update',
272 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
271 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
273 user_route=True)
272 user_route=True)
274 config.add_route(
273 config.add_route(
275 name='user_update',
274 name='user_update',
276 pattern='/users/{user_id:\d+}/update',
275 pattern='/users/{user_id:\d+}/update',
277 user_route=True)
276 user_route=True)
278 config.add_route(
277 config.add_route(
279 name='user_delete',
278 name='user_delete',
280 pattern='/users/{user_id:\d+}/delete',
279 pattern='/users/{user_id:\d+}/delete',
281 user_route=True)
280 user_route=True)
282 config.add_route(
281 config.add_route(
283 name='user_enable_force_password_reset',
282 name='user_enable_force_password_reset',
284 pattern='/users/{user_id:\d+}/password_reset_enable',
283 pattern='/users/{user_id:\d+}/password_reset_enable',
285 user_route=True)
284 user_route=True)
286 config.add_route(
285 config.add_route(
287 name='user_disable_force_password_reset',
286 name='user_disable_force_password_reset',
288 pattern='/users/{user_id:\d+}/password_reset_disable',
287 pattern='/users/{user_id:\d+}/password_reset_disable',
289 user_route=True)
288 user_route=True)
290 config.add_route(
289 config.add_route(
291 name='user_create_personal_repo_group',
290 name='user_create_personal_repo_group',
292 pattern='/users/{user_id:\d+}/create_repo_group',
291 pattern='/users/{user_id:\d+}/create_repo_group',
293 user_route=True)
292 user_route=True)
294
293
295 # user auth tokens
294 # user auth tokens
296 config.add_route(
295 config.add_route(
297 name='edit_user_auth_tokens',
296 name='edit_user_auth_tokens',
298 pattern='/users/{user_id:\d+}/edit/auth_tokens',
297 pattern='/users/{user_id:\d+}/edit/auth_tokens',
299 user_route=True)
298 user_route=True)
300 config.add_route(
299 config.add_route(
301 name='edit_user_auth_tokens_add',
300 name='edit_user_auth_tokens_add',
302 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
301 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
303 user_route=True)
302 user_route=True)
304 config.add_route(
303 config.add_route(
305 name='edit_user_auth_tokens_delete',
304 name='edit_user_auth_tokens_delete',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
305 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
307 user_route=True)
306 user_route=True)
308
307
309 # user ssh keys
308 # user ssh keys
310 config.add_route(
309 config.add_route(
311 name='edit_user_ssh_keys',
310 name='edit_user_ssh_keys',
312 pattern='/users/{user_id:\d+}/edit/ssh_keys',
311 pattern='/users/{user_id:\d+}/edit/ssh_keys',
313 user_route=True)
312 user_route=True)
314 config.add_route(
313 config.add_route(
315 name='edit_user_ssh_keys_generate_keypair',
314 name='edit_user_ssh_keys_generate_keypair',
316 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
315 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
317 user_route=True)
316 user_route=True)
318 config.add_route(
317 config.add_route(
319 name='edit_user_ssh_keys_add',
318 name='edit_user_ssh_keys_add',
320 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
319 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
321 user_route=True)
320 user_route=True)
322 config.add_route(
321 config.add_route(
323 name='edit_user_ssh_keys_delete',
322 name='edit_user_ssh_keys_delete',
324 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
323 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
325 user_route=True)
324 user_route=True)
326
325
327 # user emails
326 # user emails
328 config.add_route(
327 config.add_route(
329 name='edit_user_emails',
328 name='edit_user_emails',
330 pattern='/users/{user_id:\d+}/edit/emails',
329 pattern='/users/{user_id:\d+}/edit/emails',
331 user_route=True)
330 user_route=True)
332 config.add_route(
331 config.add_route(
333 name='edit_user_emails_add',
332 name='edit_user_emails_add',
334 pattern='/users/{user_id:\d+}/edit/emails/new',
333 pattern='/users/{user_id:\d+}/edit/emails/new',
335 user_route=True)
334 user_route=True)
336 config.add_route(
335 config.add_route(
337 name='edit_user_emails_delete',
336 name='edit_user_emails_delete',
338 pattern='/users/{user_id:\d+}/edit/emails/delete',
337 pattern='/users/{user_id:\d+}/edit/emails/delete',
339 user_route=True)
338 user_route=True)
340
339
341 # user IPs
340 # user IPs
342 config.add_route(
341 config.add_route(
343 name='edit_user_ips',
342 name='edit_user_ips',
344 pattern='/users/{user_id:\d+}/edit/ips',
343 pattern='/users/{user_id:\d+}/edit/ips',
345 user_route=True)
344 user_route=True)
346 config.add_route(
345 config.add_route(
347 name='edit_user_ips_add',
346 name='edit_user_ips_add',
348 pattern='/users/{user_id:\d+}/edit/ips/new',
347 pattern='/users/{user_id:\d+}/edit/ips/new',
349 user_route_with_default=True) # enabled for default user too
348 user_route_with_default=True) # enabled for default user too
350 config.add_route(
349 config.add_route(
351 name='edit_user_ips_delete',
350 name='edit_user_ips_delete',
352 pattern='/users/{user_id:\d+}/edit/ips/delete',
351 pattern='/users/{user_id:\d+}/edit/ips/delete',
353 user_route_with_default=True) # enabled for default user too
352 user_route_with_default=True) # enabled for default user too
354
353
355 # user perms
354 # user perms
356 config.add_route(
355 config.add_route(
357 name='edit_user_perms_summary',
356 name='edit_user_perms_summary',
358 pattern='/users/{user_id:\d+}/edit/permissions_summary',
357 pattern='/users/{user_id:\d+}/edit/permissions_summary',
359 user_route=True)
358 user_route=True)
360 config.add_route(
359 config.add_route(
361 name='edit_user_perms_summary_json',
360 name='edit_user_perms_summary_json',
362 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
361 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
363 user_route=True)
362 user_route=True)
364
363
365 # user user groups management
364 # user user groups management
366 config.add_route(
365 config.add_route(
367 name='edit_user_groups_management',
366 name='edit_user_groups_management',
368 pattern='/users/{user_id:\d+}/edit/groups_management',
367 pattern='/users/{user_id:\d+}/edit/groups_management',
369 user_route=True)
368 user_route=True)
370
369
371 config.add_route(
370 config.add_route(
372 name='edit_user_groups_management_updates',
371 name='edit_user_groups_management_updates',
373 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
372 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
374 user_route=True)
373 user_route=True)
375
374
376 # user audit logs
375 # user audit logs
377 config.add_route(
376 config.add_route(
378 name='edit_user_audit_logs',
377 name='edit_user_audit_logs',
379 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
378 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
380
379
381 config.add_route(
380 config.add_route(
382 name='edit_user_audit_logs_download',
381 name='edit_user_audit_logs_download',
383 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
382 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
384
383
385 # user caches
384 # user caches
386 config.add_route(
385 config.add_route(
387 name='edit_user_caches',
386 name='edit_user_caches',
388 pattern='/users/{user_id:\d+}/edit/caches',
387 pattern='/users/{user_id:\d+}/edit/caches',
389 user_route=True)
388 user_route=True)
390 config.add_route(
389 config.add_route(
391 name='edit_user_caches_update',
390 name='edit_user_caches_update',
392 pattern='/users/{user_id:\d+}/edit/caches/update',
391 pattern='/users/{user_id:\d+}/edit/caches/update',
393 user_route=True)
392 user_route=True)
394
393
395 # user-groups admin
394 # user-groups admin
396 config.add_route(
395 config.add_route(
397 name='user_groups',
396 name='user_groups',
398 pattern='/user_groups')
397 pattern='/user_groups')
399
398
400 config.add_route(
399 config.add_route(
401 name='user_groups_data',
400 name='user_groups_data',
402 pattern='/user_groups_data')
401 pattern='/user_groups_data')
403
402
404 config.add_route(
403 config.add_route(
405 name='user_groups_new',
404 name='user_groups_new',
406 pattern='/user_groups/new')
405 pattern='/user_groups/new')
407
406
408 config.add_route(
407 config.add_route(
409 name='user_groups_create',
408 name='user_groups_create',
410 pattern='/user_groups/create')
409 pattern='/user_groups/create')
411
410
412 # repos admin
411 # repos admin
413 config.add_route(
412 config.add_route(
414 name='repos',
413 name='repos',
415 pattern='/repos')
414 pattern='/repos')
416
415
417 config.add_route(
416 config.add_route(
417 name='repos_data',
418 pattern='/repos_data')
419
420 config.add_route(
418 name='repo_new',
421 name='repo_new',
419 pattern='/repos/new')
422 pattern='/repos/new')
420
423
421 config.add_route(
424 config.add_route(
422 name='repo_create',
425 name='repo_create',
423 pattern='/repos/create')
426 pattern='/repos/create')
424
427
425 # repo groups admin
428 # repo groups admin
426 config.add_route(
429 config.add_route(
427 name='repo_groups',
430 name='repo_groups',
428 pattern='/repo_groups')
431 pattern='/repo_groups')
429
432
430 config.add_route(
433 config.add_route(
431 name='repo_groups_data',
434 name='repo_groups_data',
432 pattern='/repo_groups_data')
435 pattern='/repo_groups_data')
433
436
434 config.add_route(
437 config.add_route(
435 name='repo_group_new',
438 name='repo_group_new',
436 pattern='/repo_group/new')
439 pattern='/repo_group/new')
437
440
438 config.add_route(
441 config.add_route(
439 name='repo_group_create',
442 name='repo_group_create',
440 pattern='/repo_group/create')
443 pattern='/repo_group/create')
441
444
442
445
443 def includeme(config):
446 def includeme(config):
444 from rhodecode.apps._base.navigation import includeme as nav_includeme
447 from rhodecode.apps._base.navigation import includeme as nav_includeme
445
448
446 # Create admin navigation registry and add it to the pyramid registry.
449 # Create admin navigation registry and add it to the pyramid registry.
447 nav_includeme(config)
450 nav_includeme(config)
448
451
449 # main admin routes
452 # main admin routes
450 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
453 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
451 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
454 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
452
455
453 # Scan module for configuration decorators.
456 # Scan module for configuration decorators.
454 config.scan('.views', ignore='.tests')
457 config.scan('.views', ignore='.tests')
@@ -1,365 +1,361 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import datetime
20 import datetime
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34
34
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
36 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
37 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h, audit_logger
38 from rhodecode.lib import helpers as h, audit_logger
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 from rhodecode.model.forms import RepoGroupForm
40 from rhodecode.model.forms import RepoGroupForm
41 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.repo_group import RepoGroupModel
42 from rhodecode.model.repo_group import RepoGroupModel
43 from rhodecode.model.scm import RepoGroupList
43 from rhodecode.model.scm import RepoGroupList
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 return c
55 return c
56
56
57 def _load_form_data(self, c):
57 def _load_form_data(self, c):
58 allow_empty_group = False
58 allow_empty_group = False
59
59
60 if self._can_create_repo_group():
60 if self._can_create_repo_group():
61 # we're global admin, we're ok and we can create TOP level groups
61 # we're global admin, we're ok and we can create TOP level groups
62 allow_empty_group = True
62 allow_empty_group = True
63
63
64 # override the choices for this form, we need to filter choices
64 # override the choices for this form, we need to filter choices
65 # and display only those we have ADMIN right
65 # and display only those we have ADMIN right
66 groups_with_admin_rights = RepoGroupList(
66 groups_with_admin_rights = RepoGroupList(
67 RepoGroup.query().all(),
67 RepoGroup.query().all(),
68 perm_set=['group.admin'])
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 c.repo_groups = RepoGroup.groups_choices(
69 c.repo_groups = RepoGroup.groups_choices(
70 groups=groups_with_admin_rights,
70 groups=groups_with_admin_rights,
71 show_empty_group=allow_empty_group)
71 show_empty_group=allow_empty_group)
72
72
73 def _can_create_repo_group(self, parent_group_id=None):
73 def _can_create_repo_group(self, parent_group_id=None):
74 is_admin = HasPermissionAny('hg.admin')('group create controller')
74 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 create_repo_group = HasPermissionAny(
75 create_repo_group = HasPermissionAny(
76 'hg.repogroup.create.true')('group create controller')
76 'hg.repogroup.create.true')('group create controller')
77 if is_admin or (create_repo_group and not parent_group_id):
77 if is_admin or (create_repo_group and not parent_group_id):
78 # we're global admin, or we have global repo group create
78 # we're global admin, or we have global repo group create
79 # permission
79 # permission
80 # we're ok and we can create TOP level groups
80 # we're ok and we can create TOP level groups
81 return True
81 return True
82 elif parent_group_id:
82 elif parent_group_id:
83 # we check the permission if we can write to parent group
83 # we check the permission if we can write to parent group
84 group = RepoGroup.get(parent_group_id)
84 group = RepoGroup.get(parent_group_id)
85 group_name = group.group_name if group else None
85 group_name = group.group_name if group else None
86 if HasRepoGroupPermissionAny('group.admin')(
86 if HasRepoGroupPermissionAny('group.admin')(
87 group_name, 'check if user is an admin of group'):
87 group_name, 'check if user is an admin of group'):
88 # we're an admin of passed in group, we're ok.
88 # we're an admin of passed in group, we're ok.
89 return True
89 return True
90 else:
90 else:
91 return False
91 return False
92 return False
92 return False
93
93
94 # permission check in data loading of
94 # permission check in data loading of
95 # `repo_group_list_data` via RepoGroupList
95 # `repo_group_list_data` via RepoGroupList
96 @LoginRequired()
96 @LoginRequired()
97 @NotAnonymous()
97 @NotAnonymous()
98 @view_config(
98 @view_config(
99 route_name='repo_groups', request_method='GET',
99 route_name='repo_groups', request_method='GET',
100 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
100 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 def repo_group_list(self):
101 def repo_group_list(self):
102 c = self.load_default_context()
102 c = self.load_default_context()
103 return self._get_template_context(c)
103 return self._get_template_context(c)
104
104
105 # permission check inside
105 # permission check inside
106 @LoginRequired()
106 @LoginRequired()
107 @NotAnonymous()
107 @NotAnonymous()
108 @view_config(
108 @view_config(
109 route_name='repo_groups_data', request_method='GET',
109 route_name='repo_groups_data', request_method='GET',
110 renderer='json_ext', xhr=True)
110 renderer='json_ext', xhr=True)
111 def repo_group_list_data(self):
111 def repo_group_list_data(self):
112 self.load_default_context()
112 self.load_default_context()
113 column_map = {
113 column_map = {
114 'name_raw': 'group_name_hash',
114 'name_raw': 'group_name_hash',
115 'desc': 'group_description',
115 'desc': 'group_description',
116 'last_change_raw': 'updated_on',
116 'last_change_raw': 'updated_on',
117 'top_level_repos': 'repos_total',
117 'top_level_repos': 'repos_total',
118 'owner': 'user_username',
118 'owner': 'user_username',
119 }
119 }
120 draw, start, limit = self._extract_chunk(self.request)
120 draw, start, limit = self._extract_chunk(self.request)
121 search_q, order_by, order_dir = self._extract_ordering(
121 search_q, order_by, order_dir = self._extract_ordering(
122 self.request, column_map=column_map)
122 self.request, column_map=column_map)
123
123
124 _render = self.request.get_partial_renderer(
124 _render = self.request.get_partial_renderer(
125 'rhodecode:templates/data_table/_dt_elements.mako')
125 'rhodecode:templates/data_table/_dt_elements.mako')
126 c = _render.get_call_context()
126 c = _render.get_call_context()
127
127
128 def quick_menu(repo_group_name):
128 def quick_menu(repo_group_name):
129 return _render('quick_repo_group_menu', repo_group_name)
129 return _render('quick_repo_group_menu', repo_group_name)
130
130
131 def repo_group_lnk(repo_group_name):
131 def repo_group_lnk(repo_group_name):
132 return _render('repo_group_name', repo_group_name)
132 return _render('repo_group_name', repo_group_name)
133
133
134 def last_change(last_change):
134 def last_change(last_change):
135 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
135 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 ts = time.time()
136 ts = time.time()
137 utc_offset = (datetime.datetime.fromtimestamp(ts)
137 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
138 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 last_change = last_change + datetime.timedelta(seconds=utc_offset)
139 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 return _render("last_change", last_change)
140 return _render("last_change", last_change)
141
141
142 def desc(desc, personal):
142 def desc(desc, personal):
143 return _render(
143 return _render(
144 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
144 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145
145
146 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
146 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 return _render(
147 return _render(
148 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
148 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149
149
150 def user_profile(username):
150 def user_profile(username):
151 return _render('user_profile', username)
151 return _render('user_profile', username)
152
152
153 auth_repo_group_list = RepoGroupList(
153 _perms = ['group.admin']
154 RepoGroup.query().all(), perm_set=['group.admin'])
154 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
155
156 allowed_ids = [-1]
157 for repo_group in auth_repo_group_list:
158 allowed_ids.append(repo_group.group_id)
159
155
160 repo_groups_data_total_count = RepoGroup.query()\
156 repo_groups_data_total_count = RepoGroup.query()\
161 .filter(or_(
157 .filter(or_(
162 # generate multiple IN to fix limitation problems
158 # generate multiple IN to fix limitation problems
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 )) \
160 )) \
165 .count()
161 .count()
166
162
167 repo_groups_data_total_inactive_count = RepoGroup.query()\
163 repo_groups_data_total_inactive_count = RepoGroup.query()\
168 .filter(RepoGroup.group_id.in_(allowed_ids))\
164 .filter(RepoGroup.group_id.in_(allowed_ids))\
169 .count()
165 .count()
170
166
171 repo_count = count(Repository.repo_id)
167 repo_count = count(Repository.repo_id)
172 base_q = Session.query(
168 base_q = Session.query(
173 RepoGroup.group_name,
169 RepoGroup.group_name,
174 RepoGroup.group_name_hash,
170 RepoGroup.group_name_hash,
175 RepoGroup.group_description,
171 RepoGroup.group_description,
176 RepoGroup.group_id,
172 RepoGroup.group_id,
177 RepoGroup.personal,
173 RepoGroup.personal,
178 RepoGroup.updated_on,
174 RepoGroup.updated_on,
179 User,
175 User,
180 repo_count.label('repos_count')
176 repo_count.label('repos_count')
181 ) \
177 ) \
182 .filter(or_(
178 .filter(or_(
183 # generate multiple IN to fix limitation problems
179 # generate multiple IN to fix limitation problems
184 *in_filter_generator(RepoGroup.group_id, allowed_ids)
180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
185 )) \
181 )) \
186 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
182 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
187 .join(User, User.user_id == RepoGroup.user_id) \
183 .join(User, User.user_id == RepoGroup.user_id) \
188 .group_by(RepoGroup, User)
184 .group_by(RepoGroup, User)
189
185
190 if search_q:
186 if search_q:
191 like_expression = u'%{}%'.format(safe_unicode(search_q))
187 like_expression = u'%{}%'.format(safe_unicode(search_q))
192 base_q = base_q.filter(or_(
188 base_q = base_q.filter(or_(
193 RepoGroup.group_name.ilike(like_expression),
189 RepoGroup.group_name.ilike(like_expression),
194 ))
190 ))
195
191
196 repo_groups_data_total_filtered_count = base_q.count()
192 repo_groups_data_total_filtered_count = base_q.count()
197 # the inactive isn't really used, but we still make it same as other data grids
193 # the inactive isn't really used, but we still make it same as other data grids
198 # which use inactive (users,user groups)
194 # which use inactive (users,user groups)
199 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
200
196
201 sort_defined = False
197 sort_defined = False
202 if order_by == 'group_name':
198 if order_by == 'group_name':
203 sort_col = func.lower(RepoGroup.group_name)
199 sort_col = func.lower(RepoGroup.group_name)
204 sort_defined = True
200 sort_defined = True
205 elif order_by == 'repos_total':
201 elif order_by == 'repos_total':
206 sort_col = repo_count
202 sort_col = repo_count
207 sort_defined = True
203 sort_defined = True
208 elif order_by == 'user_username':
204 elif order_by == 'user_username':
209 sort_col = User.username
205 sort_col = User.username
210 else:
206 else:
211 sort_col = getattr(RepoGroup, order_by, None)
207 sort_col = getattr(RepoGroup, order_by, None)
212
208
213 if sort_defined or sort_col:
209 if sort_defined or sort_col:
214 if order_dir == 'asc':
210 if order_dir == 'asc':
215 sort_col = sort_col.asc()
211 sort_col = sort_col.asc()
216 else:
212 else:
217 sort_col = sort_col.desc()
213 sort_col = sort_col.desc()
218
214
219 base_q = base_q.order_by(sort_col)
215 base_q = base_q.order_by(sort_col)
220 base_q = base_q.offset(start).limit(limit)
216 base_q = base_q.offset(start).limit(limit)
221
217
222 # authenticated access to user groups
218 # authenticated access to user groups
223 auth_repo_group_list = base_q.all()
219 auth_repo_group_list = base_q.all()
224
220
225 repo_groups_data = []
221 repo_groups_data = []
226 for repo_gr in auth_repo_group_list:
222 for repo_gr in auth_repo_group_list:
227 row = {
223 row = {
228 "menu": quick_menu(repo_gr.group_name),
224 "menu": quick_menu(repo_gr.group_name),
229 "name": repo_group_lnk(repo_gr.group_name),
225 "name": repo_group_lnk(repo_gr.group_name),
230 "name_raw": repo_gr.group_name,
226 "name_raw": repo_gr.group_name,
231 "last_change": last_change(repo_gr.updated_on),
227 "last_change": last_change(repo_gr.updated_on),
232 "last_change_raw": datetime_to_time(repo_gr.updated_on),
228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
233
229
234 "last_changeset": "",
230 "last_changeset": "",
235 "last_changeset_raw": "",
231 "last_changeset_raw": "",
236
232
237 "desc": desc(repo_gr.group_description, repo_gr.personal),
233 "desc": desc(repo_gr.group_description, repo_gr.personal),
238 "owner": user_profile(repo_gr.User.username),
234 "owner": user_profile(repo_gr.User.username),
239 "top_level_repos": repo_gr.repos_count,
235 "top_level_repos": repo_gr.repos_count,
240 "action": repo_group_actions(
236 "action": repo_group_actions(
241 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
242
238
243 }
239 }
244
240
245 repo_groups_data.append(row)
241 repo_groups_data.append(row)
246
242
247 data = ({
243 data = ({
248 'draw': draw,
244 'draw': draw,
249 'data': repo_groups_data,
245 'data': repo_groups_data,
250 'recordsTotal': repo_groups_data_total_count,
246 'recordsTotal': repo_groups_data_total_count,
251 'recordsTotalInactive': repo_groups_data_total_inactive_count,
247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
252 'recordsFiltered': repo_groups_data_total_filtered_count,
248 'recordsFiltered': repo_groups_data_total_filtered_count,
253 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
254 })
250 })
255
251
256 return data
252 return data
257
253
258 @LoginRequired()
254 @LoginRequired()
259 @NotAnonymous()
255 @NotAnonymous()
260 # perm checks inside
256 # perm checks inside
261 @view_config(
257 @view_config(
262 route_name='repo_group_new', request_method='GET',
258 route_name='repo_group_new', request_method='GET',
263 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
264 def repo_group_new(self):
260 def repo_group_new(self):
265 c = self.load_default_context()
261 c = self.load_default_context()
266
262
267 # perm check for admin, create_group perm or admin of parent_group
263 # perm check for admin, create_group perm or admin of parent_group
268 parent_group_id = safe_int(self.request.GET.get('parent_group'))
264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
269 if not self._can_create_repo_group(parent_group_id):
265 if not self._can_create_repo_group(parent_group_id):
270 raise HTTPForbidden()
266 raise HTTPForbidden()
271
267
272 self._load_form_data(c)
268 self._load_form_data(c)
273
269
274 defaults = {} # Future proof for default of repo group
270 defaults = {} # Future proof for default of repo group
275 data = render(
271 data = render(
276 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
272 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
277 self._get_template_context(c), self.request)
273 self._get_template_context(c), self.request)
278 html = formencode.htmlfill.render(
274 html = formencode.htmlfill.render(
279 data,
275 data,
280 defaults=defaults,
276 defaults=defaults,
281 encoding="UTF-8",
277 encoding="UTF-8",
282 force_defaults=False
278 force_defaults=False
283 )
279 )
284 return Response(html)
280 return Response(html)
285
281
286 @LoginRequired()
282 @LoginRequired()
287 @NotAnonymous()
283 @NotAnonymous()
288 @CSRFRequired()
284 @CSRFRequired()
289 # perm checks inside
285 # perm checks inside
290 @view_config(
286 @view_config(
291 route_name='repo_group_create', request_method='POST',
287 route_name='repo_group_create', request_method='POST',
292 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
288 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
293 def repo_group_create(self):
289 def repo_group_create(self):
294 c = self.load_default_context()
290 c = self.load_default_context()
295 _ = self.request.translate
291 _ = self.request.translate
296
292
297 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
293 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
298 can_create = self._can_create_repo_group(parent_group_id)
294 can_create = self._can_create_repo_group(parent_group_id)
299
295
300 self._load_form_data(c)
296 self._load_form_data(c)
301 # permissions for can create group based on parent_id are checked
297 # permissions for can create group based on parent_id are checked
302 # here in the Form
298 # here in the Form
303 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
299 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
304 repo_group_form = RepoGroupForm(
300 repo_group_form = RepoGroupForm(
305 self.request.translate, available_groups=available_groups,
301 self.request.translate, available_groups=available_groups,
306 can_create_in_root=can_create)()
302 can_create_in_root=can_create)()
307
303
308 repo_group_name = self.request.POST.get('group_name')
304 repo_group_name = self.request.POST.get('group_name')
309 try:
305 try:
310 owner = self._rhodecode_user
306 owner = self._rhodecode_user
311 form_result = repo_group_form.to_python(dict(self.request.POST))
307 form_result = repo_group_form.to_python(dict(self.request.POST))
312 copy_permissions = form_result.get('group_copy_permissions')
308 copy_permissions = form_result.get('group_copy_permissions')
313 repo_group = RepoGroupModel().create(
309 repo_group = RepoGroupModel().create(
314 group_name=form_result['group_name_full'],
310 group_name=form_result['group_name_full'],
315 group_description=form_result['group_description'],
311 group_description=form_result['group_description'],
316 owner=owner.user_id,
312 owner=owner.user_id,
317 copy_permissions=form_result['group_copy_permissions']
313 copy_permissions=form_result['group_copy_permissions']
318 )
314 )
319 Session().flush()
315 Session().flush()
320
316
321 repo_group_data = repo_group.get_api_data()
317 repo_group_data = repo_group.get_api_data()
322 audit_logger.store_web(
318 audit_logger.store_web(
323 'repo_group.create', action_data={'data': repo_group_data},
319 'repo_group.create', action_data={'data': repo_group_data},
324 user=self._rhodecode_user)
320 user=self._rhodecode_user)
325
321
326 Session().commit()
322 Session().commit()
327
323
328 _new_group_name = form_result['group_name_full']
324 _new_group_name = form_result['group_name_full']
329
325
330 repo_group_url = h.link_to(
326 repo_group_url = h.link_to(
331 _new_group_name,
327 _new_group_name,
332 h.route_path('repo_group_home', repo_group_name=_new_group_name))
328 h.route_path('repo_group_home', repo_group_name=_new_group_name))
333 h.flash(h.literal(_('Created repository group %s')
329 h.flash(h.literal(_('Created repository group %s')
334 % repo_group_url), category='success')
330 % repo_group_url), category='success')
335
331
336 except formencode.Invalid as errors:
332 except formencode.Invalid as errors:
337 data = render(
333 data = render(
338 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
334 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
339 self._get_template_context(c), self.request)
335 self._get_template_context(c), self.request)
340 html = formencode.htmlfill.render(
336 html = formencode.htmlfill.render(
341 data,
337 data,
342 defaults=errors.value,
338 defaults=errors.value,
343 errors=errors.error_dict or {},
339 errors=errors.error_dict or {},
344 prefix_error=False,
340 prefix_error=False,
345 encoding="UTF-8",
341 encoding="UTF-8",
346 force_defaults=False
342 force_defaults=False
347 )
343 )
348 return Response(html)
344 return Response(html)
349 except Exception:
345 except Exception:
350 log.exception("Exception during creation of repository group")
346 log.exception("Exception during creation of repository group")
351 h.flash(_('Error occurred during creation of repository group %s')
347 h.flash(_('Error occurred during creation of repository group %s')
352 % repo_group_name, category='error')
348 % repo_group_name, category='error')
353 raise HTTPFound(h.route_path('home'))
349 raise HTTPFound(h.route_path('home'))
354
350
355 affected_user_ids = [self._rhodecode_user.user_id]
351 affected_user_ids = [self._rhodecode_user.user_id]
356 if copy_permissions:
352 if copy_permissions:
357 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
353 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
358 copy_perms = [perm['user_id'] for perm in user_group_perms]
354 copy_perms = [perm['user_id'] for perm in user_group_perms]
359 # also include those newly created by copy
355 # also include those newly created by copy
360 affected_user_ids.extend(copy_perms)
356 affected_user_ids.extend(copy_perms)
361 PermissionModel().trigger_permission_flush(affected_user_ids)
357 PermissionModel().trigger_permission_flush(affected_user_ids)
362
358
363 raise HTTPFound(
359 raise HTTPFound(
364 h.route_path('repo_group_home',
360 h.route_path('repo_group_home',
365 repo_group_name=form_result['group_name_full']))
361 repo_group_name=form_result['group_name_full']))
@@ -1,187 +1,266 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.celerylib.utils import get_task_id
32 from rhodecode.lib.celerylib.utils import get_task_id
33
33
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import (
34 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
35 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
36 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
39 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
39 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 from rhodecode.model.forms import RepoForm
40 from rhodecode.model.forms import RepoForm
42 from rhodecode.model.permission import PermissionModel
41 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
45 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.db import Repository, RepoGroup
45 from rhodecode.model.db import (
46 in_filter_generator, or_, func, Session, Repository, RepoGroup, User)
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class AdminReposView(BaseAppView, DataGridAppView):
51 class AdminReposView(BaseAppView, DataGridAppView):
52
52
53 def load_default_context(self):
53 def load_default_context(self):
54 c = self._get_local_tmpl_context()
54 c = self._get_local_tmpl_context()
55
55
56 return c
56 return c
57
57
58 def _load_form_data(self, c):
58 def _load_form_data(self, c):
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 perm_set=['group.write', 'group.admin'])
60 perm_set=['group.write', 'group.admin'])
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @NotAnonymous()
66 @NotAnonymous()
67 # perms check inside
67 # perms check inside
68 @view_config(
68 @view_config(
69 route_name='repos', request_method='GET',
69 route_name='repos', request_method='GET',
70 renderer='rhodecode:templates/admin/repos/repos.mako')
70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 def repository_list(self):
71 def repository_list(self):
72 c = self.load_default_context()
72 c = self.load_default_context()
73 return self._get_template_context(c)
73
74
74 repo_list = Repository.get_all_repos()
75 @LoginRequired()
75 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
76 @NotAnonymous()
77 # perms check inside
78 @view_config(
79 route_name='repos_data', request_method='GET',
80 renderer='json_ext', xhr=True)
81 def repository_list_data(self):
82 self.load_default_context()
83 column_map = {
84 'name_raw': 'repo_name',
85 'desc': 'description',
86 'last_change_raw': 'updated_on',
87 'owner': 'user_username',
88 }
89 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
92
93 _perms = ['repository.admin']
94 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(_perms)
95
96 repos_data_total_count = Repository.query() \
97 .filter(or_(
98 # generate multiple IN to fix limitation problems
99 *in_filter_generator(Repository.repo_id, allowed_ids))
100 ) \
101 .count()
102
103 base_q = Session.query(
104 Repository.repo_id,
105 Repository.repo_name,
106 Repository.description,
107 Repository.repo_type,
108 Repository.repo_state,
109 Repository.private,
110 Repository.archived,
111 Repository.fork,
112 Repository.updated_on,
113 Repository._changeset_cache,
114 User,
115 ) \
116 .filter(or_(
117 # generate multiple IN to fix limitation problems
118 *in_filter_generator(Repository.repo_id, allowed_ids))
119 ) \
120 .join(User, User.user_id == Repository.user_id) \
121 .group_by(Repository, User)
122
123 if search_q:
124 like_expression = u'%{}%'.format(safe_unicode(search_q))
125 base_q = base_q.filter(or_(
126 Repository.repo_name.ilike(like_expression),
127 ))
128
129 repos_data_total_filtered_count = base_q.count()
130
131 sort_defined = False
132 if order_by == 'repo_name':
133 sort_col = func.lower(Repository.repo_name)
134 sort_defined = True
135 elif order_by == 'user_username':
136 sort_col = User.username
137 else:
138 sort_col = getattr(Repository, order_by, None)
139
140 if sort_defined or sort_col:
141 if order_dir == 'asc':
142 sort_col = sort_col.asc()
143 else:
144 sort_col = sort_col.desc()
145
146 base_q = base_q.order_by(sort_col)
147 base_q = base_q.offset(start).limit(limit)
148
149 repos_list = base_q.all()
150
76 repos_data = RepoModel().get_repos_as_dict(
151 repos_data = RepoModel().get_repos_as_dict(
77 repo_list=c.repo_list, admin=True, super_user_actions=True)
152 repo_list=repos_list, admin=True, super_user_actions=True)
78 # json used to render the grid
79 c.data = json.dumps(repos_data)
80
153
81 return self._get_template_context(c)
154 data = ({
155 'draw': draw,
156 'data': repos_data,
157 'recordsTotal': repos_data_total_count,
158 'recordsFiltered': repos_data_total_filtered_count,
159 })
160 return data
82
161
83 @LoginRequired()
162 @LoginRequired()
84 @NotAnonymous()
163 @NotAnonymous()
85 # perms check inside
164 # perms check inside
86 @view_config(
165 @view_config(
87 route_name='repo_new', request_method='GET',
166 route_name='repo_new', request_method='GET',
88 renderer='rhodecode:templates/admin/repos/repo_add.mako')
167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
89 def repository_new(self):
168 def repository_new(self):
90 c = self.load_default_context()
169 c = self.load_default_context()
91
170
92 new_repo = self.request.GET.get('repo', '')
171 new_repo = self.request.GET.get('repo', '')
93 parent_group = safe_int(self.request.GET.get('parent_group'))
172 parent_group = safe_int(self.request.GET.get('parent_group'))
94 _gr = RepoGroup.get(parent_group)
173 _gr = RepoGroup.get(parent_group)
95
174
96 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
97 # you're not super admin nor have global create permissions,
176 # you're not super admin nor have global create permissions,
98 # but maybe you have at least write permission to a parent group ?
177 # but maybe you have at least write permission to a parent group ?
99
178
100 gr_name = _gr.group_name if _gr else None
179 gr_name = _gr.group_name if _gr else None
101 # create repositories with write permission on group is set to true
180 # create repositories with write permission on group is set to true
102 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
103 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
104 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
105 if not (group_admin or (group_write and create_on_write)):
184 if not (group_admin or (group_write and create_on_write)):
106 raise HTTPForbidden()
185 raise HTTPForbidden()
107
186
108 self._load_form_data(c)
187 self._load_form_data(c)
109 c.new_repo = repo_name_slug(new_repo)
188 c.new_repo = repo_name_slug(new_repo)
110
189
111 # apply the defaults from defaults page
190 # apply the defaults from defaults page
112 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
113 # set checkbox to autochecked
192 # set checkbox to autochecked
114 defaults['repo_copy_permissions'] = True
193 defaults['repo_copy_permissions'] = True
115
194
116 parent_group_choice = '-1'
195 parent_group_choice = '-1'
117 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
118 parent_group_choice = self._rhodecode_user.personal_repo_group
197 parent_group_choice = self._rhodecode_user.personal_repo_group
119
198
120 if parent_group and _gr:
199 if parent_group and _gr:
121 if parent_group in [x[0] for x in c.repo_groups]:
200 if parent_group in [x[0] for x in c.repo_groups]:
122 parent_group_choice = safe_unicode(parent_group)
201 parent_group_choice = safe_unicode(parent_group)
123
202
124 defaults.update({'repo_group': parent_group_choice})
203 defaults.update({'repo_group': parent_group_choice})
125
204
126 data = render('rhodecode:templates/admin/repos/repo_add.mako',
205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
127 self._get_template_context(c), self.request)
206 self._get_template_context(c), self.request)
128 html = formencode.htmlfill.render(
207 html = formencode.htmlfill.render(
129 data,
208 data,
130 defaults=defaults,
209 defaults=defaults,
131 encoding="UTF-8",
210 encoding="UTF-8",
132 force_defaults=False
211 force_defaults=False
133 )
212 )
134 return Response(html)
213 return Response(html)
135
214
136 @LoginRequired()
215 @LoginRequired()
137 @NotAnonymous()
216 @NotAnonymous()
138 @CSRFRequired()
217 @CSRFRequired()
139 # perms check inside
218 # perms check inside
140 @view_config(
219 @view_config(
141 route_name='repo_create', request_method='POST',
220 route_name='repo_create', request_method='POST',
142 renderer='rhodecode:templates/admin/repos/repos.mako')
221 renderer='rhodecode:templates/admin/repos/repos.mako')
143 def repository_create(self):
222 def repository_create(self):
144 c = self.load_default_context()
223 c = self.load_default_context()
145
224
146 form_result = {}
225 form_result = {}
147 self._load_form_data(c)
226 self._load_form_data(c)
148
227
149 try:
228 try:
150 # CanWriteToGroup validators checks permissions of this POST
229 # CanWriteToGroup validators checks permissions of this POST
151 form = RepoForm(
230 form = RepoForm(
152 self.request.translate, repo_groups=c.repo_groups_choices)()
231 self.request.translate, repo_groups=c.repo_groups_choices)()
153 form_result = form.to_python(dict(self.request.POST))
232 form_result = form.to_python(dict(self.request.POST))
154 copy_permissions = form_result.get('repo_copy_permissions')
233 copy_permissions = form_result.get('repo_copy_permissions')
155 # create is done sometimes async on celery, db transaction
234 # create is done sometimes async on celery, db transaction
156 # management is handled there.
235 # management is handled there.
157 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
158 task_id = get_task_id(task)
237 task_id = get_task_id(task)
159 except formencode.Invalid as errors:
238 except formencode.Invalid as errors:
160 data = render('rhodecode:templates/admin/repos/repo_add.mako',
239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
161 self._get_template_context(c), self.request)
240 self._get_template_context(c), self.request)
162 html = formencode.htmlfill.render(
241 html = formencode.htmlfill.render(
163 data,
242 data,
164 defaults=errors.value,
243 defaults=errors.value,
165 errors=errors.error_dict or {},
244 errors=errors.error_dict or {},
166 prefix_error=False,
245 prefix_error=False,
167 encoding="UTF-8",
246 encoding="UTF-8",
168 force_defaults=False
247 force_defaults=False
169 )
248 )
170 return Response(html)
249 return Response(html)
171
250
172 except Exception as e:
251 except Exception as e:
173 msg = self._log_creation_exception(e, form_result.get('repo_name'))
252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
174 h.flash(msg, category='error')
253 h.flash(msg, category='error')
175 raise HTTPFound(h.route_path('home'))
254 raise HTTPFound(h.route_path('home'))
176
255
177 repo_name = form_result.get('repo_name_full')
256 repo_name = form_result.get('repo_name_full')
178
257
179 affected_user_ids = [self._rhodecode_user.user_id]
258 affected_user_ids = [self._rhodecode_user.user_id]
180 if copy_permissions:
259 if copy_permissions:
181 # permission flush is done in repo creating
260 # permission flush is done in repo creating
182 pass
261 pass
183 PermissionModel().trigger_permission_flush(affected_user_ids)
262 PermissionModel().trigger_permission_flush(affected_user_ids)
184
263
185 raise HTTPFound(
264 raise HTTPFound(
186 h.route_path('repo_creating', repo_name=repo_name,
265 h.route_path('repo_creating', repo_name=repo_name,
187 _query=dict(task_id=task_id)))
266 _query=dict(task_id=task_id)))
@@ -1,273 +1,269 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.response import Response
28 from pyramid.response import Response
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 from rhodecode.lib import helpers as h, audit_logger
35 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib.utils2 import safe_unicode
36 from rhodecode.lib.utils2 import safe_unicode
37
37
38 from rhodecode.model.forms import UserGroupForm
38 from rhodecode.model.forms import UserGroupForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.scm import UserGroupList
40 from rhodecode.model.scm import UserGroupList
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.user_group import UserGroupModel
44 from rhodecode.model.user_group import UserGroupModel
45 from rhodecode.model.db import true
45 from rhodecode.model.db import true
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 PermissionModel().set_global_permission_choices(
55 PermissionModel().set_global_permission_choices(
56 c, gettext_translator=self.request.translate)
56 c, gettext_translator=self.request.translate)
57
57
58 return c
58 return c
59
59
60 # permission check in data loading of
60 # permission check in data loading of
61 # `user_groups_list_data` via UserGroupList
61 # `user_groups_list_data` via UserGroupList
62 @LoginRequired()
62 @LoginRequired()
63 @NotAnonymous()
63 @NotAnonymous()
64 @view_config(
64 @view_config(
65 route_name='user_groups', request_method='GET',
65 route_name='user_groups', request_method='GET',
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 def user_groups_list(self):
67 def user_groups_list(self):
68 c = self.load_default_context()
68 c = self.load_default_context()
69 return self._get_template_context(c)
69 return self._get_template_context(c)
70
70
71 # permission check inside
71 # permission check inside
72 @LoginRequired()
72 @LoginRequired()
73 @NotAnonymous()
73 @NotAnonymous()
74 @view_config(
74 @view_config(
75 route_name='user_groups_data', request_method='GET',
75 route_name='user_groups_data', request_method='GET',
76 renderer='json_ext', xhr=True)
76 renderer='json_ext', xhr=True)
77 def user_groups_list_data(self):
77 def user_groups_list_data(self):
78 self.load_default_context()
78 self.load_default_context()
79 column_map = {
79 column_map = {
80 'active': 'users_group_active',
80 'active': 'users_group_active',
81 'description': 'user_group_description',
81 'description': 'user_group_description',
82 'members': 'members_total',
82 'members': 'members_total',
83 'owner': 'user_username',
83 'owner': 'user_username',
84 'sync': 'group_data'
84 'sync': 'group_data'
85 }
85 }
86 draw, start, limit = self._extract_chunk(self.request)
86 draw, start, limit = self._extract_chunk(self.request)
87 search_q, order_by, order_dir = self._extract_ordering(
87 search_q, order_by, order_dir = self._extract_ordering(
88 self.request, column_map=column_map)
88 self.request, column_map=column_map)
89
89
90 _render = self.request.get_partial_renderer(
90 _render = self.request.get_partial_renderer(
91 'rhodecode:templates/data_table/_dt_elements.mako')
91 'rhodecode:templates/data_table/_dt_elements.mako')
92
92
93 def user_group_name(user_group_name):
93 def user_group_name(user_group_name):
94 return _render("user_group_name", user_group_name)
94 return _render("user_group_name", user_group_name)
95
95
96 def user_group_actions(user_group_id, user_group_name):
96 def user_group_actions(user_group_id, user_group_name):
97 return _render("user_group_actions", user_group_id, user_group_name)
97 return _render("user_group_actions", user_group_id, user_group_name)
98
98
99 def user_profile(username):
99 def user_profile(username):
100 return _render('user_profile', username)
100 return _render('user_profile', username)
101
101
102 auth_user_group_list = UserGroupList(
102 _perms = ['usergroup.admin']
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
103 allowed_ids = [-1] + self._rhodecode_user.user_group_acl_ids_from_stack(_perms)
104
105 allowed_ids = [-1]
106 for user_group in auth_user_group_list:
107 allowed_ids.append(user_group.users_group_id)
108
104
109 user_groups_data_total_count = UserGroup.query()\
105 user_groups_data_total_count = UserGroup.query()\
110 .filter(or_(
106 .filter(or_(
111 # generate multiple IN to fix limitation problems
107 # generate multiple IN to fix limitation problems
112 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
108 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
113 ))\
109 ))\
114 .count()
110 .count()
115
111
116 user_groups_data_total_inactive_count = UserGroup.query()\
112 user_groups_data_total_inactive_count = UserGroup.query()\
117 .filter(or_(
113 .filter(or_(
118 # generate multiple IN to fix limitation problems
114 # generate multiple IN to fix limitation problems
119 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
115 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
120 ))\
116 ))\
121 .filter(UserGroup.users_group_active != true()).count()
117 .filter(UserGroup.users_group_active != true()).count()
122
118
123 member_count = count(UserGroupMember.user_id)
119 member_count = count(UserGroupMember.user_id)
124 base_q = Session.query(
120 base_q = Session.query(
125 UserGroup.users_group_name,
121 UserGroup.users_group_name,
126 UserGroup.user_group_description,
122 UserGroup.user_group_description,
127 UserGroup.users_group_active,
123 UserGroup.users_group_active,
128 UserGroup.users_group_id,
124 UserGroup.users_group_id,
129 UserGroup.group_data,
125 UserGroup.group_data,
130 User,
126 User,
131 member_count.label('member_count')
127 member_count.label('member_count')
132 ) \
128 ) \
133 .filter(or_(
129 .filter(or_(
134 # generate multiple IN to fix limitation problems
130 # generate multiple IN to fix limitation problems
135 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
131 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
136 )) \
132 )) \
137 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
133 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
138 .join(User, User.user_id == UserGroup.user_id) \
134 .join(User, User.user_id == UserGroup.user_id) \
139 .group_by(UserGroup, User)
135 .group_by(UserGroup, User)
140
136
141 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
137 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
142
138
143 if search_q:
139 if search_q:
144 like_expression = u'%{}%'.format(safe_unicode(search_q))
140 like_expression = u'%{}%'.format(safe_unicode(search_q))
145 base_q = base_q.filter(or_(
141 base_q = base_q.filter(or_(
146 UserGroup.users_group_name.ilike(like_expression),
142 UserGroup.users_group_name.ilike(like_expression),
147 ))
143 ))
148 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
144 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
149
145
150 user_groups_data_total_filtered_count = base_q.count()
146 user_groups_data_total_filtered_count = base_q.count()
151 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
147 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
152
148
153 sort_defined = False
149 sort_defined = False
154 if order_by == 'members_total':
150 if order_by == 'members_total':
155 sort_col = member_count
151 sort_col = member_count
156 sort_defined = True
152 sort_defined = True
157 elif order_by == 'user_username':
153 elif order_by == 'user_username':
158 sort_col = User.username
154 sort_col = User.username
159 else:
155 else:
160 sort_col = getattr(UserGroup, order_by, None)
156 sort_col = getattr(UserGroup, order_by, None)
161
157
162 if sort_defined or sort_col:
158 if sort_defined or sort_col:
163 if order_dir == 'asc':
159 if order_dir == 'asc':
164 sort_col = sort_col.asc()
160 sort_col = sort_col.asc()
165 else:
161 else:
166 sort_col = sort_col.desc()
162 sort_col = sort_col.desc()
167
163
168 base_q = base_q.order_by(sort_col)
164 base_q = base_q.order_by(sort_col)
169 base_q = base_q.offset(start).limit(limit)
165 base_q = base_q.offset(start).limit(limit)
170
166
171 # authenticated access to user groups
167 # authenticated access to user groups
172 auth_user_group_list = base_q.all()
168 auth_user_group_list = base_q.all()
173
169
174 user_groups_data = []
170 user_groups_data = []
175 for user_gr in auth_user_group_list:
171 for user_gr in auth_user_group_list:
176 row = {
172 row = {
177 "users_group_name": user_group_name(user_gr.users_group_name),
173 "users_group_name": user_group_name(user_gr.users_group_name),
178 "name_raw": h.escape(user_gr.users_group_name),
174 "name_raw": h.escape(user_gr.users_group_name),
179 "description": h.escape(user_gr.user_group_description),
175 "description": h.escape(user_gr.user_group_description),
180 "members": user_gr.member_count,
176 "members": user_gr.member_count,
181 # NOTE(marcink): because of advanced query we
177 # NOTE(marcink): because of advanced query we
182 # need to load it like that
178 # need to load it like that
183 "sync": UserGroup._load_sync(
179 "sync": UserGroup._load_sync(
184 UserGroup._load_group_data(user_gr.group_data)),
180 UserGroup._load_group_data(user_gr.group_data)),
185 "active": h.bool2icon(user_gr.users_group_active),
181 "active": h.bool2icon(user_gr.users_group_active),
186 "owner": user_profile(user_gr.User.username),
182 "owner": user_profile(user_gr.User.username),
187 "action": user_group_actions(
183 "action": user_group_actions(
188 user_gr.users_group_id, user_gr.users_group_name)
184 user_gr.users_group_id, user_gr.users_group_name)
189 }
185 }
190 user_groups_data.append(row)
186 user_groups_data.append(row)
191
187
192 data = ({
188 data = ({
193 'draw': draw,
189 'draw': draw,
194 'data': user_groups_data,
190 'data': user_groups_data,
195 'recordsTotal': user_groups_data_total_count,
191 'recordsTotal': user_groups_data_total_count,
196 'recordsTotalInactive': user_groups_data_total_inactive_count,
192 'recordsTotalInactive': user_groups_data_total_inactive_count,
197 'recordsFiltered': user_groups_data_total_filtered_count,
193 'recordsFiltered': user_groups_data_total_filtered_count,
198 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
194 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
199 })
195 })
200
196
201 return data
197 return data
202
198
203 @LoginRequired()
199 @LoginRequired()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
200 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
205 @view_config(
201 @view_config(
206 route_name='user_groups_new', request_method='GET',
202 route_name='user_groups_new', request_method='GET',
207 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
203 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
208 def user_groups_new(self):
204 def user_groups_new(self):
209 c = self.load_default_context()
205 c = self.load_default_context()
210 return self._get_template_context(c)
206 return self._get_template_context(c)
211
207
212 @LoginRequired()
208 @LoginRequired()
213 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
209 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
214 @CSRFRequired()
210 @CSRFRequired()
215 @view_config(
211 @view_config(
216 route_name='user_groups_create', request_method='POST',
212 route_name='user_groups_create', request_method='POST',
217 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
213 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
218 def user_groups_create(self):
214 def user_groups_create(self):
219 _ = self.request.translate
215 _ = self.request.translate
220 c = self.load_default_context()
216 c = self.load_default_context()
221 users_group_form = UserGroupForm(self.request.translate)()
217 users_group_form = UserGroupForm(self.request.translate)()
222
218
223 user_group_name = self.request.POST.get('users_group_name')
219 user_group_name = self.request.POST.get('users_group_name')
224 try:
220 try:
225 form_result = users_group_form.to_python(dict(self.request.POST))
221 form_result = users_group_form.to_python(dict(self.request.POST))
226 user_group = UserGroupModel().create(
222 user_group = UserGroupModel().create(
227 name=form_result['users_group_name'],
223 name=form_result['users_group_name'],
228 description=form_result['user_group_description'],
224 description=form_result['user_group_description'],
229 owner=self._rhodecode_user.user_id,
225 owner=self._rhodecode_user.user_id,
230 active=form_result['users_group_active'])
226 active=form_result['users_group_active'])
231 Session().flush()
227 Session().flush()
232 creation_data = user_group.get_api_data()
228 creation_data = user_group.get_api_data()
233 user_group_name = form_result['users_group_name']
229 user_group_name = form_result['users_group_name']
234
230
235 audit_logger.store_web(
231 audit_logger.store_web(
236 'user_group.create', action_data={'data': creation_data},
232 'user_group.create', action_data={'data': creation_data},
237 user=self._rhodecode_user)
233 user=self._rhodecode_user)
238
234
239 user_group_link = h.link_to(
235 user_group_link = h.link_to(
240 h.escape(user_group_name),
236 h.escape(user_group_name),
241 h.route_path(
237 h.route_path(
242 'edit_user_group', user_group_id=user_group.users_group_id))
238 'edit_user_group', user_group_id=user_group.users_group_id))
243 h.flash(h.literal(_('Created user group %(user_group_link)s')
239 h.flash(h.literal(_('Created user group %(user_group_link)s')
244 % {'user_group_link': user_group_link}),
240 % {'user_group_link': user_group_link}),
245 category='success')
241 category='success')
246 Session().commit()
242 Session().commit()
247 user_group_id = user_group.users_group_id
243 user_group_id = user_group.users_group_id
248 except formencode.Invalid as errors:
244 except formencode.Invalid as errors:
249
245
250 data = render(
246 data = render(
251 'rhodecode:templates/admin/user_groups/user_group_add.mako',
247 'rhodecode:templates/admin/user_groups/user_group_add.mako',
252 self._get_template_context(c), self.request)
248 self._get_template_context(c), self.request)
253 html = formencode.htmlfill.render(
249 html = formencode.htmlfill.render(
254 data,
250 data,
255 defaults=errors.value,
251 defaults=errors.value,
256 errors=errors.error_dict or {},
252 errors=errors.error_dict or {},
257 prefix_error=False,
253 prefix_error=False,
258 encoding="UTF-8",
254 encoding="UTF-8",
259 force_defaults=False
255 force_defaults=False
260 )
256 )
261 return Response(html)
257 return Response(html)
262
258
263 except Exception:
259 except Exception:
264 log.exception("Exception creating user group")
260 log.exception("Exception creating user group")
265 h.flash(_('Error occurred during creation of user group %s') \
261 h.flash(_('Error occurred during creation of user group %s') \
266 % user_group_name, category='error')
262 % user_group_name, category='error')
267 raise HTTPFound(h.route_path('user_groups_new'))
263 raise HTTPFound(h.route_path('user_groups_new'))
268
264
269 affected_user_ids = [self._rhodecode_user.user_id]
265 affected_user_ids = [self._rhodecode_user.user_id]
270 PermissionModel().trigger_permission_flush(affected_user_ids)
266 PermissionModel().trigger_permission_flush(affected_user_ids)
271
267
272 raise HTTPFound(
268 raise HTTPFound(
273 h.route_path('edit_user_group', user_group_id=user_group_id))
269 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,2368 +1,2410 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71 passwd_gen = PasswordGenerator()
71 passwd_gen = PasswordGenerator()
72 #print 8-letter password containing only big and small letters
72 #print 8-letter password containing only big and small letters
73 of alphabet
73 of alphabet
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 """
75 """
76 ALPHABETS_NUM = r'''1234567890'''
76 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86
86
87 def __init__(self, passwd=''):
87 def __init__(self, passwd=''):
88 self.passwd = passwd
88 self.passwd = passwd
89
89
90 def gen_password(self, length, type_=None):
90 def gen_password(self, length, type_=None):
91 if type_ is None:
91 if type_ is None:
92 type_ = self.ALPHABETS_FULL
92 type_ = self.ALPHABETS_FULL
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 return self.passwd
94 return self.passwd
95
95
96
96
97 class _RhodeCodeCryptoBase(object):
97 class _RhodeCodeCryptoBase(object):
98 ENC_PREF = None
98 ENC_PREF = None
99
99
100 def hash_create(self, str_):
100 def hash_create(self, str_):
101 """
101 """
102 hash the string using
102 hash the string using
103
103
104 :param str_: password to hash
104 :param str_: password to hash
105 """
105 """
106 raise NotImplementedError
106 raise NotImplementedError
107
107
108 def hash_check_with_upgrade(self, password, hashed):
108 def hash_check_with_upgrade(self, password, hashed):
109 """
109 """
110 Returns tuple in which first element is boolean that states that
110 Returns tuple in which first element is boolean that states that
111 given password matches it's hashed version, and the second is new hash
111 given password matches it's hashed version, and the second is new hash
112 of the password, in case this password should be migrated to new
112 of the password, in case this password should be migrated to new
113 cipher.
113 cipher.
114 """
114 """
115 checked_hash = self.hash_check(password, hashed)
115 checked_hash = self.hash_check(password, hashed)
116 return checked_hash, None
116 return checked_hash, None
117
117
118 def hash_check(self, password, hashed):
118 def hash_check(self, password, hashed):
119 """
119 """
120 Checks matching password with it's hashed value.
120 Checks matching password with it's hashed value.
121
121
122 :param password: password
122 :param password: password
123 :param hashed: password in hashed form
123 :param hashed: password in hashed form
124 """
124 """
125 raise NotImplementedError
125 raise NotImplementedError
126
126
127 def _assert_bytes(self, value):
127 def _assert_bytes(self, value):
128 """
128 """
129 Passing in an `unicode` object can lead to hard to detect issues
129 Passing in an `unicode` object can lead to hard to detect issues
130 if passwords contain non-ascii characters. Doing a type check
130 if passwords contain non-ascii characters. Doing a type check
131 during runtime, so that such mistakes are detected early on.
131 during runtime, so that such mistakes are detected early on.
132 """
132 """
133 if not isinstance(value, str):
133 if not isinstance(value, str):
134 raise TypeError(
134 raise TypeError(
135 "Bytestring required as input, got %r." % (value, ))
135 "Bytestring required as input, got %r." % (value, ))
136
136
137
137
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 ENC_PREF = ('$2a$10', '$2b$10')
139 ENC_PREF = ('$2a$10', '$2b$10')
140
140
141 def hash_create(self, str_):
141 def hash_create(self, str_):
142 self._assert_bytes(str_)
142 self._assert_bytes(str_)
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144
144
145 def hash_check_with_upgrade(self, password, hashed):
145 def hash_check_with_upgrade(self, password, hashed):
146 """
146 """
147 Returns tuple in which first element is boolean that states that
147 Returns tuple in which first element is boolean that states that
148 given password matches it's hashed version, and the second is new hash
148 given password matches it's hashed version, and the second is new hash
149 of the password, in case this password should be migrated to new
149 of the password, in case this password should be migrated to new
150 cipher.
150 cipher.
151
151
152 This implements special upgrade logic which works like that:
152 This implements special upgrade logic which works like that:
153 - check if the given password == bcrypted hash, if yes then we
153 - check if the given password == bcrypted hash, if yes then we
154 properly used password and it was already in bcrypt. Proceed
154 properly used password and it was already in bcrypt. Proceed
155 without any changes
155 without any changes
156 - if bcrypt hash check is not working try with sha256. If hash compare
156 - if bcrypt hash check is not working try with sha256. If hash compare
157 is ok, it means we using correct but old hashed password. indicate
157 is ok, it means we using correct but old hashed password. indicate
158 hash change and proceed
158 hash change and proceed
159 """
159 """
160
160
161 new_hash = None
161 new_hash = None
162
162
163 # regular pw check
163 # regular pw check
164 password_match_bcrypt = self.hash_check(password, hashed)
164 password_match_bcrypt = self.hash_check(password, hashed)
165
165
166 # now we want to know if the password was maybe from sha256
166 # now we want to know if the password was maybe from sha256
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 if not password_match_bcrypt:
168 if not password_match_bcrypt:
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 new_hash = self.hash_create(password) # make new bcrypt hash
170 new_hash = self.hash_create(password) # make new bcrypt hash
171 password_match_bcrypt = True
171 password_match_bcrypt = True
172
172
173 return password_match_bcrypt, new_hash
173 return password_match_bcrypt, new_hash
174
174
175 def hash_check(self, password, hashed):
175 def hash_check(self, password, hashed):
176 """
176 """
177 Checks matching password with it's hashed value.
177 Checks matching password with it's hashed value.
178
178
179 :param password: password
179 :param password: password
180 :param hashed: password in hashed form
180 :param hashed: password in hashed form
181 """
181 """
182 self._assert_bytes(password)
182 self._assert_bytes(password)
183 try:
183 try:
184 return bcrypt.hashpw(password, hashed) == hashed
184 return bcrypt.hashpw(password, hashed) == hashed
185 except ValueError as e:
185 except ValueError as e:
186 # we're having a invalid salt here probably, we should not crash
186 # we're having a invalid salt here probably, we should not crash
187 # just return with False as it would be a wrong password.
187 # just return with False as it would be a wrong password.
188 log.debug('Failed to check password hash using bcrypt %s',
188 log.debug('Failed to check password hash using bcrypt %s',
189 safe_str(e))
189 safe_str(e))
190
190
191 return False
191 return False
192
192
193
193
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 ENC_PREF = '_'
195 ENC_PREF = '_'
196
196
197 def hash_create(self, str_):
197 def hash_create(self, str_):
198 self._assert_bytes(str_)
198 self._assert_bytes(str_)
199 return hashlib.sha256(str_).hexdigest()
199 return hashlib.sha256(str_).hexdigest()
200
200
201 def hash_check(self, password, hashed):
201 def hash_check(self, password, hashed):
202 """
202 """
203 Checks matching password with it's hashed value.
203 Checks matching password with it's hashed value.
204
204
205 :param password: password
205 :param password: password
206 :param hashed: password in hashed form
206 :param hashed: password in hashed form
207 """
207 """
208 self._assert_bytes(password)
208 self._assert_bytes(password)
209 return hashlib.sha256(password).hexdigest() == hashed
209 return hashlib.sha256(password).hexdigest() == hashed
210
210
211
211
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 ENC_PREF = '_'
213 ENC_PREF = '_'
214
214
215 def hash_create(self, str_):
215 def hash_create(self, str_):
216 self._assert_bytes(str_)
216 self._assert_bytes(str_)
217 return sha1(str_)
217 return sha1(str_)
218
218
219 def hash_check(self, password, hashed):
219 def hash_check(self, password, hashed):
220 """
220 """
221 Checks matching password with it's hashed value.
221 Checks matching password with it's hashed value.
222
222
223 :param password: password
223 :param password: password
224 :param hashed: password in hashed form
224 :param hashed: password in hashed form
225 """
225 """
226 self._assert_bytes(password)
226 self._assert_bytes(password)
227 return sha1(password) == hashed
227 return sha1(password) == hashed
228
228
229
229
230 def crypto_backend():
230 def crypto_backend():
231 """
231 """
232 Return the matching crypto backend.
232 Return the matching crypto backend.
233
233
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 tests faster since BCRYPT is expensive to calculate
235 tests faster since BCRYPT is expensive to calculate
236 """
236 """
237 if rhodecode.is_test:
237 if rhodecode.is_test:
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 else:
239 else:
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241
241
242 return RhodeCodeCrypto
242 return RhodeCodeCrypto
243
243
244
244
245 def get_crypt_password(password):
245 def get_crypt_password(password):
246 """
246 """
247 Create the hash of `password` with the active crypto backend.
247 Create the hash of `password` with the active crypto backend.
248
248
249 :param password: The cleartext password.
249 :param password: The cleartext password.
250 :type password: unicode
250 :type password: unicode
251 """
251 """
252 password = safe_str(password)
252 password = safe_str(password)
253 return crypto_backend().hash_create(password)
253 return crypto_backend().hash_create(password)
254
254
255
255
256 def check_password(password, hashed):
256 def check_password(password, hashed):
257 """
257 """
258 Check if the value in `password` matches the hash in `hashed`.
258 Check if the value in `password` matches the hash in `hashed`.
259
259
260 :param password: The cleartext password.
260 :param password: The cleartext password.
261 :type password: unicode
261 :type password: unicode
262
262
263 :param hashed: The expected hashed version of the password.
263 :param hashed: The expected hashed version of the password.
264 :type hashed: The hash has to be passed in in text representation.
264 :type hashed: The hash has to be passed in in text representation.
265 """
265 """
266 password = safe_str(password)
266 password = safe_str(password)
267 return crypto_backend().hash_check(password, hashed)
267 return crypto_backend().hash_check(password, hashed)
268
268
269
269
270 def generate_auth_token(data, salt=None):
270 def generate_auth_token(data, salt=None):
271 """
271 """
272 Generates API KEY from given string
272 Generates API KEY from given string
273 """
273 """
274
274
275 if salt is None:
275 if salt is None:
276 salt = os.urandom(16)
276 salt = os.urandom(16)
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278
278
279
279
280 def get_came_from(request):
280 def get_came_from(request):
281 """
281 """
282 get query_string+path from request sanitized after removing auth_token
282 get query_string+path from request sanitized after removing auth_token
283 """
283 """
284 _req = request
284 _req = request
285
285
286 path = _req.path
286 path = _req.path
287 if 'auth_token' in _req.GET:
287 if 'auth_token' in _req.GET:
288 # sanitize the request and remove auth_token for redirection
288 # sanitize the request and remove auth_token for redirection
289 _req.GET.pop('auth_token')
289 _req.GET.pop('auth_token')
290 qs = _req.query_string
290 qs = _req.query_string
291 if qs:
291 if qs:
292 path += '?' + qs
292 path += '?' + qs
293
293
294 return path
294 return path
295
295
296
296
297 class CookieStoreWrapper(object):
297 class CookieStoreWrapper(object):
298
298
299 def __init__(self, cookie_store):
299 def __init__(self, cookie_store):
300 self.cookie_store = cookie_store
300 self.cookie_store = cookie_store
301
301
302 def __repr__(self):
302 def __repr__(self):
303 return 'CookieStore<%s>' % (self.cookie_store)
303 return 'CookieStore<%s>' % (self.cookie_store)
304
304
305 def get(self, key, other=None):
305 def get(self, key, other=None):
306 if isinstance(self.cookie_store, dict):
306 if isinstance(self.cookie_store, dict):
307 return self.cookie_store.get(key, other)
307 return self.cookie_store.get(key, other)
308 elif isinstance(self.cookie_store, AuthUser):
308 elif isinstance(self.cookie_store, AuthUser):
309 return self.cookie_store.__dict__.get(key, other)
309 return self.cookie_store.__dict__.get(key, other)
310
310
311
311
312 def _cached_perms_data(user_id, scope, user_is_admin,
312 def _cached_perms_data(user_id, scope, user_is_admin,
313 user_inherit_default_permissions, explicit, algo,
313 user_inherit_default_permissions, explicit, algo,
314 calculate_super_admin):
314 calculate_super_admin):
315
315
316 permissions = PermissionCalculator(
316 permissions = PermissionCalculator(
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 explicit, algo, calculate_super_admin)
318 explicit, algo, calculate_super_admin)
319 return permissions.calculate()
319 return permissions.calculate()
320
320
321
321
322 class PermOrigin(object):
322 class PermOrigin(object):
323 SUPER_ADMIN = 'superadmin'
323 SUPER_ADMIN = 'superadmin'
324 ARCHIVED = 'archived'
324 ARCHIVED = 'archived'
325
325
326 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
332
332
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
338
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
344
345
345
346 class PermOriginDict(dict):
346 class PermOriginDict(dict):
347 """
347 """
348 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
349
349
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
353
354 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default', 1
356 >>> perms['resource']
356 >>> perms['resource']
357 'read'
357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin', 2
359 >>> perms['resource']
359 >>> perms['resource']
360 'write'
360 'write'
361 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 """
363 """
364
364
365 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
368
368
369 def __setitem__(self, key, (perm, origin)):
369 def __setitem__(self, key, (perm, origin, obj_id)):
370 self.perm_origin_stack.setdefault(key, []).append(
370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
371 (perm, origin, obj_id))
372 dict.__setitem__(self, key, perm)
372 dict.__setitem__(self, key, perm)
373
373
374
374
375 class BranchPermOriginDict(PermOriginDict):
375 class BranchPermOriginDict(PermOriginDict):
376 """
376 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
377 Dedicated branch permissions dict, with tracking of patterns and origins.
378
378
379 >>> perms = BranchPermOriginDict()
379 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
381 >>> perms['resource']
382 {'*pattern': 'read'}
382 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
384 >>> perms['resource']
385 {'*pattern': 'write'}
385 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
386 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
388 """
389 def __setitem__(self, key, (pattern, perm, origin)):
389 def __setitem__(self, key, (pattern, perm, origin)):
390
390
391 self.perm_origin_stack.setdefault(key, {}) \
391 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
392 .setdefault(pattern, []).append((perm, origin))
393
393
394 if key in self:
394 if key in self:
395 self[key].__setitem__(pattern, perm)
395 self[key].__setitem__(pattern, perm)
396 else:
396 else:
397 patterns = collections.OrderedDict()
397 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
398 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
399 dict.__setitem__(self, key, patterns)
400
400
401
401
402 class PermissionCalculator(object):
402 class PermissionCalculator(object):
403
403
404 def __init__(
404 def __init__(
405 self, user_id, scope, user_is_admin,
405 self, user_id, scope, user_is_admin,
406 user_inherit_default_permissions, explicit, algo,
406 user_inherit_default_permissions, explicit, algo,
407 calculate_super_admin_as_user=False):
407 calculate_super_admin_as_user=False):
408
408
409 self.user_id = user_id
409 self.user_id = user_id
410 self.user_is_admin = user_is_admin
410 self.user_is_admin = user_is_admin
411 self.inherit_default_permissions = user_inherit_default_permissions
411 self.inherit_default_permissions = user_inherit_default_permissions
412 self.explicit = explicit
412 self.explicit = explicit
413 self.algo = algo
413 self.algo = algo
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415
415
416 scope = scope or {}
416 scope = scope or {}
417 self.scope_repo_id = scope.get('repo_id')
417 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
420
420
421 self.default_user_id = User.get_default_user(cache=True).user_id
421 self.default_user_id = User.get_default_user(cache=True).user_id
422
422
423 self.permissions_repositories = PermOriginDict()
423 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
427 self.permissions_global = set()
428
428
429 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
430 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
433 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
434 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
435 self.default_user_id, self.scope_user_group_id)
436
436
437 # default branch perms
437 # default branch perms
438 self.default_branch_repo_perms = \
438 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
439 Permission.get_default_repo_branch_perms(
440 self.default_user_id, self.scope_repo_id)
440 self.default_user_id, self.scope_repo_id)
441
441
442 def calculate(self):
442 def calculate(self):
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
444 return self._calculate_admin_permissions()
445
445
446 self._calculate_global_default_permissions()
446 self._calculate_global_default_permissions()
447 self._calculate_global_permissions()
447 self._calculate_global_permissions()
448 self._calculate_default_permissions()
448 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
449 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
450 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
451 self._calculate_repository_group_permissions()
452 self._calculate_user_group_permissions()
452 self._calculate_user_group_permissions()
453 return self._permission_structure()
453 return self._permission_structure()
454
454
455 def _calculate_admin_permissions(self):
455 def _calculate_admin_permissions(self):
456 """
456 """
457 admin user have all default rights for repositories
457 admin user have all default rights for repositories
458 and groups set to admin
458 and groups set to admin
459 """
459 """
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
462
463 # repositories
463 # repositories
464 for perm in self.default_repo_perms:
464 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 obj_id = perm.UserRepoToPerm.repository.repo_id
466 archived = perm.UserRepoToPerm.repository.archived
467 archived = perm.UserRepoToPerm.repository.archived
467 p = 'repository.admin'
468 p = 'repository.admin'
468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
469 # special case for archived repositories, which we block still even for
470 # special case for archived repositories, which we block still even for
470 # super admins
471 # super admins
471 if archived:
472 if archived:
472 p = 'repository.read'
473 p = 'repository.read'
473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
474 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
474
475
475 # repository groups
476 # repository groups
476 for perm in self.default_repo_groups_perms:
477 for perm in self.default_repo_groups_perms:
477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 rg_k = perm.UserRepoGroupToPerm.group.group_name
479 obj_id = perm.UserRepoGroupToPerm.group.group_id
478 p = 'group.admin'
480 p = 'group.admin'
479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
481 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
480
482
481 # user groups
483 # user groups
482 for perm in self.default_user_group_perms:
484 for perm in self.default_user_group_perms:
483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
485 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
486 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
484 p = 'usergroup.admin'
487 p = 'usergroup.admin'
485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
488 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
486
489
487 # branch permissions
490 # branch permissions
488 # since super-admin also can have custom rule permissions
491 # since super-admin also can have custom rule permissions
489 # we *always* need to calculate those inherited from default, and also explicit
492 # we *always* need to calculate those inherited from default, and also explicit
490 self._calculate_default_permissions_repository_branches(
493 self._calculate_default_permissions_repository_branches(
491 user_inherit_object_permissions=False)
494 user_inherit_object_permissions=False)
492 self._calculate_repository_branch_permissions()
495 self._calculate_repository_branch_permissions()
493
496
494 return self._permission_structure()
497 return self._permission_structure()
495
498
496 def _calculate_global_default_permissions(self):
499 def _calculate_global_default_permissions(self):
497 """
500 """
498 global permissions taken from the default user
501 global permissions taken from the default user
499 """
502 """
500 default_global_perms = UserToPerm.query()\
503 default_global_perms = UserToPerm.query()\
501 .filter(UserToPerm.user_id == self.default_user_id)\
504 .filter(UserToPerm.user_id == self.default_user_id)\
502 .options(joinedload(UserToPerm.permission))
505 .options(joinedload(UserToPerm.permission))
503
506
504 for perm in default_global_perms:
507 for perm in default_global_perms:
505 self.permissions_global.add(perm.permission.permission_name)
508 self.permissions_global.add(perm.permission.permission_name)
506
509
507 if self.user_is_admin:
510 if self.user_is_admin:
508 self.permissions_global.add('hg.admin')
511 self.permissions_global.add('hg.admin')
509 self.permissions_global.add('hg.create.write_on_repogroup.true')
512 self.permissions_global.add('hg.create.write_on_repogroup.true')
510
513
511 def _calculate_global_permissions(self):
514 def _calculate_global_permissions(self):
512 """
515 """
513 Set global system permissions with user permissions or permissions
516 Set global system permissions with user permissions or permissions
514 taken from the user groups of the current user.
517 taken from the user groups of the current user.
515
518
516 The permissions include repo creating, repo group creating, forking
519 The permissions include repo creating, repo group creating, forking
517 etc.
520 etc.
518 """
521 """
519
522
520 # now we read the defined permissions and overwrite what we have set
523 # now we read the defined permissions and overwrite what we have set
521 # before those can be configured from groups or users explicitly.
524 # before those can be configured from groups or users explicitly.
522
525
523 # In case we want to extend this list we should make sure
526 # In case we want to extend this list we should make sure
524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
527 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 _configurable = frozenset([
528 _configurable = frozenset([
526 'hg.fork.none', 'hg.fork.repository',
529 'hg.fork.none', 'hg.fork.repository',
527 'hg.create.none', 'hg.create.repository',
530 'hg.create.none', 'hg.create.repository',
528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
531 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
532 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
533 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
534 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 ])
535 ])
533
536
534 # USER GROUPS comes first user group global permissions
537 # USER GROUPS comes first user group global permissions
535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
538 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 .options(joinedload(UserGroupToPerm.permission))\
539 .options(joinedload(UserGroupToPerm.permission))\
537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
540 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 UserGroupMember.users_group_id))\
541 UserGroupMember.users_group_id))\
539 .filter(UserGroupMember.user_id == self.user_id)\
542 .filter(UserGroupMember.user_id == self.user_id)\
540 .order_by(UserGroupToPerm.users_group_id)\
543 .order_by(UserGroupToPerm.users_group_id)\
541 .all()
544 .all()
542
545
543 # need to group here by groups since user can be in more than
546 # need to group here by groups since user can be in more than
544 # one group, so we get all groups
547 # one group, so we get all groups
545 _explicit_grouped_perms = [
548 _explicit_grouped_perms = [
546 [x, list(y)] for x, y in
549 [x, list(y)] for x, y in
547 itertools.groupby(user_perms_from_users_groups,
550 itertools.groupby(user_perms_from_users_groups,
548 lambda _x: _x.users_group)]
551 lambda _x: _x.users_group)]
549
552
550 for gr, perms in _explicit_grouped_perms:
553 for gr, perms in _explicit_grouped_perms:
551 # since user can be in multiple groups iterate over them and
554 # since user can be in multiple groups iterate over them and
552 # select the lowest permissions first (more explicit)
555 # select the lowest permissions first (more explicit)
553 # TODO(marcink): do this^^
556 # TODO(marcink): do this^^
554
557
555 # group doesn't inherit default permissions so we actually set them
558 # group doesn't inherit default permissions so we actually set them
556 if not gr.inherit_default_permissions:
559 if not gr.inherit_default_permissions:
557 # NEED TO IGNORE all previously set configurable permissions
560 # NEED TO IGNORE all previously set configurable permissions
558 # and replace them with explicitly set from this user
561 # and replace them with explicitly set from this user
559 # group permissions
562 # group permissions
560 self.permissions_global = self.permissions_global.difference(
563 self.permissions_global = self.permissions_global.difference(
561 _configurable)
564 _configurable)
562 for perm in perms:
565 for perm in perms:
563 self.permissions_global.add(perm.permission.permission_name)
566 self.permissions_global.add(perm.permission.permission_name)
564
567
565 # user explicit global permissions
568 # user explicit global permissions
566 user_perms = Session().query(UserToPerm)\
569 user_perms = Session().query(UserToPerm)\
567 .options(joinedload(UserToPerm.permission))\
570 .options(joinedload(UserToPerm.permission))\
568 .filter(UserToPerm.user_id == self.user_id).all()
571 .filter(UserToPerm.user_id == self.user_id).all()
569
572
570 if not self.inherit_default_permissions:
573 if not self.inherit_default_permissions:
571 # NEED TO IGNORE all configurable permissions and
574 # NEED TO IGNORE all configurable permissions and
572 # replace them with explicitly set from this user permissions
575 # replace them with explicitly set from this user permissions
573 self.permissions_global = self.permissions_global.difference(
576 self.permissions_global = self.permissions_global.difference(
574 _configurable)
577 _configurable)
575 for perm in user_perms:
578 for perm in user_perms:
576 self.permissions_global.add(perm.permission.permission_name)
579 self.permissions_global.add(perm.permission.permission_name)
577
580
578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
581 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 for perm in self.default_repo_perms:
582 for perm in self.default_repo_perms:
580 r_k = perm.UserRepoToPerm.repository.repo_name
583 r_k = perm.UserRepoToPerm.repository.repo_name
584 obj_id = perm.UserRepoToPerm.repository.repo_id
581 archived = perm.UserRepoToPerm.repository.archived
585 archived = perm.UserRepoToPerm.repository.archived
582 p = perm.Permission.permission_name
586 p = perm.Permission.permission_name
583 o = PermOrigin.REPO_DEFAULT
587 o = PermOrigin.REPO_DEFAULT
584 self.permissions_repositories[r_k] = p, o
588 self.permissions_repositories[r_k] = p, o, obj_id
585
589
586 # if we decide this user isn't inheriting permissions from
590 # if we decide this user isn't inheriting permissions from
587 # default user we set him to .none so only explicit
591 # default user we set him to .none so only explicit
588 # permissions work
592 # permissions work
589 if not user_inherit_object_permissions:
593 if not user_inherit_object_permissions:
590 p = 'repository.none'
594 p = 'repository.none'
591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
595 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 self.permissions_repositories[r_k] = p, o
596 self.permissions_repositories[r_k] = p, o, obj_id
593
597
594 if perm.Repository.private and not (
598 if perm.Repository.private and not (
595 perm.Repository.user_id == self.user_id):
599 perm.Repository.user_id == self.user_id):
596 # disable defaults for private repos,
600 # disable defaults for private repos,
597 p = 'repository.none'
601 p = 'repository.none'
598 o = PermOrigin.REPO_PRIVATE
602 o = PermOrigin.REPO_PRIVATE
599 self.permissions_repositories[r_k] = p, o
603 self.permissions_repositories[r_k] = p, o, obj_id
600
604
601 elif perm.Repository.user_id == self.user_id:
605 elif perm.Repository.user_id == self.user_id:
602 # set admin if owner
606 # set admin if owner
603 p = 'repository.admin'
607 p = 'repository.admin'
604 o = PermOrigin.REPO_OWNER
608 o = PermOrigin.REPO_OWNER
605 self.permissions_repositories[r_k] = p, o
609 self.permissions_repositories[r_k] = p, o, obj_id
606
610
607 if self.user_is_admin:
611 if self.user_is_admin:
608 p = 'repository.admin'
612 p = 'repository.admin'
609 o = PermOrigin.SUPER_ADMIN
613 o = PermOrigin.SUPER_ADMIN
610 self.permissions_repositories[r_k] = p, o
614 self.permissions_repositories[r_k] = p, o, obj_id
611
615
612 # finally in case of archived repositories, we downgrade higher
616 # finally in case of archived repositories, we downgrade higher
613 # permissions to read
617 # permissions to read
614 if archived:
618 if archived:
615 current_perm = self.permissions_repositories[r_k]
619 current_perm = self.permissions_repositories[r_k]
616 if current_perm in ['repository.write', 'repository.admin']:
620 if current_perm in ['repository.write', 'repository.admin']:
617 p = 'repository.read'
621 p = 'repository.read'
618 o = PermOrigin.ARCHIVED
622 o = PermOrigin.ARCHIVED
619 self.permissions_repositories[r_k] = p, o
623 self.permissions_repositories[r_k] = p, o, obj_id
620
624
621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
625 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 for perm in self.default_branch_repo_perms:
626 for perm in self.default_branch_repo_perms:
623
627
624 r_k = perm.UserRepoToPerm.repository.repo_name
628 r_k = perm.UserRepoToPerm.repository.repo_name
625 p = perm.Permission.permission_name
629 p = perm.Permission.permission_name
626 pattern = perm.UserToRepoBranchPermission.branch_pattern
630 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
631 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628
632
629 if not self.explicit:
633 if not self.explicit:
630 cur_perm = self.permissions_repository_branches.get(r_k)
634 cur_perm = self.permissions_repository_branches.get(r_k)
631 if cur_perm:
635 if cur_perm:
632 cur_perm = cur_perm[pattern]
636 cur_perm = cur_perm[pattern]
633 cur_perm = cur_perm or 'branch.none'
637 cur_perm = cur_perm or 'branch.none'
634
638
635 p = self._choose_permission(p, cur_perm)
639 p = self._choose_permission(p, cur_perm)
636
640
637 # NOTE(marcink): register all pattern/perm instances in this
641 # NOTE(marcink): register all pattern/perm instances in this
638 # special dict that aggregates entries
642 # special dict that aggregates entries
639 self.permissions_repository_branches[r_k] = pattern, p, o
643 self.permissions_repository_branches[r_k] = pattern, p, o
640
644
641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
645 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 for perm in self.default_repo_groups_perms:
646 for perm in self.default_repo_groups_perms:
643 rg_k = perm.UserRepoGroupToPerm.group.group_name
647 rg_k = perm.UserRepoGroupToPerm.group.group_name
648 obj_id = perm.UserRepoGroupToPerm.group.group_id
644 p = perm.Permission.permission_name
649 p = perm.Permission.permission_name
645 o = PermOrigin.REPOGROUP_DEFAULT
650 o = PermOrigin.REPOGROUP_DEFAULT
646 self.permissions_repository_groups[rg_k] = p, o
651 self.permissions_repository_groups[rg_k] = p, o, obj_id
647
652
648 # if we decide this user isn't inheriting permissions from default
653 # if we decide this user isn't inheriting permissions from default
649 # user we set him to .none so only explicit permissions work
654 # user we set him to .none so only explicit permissions work
650 if not user_inherit_object_permissions:
655 if not user_inherit_object_permissions:
651 p = 'group.none'
656 p = 'group.none'
652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
657 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
653 self.permissions_repository_groups[rg_k] = p, o
658 self.permissions_repository_groups[rg_k] = p, o, obj_id
654
659
655 if perm.RepoGroup.user_id == self.user_id:
660 if perm.RepoGroup.user_id == self.user_id:
656 # set admin if owner
661 # set admin if owner
657 p = 'group.admin'
662 p = 'group.admin'
658 o = PermOrigin.REPOGROUP_OWNER
663 o = PermOrigin.REPOGROUP_OWNER
659 self.permissions_repository_groups[rg_k] = p, o
664 self.permissions_repository_groups[rg_k] = p, o, obj_id
660
665
661 if self.user_is_admin:
666 if self.user_is_admin:
662 p = 'group.admin'
667 p = 'group.admin'
663 o = PermOrigin.SUPER_ADMIN
668 o = PermOrigin.SUPER_ADMIN
664 self.permissions_repository_groups[rg_k] = p, o
669 self.permissions_repository_groups[rg_k] = p, o, obj_id
665
670
666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
671 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 for perm in self.default_user_group_perms:
672 for perm in self.default_user_group_perms:
668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
673 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
674 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
669 p = perm.Permission.permission_name
675 p = perm.Permission.permission_name
670 o = PermOrigin.USERGROUP_DEFAULT
676 o = PermOrigin.USERGROUP_DEFAULT
671 self.permissions_user_groups[u_k] = p, o
677 self.permissions_user_groups[u_k] = p, o, obj_id
672
678
673 # if we decide this user isn't inheriting permissions from default
679 # if we decide this user isn't inheriting permissions from default
674 # user we set him to .none so only explicit permissions work
680 # user we set him to .none so only explicit permissions work
675 if not user_inherit_object_permissions:
681 if not user_inherit_object_permissions:
676 p = 'usergroup.none'
682 p = 'usergroup.none'
677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
683 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
678 self.permissions_user_groups[u_k] = p, o
684 self.permissions_user_groups[u_k] = p, o, obj_id
679
685
680 if perm.UserGroup.user_id == self.user_id:
686 if perm.UserGroup.user_id == self.user_id:
681 # set admin if owner
687 # set admin if owner
682 p = 'usergroup.admin'
688 p = 'usergroup.admin'
683 o = PermOrigin.USERGROUP_OWNER
689 o = PermOrigin.USERGROUP_OWNER
684 self.permissions_user_groups[u_k] = p, o
690 self.permissions_user_groups[u_k] = p, o, obj_id
685
691
686 if self.user_is_admin:
692 if self.user_is_admin:
687 p = 'usergroup.admin'
693 p = 'usergroup.admin'
688 o = PermOrigin.SUPER_ADMIN
694 o = PermOrigin.SUPER_ADMIN
689 self.permissions_user_groups[u_k] = p, o
695 self.permissions_user_groups[u_k] = p, o, obj_id
690
696
691 def _calculate_default_permissions(self):
697 def _calculate_default_permissions(self):
692 """
698 """
693 Set default user permissions for repositories, repository branches,
699 Set default user permissions for repositories, repository branches,
694 repository groups, user groups taken from the default user.
700 repository groups, user groups taken from the default user.
695
701
696 Calculate inheritance of object permissions based on what we have now
702 Calculate inheritance of object permissions based on what we have now
697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
703 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 explicitly set. Inherit is the opposite of .false being there.
704 explicitly set. Inherit is the opposite of .false being there.
699
705
700 .. note::
706 .. note::
701
707
702 the syntax is little bit odd but what we need to check here is
708 the syntax is little bit odd but what we need to check here is
703 the opposite of .false permission being in the list so even for
709 the opposite of .false permission being in the list so even for
704 inconsistent state when both .true/.false is there
710 inconsistent state when both .true/.false is there
705 .false is more important
711 .false is more important
706
712
707 """
713 """
708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
714 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 in self.permissions_global)
715 in self.permissions_global)
710
716
711 # default permissions inherited from `default` user permissions
717 # default permissions inherited from `default` user permissions
712 self._calculate_default_permissions_repositories(
718 self._calculate_default_permissions_repositories(
713 user_inherit_object_permissions)
719 user_inherit_object_permissions)
714
720
715 self._calculate_default_permissions_repository_branches(
721 self._calculate_default_permissions_repository_branches(
716 user_inherit_object_permissions)
722 user_inherit_object_permissions)
717
723
718 self._calculate_default_permissions_repository_groups(
724 self._calculate_default_permissions_repository_groups(
719 user_inherit_object_permissions)
725 user_inherit_object_permissions)
720
726
721 self._calculate_default_permissions_user_groups(
727 self._calculate_default_permissions_user_groups(
722 user_inherit_object_permissions)
728 user_inherit_object_permissions)
723
729
724 def _calculate_repository_permissions(self):
730 def _calculate_repository_permissions(self):
725 """
731 """
726 Repository access permissions for the current user.
732 Repository access permissions for the current user.
727
733
728 Check if the user is part of user groups for this repository and
734 Check if the user is part of user groups for this repository and
729 fill in the permission from it. `_choose_permission` decides of which
735 fill in the permission from it. `_choose_permission` decides of which
730 permission should be selected based on selected method.
736 permission should be selected based on selected method.
731 """
737 """
732
738
733 # user group for repositories permissions
739 # user group for repositories permissions
734 user_repo_perms_from_user_group = Permission\
740 user_repo_perms_from_user_group = Permission\
735 .get_default_repo_perms_from_user_group(
741 .get_default_repo_perms_from_user_group(
736 self.user_id, self.scope_repo_id)
742 self.user_id, self.scope_repo_id)
737
743
738 multiple_counter = collections.defaultdict(int)
744 multiple_counter = collections.defaultdict(int)
739 for perm in user_repo_perms_from_user_group:
745 for perm in user_repo_perms_from_user_group:
740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
746 r_k = perm.UserGroupRepoToPerm.repository.repo_name
747 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
741 multiple_counter[r_k] += 1
748 multiple_counter[r_k] += 1
742 p = perm.Permission.permission_name
749 p = perm.Permission.permission_name
743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
750 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 .users_group.users_group_name
751 .users_group.users_group_name
745
752
746 if multiple_counter[r_k] > 1:
753 if multiple_counter[r_k] > 1:
747 cur_perm = self.permissions_repositories[r_k]
754 cur_perm = self.permissions_repositories[r_k]
748 p = self._choose_permission(p, cur_perm)
755 p = self._choose_permission(p, cur_perm)
749
756
750 self.permissions_repositories[r_k] = p, o
757 self.permissions_repositories[r_k] = p, o, obj_id
751
758
752 if perm.Repository.user_id == self.user_id:
759 if perm.Repository.user_id == self.user_id:
753 # set admin if owner
760 # set admin if owner
754 p = 'repository.admin'
761 p = 'repository.admin'
755 o = PermOrigin.REPO_OWNER
762 o = PermOrigin.REPO_OWNER
756 self.permissions_repositories[r_k] = p, o
763 self.permissions_repositories[r_k] = p, o, obj_id
757
764
758 if self.user_is_admin:
765 if self.user_is_admin:
759 p = 'repository.admin'
766 p = 'repository.admin'
760 o = PermOrigin.SUPER_ADMIN
767 o = PermOrigin.SUPER_ADMIN
761 self.permissions_repositories[r_k] = p, o
768 self.permissions_repositories[r_k] = p, o, obj_id
762
769
763 # user explicit permissions for repositories, overrides any specified
770 # user explicit permissions for repositories, overrides any specified
764 # by the group permission
771 # by the group permission
765 user_repo_perms = Permission.get_default_repo_perms(
772 user_repo_perms = Permission.get_default_repo_perms(
766 self.user_id, self.scope_repo_id)
773 self.user_id, self.scope_repo_id)
767 for perm in user_repo_perms:
774 for perm in user_repo_perms:
768 r_k = perm.UserRepoToPerm.repository.repo_name
775 r_k = perm.UserRepoToPerm.repository.repo_name
776 obj_id = perm.UserRepoToPerm.repository.repo_id
769 p = perm.Permission.permission_name
777 p = perm.Permission.permission_name
770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771
779
772 if not self.explicit:
780 if not self.explicit:
773 cur_perm = self.permissions_repositories.get(
781 cur_perm = self.permissions_repositories.get(
774 r_k, 'repository.none')
782 r_k, 'repository.none')
775 p = self._choose_permission(p, cur_perm)
783 p = self._choose_permission(p, cur_perm)
776
784
777 self.permissions_repositories[r_k] = p, o
785 self.permissions_repositories[r_k] = p, o, obj_id
778
786
779 if perm.Repository.user_id == self.user_id:
787 if perm.Repository.user_id == self.user_id:
780 # set admin if owner
788 # set admin if owner
781 p = 'repository.admin'
789 p = 'repository.admin'
782 o = PermOrigin.REPO_OWNER
790 o = PermOrigin.REPO_OWNER
783 self.permissions_repositories[r_k] = p, o
791 self.permissions_repositories[r_k] = p, o, obj_id
784
792
785 if self.user_is_admin:
793 if self.user_is_admin:
786 p = 'repository.admin'
794 p = 'repository.admin'
787 o = PermOrigin.SUPER_ADMIN
795 o = PermOrigin.SUPER_ADMIN
788 self.permissions_repositories[r_k] = p, o
796 self.permissions_repositories[r_k] = p, o, obj_id
789
797
790 def _calculate_repository_branch_permissions(self):
798 def _calculate_repository_branch_permissions(self):
791 # user group for repositories permissions
799 # user group for repositories permissions
792 user_repo_branch_perms_from_user_group = Permission\
800 user_repo_branch_perms_from_user_group = Permission\
793 .get_default_repo_branch_perms_from_user_group(
801 .get_default_repo_branch_perms_from_user_group(
794 self.user_id, self.scope_repo_id)
802 self.user_id, self.scope_repo_id)
795
803
796 multiple_counter = collections.defaultdict(int)
804 multiple_counter = collections.defaultdict(int)
797 for perm in user_repo_branch_perms_from_user_group:
805 for perm in user_repo_branch_perms_from_user_group:
798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
806 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 p = perm.Permission.permission_name
807 p = perm.Permission.permission_name
800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
808 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
809 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 .users_group.users_group_name
810 .users_group.users_group_name
803
811
804 multiple_counter[r_k] += 1
812 multiple_counter[r_k] += 1
805 if multiple_counter[r_k] > 1:
813 if multiple_counter[r_k] > 1:
806 cur_perm = self.permissions_repository_branches[r_k][pattern]
814 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 p = self._choose_permission(p, cur_perm)
815 p = self._choose_permission(p, cur_perm)
808
816
809 self.permissions_repository_branches[r_k] = pattern, p, o
817 self.permissions_repository_branches[r_k] = pattern, p, o
810
818
811 # user explicit branch permissions for repositories, overrides
819 # user explicit branch permissions for repositories, overrides
812 # any specified by the group permission
820 # any specified by the group permission
813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
821 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 self.user_id, self.scope_repo_id)
822 self.user_id, self.scope_repo_id)
815
823
816 for perm in user_repo_branch_perms:
824 for perm in user_repo_branch_perms:
817
825
818 r_k = perm.UserRepoToPerm.repository.repo_name
826 r_k = perm.UserRepoToPerm.repository.repo_name
819 p = perm.Permission.permission_name
827 p = perm.Permission.permission_name
820 pattern = perm.UserToRepoBranchPermission.branch_pattern
828 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
829 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822
830
823 if not self.explicit:
831 if not self.explicit:
824 cur_perm = self.permissions_repository_branches.get(r_k)
832 cur_perm = self.permissions_repository_branches.get(r_k)
825 if cur_perm:
833 if cur_perm:
826 cur_perm = cur_perm[pattern]
834 cur_perm = cur_perm[pattern]
827 cur_perm = cur_perm or 'branch.none'
835 cur_perm = cur_perm or 'branch.none'
828 p = self._choose_permission(p, cur_perm)
836 p = self._choose_permission(p, cur_perm)
829
837
830 # NOTE(marcink): register all pattern/perm instances in this
838 # NOTE(marcink): register all pattern/perm instances in this
831 # special dict that aggregates entries
839 # special dict that aggregates entries
832 self.permissions_repository_branches[r_k] = pattern, p, o
840 self.permissions_repository_branches[r_k] = pattern, p, o
833
841
834 def _calculate_repository_group_permissions(self):
842 def _calculate_repository_group_permissions(self):
835 """
843 """
836 Repository group permissions for the current user.
844 Repository group permissions for the current user.
837
845
838 Check if the user is part of user groups for repository groups and
846 Check if the user is part of user groups for repository groups and
839 fill in the permissions from it. `_choose_permission` decides of which
847 fill in the permissions from it. `_choose_permission` decides of which
840 permission should be selected based on selected method.
848 permission should be selected based on selected method.
841 """
849 """
842 # user group for repo groups permissions
850 # user group for repo groups permissions
843 user_repo_group_perms_from_user_group = Permission\
851 user_repo_group_perms_from_user_group = Permission\
844 .get_default_group_perms_from_user_group(
852 .get_default_group_perms_from_user_group(
845 self.user_id, self.scope_repo_group_id)
853 self.user_id, self.scope_repo_group_id)
846
854
847 multiple_counter = collections.defaultdict(int)
855 multiple_counter = collections.defaultdict(int)
848 for perm in user_repo_group_perms_from_user_group:
856 for perm in user_repo_group_perms_from_user_group:
849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
857 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
858 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
850 multiple_counter[rg_k] += 1
859 multiple_counter[rg_k] += 1
851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
860 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 .users_group.users_group_name
861 .users_group.users_group_name
853 p = perm.Permission.permission_name
862 p = perm.Permission.permission_name
854
863
855 if multiple_counter[rg_k] > 1:
864 if multiple_counter[rg_k] > 1:
856 cur_perm = self.permissions_repository_groups[rg_k]
865 cur_perm = self.permissions_repository_groups[rg_k]
857 p = self._choose_permission(p, cur_perm)
866 p = self._choose_permission(p, cur_perm)
858 self.permissions_repository_groups[rg_k] = p, o
867 self.permissions_repository_groups[rg_k] = p, o, obj_id
859
868
860 if perm.RepoGroup.user_id == self.user_id:
869 if perm.RepoGroup.user_id == self.user_id:
861 # set admin if owner, even for member of other user group
870 # set admin if owner, even for member of other user group
862 p = 'group.admin'
871 p = 'group.admin'
863 o = PermOrigin.REPOGROUP_OWNER
872 o = PermOrigin.REPOGROUP_OWNER
864 self.permissions_repository_groups[rg_k] = p, o
873 self.permissions_repository_groups[rg_k] = p, o, obj_id
865
874
866 if self.user_is_admin:
875 if self.user_is_admin:
867 p = 'group.admin'
876 p = 'group.admin'
868 o = PermOrigin.SUPER_ADMIN
877 o = PermOrigin.SUPER_ADMIN
869 self.permissions_repository_groups[rg_k] = p, o
878 self.permissions_repository_groups[rg_k] = p, o, obj_id
870
879
871 # user explicit permissions for repository groups
880 # user explicit permissions for repository groups
872 user_repo_groups_perms = Permission.get_default_group_perms(
881 user_repo_groups_perms = Permission.get_default_group_perms(
873 self.user_id, self.scope_repo_group_id)
882 self.user_id, self.scope_repo_group_id)
874 for perm in user_repo_groups_perms:
883 for perm in user_repo_groups_perms:
875 rg_k = perm.UserRepoGroupToPerm.group.group_name
884 rg_k = perm.UserRepoGroupToPerm.group.group_name
885 obj_id = perm.UserRepoGroupToPerm.group.group_id
876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
886 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 .user.username
887 .user.username
878 p = perm.Permission.permission_name
888 p = perm.Permission.permission_name
879
889
880 if not self.explicit:
890 if not self.explicit:
881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
891 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 p = self._choose_permission(p, cur_perm)
892 p = self._choose_permission(p, cur_perm)
883
893
884 self.permissions_repository_groups[rg_k] = p, o
894 self.permissions_repository_groups[rg_k] = p, o, obj_id
885
895
886 if perm.RepoGroup.user_id == self.user_id:
896 if perm.RepoGroup.user_id == self.user_id:
887 # set admin if owner
897 # set admin if owner
888 p = 'group.admin'
898 p = 'group.admin'
889 o = PermOrigin.REPOGROUP_OWNER
899 o = PermOrigin.REPOGROUP_OWNER
890 self.permissions_repository_groups[rg_k] = p, o
900 self.permissions_repository_groups[rg_k] = p, o, obj_id
891
901
892 if self.user_is_admin:
902 if self.user_is_admin:
893 p = 'group.admin'
903 p = 'group.admin'
894 o = PermOrigin.SUPER_ADMIN
904 o = PermOrigin.SUPER_ADMIN
895 self.permissions_repository_groups[rg_k] = p, o
905 self.permissions_repository_groups[rg_k] = p, o, obj_id
896
906
897 def _calculate_user_group_permissions(self):
907 def _calculate_user_group_permissions(self):
898 """
908 """
899 User group permissions for the current user.
909 User group permissions for the current user.
900 """
910 """
901 # user group for user group permissions
911 # user group for user group permissions
902 user_group_from_user_group = Permission\
912 user_group_from_user_group = Permission\
903 .get_default_user_group_perms_from_user_group(
913 .get_default_user_group_perms_from_user_group(
904 self.user_id, self.scope_user_group_id)
914 self.user_id, self.scope_user_group_id)
905
915
906 multiple_counter = collections.defaultdict(int)
916 multiple_counter = collections.defaultdict(int)
907 for perm in user_group_from_user_group:
917 for perm in user_group_from_user_group:
908 ug_k = perm.UserGroupUserGroupToPerm\
918 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
909 .target_user_group.users_group_name
919 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
910 multiple_counter[ug_k] += 1
920 multiple_counter[ug_k] += 1
911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
921 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 .user_group.users_group_name
922 .user_group.users_group_name
913 p = perm.Permission.permission_name
923 p = perm.Permission.permission_name
914
924
915 if multiple_counter[ug_k] > 1:
925 if multiple_counter[ug_k] > 1:
916 cur_perm = self.permissions_user_groups[ug_k]
926 cur_perm = self.permissions_user_groups[ug_k]
917 p = self._choose_permission(p, cur_perm)
927 p = self._choose_permission(p, cur_perm)
918
928
919 self.permissions_user_groups[ug_k] = p, o
929 self.permissions_user_groups[ug_k] = p, o, obj_id
920
930
921 if perm.UserGroup.user_id == self.user_id:
931 if perm.UserGroup.user_id == self.user_id:
922 # set admin if owner, even for member of other user group
932 # set admin if owner, even for member of other user group
923 p = 'usergroup.admin'
933 p = 'usergroup.admin'
924 o = PermOrigin.USERGROUP_OWNER
934 o = PermOrigin.USERGROUP_OWNER
925 self.permissions_user_groups[ug_k] = p, o
935 self.permissions_user_groups[ug_k] = p, o, obj_id
926
936
927 if self.user_is_admin:
937 if self.user_is_admin:
928 p = 'usergroup.admin'
938 p = 'usergroup.admin'
929 o = PermOrigin.SUPER_ADMIN
939 o = PermOrigin.SUPER_ADMIN
930 self.permissions_user_groups[ug_k] = p, o
940 self.permissions_user_groups[ug_k] = p, o, obj_id
931
941
932 # user explicit permission for user groups
942 # user explicit permission for user groups
933 user_user_groups_perms = Permission.get_default_user_group_perms(
943 user_user_groups_perms = Permission.get_default_user_group_perms(
934 self.user_id, self.scope_user_group_id)
944 self.user_id, self.scope_user_group_id)
935 for perm in user_user_groups_perms:
945 for perm in user_user_groups_perms:
936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
946 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
947 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
948 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 .user.username
949 .user.username
939 p = perm.Permission.permission_name
950 p = perm.Permission.permission_name
940
951
941 if not self.explicit:
952 if not self.explicit:
942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
953 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 p = self._choose_permission(p, cur_perm)
954 p = self._choose_permission(p, cur_perm)
944
955
945 self.permissions_user_groups[ug_k] = p, o
956 self.permissions_user_groups[ug_k] = p, o, obj_id
946
957
947 if perm.UserGroup.user_id == self.user_id:
958 if perm.UserGroup.user_id == self.user_id:
948 # set admin if owner
959 # set admin if owner
949 p = 'usergroup.admin'
960 p = 'usergroup.admin'
950 o = PermOrigin.USERGROUP_OWNER
961 o = PermOrigin.USERGROUP_OWNER
951 self.permissions_user_groups[ug_k] = p, o
962 self.permissions_user_groups[ug_k] = p, o, obj_id
952
963
953 if self.user_is_admin:
964 if self.user_is_admin:
954 p = 'usergroup.admin'
965 p = 'usergroup.admin'
955 o = PermOrigin.SUPER_ADMIN
966 o = PermOrigin.SUPER_ADMIN
956 self.permissions_user_groups[ug_k] = p, o
967 self.permissions_user_groups[ug_k] = p, o, obj_id
957
968
958 def _choose_permission(self, new_perm, cur_perm):
969 def _choose_permission(self, new_perm, cur_perm):
959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
970 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
971 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 if self.algo == 'higherwin':
972 if self.algo == 'higherwin':
962 if new_perm_val > cur_perm_val:
973 if new_perm_val > cur_perm_val:
963 return new_perm
974 return new_perm
964 return cur_perm
975 return cur_perm
965 elif self.algo == 'lowerwin':
976 elif self.algo == 'lowerwin':
966 if new_perm_val < cur_perm_val:
977 if new_perm_val < cur_perm_val:
967 return new_perm
978 return new_perm
968 return cur_perm
979 return cur_perm
969
980
970 def _permission_structure(self):
981 def _permission_structure(self):
971 return {
982 return {
972 'global': self.permissions_global,
983 'global': self.permissions_global,
973 'repositories': self.permissions_repositories,
984 'repositories': self.permissions_repositories,
974 'repository_branches': self.permissions_repository_branches,
985 'repository_branches': self.permissions_repository_branches,
975 'repositories_groups': self.permissions_repository_groups,
986 'repositories_groups': self.permissions_repository_groups,
976 'user_groups': self.permissions_user_groups,
987 'user_groups': self.permissions_user_groups,
977 }
988 }
978
989
979
990
980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
991 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 """
992 """
982 Check if given controller_name is in whitelist of auth token access
993 Check if given controller_name is in whitelist of auth token access
983 """
994 """
984 if not whitelist:
995 if not whitelist:
985 from rhodecode import CONFIG
996 from rhodecode import CONFIG
986 whitelist = aslist(
997 whitelist = aslist(
987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
998 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 # backward compat translation
999 # backward compat translation
989 compat = {
1000 compat = {
990 # old controller, new VIEW
1001 # old controller, new VIEW
991 'ChangesetController:*': 'RepoCommitsView:*',
1002 'ChangesetController:*': 'RepoCommitsView:*',
992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1003 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1004 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1005 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1006 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 'GistsController:*': 'GistView:*',
1007 'GistsController:*': 'GistView:*',
997 }
1008 }
998
1009
999 log.debug(
1010 log.debug(
1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1011 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 auth_token_access_valid = False
1012 auth_token_access_valid = False
1002
1013
1003 for entry in whitelist:
1014 for entry in whitelist:
1004 token_match = True
1015 token_match = True
1005 if entry in compat:
1016 if entry in compat:
1006 # translate from old Controllers to Pyramid Views
1017 # translate from old Controllers to Pyramid Views
1007 entry = compat[entry]
1018 entry = compat[entry]
1008
1019
1009 if '@' in entry:
1020 if '@' in entry:
1010 # specific AuthToken
1021 # specific AuthToken
1011 entry, allowed_token = entry.split('@', 1)
1022 entry, allowed_token = entry.split('@', 1)
1012 token_match = auth_token == allowed_token
1023 token_match = auth_token == allowed_token
1013
1024
1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1025 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 auth_token_access_valid = True
1026 auth_token_access_valid = True
1016 break
1027 break
1017
1028
1018 if auth_token_access_valid:
1029 if auth_token_access_valid:
1019 log.debug('view: `%s` matches entry in whitelist: %s',
1030 log.debug('view: `%s` matches entry in whitelist: %s',
1020 view_name, whitelist)
1031 view_name, whitelist)
1021
1032
1022 else:
1033 else:
1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1034 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 % (view_name, whitelist))
1035 % (view_name, whitelist))
1025 if auth_token:
1036 if auth_token:
1026 # if we use auth token key and don't have access it's a warning
1037 # if we use auth token key and don't have access it's a warning
1027 log.warning(msg)
1038 log.warning(msg)
1028 else:
1039 else:
1029 log.debug(msg)
1040 log.debug(msg)
1030
1041
1031 return auth_token_access_valid
1042 return auth_token_access_valid
1032
1043
1033
1044
1034 class AuthUser(object):
1045 class AuthUser(object):
1035 """
1046 """
1036 A simple object that handles all attributes of user in RhodeCode
1047 A simple object that handles all attributes of user in RhodeCode
1037
1048
1038 It does lookup based on API key,given user, or user present in session
1049 It does lookup based on API key,given user, or user present in session
1039 Then it fills all required information for such user. It also checks if
1050 Then it fills all required information for such user. It also checks if
1040 anonymous access is enabled and if so, it returns default user as logged in
1051 anonymous access is enabled and if so, it returns default user as logged in
1041 """
1052 """
1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1053 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043
1054
1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1055 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045
1056
1046 self.user_id = user_id
1057 self.user_id = user_id
1047 self._api_key = api_key
1058 self._api_key = api_key
1048
1059
1049 self.api_key = None
1060 self.api_key = None
1050 self.username = username
1061 self.username = username
1051 self.ip_addr = ip_addr
1062 self.ip_addr = ip_addr
1052 self.name = ''
1063 self.name = ''
1053 self.lastname = ''
1064 self.lastname = ''
1054 self.first_name = ''
1065 self.first_name = ''
1055 self.last_name = ''
1066 self.last_name = ''
1056 self.email = ''
1067 self.email = ''
1057 self.is_authenticated = False
1068 self.is_authenticated = False
1058 self.admin = False
1069 self.admin = False
1059 self.inherit_default_permissions = False
1070 self.inherit_default_permissions = False
1060 self.password = ''
1071 self.password = ''
1061
1072
1062 self.anonymous_user = None # propagated on propagate_data
1073 self.anonymous_user = None # propagated on propagate_data
1063 self.propagate_data()
1074 self.propagate_data()
1064 self._instance = None
1075 self._instance = None
1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1076 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066
1077
1067 @LazyProperty
1078 @LazyProperty
1068 def permissions(self):
1079 def permissions(self):
1069 return self.get_perms(user=self, cache=None)
1080 return self.get_perms(user=self, cache=None)
1070
1081
1071 @LazyProperty
1082 @LazyProperty
1072 def permissions_safe(self):
1083 def permissions_safe(self):
1073 """
1084 """
1074 Filtered permissions excluding not allowed repositories
1085 Filtered permissions excluding not allowed repositories
1075 """
1086 """
1076 perms = self.get_perms(user=self, cache=None)
1087 perms = self.get_perms(user=self, cache=None)
1077
1088
1078 perms['repositories'] = {
1089 perms['repositories'] = {
1079 k: v for k, v in perms['repositories'].items()
1090 k: v for k, v in perms['repositories'].items()
1080 if v != 'repository.none'}
1091 if v != 'repository.none'}
1081 perms['repositories_groups'] = {
1092 perms['repositories_groups'] = {
1082 k: v for k, v in perms['repositories_groups'].items()
1093 k: v for k, v in perms['repositories_groups'].items()
1083 if v != 'group.none'}
1094 if v != 'group.none'}
1084 perms['user_groups'] = {
1095 perms['user_groups'] = {
1085 k: v for k, v in perms['user_groups'].items()
1096 k: v for k, v in perms['user_groups'].items()
1086 if v != 'usergroup.none'}
1097 if v != 'usergroup.none'}
1087 perms['repository_branches'] = {
1098 perms['repository_branches'] = {
1088 k: v for k, v in perms['repository_branches'].iteritems()
1099 k: v for k, v in perms['repository_branches'].iteritems()
1089 if v != 'branch.none'}
1100 if v != 'branch.none'}
1090 return perms
1101 return perms
1091
1102
1092 @LazyProperty
1103 @LazyProperty
1093 def permissions_full_details(self):
1104 def permissions_full_details(self):
1094 return self.get_perms(
1105 return self.get_perms(
1095 user=self, cache=None, calculate_super_admin=True)
1106 user=self, cache=None, calculate_super_admin=True)
1096
1107
1097 def permissions_with_scope(self, scope):
1108 def permissions_with_scope(self, scope):
1098 """
1109 """
1099 Call the get_perms function with scoped data. The scope in that function
1110 Call the get_perms function with scoped data. The scope in that function
1100 narrows the SQL calls to the given ID of objects resulting in fetching
1111 narrows the SQL calls to the given ID of objects resulting in fetching
1101 Just particular permission we want to obtain. If scope is an empty dict
1112 Just particular permission we want to obtain. If scope is an empty dict
1102 then it basically narrows the scope to GLOBAL permissions only.
1113 then it basically narrows the scope to GLOBAL permissions only.
1103
1114
1104 :param scope: dict
1115 :param scope: dict
1105 """
1116 """
1106 if 'repo_name' in scope:
1117 if 'repo_name' in scope:
1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1118 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 if obj:
1119 if obj:
1109 scope['repo_id'] = obj.repo_id
1120 scope['repo_id'] = obj.repo_id
1110 _scope = collections.OrderedDict()
1121 _scope = collections.OrderedDict()
1111 _scope['repo_id'] = -1
1122 _scope['repo_id'] = -1
1112 _scope['user_group_id'] = -1
1123 _scope['user_group_id'] = -1
1113 _scope['repo_group_id'] = -1
1124 _scope['repo_group_id'] = -1
1114
1125
1115 for k in sorted(scope.keys()):
1126 for k in sorted(scope.keys()):
1116 _scope[k] = scope[k]
1127 _scope[k] = scope[k]
1117
1128
1118 # store in cache to mimic how the @LazyProperty works,
1129 # store in cache to mimic how the @LazyProperty works,
1119 # the difference here is that we use the unique key calculated
1130 # the difference here is that we use the unique key calculated
1120 # from params and values
1131 # from params and values
1121 return self.get_perms(user=self, cache=None, scope=_scope)
1132 return self.get_perms(user=self, cache=None, scope=_scope)
1122
1133
1123 def get_instance(self):
1134 def get_instance(self):
1124 return User.get(self.user_id)
1135 return User.get(self.user_id)
1125
1136
1126 def propagate_data(self):
1137 def propagate_data(self):
1127 """
1138 """
1128 Fills in user data and propagates values to this instance. Maps fetched
1139 Fills in user data and propagates values to this instance. Maps fetched
1129 user attributes to this class instance attributes
1140 user attributes to this class instance attributes
1130 """
1141 """
1131 log.debug('AuthUser: starting data propagation for new potential user')
1142 log.debug('AuthUser: starting data propagation for new potential user')
1132 user_model = UserModel()
1143 user_model = UserModel()
1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1144 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 is_user_loaded = False
1145 is_user_loaded = False
1135
1146
1136 # lookup by userid
1147 # lookup by userid
1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1148 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1149 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1150 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140
1151
1141 # try go get user by api key
1152 # try go get user by api key
1142 elif self._api_key and self._api_key != anon_user.api_key:
1153 elif self._api_key and self._api_key != anon_user.api_key:
1143 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1154 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1155 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145
1156
1146 # lookup by username
1157 # lookup by username
1147 elif self.username:
1158 elif self.username:
1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1159 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1160 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 else:
1161 else:
1151 log.debug('No data in %s that could been used to log in', self)
1162 log.debug('No data in %s that could been used to log in', self)
1152
1163
1153 if not is_user_loaded:
1164 if not is_user_loaded:
1154 log.debug(
1165 log.debug(
1155 'Failed to load user. Fallback to default user %s', anon_user)
1166 'Failed to load user. Fallback to default user %s', anon_user)
1156 # if we cannot authenticate user try anonymous
1167 # if we cannot authenticate user try anonymous
1157 if anon_user.active:
1168 if anon_user.active:
1158 log.debug('default user is active, using it as a session user')
1169 log.debug('default user is active, using it as a session user')
1159 user_model.fill_data(self, user_id=anon_user.user_id)
1170 user_model.fill_data(self, user_id=anon_user.user_id)
1160 # then we set this user is logged in
1171 # then we set this user is logged in
1161 self.is_authenticated = True
1172 self.is_authenticated = True
1162 else:
1173 else:
1163 log.debug('default user is NOT active')
1174 log.debug('default user is NOT active')
1164 # in case of disabled anonymous user we reset some of the
1175 # in case of disabled anonymous user we reset some of the
1165 # parameters so such user is "corrupted", skipping the fill_data
1176 # parameters so such user is "corrupted", skipping the fill_data
1166 for attr in ['user_id', 'username', 'admin', 'active']:
1177 for attr in ['user_id', 'username', 'admin', 'active']:
1167 setattr(self, attr, None)
1178 setattr(self, attr, None)
1168 self.is_authenticated = False
1179 self.is_authenticated = False
1169
1180
1170 if not self.username:
1181 if not self.username:
1171 self.username = 'None'
1182 self.username = 'None'
1172
1183
1173 log.debug('AuthUser: propagated user is now %s', self)
1184 log.debug('AuthUser: propagated user is now %s', self)
1174
1185
1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1186 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 calculate_super_admin=False, cache=None):
1187 calculate_super_admin=False, cache=None):
1177 """
1188 """
1178 Fills user permission attribute with permissions taken from database
1189 Fills user permission attribute with permissions taken from database
1179 works for permissions given for repositories, and for permissions that
1190 works for permissions given for repositories, and for permissions that
1180 are granted to groups
1191 are granted to groups
1181
1192
1182 :param user: instance of User object from database
1193 :param user: instance of User object from database
1183 :param explicit: In case there are permissions both for user and a group
1194 :param explicit: In case there are permissions both for user and a group
1184 that user is part of, explicit flag will defiine if user will
1195 that user is part of, explicit flag will defiine if user will
1185 explicitly override permissions from group, if it's False it will
1196 explicitly override permissions from group, if it's False it will
1186 make decision based on the algo
1197 make decision based on the algo
1187 :param algo: algorithm to decide what permission should be choose if
1198 :param algo: algorithm to decide what permission should be choose if
1188 it's multiple defined, eg user in two different groups. It also
1199 it's multiple defined, eg user in two different groups. It also
1189 decides if explicit flag is turned off how to specify the permission
1200 decides if explicit flag is turned off how to specify the permission
1190 for case when user is in a group + have defined separate permission
1201 for case when user is in a group + have defined separate permission
1191 :param calculate_super_admin: calculate permissions for super-admin in the
1202 :param calculate_super_admin: calculate permissions for super-admin in the
1192 same way as for regular user without speedups
1203 same way as for regular user without speedups
1193 :param cache: Use caching for calculation, None = let the cache backend decide
1204 :param cache: Use caching for calculation, None = let the cache backend decide
1194 """
1205 """
1195 user_id = user.user_id
1206 user_id = user.user_id
1196 user_is_admin = user.is_admin
1207 user_is_admin = user.is_admin
1197
1208
1198 # inheritance of global permissions like create repo/fork repo etc
1209 # inheritance of global permissions like create repo/fork repo etc
1199 user_inherit_default_permissions = user.inherit_default_permissions
1210 user_inherit_default_permissions = user.inherit_default_permissions
1200
1211
1201 cache_seconds = safe_int(
1212 cache_seconds = safe_int(
1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1213 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203
1214
1204 if cache is None:
1215 if cache is None:
1205 # let the backend cache decide
1216 # let the backend cache decide
1206 cache_on = cache_seconds > 0
1217 cache_on = cache_seconds > 0
1207 else:
1218 else:
1208 cache_on = cache
1219 cache_on = cache
1209
1220
1210 log.debug(
1221 log.debug(
1211 'Computing PERMISSION tree for user %s scope `%s` '
1222 'Computing PERMISSION tree for user %s scope `%s` '
1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1223 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213
1224
1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1225 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1226 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216
1227
1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1228 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 condition=cache_on)
1229 condition=cache_on)
1219 def compute_perm_tree(cache_name,
1230 def compute_perm_tree(cache_name,
1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1231 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 explicit, algo, calculate_super_admin):
1232 explicit, algo, calculate_super_admin):
1222 return _cached_perms_data(
1233 return _cached_perms_data(
1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1234 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 explicit, algo, calculate_super_admin)
1235 explicit, algo, calculate_super_admin)
1225
1236
1226 start = time.time()
1237 start = time.time()
1227 result = compute_perm_tree(
1238 result = compute_perm_tree(
1228 'permissions', user_id, scope, user_is_admin,
1239 'permissions', user_id, scope, user_is_admin,
1229 user_inherit_default_permissions, explicit, algo,
1240 user_inherit_default_permissions, explicit, algo,
1230 calculate_super_admin)
1241 calculate_super_admin)
1231
1242
1232 result_repr = []
1243 result_repr = []
1233 for k in result:
1244 for k in result:
1234 result_repr.append((k, len(result[k])))
1245 result_repr.append((k, len(result[k])))
1235 total = time.time() - start
1246 total = time.time() - start
1236 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1247 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1237 user, total, result_repr)
1248 user, total, result_repr)
1238
1249
1239 return result
1250 return result
1240
1251
1241 @property
1252 @property
1242 def is_default(self):
1253 def is_default(self):
1243 return self.username == User.DEFAULT_USER
1254 return self.username == User.DEFAULT_USER
1244
1255
1245 @property
1256 @property
1246 def is_admin(self):
1257 def is_admin(self):
1247 return self.admin
1258 return self.admin
1248
1259
1249 @property
1260 @property
1250 def is_user_object(self):
1261 def is_user_object(self):
1251 return self.user_id is not None
1262 return self.user_id is not None
1252
1263
1253 @property
1264 @property
1254 def repositories_admin(self):
1265 def repositories_admin(self):
1255 """
1266 """
1256 Returns list of repositories you're an admin of
1267 Returns list of repositories you're an admin of
1257 """
1268 """
1258 return [
1269 return [
1259 x[0] for x in self.permissions['repositories'].items()
1270 x[0] for x in self.permissions['repositories'].items()
1260 if x[1] == 'repository.admin']
1271 if x[1] == 'repository.admin']
1261
1272
1262 @property
1273 @property
1263 def repository_groups_admin(self):
1274 def repository_groups_admin(self):
1264 """
1275 """
1265 Returns list of repository groups you're an admin of
1276 Returns list of repository groups you're an admin of
1266 """
1277 """
1267 return [
1278 return [
1268 x[0] for x in self.permissions['repositories_groups'].items()
1279 x[0] for x in self.permissions['repositories_groups'].items()
1269 if x[1] == 'group.admin']
1280 if x[1] == 'group.admin']
1270
1281
1271 @property
1282 @property
1272 def user_groups_admin(self):
1283 def user_groups_admin(self):
1273 """
1284 """
1274 Returns list of user groups you're an admin of
1285 Returns list of user groups you're an admin of
1275 """
1286 """
1276 return [
1287 return [
1277 x[0] for x in self.permissions['user_groups'].items()
1288 x[0] for x in self.permissions['user_groups'].items()
1278 if x[1] == 'usergroup.admin']
1289 if x[1] == 'usergroup.admin']
1279
1290
1291 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1292 if not perms:
1293 perms = ['repository.read', 'repository.write', 'repository.admin']
1294 allowed_ids = []
1295 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1296 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1297 if prefix_filter and not k.startswith(prefix_filter):
1298 continue
1299 if perm in perms:
1300 allowed_ids.append(obj_id)
1301 return allowed_ids
1302
1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1303 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 """
1304 """
1282 Returns list of repository ids that user have access to based on given
1305 Returns list of repository ids that user have access to based on given
1283 perms. The cache flag should be only used in cases that are used for
1306 perms. The cache flag should be only used in cases that are used for
1284 display purposes, NOT IN ANY CASE for permission checks.
1307 display purposes, NOT IN ANY CASE for permission checks.
1285 """
1308 """
1286 from rhodecode.model.scm import RepoList
1309 from rhodecode.model.scm import RepoList
1287 if not perms:
1310 if not perms:
1288 perms = [
1311 perms = ['repository.read', 'repository.write', 'repository.admin']
1289 'repository.read', 'repository.write', 'repository.admin']
1290
1312
1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1313 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 qry = Repository.query()
1314 qry = Repository.query()
1293 if _name_filter:
1315 if _name_filter:
1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1316 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 qry = qry.filter(
1317 qry = qry.filter(
1296 Repository.repo_name.ilike(ilike_expression))
1318 Repository.repo_name.ilike(ilike_expression))
1297
1319
1298 return [x.repo_id for x in
1320 return [x.repo_id for x in
1299 RepoList(qry, perm_set=perm_def)]
1321 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1300
1322
1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1323 return _cached_repo_acl(self.user_id, perms, name_filter)
1302
1324
1325 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1326 if not perms:
1327 perms = ['group.read', 'group.write', 'group.admin']
1328 allowed_ids = []
1329 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1330 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1331 if prefix_filter and not k.startswith(prefix_filter):
1332 continue
1333 if perm in perms:
1334 allowed_ids.append(obj_id)
1335 return allowed_ids
1336
1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1337 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 """
1338 """
1305 Returns list of repository group ids that user have access to based on given
1339 Returns list of repository group ids that user have access to based on given
1306 perms. The cache flag should be only used in cases that are used for
1340 perms. The cache flag should be only used in cases that are used for
1307 display purposes, NOT IN ANY CASE for permission checks.
1341 display purposes, NOT IN ANY CASE for permission checks.
1308 """
1342 """
1309 from rhodecode.model.scm import RepoGroupList
1343 from rhodecode.model.scm import RepoGroupList
1310 if not perms:
1344 if not perms:
1311 perms = [
1345 perms = ['group.read', 'group.write', 'group.admin']
1312 'group.read', 'group.write', 'group.admin']
1313
1346
1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1347 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 qry = RepoGroup.query()
1348 qry = RepoGroup.query()
1316 if _name_filter:
1349 if _name_filter:
1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1350 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 qry = qry.filter(
1351 qry = qry.filter(
1319 RepoGroup.group_name.ilike(ilike_expression))
1352 RepoGroup.group_name.ilike(ilike_expression))
1320
1353
1321 return [x.group_id for x in
1354 return [x.group_id for x in
1322 RepoGroupList(qry, perm_set=perm_def)]
1355 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1323
1356
1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1357 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1325
1358
1359 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1360 if not perms:
1361 perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1362 allowed_ids = []
1363 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1364 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1365 if perm in perms:
1366 allowed_ids.append(obj_id)
1367 return allowed_ids
1368
1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1369 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 """
1370 """
1328 Returns list of user group ids that user have access to based on given
1371 Returns list of user group ids that user have access to based on given
1329 perms. The cache flag should be only used in cases that are used for
1372 perms. The cache flag should be only used in cases that are used for
1330 display purposes, NOT IN ANY CASE for permission checks.
1373 display purposes, NOT IN ANY CASE for permission checks.
1331 """
1374 """
1332 from rhodecode.model.scm import UserGroupList
1375 from rhodecode.model.scm import UserGroupList
1333 if not perms:
1376 if not perms:
1334 perms = [
1377 perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1336
1378
1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1379 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 qry = UserGroup.query()
1380 qry = UserGroup.query()
1339 if name_filter:
1381 if name_filter:
1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1382 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 qry = qry.filter(
1383 qry = qry.filter(
1342 UserGroup.users_group_name.ilike(ilike_expression))
1384 UserGroup.users_group_name.ilike(ilike_expression))
1343
1385
1344 return [x.users_group_id for x in
1386 return [x.users_group_id for x in
1345 UserGroupList(qry, perm_set=perm_def)]
1387 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1346
1388
1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1389 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348
1390
1349 @property
1391 @property
1350 def ip_allowed(self):
1392 def ip_allowed(self):
1351 """
1393 """
1352 Checks if ip_addr used in constructor is allowed from defined list of
1394 Checks if ip_addr used in constructor is allowed from defined list of
1353 allowed ip_addresses for user
1395 allowed ip_addresses for user
1354
1396
1355 :returns: boolean, True if ip is in allowed ip range
1397 :returns: boolean, True if ip is in allowed ip range
1356 """
1398 """
1357 # check IP
1399 # check IP
1358 inherit = self.inherit_default_permissions
1400 inherit = self.inherit_default_permissions
1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1401 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 inherit_from_default=inherit)
1402 inherit_from_default=inherit)
1361 @property
1403 @property
1362 def personal_repo_group(self):
1404 def personal_repo_group(self):
1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1405 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364
1406
1365 @LazyProperty
1407 @LazyProperty
1366 def feed_token(self):
1408 def feed_token(self):
1367 return self.get_instance().feed_token
1409 return self.get_instance().feed_token
1368
1410
1369 @LazyProperty
1411 @LazyProperty
1370 def artifact_token(self):
1412 def artifact_token(self):
1371 return self.get_instance().artifact_token
1413 return self.get_instance().artifact_token
1372
1414
1373 @classmethod
1415 @classmethod
1374 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1416 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1375 allowed_ips = AuthUser.get_allowed_ips(
1417 allowed_ips = AuthUser.get_allowed_ips(
1376 user_id, cache=True, inherit_from_default=inherit_from_default)
1418 user_id, cache=True, inherit_from_default=inherit_from_default)
1377 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1419 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1378 log.debug('IP:%s for user %s is in range of %s',
1420 log.debug('IP:%s for user %s is in range of %s',
1379 ip_addr, user_id, allowed_ips)
1421 ip_addr, user_id, allowed_ips)
1380 return True
1422 return True
1381 else:
1423 else:
1382 log.info('Access for IP:%s forbidden for user %s, '
1424 log.info('Access for IP:%s forbidden for user %s, '
1383 'not in %s', ip_addr, user_id, allowed_ips)
1425 'not in %s', ip_addr, user_id, allowed_ips)
1384 return False
1426 return False
1385
1427
1386 def get_branch_permissions(self, repo_name, perms=None):
1428 def get_branch_permissions(self, repo_name, perms=None):
1387 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1429 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1388 branch_perms = perms.get('repository_branches', {})
1430 branch_perms = perms.get('repository_branches', {})
1389 if not branch_perms:
1431 if not branch_perms:
1390 return {}
1432 return {}
1391 repo_branch_perms = branch_perms.get(repo_name)
1433 repo_branch_perms = branch_perms.get(repo_name)
1392 return repo_branch_perms or {}
1434 return repo_branch_perms or {}
1393
1435
1394 def get_rule_and_branch_permission(self, repo_name, branch_name):
1436 def get_rule_and_branch_permission(self, repo_name, branch_name):
1395 """
1437 """
1396 Check if this AuthUser has defined any permissions for branches. If any of
1438 Check if this AuthUser has defined any permissions for branches. If any of
1397 the rules match in order, we return the matching permissions
1439 the rules match in order, we return the matching permissions
1398 """
1440 """
1399
1441
1400 rule = default_perm = ''
1442 rule = default_perm = ''
1401
1443
1402 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1444 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1403 if not repo_branch_perms:
1445 if not repo_branch_perms:
1404 return rule, default_perm
1446 return rule, default_perm
1405
1447
1406 # now calculate the permissions
1448 # now calculate the permissions
1407 for pattern, branch_perm in repo_branch_perms.items():
1449 for pattern, branch_perm in repo_branch_perms.items():
1408 if fnmatch.fnmatch(branch_name, pattern):
1450 if fnmatch.fnmatch(branch_name, pattern):
1409 rule = '`{}`=>{}'.format(pattern, branch_perm)
1451 rule = '`{}`=>{}'.format(pattern, branch_perm)
1410 return rule, branch_perm
1452 return rule, branch_perm
1411
1453
1412 return rule, default_perm
1454 return rule, default_perm
1413
1455
1414 def __repr__(self):
1456 def __repr__(self):
1415 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1457 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1416 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1458 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1417
1459
1418 def set_authenticated(self, authenticated=True):
1460 def set_authenticated(self, authenticated=True):
1419 if self.user_id != self.anonymous_user.user_id:
1461 if self.user_id != self.anonymous_user.user_id:
1420 self.is_authenticated = authenticated
1462 self.is_authenticated = authenticated
1421
1463
1422 def get_cookie_store(self):
1464 def get_cookie_store(self):
1423 return {
1465 return {
1424 'username': self.username,
1466 'username': self.username,
1425 'password': md5(self.password or ''),
1467 'password': md5(self.password or ''),
1426 'user_id': self.user_id,
1468 'user_id': self.user_id,
1427 'is_authenticated': self.is_authenticated
1469 'is_authenticated': self.is_authenticated
1428 }
1470 }
1429
1471
1430 @classmethod
1472 @classmethod
1431 def from_cookie_store(cls, cookie_store):
1473 def from_cookie_store(cls, cookie_store):
1432 """
1474 """
1433 Creates AuthUser from a cookie store
1475 Creates AuthUser from a cookie store
1434
1476
1435 :param cls:
1477 :param cls:
1436 :param cookie_store:
1478 :param cookie_store:
1437 """
1479 """
1438 user_id = cookie_store.get('user_id')
1480 user_id = cookie_store.get('user_id')
1439 username = cookie_store.get('username')
1481 username = cookie_store.get('username')
1440 api_key = cookie_store.get('api_key')
1482 api_key = cookie_store.get('api_key')
1441 return AuthUser(user_id, api_key, username)
1483 return AuthUser(user_id, api_key, username)
1442
1484
1443 @classmethod
1485 @classmethod
1444 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1486 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1445 _set = set()
1487 _set = set()
1446
1488
1447 if inherit_from_default:
1489 if inherit_from_default:
1448 def_user_id = User.get_default_user(cache=True).user_id
1490 def_user_id = User.get_default_user(cache=True).user_id
1449 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1491 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1450 if cache:
1492 if cache:
1451 default_ips = default_ips.options(
1493 default_ips = default_ips.options(
1452 FromCache("sql_cache_short", "get_user_ips_default"))
1494 FromCache("sql_cache_short", "get_user_ips_default"))
1453
1495
1454 # populate from default user
1496 # populate from default user
1455 for ip in default_ips:
1497 for ip in default_ips:
1456 try:
1498 try:
1457 _set.add(ip.ip_addr)
1499 _set.add(ip.ip_addr)
1458 except ObjectDeletedError:
1500 except ObjectDeletedError:
1459 # since we use heavy caching sometimes it happens that
1501 # since we use heavy caching sometimes it happens that
1460 # we get deleted objects here, we just skip them
1502 # we get deleted objects here, we just skip them
1461 pass
1503 pass
1462
1504
1463 # NOTE:(marcink) we don't want to load any rules for empty
1505 # NOTE:(marcink) we don't want to load any rules for empty
1464 # user_id which is the case of access of non logged users when anonymous
1506 # user_id which is the case of access of non logged users when anonymous
1465 # access is disabled
1507 # access is disabled
1466 user_ips = []
1508 user_ips = []
1467 if user_id:
1509 if user_id:
1468 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1510 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1469 if cache:
1511 if cache:
1470 user_ips = user_ips.options(
1512 user_ips = user_ips.options(
1471 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1513 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1472
1514
1473 for ip in user_ips:
1515 for ip in user_ips:
1474 try:
1516 try:
1475 _set.add(ip.ip_addr)
1517 _set.add(ip.ip_addr)
1476 except ObjectDeletedError:
1518 except ObjectDeletedError:
1477 # since we use heavy caching sometimes it happens that we get
1519 # since we use heavy caching sometimes it happens that we get
1478 # deleted objects here, we just skip them
1520 # deleted objects here, we just skip them
1479 pass
1521 pass
1480 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1522 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1481
1523
1482
1524
1483 def set_available_permissions(settings):
1525 def set_available_permissions(settings):
1484 """
1526 """
1485 This function will propagate pyramid settings with all available defined
1527 This function will propagate pyramid settings with all available defined
1486 permission given in db. We don't want to check each time from db for new
1528 permission given in db. We don't want to check each time from db for new
1487 permissions since adding a new permission also requires application restart
1529 permissions since adding a new permission also requires application restart
1488 ie. to decorate new views with the newly created permission
1530 ie. to decorate new views with the newly created permission
1489
1531
1490 :param settings: current pyramid registry.settings
1532 :param settings: current pyramid registry.settings
1491
1533
1492 """
1534 """
1493 log.debug('auth: getting information about all available permissions')
1535 log.debug('auth: getting information about all available permissions')
1494 try:
1536 try:
1495 sa = meta.Session
1537 sa = meta.Session
1496 all_perms = sa.query(Permission).all()
1538 all_perms = sa.query(Permission).all()
1497 settings.setdefault('available_permissions',
1539 settings.setdefault('available_permissions',
1498 [x.permission_name for x in all_perms])
1540 [x.permission_name for x in all_perms])
1499 log.debug('auth: set available permissions')
1541 log.debug('auth: set available permissions')
1500 except Exception:
1542 except Exception:
1501 log.exception('Failed to fetch permissions from the database.')
1543 log.exception('Failed to fetch permissions from the database.')
1502 raise
1544 raise
1503
1545
1504
1546
1505 def get_csrf_token(session, force_new=False, save_if_missing=True):
1547 def get_csrf_token(session, force_new=False, save_if_missing=True):
1506 """
1548 """
1507 Return the current authentication token, creating one if one doesn't
1549 Return the current authentication token, creating one if one doesn't
1508 already exist and the save_if_missing flag is present.
1550 already exist and the save_if_missing flag is present.
1509
1551
1510 :param session: pass in the pyramid session, else we use the global ones
1552 :param session: pass in the pyramid session, else we use the global ones
1511 :param force_new: force to re-generate the token and store it in session
1553 :param force_new: force to re-generate the token and store it in session
1512 :param save_if_missing: save the newly generated token if it's missing in
1554 :param save_if_missing: save the newly generated token if it's missing in
1513 session
1555 session
1514 """
1556 """
1515 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1557 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1516 # from pyramid.csrf import get_csrf_token
1558 # from pyramid.csrf import get_csrf_token
1517
1559
1518 if (csrf_token_key not in session and save_if_missing) or force_new:
1560 if (csrf_token_key not in session and save_if_missing) or force_new:
1519 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1561 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1520 session[csrf_token_key] = token
1562 session[csrf_token_key] = token
1521 if hasattr(session, 'save'):
1563 if hasattr(session, 'save'):
1522 session.save()
1564 session.save()
1523 return session.get(csrf_token_key)
1565 return session.get(csrf_token_key)
1524
1566
1525
1567
1526 def get_request(perm_class_instance):
1568 def get_request(perm_class_instance):
1527 from pyramid.threadlocal import get_current_request
1569 from pyramid.threadlocal import get_current_request
1528 pyramid_request = get_current_request()
1570 pyramid_request = get_current_request()
1529 return pyramid_request
1571 return pyramid_request
1530
1572
1531
1573
1532 # CHECK DECORATORS
1574 # CHECK DECORATORS
1533 class CSRFRequired(object):
1575 class CSRFRequired(object):
1534 """
1576 """
1535 Decorator for authenticating a form
1577 Decorator for authenticating a form
1536
1578
1537 This decorator uses an authorization token stored in the client's
1579 This decorator uses an authorization token stored in the client's
1538 session for prevention of certain Cross-site request forgery (CSRF)
1580 session for prevention of certain Cross-site request forgery (CSRF)
1539 attacks (See
1581 attacks (See
1540 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1582 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1541 information).
1583 information).
1542
1584
1543 For use with the ``secure_form`` helper functions.
1585 For use with the ``secure_form`` helper functions.
1544
1586
1545 """
1587 """
1546 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1588 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1547 self.token = token
1589 self.token = token
1548 self.header = header
1590 self.header = header
1549 self.except_methods = except_methods or []
1591 self.except_methods = except_methods or []
1550
1592
1551 def __call__(self, func):
1593 def __call__(self, func):
1552 return get_cython_compat_decorator(self.__wrapper, func)
1594 return get_cython_compat_decorator(self.__wrapper, func)
1553
1595
1554 def _get_csrf(self, _request):
1596 def _get_csrf(self, _request):
1555 return _request.POST.get(self.token, _request.headers.get(self.header))
1597 return _request.POST.get(self.token, _request.headers.get(self.header))
1556
1598
1557 def check_csrf(self, _request, cur_token):
1599 def check_csrf(self, _request, cur_token):
1558 supplied_token = self._get_csrf(_request)
1600 supplied_token = self._get_csrf(_request)
1559 return supplied_token and supplied_token == cur_token
1601 return supplied_token and supplied_token == cur_token
1560
1602
1561 def _get_request(self):
1603 def _get_request(self):
1562 return get_request(self)
1604 return get_request(self)
1563
1605
1564 def __wrapper(self, func, *fargs, **fkwargs):
1606 def __wrapper(self, func, *fargs, **fkwargs):
1565 request = self._get_request()
1607 request = self._get_request()
1566
1608
1567 if request.method in self.except_methods:
1609 if request.method in self.except_methods:
1568 return func(*fargs, **fkwargs)
1610 return func(*fargs, **fkwargs)
1569
1611
1570 cur_token = get_csrf_token(request.session, save_if_missing=False)
1612 cur_token = get_csrf_token(request.session, save_if_missing=False)
1571 if self.check_csrf(request, cur_token):
1613 if self.check_csrf(request, cur_token):
1572 if request.POST.get(self.token):
1614 if request.POST.get(self.token):
1573 del request.POST[self.token]
1615 del request.POST[self.token]
1574 return func(*fargs, **fkwargs)
1616 return func(*fargs, **fkwargs)
1575 else:
1617 else:
1576 reason = 'token-missing'
1618 reason = 'token-missing'
1577 supplied_token = self._get_csrf(request)
1619 supplied_token = self._get_csrf(request)
1578 if supplied_token and cur_token != supplied_token:
1620 if supplied_token and cur_token != supplied_token:
1579 reason = 'token-mismatch [%s:%s]' % (
1621 reason = 'token-mismatch [%s:%s]' % (
1580 cur_token or ''[:6], supplied_token or ''[:6])
1622 cur_token or ''[:6], supplied_token or ''[:6])
1581
1623
1582 csrf_message = \
1624 csrf_message = \
1583 ("Cross-site request forgery detected, request denied. See "
1625 ("Cross-site request forgery detected, request denied. See "
1584 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1626 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1585 "more information.")
1627 "more information.")
1586 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1628 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1587 'REMOTE_ADDR:%s, HEADERS:%s' % (
1629 'REMOTE_ADDR:%s, HEADERS:%s' % (
1588 request, reason, request.remote_addr, request.headers))
1630 request, reason, request.remote_addr, request.headers))
1589
1631
1590 raise HTTPForbidden(explanation=csrf_message)
1632 raise HTTPForbidden(explanation=csrf_message)
1591
1633
1592
1634
1593 class LoginRequired(object):
1635 class LoginRequired(object):
1594 """
1636 """
1595 Must be logged in to execute this function else
1637 Must be logged in to execute this function else
1596 redirect to login page
1638 redirect to login page
1597
1639
1598 :param api_access: if enabled this checks only for valid auth token
1640 :param api_access: if enabled this checks only for valid auth token
1599 and grants access based on valid token
1641 and grants access based on valid token
1600 """
1642 """
1601 def __init__(self, auth_token_access=None):
1643 def __init__(self, auth_token_access=None):
1602 self.auth_token_access = auth_token_access
1644 self.auth_token_access = auth_token_access
1603 if self.auth_token_access:
1645 if self.auth_token_access:
1604 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1646 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1605 if not valid_type:
1647 if not valid_type:
1606 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1648 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1607 UserApiKeys.ROLES, auth_token_access))
1649 UserApiKeys.ROLES, auth_token_access))
1608
1650
1609 def __call__(self, func):
1651 def __call__(self, func):
1610 return get_cython_compat_decorator(self.__wrapper, func)
1652 return get_cython_compat_decorator(self.__wrapper, func)
1611
1653
1612 def _get_request(self):
1654 def _get_request(self):
1613 return get_request(self)
1655 return get_request(self)
1614
1656
1615 def __wrapper(self, func, *fargs, **fkwargs):
1657 def __wrapper(self, func, *fargs, **fkwargs):
1616 from rhodecode.lib import helpers as h
1658 from rhodecode.lib import helpers as h
1617 cls = fargs[0]
1659 cls = fargs[0]
1618 user = cls._rhodecode_user
1660 user = cls._rhodecode_user
1619 request = self._get_request()
1661 request = self._get_request()
1620 _ = request.translate
1662 _ = request.translate
1621
1663
1622 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1664 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1623 log.debug('Starting login restriction checks for user: %s', user)
1665 log.debug('Starting login restriction checks for user: %s', user)
1624 # check if our IP is allowed
1666 # check if our IP is allowed
1625 ip_access_valid = True
1667 ip_access_valid = True
1626 if not user.ip_allowed:
1668 if not user.ip_allowed:
1627 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1669 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1628 category='warning')
1670 category='warning')
1629 ip_access_valid = False
1671 ip_access_valid = False
1630
1672
1631 # we used stored token that is extract from GET or URL param (if any)
1673 # we used stored token that is extract from GET or URL param (if any)
1632 _auth_token = request.user_auth_token
1674 _auth_token = request.user_auth_token
1633
1675
1634 # check if we used an AUTH_TOKEN and it's a valid one
1676 # check if we used an AUTH_TOKEN and it's a valid one
1635 # defined white-list of controllers which API access will be enabled
1677 # defined white-list of controllers which API access will be enabled
1636 whitelist = None
1678 whitelist = None
1637 if self.auth_token_access:
1679 if self.auth_token_access:
1638 # since this location is allowed by @LoginRequired decorator it's our
1680 # since this location is allowed by @LoginRequired decorator it's our
1639 # only whitelist
1681 # only whitelist
1640 whitelist = [loc]
1682 whitelist = [loc]
1641 auth_token_access_valid = allowed_auth_token_access(
1683 auth_token_access_valid = allowed_auth_token_access(
1642 loc, whitelist=whitelist, auth_token=_auth_token)
1684 loc, whitelist=whitelist, auth_token=_auth_token)
1643
1685
1644 # explicit controller is enabled or API is in our whitelist
1686 # explicit controller is enabled or API is in our whitelist
1645 if auth_token_access_valid:
1687 if auth_token_access_valid:
1646 log.debug('Checking AUTH TOKEN access for %s', cls)
1688 log.debug('Checking AUTH TOKEN access for %s', cls)
1647 db_user = user.get_instance()
1689 db_user = user.get_instance()
1648
1690
1649 if db_user:
1691 if db_user:
1650 if self.auth_token_access:
1692 if self.auth_token_access:
1651 roles = self.auth_token_access
1693 roles = self.auth_token_access
1652 else:
1694 else:
1653 roles = [UserApiKeys.ROLE_HTTP]
1695 roles = [UserApiKeys.ROLE_HTTP]
1654 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1696 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1655 db_user, roles)
1697 db_user, roles)
1656 token_match = db_user.authenticate_by_token(
1698 token_match = db_user.authenticate_by_token(
1657 _auth_token, roles=roles)
1699 _auth_token, roles=roles)
1658 else:
1700 else:
1659 log.debug('Unable to fetch db instance for auth user: %s', user)
1701 log.debug('Unable to fetch db instance for auth user: %s', user)
1660 token_match = False
1702 token_match = False
1661
1703
1662 if _auth_token and token_match:
1704 if _auth_token and token_match:
1663 auth_token_access_valid = True
1705 auth_token_access_valid = True
1664 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1706 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1665 else:
1707 else:
1666 auth_token_access_valid = False
1708 auth_token_access_valid = False
1667 if not _auth_token:
1709 if not _auth_token:
1668 log.debug("AUTH TOKEN *NOT* present in request")
1710 log.debug("AUTH TOKEN *NOT* present in request")
1669 else:
1711 else:
1670 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1712 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1671
1713
1672 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1714 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1673 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1715 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1674 else 'AUTH_TOKEN_AUTH'
1716 else 'AUTH_TOKEN_AUTH'
1675
1717
1676 if ip_access_valid and (
1718 if ip_access_valid and (
1677 user.is_authenticated or auth_token_access_valid):
1719 user.is_authenticated or auth_token_access_valid):
1678 log.info('user %s authenticating with:%s IS authenticated on func %s',
1720 log.info('user %s authenticating with:%s IS authenticated on func %s',
1679 user, reason, loc)
1721 user, reason, loc)
1680
1722
1681 return func(*fargs, **fkwargs)
1723 return func(*fargs, **fkwargs)
1682 else:
1724 else:
1683 log.warning(
1725 log.warning(
1684 'user %s authenticating with:%s NOT authenticated on '
1726 'user %s authenticating with:%s NOT authenticated on '
1685 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1727 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1686 user, reason, loc, ip_access_valid, auth_token_access_valid)
1728 user, reason, loc, ip_access_valid, auth_token_access_valid)
1687 # we preserve the get PARAM
1729 # we preserve the get PARAM
1688 came_from = get_came_from(request)
1730 came_from = get_came_from(request)
1689
1731
1690 log.debug('redirecting to login page with %s', came_from)
1732 log.debug('redirecting to login page with %s', came_from)
1691 raise HTTPFound(
1733 raise HTTPFound(
1692 h.route_path('login', _query={'came_from': came_from}))
1734 h.route_path('login', _query={'came_from': came_from}))
1693
1735
1694
1736
1695 class NotAnonymous(object):
1737 class NotAnonymous(object):
1696 """
1738 """
1697 Must be logged in to execute this function else
1739 Must be logged in to execute this function else
1698 redirect to login page
1740 redirect to login page
1699 """
1741 """
1700
1742
1701 def __call__(self, func):
1743 def __call__(self, func):
1702 return get_cython_compat_decorator(self.__wrapper, func)
1744 return get_cython_compat_decorator(self.__wrapper, func)
1703
1745
1704 def _get_request(self):
1746 def _get_request(self):
1705 return get_request(self)
1747 return get_request(self)
1706
1748
1707 def __wrapper(self, func, *fargs, **fkwargs):
1749 def __wrapper(self, func, *fargs, **fkwargs):
1708 import rhodecode.lib.helpers as h
1750 import rhodecode.lib.helpers as h
1709 cls = fargs[0]
1751 cls = fargs[0]
1710 self.user = cls._rhodecode_user
1752 self.user = cls._rhodecode_user
1711 request = self._get_request()
1753 request = self._get_request()
1712 _ = request.translate
1754 _ = request.translate
1713 log.debug('Checking if user is not anonymous @%s', cls)
1755 log.debug('Checking if user is not anonymous @%s', cls)
1714
1756
1715 anonymous = self.user.username == User.DEFAULT_USER
1757 anonymous = self.user.username == User.DEFAULT_USER
1716
1758
1717 if anonymous:
1759 if anonymous:
1718 came_from = get_came_from(request)
1760 came_from = get_came_from(request)
1719 h.flash(_('You need to be a registered user to '
1761 h.flash(_('You need to be a registered user to '
1720 'perform this action'),
1762 'perform this action'),
1721 category='warning')
1763 category='warning')
1722 raise HTTPFound(
1764 raise HTTPFound(
1723 h.route_path('login', _query={'came_from': came_from}))
1765 h.route_path('login', _query={'came_from': came_from}))
1724 else:
1766 else:
1725 return func(*fargs, **fkwargs)
1767 return func(*fargs, **fkwargs)
1726
1768
1727
1769
1728 class PermsDecorator(object):
1770 class PermsDecorator(object):
1729 """
1771 """
1730 Base class for controller decorators, we extract the current user from
1772 Base class for controller decorators, we extract the current user from
1731 the class itself, which has it stored in base controllers
1773 the class itself, which has it stored in base controllers
1732 """
1774 """
1733
1775
1734 def __init__(self, *required_perms):
1776 def __init__(self, *required_perms):
1735 self.required_perms = set(required_perms)
1777 self.required_perms = set(required_perms)
1736
1778
1737 def __call__(self, func):
1779 def __call__(self, func):
1738 return get_cython_compat_decorator(self.__wrapper, func)
1780 return get_cython_compat_decorator(self.__wrapper, func)
1739
1781
1740 def _get_request(self):
1782 def _get_request(self):
1741 return get_request(self)
1783 return get_request(self)
1742
1784
1743 def __wrapper(self, func, *fargs, **fkwargs):
1785 def __wrapper(self, func, *fargs, **fkwargs):
1744 import rhodecode.lib.helpers as h
1786 import rhodecode.lib.helpers as h
1745 cls = fargs[0]
1787 cls = fargs[0]
1746 _user = cls._rhodecode_user
1788 _user = cls._rhodecode_user
1747 request = self._get_request()
1789 request = self._get_request()
1748 _ = request.translate
1790 _ = request.translate
1749
1791
1750 log.debug('checking %s permissions %s for %s %s',
1792 log.debug('checking %s permissions %s for %s %s',
1751 self.__class__.__name__, self.required_perms, cls, _user)
1793 self.__class__.__name__, self.required_perms, cls, _user)
1752
1794
1753 if self.check_permissions(_user):
1795 if self.check_permissions(_user):
1754 log.debug('Permission granted for %s %s', cls, _user)
1796 log.debug('Permission granted for %s %s', cls, _user)
1755 return func(*fargs, **fkwargs)
1797 return func(*fargs, **fkwargs)
1756
1798
1757 else:
1799 else:
1758 log.debug('Permission denied for %s %s', cls, _user)
1800 log.debug('Permission denied for %s %s', cls, _user)
1759 anonymous = _user.username == User.DEFAULT_USER
1801 anonymous = _user.username == User.DEFAULT_USER
1760
1802
1761 if anonymous:
1803 if anonymous:
1762 came_from = get_came_from(self._get_request())
1804 came_from = get_came_from(self._get_request())
1763 h.flash(_('You need to be signed in to view this page'),
1805 h.flash(_('You need to be signed in to view this page'),
1764 category='warning')
1806 category='warning')
1765 raise HTTPFound(
1807 raise HTTPFound(
1766 h.route_path('login', _query={'came_from': came_from}))
1808 h.route_path('login', _query={'came_from': came_from}))
1767
1809
1768 else:
1810 else:
1769 # redirect with 404 to prevent resource discovery
1811 # redirect with 404 to prevent resource discovery
1770 raise HTTPNotFound()
1812 raise HTTPNotFound()
1771
1813
1772 def check_permissions(self, user):
1814 def check_permissions(self, user):
1773 """Dummy function for overriding"""
1815 """Dummy function for overriding"""
1774 raise NotImplementedError(
1816 raise NotImplementedError(
1775 'You have to write this function in child class')
1817 'You have to write this function in child class')
1776
1818
1777
1819
1778 class HasPermissionAllDecorator(PermsDecorator):
1820 class HasPermissionAllDecorator(PermsDecorator):
1779 """
1821 """
1780 Checks for access permission for all given predicates. All of them
1822 Checks for access permission for all given predicates. All of them
1781 have to be meet in order to fulfill the request
1823 have to be meet in order to fulfill the request
1782 """
1824 """
1783
1825
1784 def check_permissions(self, user):
1826 def check_permissions(self, user):
1785 perms = user.permissions_with_scope({})
1827 perms = user.permissions_with_scope({})
1786 if self.required_perms.issubset(perms['global']):
1828 if self.required_perms.issubset(perms['global']):
1787 return True
1829 return True
1788 return False
1830 return False
1789
1831
1790
1832
1791 class HasPermissionAnyDecorator(PermsDecorator):
1833 class HasPermissionAnyDecorator(PermsDecorator):
1792 """
1834 """
1793 Checks for access permission for any of given predicates. In order to
1835 Checks for access permission for any of given predicates. In order to
1794 fulfill the request any of predicates must be meet
1836 fulfill the request any of predicates must be meet
1795 """
1837 """
1796
1838
1797 def check_permissions(self, user):
1839 def check_permissions(self, user):
1798 perms = user.permissions_with_scope({})
1840 perms = user.permissions_with_scope({})
1799 if self.required_perms.intersection(perms['global']):
1841 if self.required_perms.intersection(perms['global']):
1800 return True
1842 return True
1801 return False
1843 return False
1802
1844
1803
1845
1804 class HasRepoPermissionAllDecorator(PermsDecorator):
1846 class HasRepoPermissionAllDecorator(PermsDecorator):
1805 """
1847 """
1806 Checks for access permission for all given predicates for specific
1848 Checks for access permission for all given predicates for specific
1807 repository. All of them have to be meet in order to fulfill the request
1849 repository. All of them have to be meet in order to fulfill the request
1808 """
1850 """
1809 def _get_repo_name(self):
1851 def _get_repo_name(self):
1810 _request = self._get_request()
1852 _request = self._get_request()
1811 return get_repo_slug(_request)
1853 return get_repo_slug(_request)
1812
1854
1813 def check_permissions(self, user):
1855 def check_permissions(self, user):
1814 perms = user.permissions
1856 perms = user.permissions
1815 repo_name = self._get_repo_name()
1857 repo_name = self._get_repo_name()
1816
1858
1817 try:
1859 try:
1818 user_perms = {perms['repositories'][repo_name]}
1860 user_perms = {perms['repositories'][repo_name]}
1819 except KeyError:
1861 except KeyError:
1820 log.debug('cannot locate repo with name: `%s` in permissions defs',
1862 log.debug('cannot locate repo with name: `%s` in permissions defs',
1821 repo_name)
1863 repo_name)
1822 return False
1864 return False
1823
1865
1824 log.debug('checking `%s` permissions for repo `%s`',
1866 log.debug('checking `%s` permissions for repo `%s`',
1825 user_perms, repo_name)
1867 user_perms, repo_name)
1826 if self.required_perms.issubset(user_perms):
1868 if self.required_perms.issubset(user_perms):
1827 return True
1869 return True
1828 return False
1870 return False
1829
1871
1830
1872
1831 class HasRepoPermissionAnyDecorator(PermsDecorator):
1873 class HasRepoPermissionAnyDecorator(PermsDecorator):
1832 """
1874 """
1833 Checks for access permission for any of given predicates for specific
1875 Checks for access permission for any of given predicates for specific
1834 repository. In order to fulfill the request any of predicates must be meet
1876 repository. In order to fulfill the request any of predicates must be meet
1835 """
1877 """
1836 def _get_repo_name(self):
1878 def _get_repo_name(self):
1837 _request = self._get_request()
1879 _request = self._get_request()
1838 return get_repo_slug(_request)
1880 return get_repo_slug(_request)
1839
1881
1840 def check_permissions(self, user):
1882 def check_permissions(self, user):
1841 perms = user.permissions
1883 perms = user.permissions
1842 repo_name = self._get_repo_name()
1884 repo_name = self._get_repo_name()
1843
1885
1844 try:
1886 try:
1845 user_perms = {perms['repositories'][repo_name]}
1887 user_perms = {perms['repositories'][repo_name]}
1846 except KeyError:
1888 except KeyError:
1847 log.debug(
1889 log.debug(
1848 'cannot locate repo with name: `%s` in permissions defs',
1890 'cannot locate repo with name: `%s` in permissions defs',
1849 repo_name)
1891 repo_name)
1850 return False
1892 return False
1851
1893
1852 log.debug('checking `%s` permissions for repo `%s`',
1894 log.debug('checking `%s` permissions for repo `%s`',
1853 user_perms, repo_name)
1895 user_perms, repo_name)
1854 if self.required_perms.intersection(user_perms):
1896 if self.required_perms.intersection(user_perms):
1855 return True
1897 return True
1856 return False
1898 return False
1857
1899
1858
1900
1859 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1901 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1860 """
1902 """
1861 Checks for access permission for all given predicates for specific
1903 Checks for access permission for all given predicates for specific
1862 repository group. All of them have to be meet in order to
1904 repository group. All of them have to be meet in order to
1863 fulfill the request
1905 fulfill the request
1864 """
1906 """
1865 def _get_repo_group_name(self):
1907 def _get_repo_group_name(self):
1866 _request = self._get_request()
1908 _request = self._get_request()
1867 return get_repo_group_slug(_request)
1909 return get_repo_group_slug(_request)
1868
1910
1869 def check_permissions(self, user):
1911 def check_permissions(self, user):
1870 perms = user.permissions
1912 perms = user.permissions
1871 group_name = self._get_repo_group_name()
1913 group_name = self._get_repo_group_name()
1872 try:
1914 try:
1873 user_perms = {perms['repositories_groups'][group_name]}
1915 user_perms = {perms['repositories_groups'][group_name]}
1874 except KeyError:
1916 except KeyError:
1875 log.debug(
1917 log.debug(
1876 'cannot locate repo group with name: `%s` in permissions defs',
1918 'cannot locate repo group with name: `%s` in permissions defs',
1877 group_name)
1919 group_name)
1878 return False
1920 return False
1879
1921
1880 log.debug('checking `%s` permissions for repo group `%s`',
1922 log.debug('checking `%s` permissions for repo group `%s`',
1881 user_perms, group_name)
1923 user_perms, group_name)
1882 if self.required_perms.issubset(user_perms):
1924 if self.required_perms.issubset(user_perms):
1883 return True
1925 return True
1884 return False
1926 return False
1885
1927
1886
1928
1887 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1929 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1888 """
1930 """
1889 Checks for access permission for any of given predicates for specific
1931 Checks for access permission for any of given predicates for specific
1890 repository group. In order to fulfill the request any
1932 repository group. In order to fulfill the request any
1891 of predicates must be met
1933 of predicates must be met
1892 """
1934 """
1893 def _get_repo_group_name(self):
1935 def _get_repo_group_name(self):
1894 _request = self._get_request()
1936 _request = self._get_request()
1895 return get_repo_group_slug(_request)
1937 return get_repo_group_slug(_request)
1896
1938
1897 def check_permissions(self, user):
1939 def check_permissions(self, user):
1898 perms = user.permissions
1940 perms = user.permissions
1899 group_name = self._get_repo_group_name()
1941 group_name = self._get_repo_group_name()
1900
1942
1901 try:
1943 try:
1902 user_perms = {perms['repositories_groups'][group_name]}
1944 user_perms = {perms['repositories_groups'][group_name]}
1903 except KeyError:
1945 except KeyError:
1904 log.debug(
1946 log.debug(
1905 'cannot locate repo group with name: `%s` in permissions defs',
1947 'cannot locate repo group with name: `%s` in permissions defs',
1906 group_name)
1948 group_name)
1907 return False
1949 return False
1908
1950
1909 log.debug('checking `%s` permissions for repo group `%s`',
1951 log.debug('checking `%s` permissions for repo group `%s`',
1910 user_perms, group_name)
1952 user_perms, group_name)
1911 if self.required_perms.intersection(user_perms):
1953 if self.required_perms.intersection(user_perms):
1912 return True
1954 return True
1913 return False
1955 return False
1914
1956
1915
1957
1916 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1958 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1917 """
1959 """
1918 Checks for access permission for all given predicates for specific
1960 Checks for access permission for all given predicates for specific
1919 user group. All of them have to be meet in order to fulfill the request
1961 user group. All of them have to be meet in order to fulfill the request
1920 """
1962 """
1921 def _get_user_group_name(self):
1963 def _get_user_group_name(self):
1922 _request = self._get_request()
1964 _request = self._get_request()
1923 return get_user_group_slug(_request)
1965 return get_user_group_slug(_request)
1924
1966
1925 def check_permissions(self, user):
1967 def check_permissions(self, user):
1926 perms = user.permissions
1968 perms = user.permissions
1927 group_name = self._get_user_group_name()
1969 group_name = self._get_user_group_name()
1928 try:
1970 try:
1929 user_perms = {perms['user_groups'][group_name]}
1971 user_perms = {perms['user_groups'][group_name]}
1930 except KeyError:
1972 except KeyError:
1931 return False
1973 return False
1932
1974
1933 if self.required_perms.issubset(user_perms):
1975 if self.required_perms.issubset(user_perms):
1934 return True
1976 return True
1935 return False
1977 return False
1936
1978
1937
1979
1938 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1980 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1939 """
1981 """
1940 Checks for access permission for any of given predicates for specific
1982 Checks for access permission for any of given predicates for specific
1941 user group. In order to fulfill the request any of predicates must be meet
1983 user group. In order to fulfill the request any of predicates must be meet
1942 """
1984 """
1943 def _get_user_group_name(self):
1985 def _get_user_group_name(self):
1944 _request = self._get_request()
1986 _request = self._get_request()
1945 return get_user_group_slug(_request)
1987 return get_user_group_slug(_request)
1946
1988
1947 def check_permissions(self, user):
1989 def check_permissions(self, user):
1948 perms = user.permissions
1990 perms = user.permissions
1949 group_name = self._get_user_group_name()
1991 group_name = self._get_user_group_name()
1950 try:
1992 try:
1951 user_perms = {perms['user_groups'][group_name]}
1993 user_perms = {perms['user_groups'][group_name]}
1952 except KeyError:
1994 except KeyError:
1953 return False
1995 return False
1954
1996
1955 if self.required_perms.intersection(user_perms):
1997 if self.required_perms.intersection(user_perms):
1956 return True
1998 return True
1957 return False
1999 return False
1958
2000
1959
2001
1960 # CHECK FUNCTIONS
2002 # CHECK FUNCTIONS
1961 class PermsFunction(object):
2003 class PermsFunction(object):
1962 """Base function for other check functions"""
2004 """Base function for other check functions"""
1963
2005
1964 def __init__(self, *perms):
2006 def __init__(self, *perms):
1965 self.required_perms = set(perms)
2007 self.required_perms = set(perms)
1966 self.repo_name = None
2008 self.repo_name = None
1967 self.repo_group_name = None
2009 self.repo_group_name = None
1968 self.user_group_name = None
2010 self.user_group_name = None
1969
2011
1970 def __bool__(self):
2012 def __bool__(self):
1971 frame = inspect.currentframe()
2013 frame = inspect.currentframe()
1972 stack_trace = traceback.format_stack(frame)
2014 stack_trace = traceback.format_stack(frame)
1973 log.error('Checking bool value on a class instance of perm '
2015 log.error('Checking bool value on a class instance of perm '
1974 'function is not allowed: %s', ''.join(stack_trace))
2016 'function is not allowed: %s', ''.join(stack_trace))
1975 # rather than throwing errors, here we always return False so if by
2017 # rather than throwing errors, here we always return False so if by
1976 # accident someone checks truth for just an instance it will always end
2018 # accident someone checks truth for just an instance it will always end
1977 # up in returning False
2019 # up in returning False
1978 return False
2020 return False
1979 __nonzero__ = __bool__
2021 __nonzero__ = __bool__
1980
2022
1981 def __call__(self, check_location='', user=None):
2023 def __call__(self, check_location='', user=None):
1982 if not user:
2024 if not user:
1983 log.debug('Using user attribute from global request')
2025 log.debug('Using user attribute from global request')
1984 request = self._get_request()
2026 request = self._get_request()
1985 user = request.user
2027 user = request.user
1986
2028
1987 # init auth user if not already given
2029 # init auth user if not already given
1988 if not isinstance(user, AuthUser):
2030 if not isinstance(user, AuthUser):
1989 log.debug('Wrapping user %s into AuthUser', user)
2031 log.debug('Wrapping user %s into AuthUser', user)
1990 user = AuthUser(user.user_id)
2032 user = AuthUser(user.user_id)
1991
2033
1992 cls_name = self.__class__.__name__
2034 cls_name = self.__class__.__name__
1993 check_scope = self._get_check_scope(cls_name)
2035 check_scope = self._get_check_scope(cls_name)
1994 check_location = check_location or 'unspecified location'
2036 check_location = check_location or 'unspecified location'
1995
2037
1996 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2038 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1997 self.required_perms, user, check_scope, check_location)
2039 self.required_perms, user, check_scope, check_location)
1998 if not user:
2040 if not user:
1999 log.warning('Empty user given for permission check')
2041 log.warning('Empty user given for permission check')
2000 return False
2042 return False
2001
2043
2002 if self.check_permissions(user):
2044 if self.check_permissions(user):
2003 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2045 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2004 check_scope, user, check_location)
2046 check_scope, user, check_location)
2005 return True
2047 return True
2006
2048
2007 else:
2049 else:
2008 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2050 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2009 check_scope, user, check_location)
2051 check_scope, user, check_location)
2010 return False
2052 return False
2011
2053
2012 def _get_request(self):
2054 def _get_request(self):
2013 return get_request(self)
2055 return get_request(self)
2014
2056
2015 def _get_check_scope(self, cls_name):
2057 def _get_check_scope(self, cls_name):
2016 return {
2058 return {
2017 'HasPermissionAll': 'GLOBAL',
2059 'HasPermissionAll': 'GLOBAL',
2018 'HasPermissionAny': 'GLOBAL',
2060 'HasPermissionAny': 'GLOBAL',
2019 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2061 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2020 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2062 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2021 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2063 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2022 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2064 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2023 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2065 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2024 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2066 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2025 }.get(cls_name, '?:%s' % cls_name)
2067 }.get(cls_name, '?:%s' % cls_name)
2026
2068
2027 def check_permissions(self, user):
2069 def check_permissions(self, user):
2028 """Dummy function for overriding"""
2070 """Dummy function for overriding"""
2029 raise Exception('You have to write this function in child class')
2071 raise Exception('You have to write this function in child class')
2030
2072
2031
2073
2032 class HasPermissionAll(PermsFunction):
2074 class HasPermissionAll(PermsFunction):
2033 def check_permissions(self, user):
2075 def check_permissions(self, user):
2034 perms = user.permissions_with_scope({})
2076 perms = user.permissions_with_scope({})
2035 if self.required_perms.issubset(perms.get('global')):
2077 if self.required_perms.issubset(perms.get('global')):
2036 return True
2078 return True
2037 return False
2079 return False
2038
2080
2039
2081
2040 class HasPermissionAny(PermsFunction):
2082 class HasPermissionAny(PermsFunction):
2041 def check_permissions(self, user):
2083 def check_permissions(self, user):
2042 perms = user.permissions_with_scope({})
2084 perms = user.permissions_with_scope({})
2043 if self.required_perms.intersection(perms.get('global')):
2085 if self.required_perms.intersection(perms.get('global')):
2044 return True
2086 return True
2045 return False
2087 return False
2046
2088
2047
2089
2048 class HasRepoPermissionAll(PermsFunction):
2090 class HasRepoPermissionAll(PermsFunction):
2049 def __call__(self, repo_name=None, check_location='', user=None):
2091 def __call__(self, repo_name=None, check_location='', user=None):
2050 self.repo_name = repo_name
2092 self.repo_name = repo_name
2051 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2093 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2052
2094
2053 def _get_repo_name(self):
2095 def _get_repo_name(self):
2054 if not self.repo_name:
2096 if not self.repo_name:
2055 _request = self._get_request()
2097 _request = self._get_request()
2056 self.repo_name = get_repo_slug(_request)
2098 self.repo_name = get_repo_slug(_request)
2057 return self.repo_name
2099 return self.repo_name
2058
2100
2059 def check_permissions(self, user):
2101 def check_permissions(self, user):
2060 self.repo_name = self._get_repo_name()
2102 self.repo_name = self._get_repo_name()
2061 perms = user.permissions
2103 perms = user.permissions
2062 try:
2104 try:
2063 user_perms = {perms['repositories'][self.repo_name]}
2105 user_perms = {perms['repositories'][self.repo_name]}
2064 except KeyError:
2106 except KeyError:
2065 return False
2107 return False
2066 if self.required_perms.issubset(user_perms):
2108 if self.required_perms.issubset(user_perms):
2067 return True
2109 return True
2068 return False
2110 return False
2069
2111
2070
2112
2071 class HasRepoPermissionAny(PermsFunction):
2113 class HasRepoPermissionAny(PermsFunction):
2072 def __call__(self, repo_name=None, check_location='', user=None):
2114 def __call__(self, repo_name=None, check_location='', user=None):
2073 self.repo_name = repo_name
2115 self.repo_name = repo_name
2074 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2116 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2075
2117
2076 def _get_repo_name(self):
2118 def _get_repo_name(self):
2077 if not self.repo_name:
2119 if not self.repo_name:
2078 _request = self._get_request()
2120 _request = self._get_request()
2079 self.repo_name = get_repo_slug(_request)
2121 self.repo_name = get_repo_slug(_request)
2080 return self.repo_name
2122 return self.repo_name
2081
2123
2082 def check_permissions(self, user):
2124 def check_permissions(self, user):
2083 self.repo_name = self._get_repo_name()
2125 self.repo_name = self._get_repo_name()
2084 perms = user.permissions
2126 perms = user.permissions
2085 try:
2127 try:
2086 user_perms = {perms['repositories'][self.repo_name]}
2128 user_perms = {perms['repositories'][self.repo_name]}
2087 except KeyError:
2129 except KeyError:
2088 return False
2130 return False
2089 if self.required_perms.intersection(user_perms):
2131 if self.required_perms.intersection(user_perms):
2090 return True
2132 return True
2091 return False
2133 return False
2092
2134
2093
2135
2094 class HasRepoGroupPermissionAny(PermsFunction):
2136 class HasRepoGroupPermissionAny(PermsFunction):
2095 def __call__(self, group_name=None, check_location='', user=None):
2137 def __call__(self, group_name=None, check_location='', user=None):
2096 self.repo_group_name = group_name
2138 self.repo_group_name = group_name
2097 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2139 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2098
2140
2099 def check_permissions(self, user):
2141 def check_permissions(self, user):
2100 perms = user.permissions
2142 perms = user.permissions
2101 try:
2143 try:
2102 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2144 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2103 except KeyError:
2145 except KeyError:
2104 return False
2146 return False
2105 if self.required_perms.intersection(user_perms):
2147 if self.required_perms.intersection(user_perms):
2106 return True
2148 return True
2107 return False
2149 return False
2108
2150
2109
2151
2110 class HasRepoGroupPermissionAll(PermsFunction):
2152 class HasRepoGroupPermissionAll(PermsFunction):
2111 def __call__(self, group_name=None, check_location='', user=None):
2153 def __call__(self, group_name=None, check_location='', user=None):
2112 self.repo_group_name = group_name
2154 self.repo_group_name = group_name
2113 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2155 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2114
2156
2115 def check_permissions(self, user):
2157 def check_permissions(self, user):
2116 perms = user.permissions
2158 perms = user.permissions
2117 try:
2159 try:
2118 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2160 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2119 except KeyError:
2161 except KeyError:
2120 return False
2162 return False
2121 if self.required_perms.issubset(user_perms):
2163 if self.required_perms.issubset(user_perms):
2122 return True
2164 return True
2123 return False
2165 return False
2124
2166
2125
2167
2126 class HasUserGroupPermissionAny(PermsFunction):
2168 class HasUserGroupPermissionAny(PermsFunction):
2127 def __call__(self, user_group_name=None, check_location='', user=None):
2169 def __call__(self, user_group_name=None, check_location='', user=None):
2128 self.user_group_name = user_group_name
2170 self.user_group_name = user_group_name
2129 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2171 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2130
2172
2131 def check_permissions(self, user):
2173 def check_permissions(self, user):
2132 perms = user.permissions
2174 perms = user.permissions
2133 try:
2175 try:
2134 user_perms = {perms['user_groups'][self.user_group_name]}
2176 user_perms = {perms['user_groups'][self.user_group_name]}
2135 except KeyError:
2177 except KeyError:
2136 return False
2178 return False
2137 if self.required_perms.intersection(user_perms):
2179 if self.required_perms.intersection(user_perms):
2138 return True
2180 return True
2139 return False
2181 return False
2140
2182
2141
2183
2142 class HasUserGroupPermissionAll(PermsFunction):
2184 class HasUserGroupPermissionAll(PermsFunction):
2143 def __call__(self, user_group_name=None, check_location='', user=None):
2185 def __call__(self, user_group_name=None, check_location='', user=None):
2144 self.user_group_name = user_group_name
2186 self.user_group_name = user_group_name
2145 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2187 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2146
2188
2147 def check_permissions(self, user):
2189 def check_permissions(self, user):
2148 perms = user.permissions
2190 perms = user.permissions
2149 try:
2191 try:
2150 user_perms = {perms['user_groups'][self.user_group_name]}
2192 user_perms = {perms['user_groups'][self.user_group_name]}
2151 except KeyError:
2193 except KeyError:
2152 return False
2194 return False
2153 if self.required_perms.issubset(user_perms):
2195 if self.required_perms.issubset(user_perms):
2154 return True
2196 return True
2155 return False
2197 return False
2156
2198
2157
2199
2158 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2200 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2159 class HasPermissionAnyMiddleware(object):
2201 class HasPermissionAnyMiddleware(object):
2160 def __init__(self, *perms):
2202 def __init__(self, *perms):
2161 self.required_perms = set(perms)
2203 self.required_perms = set(perms)
2162
2204
2163 def __call__(self, auth_user, repo_name):
2205 def __call__(self, auth_user, repo_name):
2164 # repo_name MUST be unicode, since we handle keys in permission
2206 # repo_name MUST be unicode, since we handle keys in permission
2165 # dict by unicode
2207 # dict by unicode
2166 repo_name = safe_unicode(repo_name)
2208 repo_name = safe_unicode(repo_name)
2167 log.debug(
2209 log.debug(
2168 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2210 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2169 self.required_perms, auth_user, repo_name)
2211 self.required_perms, auth_user, repo_name)
2170
2212
2171 if self.check_permissions(auth_user, repo_name):
2213 if self.check_permissions(auth_user, repo_name):
2172 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2214 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2173 repo_name, auth_user, 'PermissionMiddleware')
2215 repo_name, auth_user, 'PermissionMiddleware')
2174 return True
2216 return True
2175
2217
2176 else:
2218 else:
2177 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2219 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2178 repo_name, auth_user, 'PermissionMiddleware')
2220 repo_name, auth_user, 'PermissionMiddleware')
2179 return False
2221 return False
2180
2222
2181 def check_permissions(self, user, repo_name):
2223 def check_permissions(self, user, repo_name):
2182 perms = user.permissions_with_scope({'repo_name': repo_name})
2224 perms = user.permissions_with_scope({'repo_name': repo_name})
2183
2225
2184 try:
2226 try:
2185 user_perms = {perms['repositories'][repo_name]}
2227 user_perms = {perms['repositories'][repo_name]}
2186 except Exception:
2228 except Exception:
2187 log.exception('Error while accessing user permissions')
2229 log.exception('Error while accessing user permissions')
2188 return False
2230 return False
2189
2231
2190 if self.required_perms.intersection(user_perms):
2232 if self.required_perms.intersection(user_perms):
2191 return True
2233 return True
2192 return False
2234 return False
2193
2235
2194
2236
2195 # SPECIAL VERSION TO HANDLE API AUTH
2237 # SPECIAL VERSION TO HANDLE API AUTH
2196 class _BaseApiPerm(object):
2238 class _BaseApiPerm(object):
2197 def __init__(self, *perms):
2239 def __init__(self, *perms):
2198 self.required_perms = set(perms)
2240 self.required_perms = set(perms)
2199
2241
2200 def __call__(self, check_location=None, user=None, repo_name=None,
2242 def __call__(self, check_location=None, user=None, repo_name=None,
2201 group_name=None, user_group_name=None):
2243 group_name=None, user_group_name=None):
2202 cls_name = self.__class__.__name__
2244 cls_name = self.__class__.__name__
2203 check_scope = 'global:%s' % (self.required_perms,)
2245 check_scope = 'global:%s' % (self.required_perms,)
2204 if repo_name:
2246 if repo_name:
2205 check_scope += ', repo_name:%s' % (repo_name,)
2247 check_scope += ', repo_name:%s' % (repo_name,)
2206
2248
2207 if group_name:
2249 if group_name:
2208 check_scope += ', repo_group_name:%s' % (group_name,)
2250 check_scope += ', repo_group_name:%s' % (group_name,)
2209
2251
2210 if user_group_name:
2252 if user_group_name:
2211 check_scope += ', user_group_name:%s' % (user_group_name,)
2253 check_scope += ', user_group_name:%s' % (user_group_name,)
2212
2254
2213 log.debug('checking cls:%s %s %s @ %s',
2255 log.debug('checking cls:%s %s %s @ %s',
2214 cls_name, self.required_perms, check_scope, check_location)
2256 cls_name, self.required_perms, check_scope, check_location)
2215 if not user:
2257 if not user:
2216 log.debug('Empty User passed into arguments')
2258 log.debug('Empty User passed into arguments')
2217 return False
2259 return False
2218
2260
2219 # process user
2261 # process user
2220 if not isinstance(user, AuthUser):
2262 if not isinstance(user, AuthUser):
2221 user = AuthUser(user.user_id)
2263 user = AuthUser(user.user_id)
2222 if not check_location:
2264 if not check_location:
2223 check_location = 'unspecified'
2265 check_location = 'unspecified'
2224 if self.check_permissions(user.permissions, repo_name, group_name,
2266 if self.check_permissions(user.permissions, repo_name, group_name,
2225 user_group_name):
2267 user_group_name):
2226 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2268 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2227 check_scope, user, check_location)
2269 check_scope, user, check_location)
2228 return True
2270 return True
2229
2271
2230 else:
2272 else:
2231 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2273 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2232 check_scope, user, check_location)
2274 check_scope, user, check_location)
2233 return False
2275 return False
2234
2276
2235 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2236 user_group_name=None):
2278 user_group_name=None):
2237 """
2279 """
2238 implement in child class should return True if permissions are ok,
2280 implement in child class should return True if permissions are ok,
2239 False otherwise
2281 False otherwise
2240
2282
2241 :param perm_defs: dict with permission definitions
2283 :param perm_defs: dict with permission definitions
2242 :param repo_name: repo name
2284 :param repo_name: repo name
2243 """
2285 """
2244 raise NotImplementedError()
2286 raise NotImplementedError()
2245
2287
2246
2288
2247 class HasPermissionAllApi(_BaseApiPerm):
2289 class HasPermissionAllApi(_BaseApiPerm):
2248 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2249 user_group_name=None):
2291 user_group_name=None):
2250 if self.required_perms.issubset(perm_defs.get('global')):
2292 if self.required_perms.issubset(perm_defs.get('global')):
2251 return True
2293 return True
2252 return False
2294 return False
2253
2295
2254
2296
2255 class HasPermissionAnyApi(_BaseApiPerm):
2297 class HasPermissionAnyApi(_BaseApiPerm):
2256 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2298 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2257 user_group_name=None):
2299 user_group_name=None):
2258 if self.required_perms.intersection(perm_defs.get('global')):
2300 if self.required_perms.intersection(perm_defs.get('global')):
2259 return True
2301 return True
2260 return False
2302 return False
2261
2303
2262
2304
2263 class HasRepoPermissionAllApi(_BaseApiPerm):
2305 class HasRepoPermissionAllApi(_BaseApiPerm):
2264 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2306 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2265 user_group_name=None):
2307 user_group_name=None):
2266 try:
2308 try:
2267 _user_perms = {perm_defs['repositories'][repo_name]}
2309 _user_perms = {perm_defs['repositories'][repo_name]}
2268 except KeyError:
2310 except KeyError:
2269 log.warning(traceback.format_exc())
2311 log.warning(traceback.format_exc())
2270 return False
2312 return False
2271 if self.required_perms.issubset(_user_perms):
2313 if self.required_perms.issubset(_user_perms):
2272 return True
2314 return True
2273 return False
2315 return False
2274
2316
2275
2317
2276 class HasRepoPermissionAnyApi(_BaseApiPerm):
2318 class HasRepoPermissionAnyApi(_BaseApiPerm):
2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2319 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2278 user_group_name=None):
2320 user_group_name=None):
2279 try:
2321 try:
2280 _user_perms = {perm_defs['repositories'][repo_name]}
2322 _user_perms = {perm_defs['repositories'][repo_name]}
2281 except KeyError:
2323 except KeyError:
2282 log.warning(traceback.format_exc())
2324 log.warning(traceback.format_exc())
2283 return False
2325 return False
2284 if self.required_perms.intersection(_user_perms):
2326 if self.required_perms.intersection(_user_perms):
2285 return True
2327 return True
2286 return False
2328 return False
2287
2329
2288
2330
2289 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2331 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2332 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2291 user_group_name=None):
2333 user_group_name=None):
2292 try:
2334 try:
2293 _user_perms = {perm_defs['repositories_groups'][group_name]}
2335 _user_perms = {perm_defs['repositories_groups'][group_name]}
2294 except KeyError:
2336 except KeyError:
2295 log.warning(traceback.format_exc())
2337 log.warning(traceback.format_exc())
2296 return False
2338 return False
2297 if self.required_perms.intersection(_user_perms):
2339 if self.required_perms.intersection(_user_perms):
2298 return True
2340 return True
2299 return False
2341 return False
2300
2342
2301
2343
2302 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2344 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2303 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2345 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2304 user_group_name=None):
2346 user_group_name=None):
2305 try:
2347 try:
2306 _user_perms = {perm_defs['repositories_groups'][group_name]}
2348 _user_perms = {perm_defs['repositories_groups'][group_name]}
2307 except KeyError:
2349 except KeyError:
2308 log.warning(traceback.format_exc())
2350 log.warning(traceback.format_exc())
2309 return False
2351 return False
2310 if self.required_perms.issubset(_user_perms):
2352 if self.required_perms.issubset(_user_perms):
2311 return True
2353 return True
2312 return False
2354 return False
2313
2355
2314
2356
2315 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2357 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2316 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2358 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2317 user_group_name=None):
2359 user_group_name=None):
2318 try:
2360 try:
2319 _user_perms = {perm_defs['user_groups'][user_group_name]}
2361 _user_perms = {perm_defs['user_groups'][user_group_name]}
2320 except KeyError:
2362 except KeyError:
2321 log.warning(traceback.format_exc())
2363 log.warning(traceback.format_exc())
2322 return False
2364 return False
2323 if self.required_perms.intersection(_user_perms):
2365 if self.required_perms.intersection(_user_perms):
2324 return True
2366 return True
2325 return False
2367 return False
2326
2368
2327
2369
2328 def check_ip_access(source_ip, allowed_ips=None):
2370 def check_ip_access(source_ip, allowed_ips=None):
2329 """
2371 """
2330 Checks if source_ip is a subnet of any of allowed_ips.
2372 Checks if source_ip is a subnet of any of allowed_ips.
2331
2373
2332 :param source_ip:
2374 :param source_ip:
2333 :param allowed_ips: list of allowed ips together with mask
2375 :param allowed_ips: list of allowed ips together with mask
2334 """
2376 """
2335 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2377 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2336 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2378 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2337 if isinstance(allowed_ips, (tuple, list, set)):
2379 if isinstance(allowed_ips, (tuple, list, set)):
2338 for ip in allowed_ips:
2380 for ip in allowed_ips:
2339 ip = safe_unicode(ip)
2381 ip = safe_unicode(ip)
2340 try:
2382 try:
2341 network_address = ipaddress.ip_network(ip, strict=False)
2383 network_address = ipaddress.ip_network(ip, strict=False)
2342 if source_ip_address in network_address:
2384 if source_ip_address in network_address:
2343 log.debug('IP %s is network %s', source_ip_address, network_address)
2385 log.debug('IP %s is network %s', source_ip_address, network_address)
2344 return True
2386 return True
2345 # for any case we cannot determine the IP, don't crash just
2387 # for any case we cannot determine the IP, don't crash just
2346 # skip it and log as error, we want to say forbidden still when
2388 # skip it and log as error, we want to say forbidden still when
2347 # sending bad IP
2389 # sending bad IP
2348 except Exception:
2390 except Exception:
2349 log.error(traceback.format_exc())
2391 log.error(traceback.format_exc())
2350 continue
2392 continue
2351 return False
2393 return False
2352
2394
2353
2395
2354 def get_cython_compat_decorator(wrapper, func):
2396 def get_cython_compat_decorator(wrapper, func):
2355 """
2397 """
2356 Creates a cython compatible decorator. The previously used
2398 Creates a cython compatible decorator. The previously used
2357 decorator.decorator() function seems to be incompatible with cython.
2399 decorator.decorator() function seems to be incompatible with cython.
2358
2400
2359 :param wrapper: __wrapper method of the decorator class
2401 :param wrapper: __wrapper method of the decorator class
2360 :param func: decorated function
2402 :param func: decorated function
2361 """
2403 """
2362 @wraps(func)
2404 @wraps(func)
2363 def local_wrapper(*args, **kwds):
2405 def local_wrapper(*args, **kwds):
2364 return wrapper(func, *args, **kwds)
2406 return wrapper(func, *args, **kwds)
2365 local_wrapper.__wrapped__ = func
2407 local_wrapper.__wrapped__ = func
2366 return local_wrapper
2408 return local_wrapper
2367
2409
2368
2410
@@ -1,5474 +1,5489 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_default_user(cls, cache=False, refresh=False):
1028 def get_default_user(cls, cache=False, refresh=False):
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1030 if user is None:
1030 if user is None:
1031 raise Exception('FATAL: Missing default account!')
1031 raise Exception('FATAL: Missing default account!')
1032 if refresh:
1032 if refresh:
1033 # The default user might be based on outdated state which
1033 # The default user might be based on outdated state which
1034 # has been loaded from the cache.
1034 # has been loaded from the cache.
1035 # A call to refresh() ensures that the
1035 # A call to refresh() ensures that the
1036 # latest state from the database is used.
1036 # latest state from the database is used.
1037 Session().refresh(user)
1037 Session().refresh(user)
1038 return user
1038 return user
1039
1039
1040 def _get_default_perms(self, user, suffix=''):
1040 def _get_default_perms(self, user, suffix=''):
1041 from rhodecode.model.permission import PermissionModel
1041 from rhodecode.model.permission import PermissionModel
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1043
1043
1044 def get_default_perms(self, suffix=''):
1044 def get_default_perms(self, suffix=''):
1045 return self._get_default_perms(self, suffix)
1045 return self._get_default_perms(self, suffix)
1046
1046
1047 def get_api_data(self, include_secrets=False, details='full'):
1047 def get_api_data(self, include_secrets=False, details='full'):
1048 """
1048 """
1049 Common function for generating user related data for API
1049 Common function for generating user related data for API
1050
1050
1051 :param include_secrets: By default secrets in the API data will be replaced
1051 :param include_secrets: By default secrets in the API data will be replaced
1052 by a placeholder value to prevent exposing this data by accident. In case
1052 by a placeholder value to prevent exposing this data by accident. In case
1053 this data shall be exposed, set this flag to ``True``.
1053 this data shall be exposed, set this flag to ``True``.
1054
1054
1055 :param details: details can be 'basic|full' basic gives only a subset of
1055 :param details: details can be 'basic|full' basic gives only a subset of
1056 the available user information that includes user_id, name and emails.
1056 the available user information that includes user_id, name and emails.
1057 """
1057 """
1058 user = self
1058 user = self
1059 user_data = self.user_data
1059 user_data = self.user_data
1060 data = {
1060 data = {
1061 'user_id': user.user_id,
1061 'user_id': user.user_id,
1062 'username': user.username,
1062 'username': user.username,
1063 'firstname': user.name,
1063 'firstname': user.name,
1064 'lastname': user.lastname,
1064 'lastname': user.lastname,
1065 'description': user.description,
1065 'description': user.description,
1066 'email': user.email,
1066 'email': user.email,
1067 'emails': user.emails,
1067 'emails': user.emails,
1068 }
1068 }
1069 if details == 'basic':
1069 if details == 'basic':
1070 return data
1070 return data
1071
1071
1072 auth_token_length = 40
1072 auth_token_length = 40
1073 auth_token_replacement = '*' * auth_token_length
1073 auth_token_replacement = '*' * auth_token_length
1074
1074
1075 extras = {
1075 extras = {
1076 'auth_tokens': [auth_token_replacement],
1076 'auth_tokens': [auth_token_replacement],
1077 'active': user.active,
1077 'active': user.active,
1078 'admin': user.admin,
1078 'admin': user.admin,
1079 'extern_type': user.extern_type,
1079 'extern_type': user.extern_type,
1080 'extern_name': user.extern_name,
1080 'extern_name': user.extern_name,
1081 'last_login': user.last_login,
1081 'last_login': user.last_login,
1082 'last_activity': user.last_activity,
1082 'last_activity': user.last_activity,
1083 'ip_addresses': user.ip_addresses,
1083 'ip_addresses': user.ip_addresses,
1084 'language': user_data.get('language')
1084 'language': user_data.get('language')
1085 }
1085 }
1086 data.update(extras)
1086 data.update(extras)
1087
1087
1088 if include_secrets:
1088 if include_secrets:
1089 data['auth_tokens'] = user.auth_tokens
1089 data['auth_tokens'] = user.auth_tokens
1090 return data
1090 return data
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 data = {
1093 data = {
1094 'full_name': self.full_name,
1094 'full_name': self.full_name,
1095 'full_name_or_username': self.full_name_or_username,
1095 'full_name_or_username': self.full_name_or_username,
1096 'short_contact': self.short_contact,
1096 'short_contact': self.short_contact,
1097 'full_contact': self.full_contact,
1097 'full_contact': self.full_contact,
1098 }
1098 }
1099 data.update(self.get_api_data())
1099 data.update(self.get_api_data())
1100 return data
1100 return data
1101
1101
1102
1102
1103 class UserApiKeys(Base, BaseModel):
1103 class UserApiKeys(Base, BaseModel):
1104 __tablename__ = 'user_api_keys'
1104 __tablename__ = 'user_api_keys'
1105 __table_args__ = (
1105 __table_args__ = (
1106 Index('uak_api_key_idx', 'api_key'),
1106 Index('uak_api_key_idx', 'api_key'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1108 base_table_args
1108 base_table_args
1109 )
1109 )
1110 __mapper_args__ = {}
1110 __mapper_args__ = {}
1111
1111
1112 # ApiKey role
1112 # ApiKey role
1113 ROLE_ALL = 'token_role_all'
1113 ROLE_ALL = 'token_role_all'
1114 ROLE_HTTP = 'token_role_http'
1114 ROLE_HTTP = 'token_role_http'
1115 ROLE_VCS = 'token_role_vcs'
1115 ROLE_VCS = 'token_role_vcs'
1116 ROLE_API = 'token_role_api'
1116 ROLE_API = 'token_role_api'
1117 ROLE_FEED = 'token_role_feed'
1117 ROLE_FEED = 'token_role_feed'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1120
1120
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1122
1122
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1127 expires = Column('expires', Float(53), nullable=False)
1127 expires = Column('expires', Float(53), nullable=False)
1128 role = Column('role', String(255), nullable=True)
1128 role = Column('role', String(255), nullable=True)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1130
1130
1131 # scope columns
1131 # scope columns
1132 repo_id = Column(
1132 repo_id = Column(
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1134 nullable=True, unique=None, default=None)
1134 nullable=True, unique=None, default=None)
1135 repo = relationship('Repository', lazy='joined')
1135 repo = relationship('Repository', lazy='joined')
1136
1136
1137 repo_group_id = Column(
1137 repo_group_id = Column(
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1139 nullable=True, unique=None, default=None)
1139 nullable=True, unique=None, default=None)
1140 repo_group = relationship('RepoGroup', lazy='joined')
1140 repo_group = relationship('RepoGroup', lazy='joined')
1141
1141
1142 user = relationship('User', lazy='joined')
1142 user = relationship('User', lazy='joined')
1143
1143
1144 def __unicode__(self):
1144 def __unicode__(self):
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1146
1146
1147 def __json__(self):
1147 def __json__(self):
1148 data = {
1148 data = {
1149 'auth_token': self.api_key,
1149 'auth_token': self.api_key,
1150 'role': self.role,
1150 'role': self.role,
1151 'scope': self.scope_humanized,
1151 'scope': self.scope_humanized,
1152 'expired': self.expired
1152 'expired': self.expired
1153 }
1153 }
1154 return data
1154 return data
1155
1155
1156 def get_api_data(self, include_secrets=False):
1156 def get_api_data(self, include_secrets=False):
1157 data = self.__json__()
1157 data = self.__json__()
1158 if include_secrets:
1158 if include_secrets:
1159 return data
1159 return data
1160 else:
1160 else:
1161 data['auth_token'] = self.token_obfuscated
1161 data['auth_token'] = self.token_obfuscated
1162 return data
1162 return data
1163
1163
1164 @hybrid_property
1164 @hybrid_property
1165 def description_safe(self):
1165 def description_safe(self):
1166 from rhodecode.lib import helpers as h
1166 from rhodecode.lib import helpers as h
1167 return h.escape(self.description)
1167 return h.escape(self.description)
1168
1168
1169 @property
1169 @property
1170 def expired(self):
1170 def expired(self):
1171 if self.expires == -1:
1171 if self.expires == -1:
1172 return False
1172 return False
1173 return time.time() > self.expires
1173 return time.time() > self.expires
1174
1174
1175 @classmethod
1175 @classmethod
1176 def _get_role_name(cls, role):
1176 def _get_role_name(cls, role):
1177 return {
1177 return {
1178 cls.ROLE_ALL: _('all'),
1178 cls.ROLE_ALL: _('all'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1179 cls.ROLE_HTTP: _('http/web interface'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1181 cls.ROLE_API: _('api calls'),
1181 cls.ROLE_API: _('api calls'),
1182 cls.ROLE_FEED: _('feed access'),
1182 cls.ROLE_FEED: _('feed access'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1184 }.get(role, role)
1184 }.get(role, role)
1185
1185
1186 @property
1186 @property
1187 def role_humanized(self):
1187 def role_humanized(self):
1188 return self._get_role_name(self.role)
1188 return self._get_role_name(self.role)
1189
1189
1190 def _get_scope(self):
1190 def _get_scope(self):
1191 if self.repo:
1191 if self.repo:
1192 return 'Repository: {}'.format(self.repo.repo_name)
1192 return 'Repository: {}'.format(self.repo.repo_name)
1193 if self.repo_group:
1193 if self.repo_group:
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1195 return 'Global'
1195 return 'Global'
1196
1196
1197 @property
1197 @property
1198 def scope_humanized(self):
1198 def scope_humanized(self):
1199 return self._get_scope()
1199 return self._get_scope()
1200
1200
1201 @property
1201 @property
1202 def token_obfuscated(self):
1202 def token_obfuscated(self):
1203 if self.api_key:
1203 if self.api_key:
1204 return self.api_key[:4] + "****"
1204 return self.api_key[:4] + "****"
1205
1205
1206
1206
1207 class UserEmailMap(Base, BaseModel):
1207 class UserEmailMap(Base, BaseModel):
1208 __tablename__ = 'user_email_map'
1208 __tablename__ = 'user_email_map'
1209 __table_args__ = (
1209 __table_args__ = (
1210 Index('uem_email_idx', 'email'),
1210 Index('uem_email_idx', 'email'),
1211 UniqueConstraint('email'),
1211 UniqueConstraint('email'),
1212 base_table_args
1212 base_table_args
1213 )
1213 )
1214 __mapper_args__ = {}
1214 __mapper_args__ = {}
1215
1215
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1219 user = relationship('User', lazy='joined')
1219 user = relationship('User', lazy='joined')
1220
1220
1221 @validates('_email')
1221 @validates('_email')
1222 def validate_email(self, key, email):
1222 def validate_email(self, key, email):
1223 # check if this email is not main one
1223 # check if this email is not main one
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1224 main_email = Session().query(User).filter(User.email == email).scalar()
1225 if main_email is not None:
1225 if main_email is not None:
1226 raise AttributeError('email %s is present is user table' % email)
1226 raise AttributeError('email %s is present is user table' % email)
1227 return email
1227 return email
1228
1228
1229 @hybrid_property
1229 @hybrid_property
1230 def email(self):
1230 def email(self):
1231 return self._email
1231 return self._email
1232
1232
1233 @email.setter
1233 @email.setter
1234 def email(self, val):
1234 def email(self, val):
1235 self._email = val.lower() if val else None
1235 self._email = val.lower() if val else None
1236
1236
1237
1237
1238 class UserIpMap(Base, BaseModel):
1238 class UserIpMap(Base, BaseModel):
1239 __tablename__ = 'user_ip_map'
1239 __tablename__ = 'user_ip_map'
1240 __table_args__ = (
1240 __table_args__ = (
1241 UniqueConstraint('user_id', 'ip_addr'),
1241 UniqueConstraint('user_id', 'ip_addr'),
1242 base_table_args
1242 base_table_args
1243 )
1243 )
1244 __mapper_args__ = {}
1244 __mapper_args__ = {}
1245
1245
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 @hybrid_property
1253 @hybrid_property
1254 def description_safe(self):
1254 def description_safe(self):
1255 from rhodecode.lib import helpers as h
1255 from rhodecode.lib import helpers as h
1256 return h.escape(self.description)
1256 return h.escape(self.description)
1257
1257
1258 @classmethod
1258 @classmethod
1259 def _get_ip_range(cls, ip_addr):
1259 def _get_ip_range(cls, ip_addr):
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1261 return [str(net.network_address), str(net.broadcast_address)]
1261 return [str(net.network_address), str(net.broadcast_address)]
1262
1262
1263 def __json__(self):
1263 def __json__(self):
1264 return {
1264 return {
1265 'ip_addr': self.ip_addr,
1265 'ip_addr': self.ip_addr,
1266 'ip_range': self._get_ip_range(self.ip_addr),
1266 'ip_range': self._get_ip_range(self.ip_addr),
1267 }
1267 }
1268
1268
1269 def __unicode__(self):
1269 def __unicode__(self):
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1271 self.user_id, self.ip_addr)
1271 self.user_id, self.ip_addr)
1272
1272
1273
1273
1274 class UserSshKeys(Base, BaseModel):
1274 class UserSshKeys(Base, BaseModel):
1275 __tablename__ = 'user_ssh_keys'
1275 __tablename__ = 'user_ssh_keys'
1276 __table_args__ = (
1276 __table_args__ = (
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1278
1278
1279 UniqueConstraint('ssh_key_fingerprint'),
1279 UniqueConstraint('ssh_key_fingerprint'),
1280
1280
1281 base_table_args
1281 base_table_args
1282 )
1282 )
1283 __mapper_args__ = {}
1283 __mapper_args__ = {}
1284
1284
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1288
1288
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1290
1290
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1294
1294
1295 user = relationship('User', lazy='joined')
1295 user = relationship('User', lazy='joined')
1296
1296
1297 def __json__(self):
1297 def __json__(self):
1298 data = {
1298 data = {
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1300 'description': self.description,
1300 'description': self.description,
1301 'created_on': self.created_on
1301 'created_on': self.created_on
1302 }
1302 }
1303 return data
1303 return data
1304
1304
1305 def get_api_data(self):
1305 def get_api_data(self):
1306 data = self.__json__()
1306 data = self.__json__()
1307 return data
1307 return data
1308
1308
1309
1309
1310 class UserLog(Base, BaseModel):
1310 class UserLog(Base, BaseModel):
1311 __tablename__ = 'user_logs'
1311 __tablename__ = 'user_logs'
1312 __table_args__ = (
1312 __table_args__ = (
1313 base_table_args,
1313 base_table_args,
1314 )
1314 )
1315
1315
1316 VERSION_1 = 'v1'
1316 VERSION_1 = 'v1'
1317 VERSION_2 = 'v2'
1317 VERSION_2 = 'v2'
1318 VERSIONS = [VERSION_1, VERSION_2]
1318 VERSIONS = [VERSION_1, VERSION_2]
1319
1319
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1328
1328
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1332
1332
1333 def __unicode__(self):
1333 def __unicode__(self):
1334 return u"<%s('id:%s:%s')>" % (
1334 return u"<%s('id:%s:%s')>" % (
1335 self.__class__.__name__, self.repository_name, self.action)
1335 self.__class__.__name__, self.repository_name, self.action)
1336
1336
1337 def __json__(self):
1337 def __json__(self):
1338 return {
1338 return {
1339 'user_id': self.user_id,
1339 'user_id': self.user_id,
1340 'username': self.username,
1340 'username': self.username,
1341 'repository_id': self.repository_id,
1341 'repository_id': self.repository_id,
1342 'repository_name': self.repository_name,
1342 'repository_name': self.repository_name,
1343 'user_ip': self.user_ip,
1343 'user_ip': self.user_ip,
1344 'action_date': self.action_date,
1344 'action_date': self.action_date,
1345 'action': self.action,
1345 'action': self.action,
1346 }
1346 }
1347
1347
1348 @hybrid_property
1348 @hybrid_property
1349 def entry_id(self):
1349 def entry_id(self):
1350 return self.user_log_id
1350 return self.user_log_id
1351
1351
1352 @property
1352 @property
1353 def action_as_day(self):
1353 def action_as_day(self):
1354 return datetime.date(*self.action_date.timetuple()[:3])
1354 return datetime.date(*self.action_date.timetuple()[:3])
1355
1355
1356 user = relationship('User')
1356 user = relationship('User')
1357 repository = relationship('Repository', cascade='')
1357 repository = relationship('Repository', cascade='')
1358
1358
1359
1359
1360 class UserGroup(Base, BaseModel):
1360 class UserGroup(Base, BaseModel):
1361 __tablename__ = 'users_groups'
1361 __tablename__ = 'users_groups'
1362 __table_args__ = (
1362 __table_args__ = (
1363 base_table_args,
1363 base_table_args,
1364 )
1364 )
1365
1365
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1374
1374
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1381
1381
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1384
1384
1385 @classmethod
1385 @classmethod
1386 def _load_group_data(cls, column):
1386 def _load_group_data(cls, column):
1387 if not column:
1387 if not column:
1388 return {}
1388 return {}
1389
1389
1390 try:
1390 try:
1391 return json.loads(column) or {}
1391 return json.loads(column) or {}
1392 except TypeError:
1392 except TypeError:
1393 return {}
1393 return {}
1394
1394
1395 @hybrid_property
1395 @hybrid_property
1396 def description_safe(self):
1396 def description_safe(self):
1397 from rhodecode.lib import helpers as h
1397 from rhodecode.lib import helpers as h
1398 return h.escape(self.user_group_description)
1398 return h.escape(self.user_group_description)
1399
1399
1400 @hybrid_property
1400 @hybrid_property
1401 def group_data(self):
1401 def group_data(self):
1402 return self._load_group_data(self._group_data)
1402 return self._load_group_data(self._group_data)
1403
1403
1404 @group_data.expression
1404 @group_data.expression
1405 def group_data(self, **kwargs):
1405 def group_data(self, **kwargs):
1406 return self._group_data
1406 return self._group_data
1407
1407
1408 @group_data.setter
1408 @group_data.setter
1409 def group_data(self, val):
1409 def group_data(self, val):
1410 try:
1410 try:
1411 self._group_data = json.dumps(val)
1411 self._group_data = json.dumps(val)
1412 except Exception:
1412 except Exception:
1413 log.error(traceback.format_exc())
1413 log.error(traceback.format_exc())
1414
1414
1415 @classmethod
1415 @classmethod
1416 def _load_sync(cls, group_data):
1416 def _load_sync(cls, group_data):
1417 if group_data:
1417 if group_data:
1418 return group_data.get('extern_type')
1418 return group_data.get('extern_type')
1419
1419
1420 @property
1420 @property
1421 def sync(self):
1421 def sync(self):
1422 return self._load_sync(self.group_data)
1422 return self._load_sync(self.group_data)
1423
1423
1424 def __unicode__(self):
1424 def __unicode__(self):
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1426 self.users_group_id,
1426 self.users_group_id,
1427 self.users_group_name)
1427 self.users_group_name)
1428
1428
1429 @classmethod
1429 @classmethod
1430 def get_by_group_name(cls, group_name, cache=False,
1430 def get_by_group_name(cls, group_name, cache=False,
1431 case_insensitive=False):
1431 case_insensitive=False):
1432 if case_insensitive:
1432 if case_insensitive:
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1434 func.lower(group_name))
1434 func.lower(group_name))
1435
1435
1436 else:
1436 else:
1437 q = cls.query().filter(cls.users_group_name == group_name)
1437 q = cls.query().filter(cls.users_group_name == group_name)
1438 if cache:
1438 if cache:
1439 q = q.options(
1439 q = q.options(
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1441 return q.scalar()
1441 return q.scalar()
1442
1442
1443 @classmethod
1443 @classmethod
1444 def get(cls, user_group_id, cache=False):
1444 def get(cls, user_group_id, cache=False):
1445 if not user_group_id:
1445 if not user_group_id:
1446 return
1446 return
1447
1447
1448 user_group = cls.query()
1448 user_group = cls.query()
1449 if cache:
1449 if cache:
1450 user_group = user_group.options(
1450 user_group = user_group.options(
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1452 return user_group.get(user_group_id)
1452 return user_group.get(user_group_id)
1453
1453
1454 def permissions(self, with_admins=True, with_owner=True,
1454 def permissions(self, with_admins=True, with_owner=True,
1455 expand_from_user_groups=False):
1455 expand_from_user_groups=False):
1456 """
1456 """
1457 Permissions for user groups
1457 Permissions for user groups
1458 """
1458 """
1459 _admin_perm = 'usergroup.admin'
1459 _admin_perm = 'usergroup.admin'
1460
1460
1461 owner_row = []
1461 owner_row = []
1462 if with_owner:
1462 if with_owner:
1463 usr = AttributeDict(self.user.get_dict())
1463 usr = AttributeDict(self.user.get_dict())
1464 usr.owner_row = True
1464 usr.owner_row = True
1465 usr.permission = _admin_perm
1465 usr.permission = _admin_perm
1466 owner_row.append(usr)
1466 owner_row.append(usr)
1467
1467
1468 super_admin_ids = []
1468 super_admin_ids = []
1469 super_admin_rows = []
1469 super_admin_rows = []
1470 if with_admins:
1470 if with_admins:
1471 for usr in User.get_all_super_admins():
1471 for usr in User.get_all_super_admins():
1472 super_admin_ids.append(usr.user_id)
1472 super_admin_ids.append(usr.user_id)
1473 # if this admin is also owner, don't double the record
1473 # if this admin is also owner, don't double the record
1474 if usr.user_id == owner_row[0].user_id:
1474 if usr.user_id == owner_row[0].user_id:
1475 owner_row[0].admin_row = True
1475 owner_row[0].admin_row = True
1476 else:
1476 else:
1477 usr = AttributeDict(usr.get_dict())
1477 usr = AttributeDict(usr.get_dict())
1478 usr.admin_row = True
1478 usr.admin_row = True
1479 usr.permission = _admin_perm
1479 usr.permission = _admin_perm
1480 super_admin_rows.append(usr)
1480 super_admin_rows.append(usr)
1481
1481
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1484 joinedload(UserUserGroupToPerm.user),
1484 joinedload(UserUserGroupToPerm.user),
1485 joinedload(UserUserGroupToPerm.permission),)
1485 joinedload(UserUserGroupToPerm.permission),)
1486
1486
1487 # get owners and admins and permissions. We do a trick of re-writing
1487 # get owners and admins and permissions. We do a trick of re-writing
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1489 # has a global reference and changing one object propagates to all
1489 # has a global reference and changing one object propagates to all
1490 # others. This means if admin is also an owner admin_row that change
1490 # others. This means if admin is also an owner admin_row that change
1491 # would propagate to both objects
1491 # would propagate to both objects
1492 perm_rows = []
1492 perm_rows = []
1493 for _usr in q.all():
1493 for _usr in q.all():
1494 usr = AttributeDict(_usr.user.get_dict())
1494 usr = AttributeDict(_usr.user.get_dict())
1495 # if this user is also owner/admin, mark as duplicate record
1495 # if this user is also owner/admin, mark as duplicate record
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1497 usr.duplicate_perm = True
1497 usr.duplicate_perm = True
1498 usr.permission = _usr.permission.permission_name
1498 usr.permission = _usr.permission.permission_name
1499 perm_rows.append(usr)
1499 perm_rows.append(usr)
1500
1500
1501 # filter the perm rows by 'default' first and then sort them by
1501 # filter the perm rows by 'default' first and then sort them by
1502 # admin,write,read,none permissions sorted again alphabetically in
1502 # admin,write,read,none permissions sorted again alphabetically in
1503 # each group
1503 # each group
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1505
1505
1506 user_groups_rows = []
1506 user_groups_rows = []
1507 if expand_from_user_groups:
1507 if expand_from_user_groups:
1508 for ug in self.permission_user_groups(with_members=True):
1508 for ug in self.permission_user_groups(with_members=True):
1509 for user_data in ug.members:
1509 for user_data in ug.members:
1510 user_groups_rows.append(user_data)
1510 user_groups_rows.append(user_data)
1511
1511
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1513
1513
1514 def permission_user_groups(self, with_members=False):
1514 def permission_user_groups(self, with_members=False):
1515 q = UserGroupUserGroupToPerm.query()\
1515 q = UserGroupUserGroupToPerm.query()\
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1519 joinedload(UserGroupUserGroupToPerm.permission),)
1520
1520
1521 perm_rows = []
1521 perm_rows = []
1522 for _user_group in q.all():
1522 for _user_group in q.all():
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1523 entry = AttributeDict(_user_group.user_group.get_dict())
1524 entry.permission = _user_group.permission.permission_name
1524 entry.permission = _user_group.permission.permission_name
1525 if with_members:
1525 if with_members:
1526 entry.members = [x.user.get_dict()
1526 entry.members = [x.user.get_dict()
1527 for x in _user_group.user_group.members]
1527 for x in _user_group.user_group.members]
1528 perm_rows.append(entry)
1528 perm_rows.append(entry)
1529
1529
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1531 return perm_rows
1531 return perm_rows
1532
1532
1533 def _get_default_perms(self, user_group, suffix=''):
1533 def _get_default_perms(self, user_group, suffix=''):
1534 from rhodecode.model.permission import PermissionModel
1534 from rhodecode.model.permission import PermissionModel
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1536
1536
1537 def get_default_perms(self, suffix=''):
1537 def get_default_perms(self, suffix=''):
1538 return self._get_default_perms(self, suffix)
1538 return self._get_default_perms(self, suffix)
1539
1539
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1541 """
1541 """
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1543 basically forwarded.
1543 basically forwarded.
1544
1544
1545 """
1545 """
1546 user_group = self
1546 user_group = self
1547 data = {
1547 data = {
1548 'users_group_id': user_group.users_group_id,
1548 'users_group_id': user_group.users_group_id,
1549 'group_name': user_group.users_group_name,
1549 'group_name': user_group.users_group_name,
1550 'group_description': user_group.user_group_description,
1550 'group_description': user_group.user_group_description,
1551 'active': user_group.users_group_active,
1551 'active': user_group.users_group_active,
1552 'owner': user_group.user.username,
1552 'owner': user_group.user.username,
1553 'sync': user_group.sync,
1553 'sync': user_group.sync,
1554 'owner_email': user_group.user.email,
1554 'owner_email': user_group.user.email,
1555 }
1555 }
1556
1556
1557 if with_group_members:
1557 if with_group_members:
1558 users = []
1558 users = []
1559 for user in user_group.members:
1559 for user in user_group.members:
1560 user = user.user
1560 user = user.user
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1561 users.append(user.get_api_data(include_secrets=include_secrets))
1562 data['users'] = users
1562 data['users'] = users
1563
1563
1564 return data
1564 return data
1565
1565
1566
1566
1567 class UserGroupMember(Base, BaseModel):
1567 class UserGroupMember(Base, BaseModel):
1568 __tablename__ = 'users_groups_members'
1568 __tablename__ = 'users_groups_members'
1569 __table_args__ = (
1569 __table_args__ = (
1570 base_table_args,
1570 base_table_args,
1571 )
1571 )
1572
1572
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1576
1576
1577 user = relationship('User', lazy='joined')
1577 user = relationship('User', lazy='joined')
1578 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1579
1579
1580 def __init__(self, gr_id='', u_id=''):
1580 def __init__(self, gr_id='', u_id=''):
1581 self.users_group_id = gr_id
1581 self.users_group_id = gr_id
1582 self.user_id = u_id
1582 self.user_id = u_id
1583
1583
1584
1584
1585 class RepositoryField(Base, BaseModel):
1585 class RepositoryField(Base, BaseModel):
1586 __tablename__ = 'repositories_fields'
1586 __tablename__ = 'repositories_fields'
1587 __table_args__ = (
1587 __table_args__ = (
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1589 base_table_args,
1589 base_table_args,
1590 )
1590 )
1591
1591
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1593
1593
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1596 field_key = Column("field_key", String(250))
1596 field_key = Column("field_key", String(250))
1597 field_label = Column("field_label", String(1024), nullable=False)
1597 field_label = Column("field_label", String(1024), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1598 field_value = Column("field_value", String(10000), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1599 field_desc = Column("field_desc", String(1024), nullable=False)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1602
1602
1603 repository = relationship('Repository')
1603 repository = relationship('Repository')
1604
1604
1605 @property
1605 @property
1606 def field_key_prefixed(self):
1606 def field_key_prefixed(self):
1607 return 'ex_%s' % self.field_key
1607 return 'ex_%s' % self.field_key
1608
1608
1609 @classmethod
1609 @classmethod
1610 def un_prefix_key(cls, key):
1610 def un_prefix_key(cls, key):
1611 if key.startswith(cls.PREFIX):
1611 if key.startswith(cls.PREFIX):
1612 return key[len(cls.PREFIX):]
1612 return key[len(cls.PREFIX):]
1613 return key
1613 return key
1614
1614
1615 @classmethod
1615 @classmethod
1616 def get_by_key_name(cls, key, repo):
1616 def get_by_key_name(cls, key, repo):
1617 row = cls.query()\
1617 row = cls.query()\
1618 .filter(cls.repository == repo)\
1618 .filter(cls.repository == repo)\
1619 .filter(cls.field_key == key).scalar()
1619 .filter(cls.field_key == key).scalar()
1620 return row
1620 return row
1621
1621
1622
1622
1623 class Repository(Base, BaseModel):
1623 class Repository(Base, BaseModel):
1624 __tablename__ = 'repositories'
1624 __tablename__ = 'repositories'
1625 __table_args__ = (
1625 __table_args__ = (
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1627 base_table_args,
1627 base_table_args,
1628 )
1628 )
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1632
1632
1633 STATE_CREATED = 'repo_state_created'
1633 STATE_CREATED = 'repo_state_created'
1634 STATE_PENDING = 'repo_state_pending'
1634 STATE_PENDING = 'repo_state_pending'
1635 STATE_ERROR = 'repo_state_error'
1635 STATE_ERROR = 'repo_state_error'
1636
1636
1637 LOCK_AUTOMATIC = 'lock_auto'
1637 LOCK_AUTOMATIC = 'lock_auto'
1638 LOCK_API = 'lock_api'
1638 LOCK_API = 'lock_api'
1639 LOCK_WEB = 'lock_web'
1639 LOCK_WEB = 'lock_web'
1640 LOCK_PULL = 'lock_pull'
1640 LOCK_PULL = 'lock_pull'
1641
1641
1642 NAME_SEP = URL_SEP
1642 NAME_SEP = URL_SEP
1643
1643
1644 repo_id = Column(
1644 repo_id = Column(
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1646 primary_key=True)
1646 primary_key=True)
1647 _repo_name = Column(
1647 _repo_name = Column(
1648 "repo_name", Text(), nullable=False, default=None)
1648 "repo_name", Text(), nullable=False, default=None)
1649 _repo_name_hash = Column(
1649 repo_name_hash = Column(
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1650 "repo_name_hash", String(255), nullable=False, unique=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1651 repo_state = Column("repo_state", String(255), nullable=True)
1652
1652
1653 clone_uri = Column(
1653 clone_uri = Column(
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1655 default=None)
1655 default=None)
1656 push_uri = Column(
1656 push_uri = Column(
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1658 default=None)
1658 default=None)
1659 repo_type = Column(
1659 repo_type = Column(
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1661 user_id = Column(
1661 user_id = Column(
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1663 unique=False, default=None)
1663 unique=False, default=None)
1664 private = Column(
1664 private = Column(
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1665 "private", Boolean(), nullable=True, unique=None, default=None)
1666 archived = Column(
1666 archived = Column(
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1668 enable_statistics = Column(
1668 enable_statistics = Column(
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1670 enable_downloads = Column(
1670 enable_downloads = Column(
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1672 description = Column(
1672 description = Column(
1673 "description", String(10000), nullable=True, unique=None, default=None)
1673 "description", String(10000), nullable=True, unique=None, default=None)
1674 created_on = Column(
1674 created_on = Column(
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1676 default=datetime.datetime.now)
1676 default=datetime.datetime.now)
1677 updated_on = Column(
1677 updated_on = Column(
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1679 default=datetime.datetime.now)
1679 default=datetime.datetime.now)
1680 _landing_revision = Column(
1680 _landing_revision = Column(
1681 "landing_revision", String(255), nullable=False, unique=False,
1681 "landing_revision", String(255), nullable=False, unique=False,
1682 default=None)
1682 default=None)
1683 enable_locking = Column(
1683 enable_locking = Column(
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1684 "enable_locking", Boolean(), nullable=False, unique=None,
1685 default=False)
1685 default=False)
1686 _locked = Column(
1686 _locked = Column(
1687 "locked", String(255), nullable=True, unique=False, default=None)
1687 "locked", String(255), nullable=True, unique=False, default=None)
1688 _changeset_cache = Column(
1688 _changeset_cache = Column(
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1690
1690
1691 fork_id = Column(
1691 fork_id = Column(
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1693 nullable=True, unique=False, default=None)
1693 nullable=True, unique=False, default=None)
1694 group_id = Column(
1694 group_id = Column(
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1696 unique=False, default=None)
1696 unique=False, default=None)
1697
1697
1698 user = relationship('User', lazy='joined')
1698 user = relationship('User', lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1700 group = relationship('RepoGroup', lazy='joined')
1701 repo_to_perm = relationship(
1701 repo_to_perm = relationship(
1702 'UserRepoToPerm', cascade='all',
1702 'UserRepoToPerm', cascade='all',
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1703 order_by='UserRepoToPerm.repo_to_perm_id')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1705 stats = relationship('Statistics', cascade='all', uselist=False)
1706
1706
1707 followers = relationship(
1707 followers = relationship(
1708 'UserFollowing',
1708 'UserFollowing',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1710 cascade='all')
1710 cascade='all')
1711 extra_fields = relationship(
1711 extra_fields = relationship(
1712 'RepositoryField', cascade="all, delete-orphan")
1712 'RepositoryField', cascade="all, delete-orphan")
1713 logs = relationship('UserLog')
1713 logs = relationship('UserLog')
1714 comments = relationship(
1714 comments = relationship(
1715 'ChangesetComment', cascade="all, delete-orphan")
1715 'ChangesetComment', cascade="all, delete-orphan")
1716 pull_requests_source = relationship(
1716 pull_requests_source = relationship(
1717 'PullRequest',
1717 'PullRequest',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1719 cascade="all, delete-orphan")
1719 cascade="all, delete-orphan")
1720 pull_requests_target = relationship(
1720 pull_requests_target = relationship(
1721 'PullRequest',
1721 'PullRequest',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1723 cascade="all, delete-orphan")
1723 cascade="all, delete-orphan")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1727
1727
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1729
1729
1730 # no cascade, set NULL
1730 # no cascade, set NULL
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1732
1732
1733 def __unicode__(self):
1733 def __unicode__(self):
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1735 safe_unicode(self.repo_name))
1735 safe_unicode(self.repo_name))
1736
1736
1737 @hybrid_property
1737 @hybrid_property
1738 def description_safe(self):
1738 def description_safe(self):
1739 from rhodecode.lib import helpers as h
1739 from rhodecode.lib import helpers as h
1740 return h.escape(self.description)
1740 return h.escape(self.description)
1741
1741
1742 @hybrid_property
1742 @hybrid_property
1743 def landing_rev(self):
1743 def landing_rev(self):
1744 # always should return [rev_type, rev]
1744 # always should return [rev_type, rev]
1745 if self._landing_revision:
1745 if self._landing_revision:
1746 _rev_info = self._landing_revision.split(':')
1746 _rev_info = self._landing_revision.split(':')
1747 if len(_rev_info) < 2:
1747 if len(_rev_info) < 2:
1748 _rev_info.insert(0, 'rev')
1748 _rev_info.insert(0, 'rev')
1749 return [_rev_info[0], _rev_info[1]]
1749 return [_rev_info[0], _rev_info[1]]
1750 return [None, None]
1750 return [None, None]
1751
1751
1752 @landing_rev.setter
1752 @landing_rev.setter
1753 def landing_rev(self, val):
1753 def landing_rev(self, val):
1754 if ':' not in val:
1754 if ':' not in val:
1755 raise ValueError('value must be delimited with `:` and consist '
1755 raise ValueError('value must be delimited with `:` and consist '
1756 'of <rev_type>:<rev>, got %s instead' % val)
1756 'of <rev_type>:<rev>, got %s instead' % val)
1757 self._landing_revision = val
1757 self._landing_revision = val
1758
1758
1759 @hybrid_property
1759 @hybrid_property
1760 def locked(self):
1760 def locked(self):
1761 if self._locked:
1761 if self._locked:
1762 user_id, timelocked, reason = self._locked.split(':')
1762 user_id, timelocked, reason = self._locked.split(':')
1763 lock_values = int(user_id), timelocked, reason
1763 lock_values = int(user_id), timelocked, reason
1764 else:
1764 else:
1765 lock_values = [None, None, None]
1765 lock_values = [None, None, None]
1766 return lock_values
1766 return lock_values
1767
1767
1768 @locked.setter
1768 @locked.setter
1769 def locked(self, val):
1769 def locked(self, val):
1770 if val and isinstance(val, (list, tuple)):
1770 if val and isinstance(val, (list, tuple)):
1771 self._locked = ':'.join(map(str, val))
1771 self._locked = ':'.join(map(str, val))
1772 else:
1772 else:
1773 self._locked = None
1773 self._locked = None
1774
1774
1775 @hybrid_property
1775 @classmethod
1776 def changeset_cache(self):
1776 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1778 dummy = EmptyCommit().__json__()
1778 dummy = EmptyCommit().__json__()
1779 if not self._changeset_cache:
1779 if not changeset_cache_raw:
1780 dummy['source_repo_id'] = self.repo_id
1780 dummy['source_repo_id'] = repo_id
1781 return json.loads(json.dumps(dummy))
1781 return json.loads(json.dumps(dummy))
1782
1782
1783 try:
1783 try:
1784 return json.loads(self._changeset_cache)
1784 return json.loads(changeset_cache_raw)
1785 except TypeError:
1785 except TypeError:
1786 return dummy
1786 return dummy
1787 except Exception:
1787 except Exception:
1788 log.error(traceback.format_exc())
1788 log.error(traceback.format_exc())
1789 return dummy
1789 return dummy
1790
1790
1791 @hybrid_property
1792 def changeset_cache(self):
1793 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1794
1791 @changeset_cache.setter
1795 @changeset_cache.setter
1792 def changeset_cache(self, val):
1796 def changeset_cache(self, val):
1793 try:
1797 try:
1794 self._changeset_cache = json.dumps(val)
1798 self._changeset_cache = json.dumps(val)
1795 except Exception:
1799 except Exception:
1796 log.error(traceback.format_exc())
1800 log.error(traceback.format_exc())
1797
1801
1798 @hybrid_property
1802 @hybrid_property
1799 def repo_name(self):
1803 def repo_name(self):
1800 return self._repo_name
1804 return self._repo_name
1801
1805
1802 @repo_name.setter
1806 @repo_name.setter
1803 def repo_name(self, value):
1807 def repo_name(self, value):
1804 self._repo_name = value
1808 self._repo_name = value
1805 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1809 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1806
1810
1807 @classmethod
1811 @classmethod
1808 def normalize_repo_name(cls, repo_name):
1812 def normalize_repo_name(cls, repo_name):
1809 """
1813 """
1810 Normalizes os specific repo_name to the format internally stored inside
1814 Normalizes os specific repo_name to the format internally stored inside
1811 database using URL_SEP
1815 database using URL_SEP
1812
1816
1813 :param cls:
1817 :param cls:
1814 :param repo_name:
1818 :param repo_name:
1815 """
1819 """
1816 return cls.NAME_SEP.join(repo_name.split(os.sep))
1820 return cls.NAME_SEP.join(repo_name.split(os.sep))
1817
1821
1818 @classmethod
1822 @classmethod
1819 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1823 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1820 session = Session()
1824 session = Session()
1821 q = session.query(cls).filter(cls.repo_name == repo_name)
1825 q = session.query(cls).filter(cls.repo_name == repo_name)
1822
1826
1823 if cache:
1827 if cache:
1824 if identity_cache:
1828 if identity_cache:
1825 val = cls.identity_cache(session, 'repo_name', repo_name)
1829 val = cls.identity_cache(session, 'repo_name', repo_name)
1826 if val:
1830 if val:
1827 return val
1831 return val
1828 else:
1832 else:
1829 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1833 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1830 q = q.options(
1834 q = q.options(
1831 FromCache("sql_cache_short", cache_key))
1835 FromCache("sql_cache_short", cache_key))
1832
1836
1833 return q.scalar()
1837 return q.scalar()
1834
1838
1835 @classmethod
1839 @classmethod
1836 def get_by_id_or_repo_name(cls, repoid):
1840 def get_by_id_or_repo_name(cls, repoid):
1837 if isinstance(repoid, (int, long)):
1841 if isinstance(repoid, (int, long)):
1838 try:
1842 try:
1839 repo = cls.get(repoid)
1843 repo = cls.get(repoid)
1840 except ValueError:
1844 except ValueError:
1841 repo = None
1845 repo = None
1842 else:
1846 else:
1843 repo = cls.get_by_repo_name(repoid)
1847 repo = cls.get_by_repo_name(repoid)
1844 return repo
1848 return repo
1845
1849
1846 @classmethod
1850 @classmethod
1847 def get_by_full_path(cls, repo_full_path):
1851 def get_by_full_path(cls, repo_full_path):
1848 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1852 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1849 repo_name = cls.normalize_repo_name(repo_name)
1853 repo_name = cls.normalize_repo_name(repo_name)
1850 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1854 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1851
1855
1852 @classmethod
1856 @classmethod
1853 def get_repo_forks(cls, repo_id):
1857 def get_repo_forks(cls, repo_id):
1854 return cls.query().filter(Repository.fork_id == repo_id)
1858 return cls.query().filter(Repository.fork_id == repo_id)
1855
1859
1856 @classmethod
1860 @classmethod
1857 def base_path(cls):
1861 def base_path(cls):
1858 """
1862 """
1859 Returns base path when all repos are stored
1863 Returns base path when all repos are stored
1860
1864
1861 :param cls:
1865 :param cls:
1862 """
1866 """
1863 q = Session().query(RhodeCodeUi)\
1867 q = Session().query(RhodeCodeUi)\
1864 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1868 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1865 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1869 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1866 return q.one().ui_value
1870 return q.one().ui_value
1867
1871
1868 @classmethod
1872 @classmethod
1869 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1873 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1870 case_insensitive=True, archived=False):
1874 case_insensitive=True, archived=False):
1871 q = Repository.query()
1875 q = Repository.query()
1872
1876
1873 if not archived:
1877 if not archived:
1874 q = q.filter(Repository.archived.isnot(true()))
1878 q = q.filter(Repository.archived.isnot(true()))
1875
1879
1876 if not isinstance(user_id, Optional):
1880 if not isinstance(user_id, Optional):
1877 q = q.filter(Repository.user_id == user_id)
1881 q = q.filter(Repository.user_id == user_id)
1878
1882
1879 if not isinstance(group_id, Optional):
1883 if not isinstance(group_id, Optional):
1880 q = q.filter(Repository.group_id == group_id)
1884 q = q.filter(Repository.group_id == group_id)
1881
1885
1882 if case_insensitive:
1886 if case_insensitive:
1883 q = q.order_by(func.lower(Repository.repo_name))
1887 q = q.order_by(func.lower(Repository.repo_name))
1884 else:
1888 else:
1885 q = q.order_by(Repository.repo_name)
1889 q = q.order_by(Repository.repo_name)
1886
1890
1887 return q.all()
1891 return q.all()
1888
1892
1889 @property
1893 @property
1890 def repo_uid(self):
1894 def repo_uid(self):
1891 return '_{}'.format(self.repo_id)
1895 return '_{}'.format(self.repo_id)
1892
1896
1893 @property
1897 @property
1894 def forks(self):
1898 def forks(self):
1895 """
1899 """
1896 Return forks of this repo
1900 Return forks of this repo
1897 """
1901 """
1898 return Repository.get_repo_forks(self.repo_id)
1902 return Repository.get_repo_forks(self.repo_id)
1899
1903
1900 @property
1904 @property
1901 def parent(self):
1905 def parent(self):
1902 """
1906 """
1903 Returns fork parent
1907 Returns fork parent
1904 """
1908 """
1905 return self.fork
1909 return self.fork
1906
1910
1907 @property
1911 @property
1908 def just_name(self):
1912 def just_name(self):
1909 return self.repo_name.split(self.NAME_SEP)[-1]
1913 return self.repo_name.split(self.NAME_SEP)[-1]
1910
1914
1911 @property
1915 @property
1912 def groups_with_parents(self):
1916 def groups_with_parents(self):
1913 groups = []
1917 groups = []
1914 if self.group is None:
1918 if self.group is None:
1915 return groups
1919 return groups
1916
1920
1917 cur_gr = self.group
1921 cur_gr = self.group
1918 groups.insert(0, cur_gr)
1922 groups.insert(0, cur_gr)
1919 while 1:
1923 while 1:
1920 gr = getattr(cur_gr, 'parent_group', None)
1924 gr = getattr(cur_gr, 'parent_group', None)
1921 cur_gr = cur_gr.parent_group
1925 cur_gr = cur_gr.parent_group
1922 if gr is None:
1926 if gr is None:
1923 break
1927 break
1924 groups.insert(0, gr)
1928 groups.insert(0, gr)
1925
1929
1926 return groups
1930 return groups
1927
1931
1928 @property
1932 @property
1929 def groups_and_repo(self):
1933 def groups_and_repo(self):
1930 return self.groups_with_parents, self
1934 return self.groups_with_parents, self
1931
1935
1932 @LazyProperty
1936 @LazyProperty
1933 def repo_path(self):
1937 def repo_path(self):
1934 """
1938 """
1935 Returns base full path for that repository means where it actually
1939 Returns base full path for that repository means where it actually
1936 exists on a filesystem
1940 exists on a filesystem
1937 """
1941 """
1938 q = Session().query(RhodeCodeUi).filter(
1942 q = Session().query(RhodeCodeUi).filter(
1939 RhodeCodeUi.ui_key == self.NAME_SEP)
1943 RhodeCodeUi.ui_key == self.NAME_SEP)
1940 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1944 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1941 return q.one().ui_value
1945 return q.one().ui_value
1942
1946
1943 @property
1947 @property
1944 def repo_full_path(self):
1948 def repo_full_path(self):
1945 p = [self.repo_path]
1949 p = [self.repo_path]
1946 # we need to split the name by / since this is how we store the
1950 # we need to split the name by / since this is how we store the
1947 # names in the database, but that eventually needs to be converted
1951 # names in the database, but that eventually needs to be converted
1948 # into a valid system path
1952 # into a valid system path
1949 p += self.repo_name.split(self.NAME_SEP)
1953 p += self.repo_name.split(self.NAME_SEP)
1950 return os.path.join(*map(safe_unicode, p))
1954 return os.path.join(*map(safe_unicode, p))
1951
1955
1952 @property
1956 @property
1953 def cache_keys(self):
1957 def cache_keys(self):
1954 """
1958 """
1955 Returns associated cache keys for that repo
1959 Returns associated cache keys for that repo
1956 """
1960 """
1957 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1961 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1958 repo_id=self.repo_id)
1962 repo_id=self.repo_id)
1959 return CacheKey.query()\
1963 return CacheKey.query()\
1960 .filter(CacheKey.cache_args == invalidation_namespace)\
1964 .filter(CacheKey.cache_args == invalidation_namespace)\
1961 .order_by(CacheKey.cache_key)\
1965 .order_by(CacheKey.cache_key)\
1962 .all()
1966 .all()
1963
1967
1964 @property
1968 @property
1965 def cached_diffs_relative_dir(self):
1969 def cached_diffs_relative_dir(self):
1966 """
1970 """
1967 Return a relative to the repository store path of cached diffs
1971 Return a relative to the repository store path of cached diffs
1968 used for safe display for users, who shouldn't know the absolute store
1972 used for safe display for users, who shouldn't know the absolute store
1969 path
1973 path
1970 """
1974 """
1971 return os.path.join(
1975 return os.path.join(
1972 os.path.dirname(self.repo_name),
1976 os.path.dirname(self.repo_name),
1973 self.cached_diffs_dir.split(os.path.sep)[-1])
1977 self.cached_diffs_dir.split(os.path.sep)[-1])
1974
1978
1975 @property
1979 @property
1976 def cached_diffs_dir(self):
1980 def cached_diffs_dir(self):
1977 path = self.repo_full_path
1981 path = self.repo_full_path
1978 return os.path.join(
1982 return os.path.join(
1979 os.path.dirname(path),
1983 os.path.dirname(path),
1980 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1984 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1981
1985
1982 def cached_diffs(self):
1986 def cached_diffs(self):
1983 diff_cache_dir = self.cached_diffs_dir
1987 diff_cache_dir = self.cached_diffs_dir
1984 if os.path.isdir(diff_cache_dir):
1988 if os.path.isdir(diff_cache_dir):
1985 return os.listdir(diff_cache_dir)
1989 return os.listdir(diff_cache_dir)
1986 return []
1990 return []
1987
1991
1988 def shadow_repos(self):
1992 def shadow_repos(self):
1989 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1993 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1990 return [
1994 return [
1991 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1995 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1992 if x.startswith(shadow_repos_pattern)]
1996 if x.startswith(shadow_repos_pattern)]
1993
1997
1994 def get_new_name(self, repo_name):
1998 def get_new_name(self, repo_name):
1995 """
1999 """
1996 returns new full repository name based on assigned group and new new
2000 returns new full repository name based on assigned group and new new
1997
2001
1998 :param group_name:
2002 :param group_name:
1999 """
2003 """
2000 path_prefix = self.group.full_path_splitted if self.group else []
2004 path_prefix = self.group.full_path_splitted if self.group else []
2001 return self.NAME_SEP.join(path_prefix + [repo_name])
2005 return self.NAME_SEP.join(path_prefix + [repo_name])
2002
2006
2003 @property
2007 @property
2004 def _config(self):
2008 def _config(self):
2005 """
2009 """
2006 Returns db based config object.
2010 Returns db based config object.
2007 """
2011 """
2008 from rhodecode.lib.utils import make_db_config
2012 from rhodecode.lib.utils import make_db_config
2009 return make_db_config(clear_session=False, repo=self)
2013 return make_db_config(clear_session=False, repo=self)
2010
2014
2011 def permissions(self, with_admins=True, with_owner=True,
2015 def permissions(self, with_admins=True, with_owner=True,
2012 expand_from_user_groups=False):
2016 expand_from_user_groups=False):
2013 """
2017 """
2014 Permissions for repositories
2018 Permissions for repositories
2015 """
2019 """
2016 _admin_perm = 'repository.admin'
2020 _admin_perm = 'repository.admin'
2017
2021
2018 owner_row = []
2022 owner_row = []
2019 if with_owner:
2023 if with_owner:
2020 usr = AttributeDict(self.user.get_dict())
2024 usr = AttributeDict(self.user.get_dict())
2021 usr.owner_row = True
2025 usr.owner_row = True
2022 usr.permission = _admin_perm
2026 usr.permission = _admin_perm
2023 usr.permission_id = None
2027 usr.permission_id = None
2024 owner_row.append(usr)
2028 owner_row.append(usr)
2025
2029
2026 super_admin_ids = []
2030 super_admin_ids = []
2027 super_admin_rows = []
2031 super_admin_rows = []
2028 if with_admins:
2032 if with_admins:
2029 for usr in User.get_all_super_admins():
2033 for usr in User.get_all_super_admins():
2030 super_admin_ids.append(usr.user_id)
2034 super_admin_ids.append(usr.user_id)
2031 # if this admin is also owner, don't double the record
2035 # if this admin is also owner, don't double the record
2032 if usr.user_id == owner_row[0].user_id:
2036 if usr.user_id == owner_row[0].user_id:
2033 owner_row[0].admin_row = True
2037 owner_row[0].admin_row = True
2034 else:
2038 else:
2035 usr = AttributeDict(usr.get_dict())
2039 usr = AttributeDict(usr.get_dict())
2036 usr.admin_row = True
2040 usr.admin_row = True
2037 usr.permission = _admin_perm
2041 usr.permission = _admin_perm
2038 usr.permission_id = None
2042 usr.permission_id = None
2039 super_admin_rows.append(usr)
2043 super_admin_rows.append(usr)
2040
2044
2041 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2045 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2042 q = q.options(joinedload(UserRepoToPerm.repository),
2046 q = q.options(joinedload(UserRepoToPerm.repository),
2043 joinedload(UserRepoToPerm.user),
2047 joinedload(UserRepoToPerm.user),
2044 joinedload(UserRepoToPerm.permission),)
2048 joinedload(UserRepoToPerm.permission),)
2045
2049
2046 # get owners and admins and permissions. We do a trick of re-writing
2050 # get owners and admins and permissions. We do a trick of re-writing
2047 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2051 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2048 # has a global reference and changing one object propagates to all
2052 # has a global reference and changing one object propagates to all
2049 # others. This means if admin is also an owner admin_row that change
2053 # others. This means if admin is also an owner admin_row that change
2050 # would propagate to both objects
2054 # would propagate to both objects
2051 perm_rows = []
2055 perm_rows = []
2052 for _usr in q.all():
2056 for _usr in q.all():
2053 usr = AttributeDict(_usr.user.get_dict())
2057 usr = AttributeDict(_usr.user.get_dict())
2054 # if this user is also owner/admin, mark as duplicate record
2058 # if this user is also owner/admin, mark as duplicate record
2055 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2059 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2056 usr.duplicate_perm = True
2060 usr.duplicate_perm = True
2057 # also check if this permission is maybe used by branch_permissions
2061 # also check if this permission is maybe used by branch_permissions
2058 if _usr.branch_perm_entry:
2062 if _usr.branch_perm_entry:
2059 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2063 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2060
2064
2061 usr.permission = _usr.permission.permission_name
2065 usr.permission = _usr.permission.permission_name
2062 usr.permission_id = _usr.repo_to_perm_id
2066 usr.permission_id = _usr.repo_to_perm_id
2063 perm_rows.append(usr)
2067 perm_rows.append(usr)
2064
2068
2065 # filter the perm rows by 'default' first and then sort them by
2069 # filter the perm rows by 'default' first and then sort them by
2066 # admin,write,read,none permissions sorted again alphabetically in
2070 # admin,write,read,none permissions sorted again alphabetically in
2067 # each group
2071 # each group
2068 perm_rows = sorted(perm_rows, key=display_user_sort)
2072 perm_rows = sorted(perm_rows, key=display_user_sort)
2069
2073
2070 user_groups_rows = []
2074 user_groups_rows = []
2071 if expand_from_user_groups:
2075 if expand_from_user_groups:
2072 for ug in self.permission_user_groups(with_members=True):
2076 for ug in self.permission_user_groups(with_members=True):
2073 for user_data in ug.members:
2077 for user_data in ug.members:
2074 user_groups_rows.append(user_data)
2078 user_groups_rows.append(user_data)
2075
2079
2076 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2080 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2077
2081
2078 def permission_user_groups(self, with_members=True):
2082 def permission_user_groups(self, with_members=True):
2079 q = UserGroupRepoToPerm.query()\
2083 q = UserGroupRepoToPerm.query()\
2080 .filter(UserGroupRepoToPerm.repository == self)
2084 .filter(UserGroupRepoToPerm.repository == self)
2081 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2085 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2082 joinedload(UserGroupRepoToPerm.users_group),
2086 joinedload(UserGroupRepoToPerm.users_group),
2083 joinedload(UserGroupRepoToPerm.permission),)
2087 joinedload(UserGroupRepoToPerm.permission),)
2084
2088
2085 perm_rows = []
2089 perm_rows = []
2086 for _user_group in q.all():
2090 for _user_group in q.all():
2087 entry = AttributeDict(_user_group.users_group.get_dict())
2091 entry = AttributeDict(_user_group.users_group.get_dict())
2088 entry.permission = _user_group.permission.permission_name
2092 entry.permission = _user_group.permission.permission_name
2089 if with_members:
2093 if with_members:
2090 entry.members = [x.user.get_dict()
2094 entry.members = [x.user.get_dict()
2091 for x in _user_group.users_group.members]
2095 for x in _user_group.users_group.members]
2092 perm_rows.append(entry)
2096 perm_rows.append(entry)
2093
2097
2094 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2098 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2095 return perm_rows
2099 return perm_rows
2096
2100
2097 def get_api_data(self, include_secrets=False):
2101 def get_api_data(self, include_secrets=False):
2098 """
2102 """
2099 Common function for generating repo api data
2103 Common function for generating repo api data
2100
2104
2101 :param include_secrets: See :meth:`User.get_api_data`.
2105 :param include_secrets: See :meth:`User.get_api_data`.
2102
2106
2103 """
2107 """
2104 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2108 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2105 # move this methods on models level.
2109 # move this methods on models level.
2106 from rhodecode.model.settings import SettingsModel
2110 from rhodecode.model.settings import SettingsModel
2107 from rhodecode.model.repo import RepoModel
2111 from rhodecode.model.repo import RepoModel
2108
2112
2109 repo = self
2113 repo = self
2110 _user_id, _time, _reason = self.locked
2114 _user_id, _time, _reason = self.locked
2111
2115
2112 data = {
2116 data = {
2113 'repo_id': repo.repo_id,
2117 'repo_id': repo.repo_id,
2114 'repo_name': repo.repo_name,
2118 'repo_name': repo.repo_name,
2115 'repo_type': repo.repo_type,
2119 'repo_type': repo.repo_type,
2116 'clone_uri': repo.clone_uri or '',
2120 'clone_uri': repo.clone_uri or '',
2117 'push_uri': repo.push_uri or '',
2121 'push_uri': repo.push_uri or '',
2118 'url': RepoModel().get_url(self),
2122 'url': RepoModel().get_url(self),
2119 'private': repo.private,
2123 'private': repo.private,
2120 'created_on': repo.created_on,
2124 'created_on': repo.created_on,
2121 'description': repo.description_safe,
2125 'description': repo.description_safe,
2122 'landing_rev': repo.landing_rev,
2126 'landing_rev': repo.landing_rev,
2123 'owner': repo.user.username,
2127 'owner': repo.user.username,
2124 'fork_of': repo.fork.repo_name if repo.fork else None,
2128 'fork_of': repo.fork.repo_name if repo.fork else None,
2125 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2129 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2126 'enable_statistics': repo.enable_statistics,
2130 'enable_statistics': repo.enable_statistics,
2127 'enable_locking': repo.enable_locking,
2131 'enable_locking': repo.enable_locking,
2128 'enable_downloads': repo.enable_downloads,
2132 'enable_downloads': repo.enable_downloads,
2129 'last_changeset': repo.changeset_cache,
2133 'last_changeset': repo.changeset_cache,
2130 'locked_by': User.get(_user_id).get_api_data(
2134 'locked_by': User.get(_user_id).get_api_data(
2131 include_secrets=include_secrets) if _user_id else None,
2135 include_secrets=include_secrets) if _user_id else None,
2132 'locked_date': time_to_datetime(_time) if _time else None,
2136 'locked_date': time_to_datetime(_time) if _time else None,
2133 'lock_reason': _reason if _reason else None,
2137 'lock_reason': _reason if _reason else None,
2134 }
2138 }
2135
2139
2136 # TODO: mikhail: should be per-repo settings here
2140 # TODO: mikhail: should be per-repo settings here
2137 rc_config = SettingsModel().get_all_settings()
2141 rc_config = SettingsModel().get_all_settings()
2138 repository_fields = str2bool(
2142 repository_fields = str2bool(
2139 rc_config.get('rhodecode_repository_fields'))
2143 rc_config.get('rhodecode_repository_fields'))
2140 if repository_fields:
2144 if repository_fields:
2141 for f in self.extra_fields:
2145 for f in self.extra_fields:
2142 data[f.field_key_prefixed] = f.field_value
2146 data[f.field_key_prefixed] = f.field_value
2143
2147
2144 return data
2148 return data
2145
2149
2146 @classmethod
2150 @classmethod
2147 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2151 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2148 if not lock_time:
2152 if not lock_time:
2149 lock_time = time.time()
2153 lock_time = time.time()
2150 if not lock_reason:
2154 if not lock_reason:
2151 lock_reason = cls.LOCK_AUTOMATIC
2155 lock_reason = cls.LOCK_AUTOMATIC
2152 repo.locked = [user_id, lock_time, lock_reason]
2156 repo.locked = [user_id, lock_time, lock_reason]
2153 Session().add(repo)
2157 Session().add(repo)
2154 Session().commit()
2158 Session().commit()
2155
2159
2156 @classmethod
2160 @classmethod
2157 def unlock(cls, repo):
2161 def unlock(cls, repo):
2158 repo.locked = None
2162 repo.locked = None
2159 Session().add(repo)
2163 Session().add(repo)
2160 Session().commit()
2164 Session().commit()
2161
2165
2162 @classmethod
2166 @classmethod
2163 def getlock(cls, repo):
2167 def getlock(cls, repo):
2164 return repo.locked
2168 return repo.locked
2165
2169
2166 def is_user_lock(self, user_id):
2170 def is_user_lock(self, user_id):
2167 if self.lock[0]:
2171 if self.lock[0]:
2168 lock_user_id = safe_int(self.lock[0])
2172 lock_user_id = safe_int(self.lock[0])
2169 user_id = safe_int(user_id)
2173 user_id = safe_int(user_id)
2170 # both are ints, and they are equal
2174 # both are ints, and they are equal
2171 return all([lock_user_id, user_id]) and lock_user_id == user_id
2175 return all([lock_user_id, user_id]) and lock_user_id == user_id
2172
2176
2173 return False
2177 return False
2174
2178
2175 def get_locking_state(self, action, user_id, only_when_enabled=True):
2179 def get_locking_state(self, action, user_id, only_when_enabled=True):
2176 """
2180 """
2177 Checks locking on this repository, if locking is enabled and lock is
2181 Checks locking on this repository, if locking is enabled and lock is
2178 present returns a tuple of make_lock, locked, locked_by.
2182 present returns a tuple of make_lock, locked, locked_by.
2179 make_lock can have 3 states None (do nothing) True, make lock
2183 make_lock can have 3 states None (do nothing) True, make lock
2180 False release lock, This value is later propagated to hooks, which
2184 False release lock, This value is later propagated to hooks, which
2181 do the locking. Think about this as signals passed to hooks what to do.
2185 do the locking. Think about this as signals passed to hooks what to do.
2182
2186
2183 """
2187 """
2184 # TODO: johbo: This is part of the business logic and should be moved
2188 # TODO: johbo: This is part of the business logic and should be moved
2185 # into the RepositoryModel.
2189 # into the RepositoryModel.
2186
2190
2187 if action not in ('push', 'pull'):
2191 if action not in ('push', 'pull'):
2188 raise ValueError("Invalid action value: %s" % repr(action))
2192 raise ValueError("Invalid action value: %s" % repr(action))
2189
2193
2190 # defines if locked error should be thrown to user
2194 # defines if locked error should be thrown to user
2191 currently_locked = False
2195 currently_locked = False
2192 # defines if new lock should be made, tri-state
2196 # defines if new lock should be made, tri-state
2193 make_lock = None
2197 make_lock = None
2194 repo = self
2198 repo = self
2195 user = User.get(user_id)
2199 user = User.get(user_id)
2196
2200
2197 lock_info = repo.locked
2201 lock_info = repo.locked
2198
2202
2199 if repo and (repo.enable_locking or not only_when_enabled):
2203 if repo and (repo.enable_locking or not only_when_enabled):
2200 if action == 'push':
2204 if action == 'push':
2201 # check if it's already locked !, if it is compare users
2205 # check if it's already locked !, if it is compare users
2202 locked_by_user_id = lock_info[0]
2206 locked_by_user_id = lock_info[0]
2203 if user.user_id == locked_by_user_id:
2207 if user.user_id == locked_by_user_id:
2204 log.debug(
2208 log.debug(
2205 'Got `push` action from user %s, now unlocking', user)
2209 'Got `push` action from user %s, now unlocking', user)
2206 # unlock if we have push from user who locked
2210 # unlock if we have push from user who locked
2207 make_lock = False
2211 make_lock = False
2208 else:
2212 else:
2209 # we're not the same user who locked, ban with
2213 # we're not the same user who locked, ban with
2210 # code defined in settings (default is 423 HTTP Locked) !
2214 # code defined in settings (default is 423 HTTP Locked) !
2211 log.debug('Repo %s is currently locked by %s', repo, user)
2215 log.debug('Repo %s is currently locked by %s', repo, user)
2212 currently_locked = True
2216 currently_locked = True
2213 elif action == 'pull':
2217 elif action == 'pull':
2214 # [0] user [1] date
2218 # [0] user [1] date
2215 if lock_info[0] and lock_info[1]:
2219 if lock_info[0] and lock_info[1]:
2216 log.debug('Repo %s is currently locked by %s', repo, user)
2220 log.debug('Repo %s is currently locked by %s', repo, user)
2217 currently_locked = True
2221 currently_locked = True
2218 else:
2222 else:
2219 log.debug('Setting lock on repo %s by %s', repo, user)
2223 log.debug('Setting lock on repo %s by %s', repo, user)
2220 make_lock = True
2224 make_lock = True
2221
2225
2222 else:
2226 else:
2223 log.debug('Repository %s do not have locking enabled', repo)
2227 log.debug('Repository %s do not have locking enabled', repo)
2224
2228
2225 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2229 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2226 make_lock, currently_locked, lock_info)
2230 make_lock, currently_locked, lock_info)
2227
2231
2228 from rhodecode.lib.auth import HasRepoPermissionAny
2232 from rhodecode.lib.auth import HasRepoPermissionAny
2229 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2233 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2230 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2234 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2231 # if we don't have at least write permission we cannot make a lock
2235 # if we don't have at least write permission we cannot make a lock
2232 log.debug('lock state reset back to FALSE due to lack '
2236 log.debug('lock state reset back to FALSE due to lack '
2233 'of at least read permission')
2237 'of at least read permission')
2234 make_lock = False
2238 make_lock = False
2235
2239
2236 return make_lock, currently_locked, lock_info
2240 return make_lock, currently_locked, lock_info
2237
2241
2238 @property
2242 @property
2239 def last_commit_cache_update_diff(self):
2243 def last_commit_cache_update_diff(self):
2240 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2244 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2241
2245
2242 @property
2246 @classmethod
2243 def last_commit_change(self):
2247 def _load_commit_change(cls, last_commit_cache):
2244 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2248 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2245 empty_date = datetime.datetime.fromtimestamp(0)
2249 empty_date = datetime.datetime.fromtimestamp(0)
2246 date_latest = self.changeset_cache.get('date', empty_date)
2250 date_latest = last_commit_cache.get('date', empty_date)
2247 try:
2251 try:
2248 return parse_datetime(date_latest)
2252 return parse_datetime(date_latest)
2249 except Exception:
2253 except Exception:
2250 return empty_date
2254 return empty_date
2251
2255
2252 @property
2256 @property
2257 def last_commit_change(self):
2258 return self._load_commit_change(self.changeset_cache)
2259
2260 @property
2253 def last_db_change(self):
2261 def last_db_change(self):
2254 return self.updated_on
2262 return self.updated_on
2255
2263
2256 @property
2264 @property
2257 def clone_uri_hidden(self):
2265 def clone_uri_hidden(self):
2258 clone_uri = self.clone_uri
2266 clone_uri = self.clone_uri
2259 if clone_uri:
2267 if clone_uri:
2260 import urlobject
2268 import urlobject
2261 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2269 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2262 if url_obj.password:
2270 if url_obj.password:
2263 clone_uri = url_obj.with_password('*****')
2271 clone_uri = url_obj.with_password('*****')
2264 return clone_uri
2272 return clone_uri
2265
2273
2266 @property
2274 @property
2267 def push_uri_hidden(self):
2275 def push_uri_hidden(self):
2268 push_uri = self.push_uri
2276 push_uri = self.push_uri
2269 if push_uri:
2277 if push_uri:
2270 import urlobject
2278 import urlobject
2271 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2279 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2272 if url_obj.password:
2280 if url_obj.password:
2273 push_uri = url_obj.with_password('*****')
2281 push_uri = url_obj.with_password('*****')
2274 return push_uri
2282 return push_uri
2275
2283
2276 def clone_url(self, **override):
2284 def clone_url(self, **override):
2277 from rhodecode.model.settings import SettingsModel
2285 from rhodecode.model.settings import SettingsModel
2278
2286
2279 uri_tmpl = None
2287 uri_tmpl = None
2280 if 'with_id' in override:
2288 if 'with_id' in override:
2281 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2289 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2282 del override['with_id']
2290 del override['with_id']
2283
2291
2284 if 'uri_tmpl' in override:
2292 if 'uri_tmpl' in override:
2285 uri_tmpl = override['uri_tmpl']
2293 uri_tmpl = override['uri_tmpl']
2286 del override['uri_tmpl']
2294 del override['uri_tmpl']
2287
2295
2288 ssh = False
2296 ssh = False
2289 if 'ssh' in override:
2297 if 'ssh' in override:
2290 ssh = True
2298 ssh = True
2291 del override['ssh']
2299 del override['ssh']
2292
2300
2293 # we didn't override our tmpl from **overrides
2301 # we didn't override our tmpl from **overrides
2294 request = get_current_request()
2302 request = get_current_request()
2295 if not uri_tmpl:
2303 if not uri_tmpl:
2296 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2304 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2297 rc_config = request.call_context.rc_config
2305 rc_config = request.call_context.rc_config
2298 else:
2306 else:
2299 rc_config = SettingsModel().get_all_settings(cache=True)
2307 rc_config = SettingsModel().get_all_settings(cache=True)
2300
2308
2301 if ssh:
2309 if ssh:
2302 uri_tmpl = rc_config.get(
2310 uri_tmpl = rc_config.get(
2303 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2311 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2304
2312
2305 else:
2313 else:
2306 uri_tmpl = rc_config.get(
2314 uri_tmpl = rc_config.get(
2307 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2315 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2308
2316
2309 return get_clone_url(request=request,
2317 return get_clone_url(request=request,
2310 uri_tmpl=uri_tmpl,
2318 uri_tmpl=uri_tmpl,
2311 repo_name=self.repo_name,
2319 repo_name=self.repo_name,
2312 repo_id=self.repo_id,
2320 repo_id=self.repo_id,
2313 repo_type=self.repo_type,
2321 repo_type=self.repo_type,
2314 **override)
2322 **override)
2315
2323
2316 def set_state(self, state):
2324 def set_state(self, state):
2317 self.repo_state = state
2325 self.repo_state = state
2318 Session().add(self)
2326 Session().add(self)
2319 #==========================================================================
2327 #==========================================================================
2320 # SCM PROPERTIES
2328 # SCM PROPERTIES
2321 #==========================================================================
2329 #==========================================================================
2322
2330
2323 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2331 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2324 return get_commit_safe(
2332 return get_commit_safe(
2325 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2333 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2326
2334
2327 def get_changeset(self, rev=None, pre_load=None):
2335 def get_changeset(self, rev=None, pre_load=None):
2328 warnings.warn("Use get_commit", DeprecationWarning)
2336 warnings.warn("Use get_commit", DeprecationWarning)
2329 commit_id = None
2337 commit_id = None
2330 commit_idx = None
2338 commit_idx = None
2331 if isinstance(rev, compat.string_types):
2339 if isinstance(rev, compat.string_types):
2332 commit_id = rev
2340 commit_id = rev
2333 else:
2341 else:
2334 commit_idx = rev
2342 commit_idx = rev
2335 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2343 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2336 pre_load=pre_load)
2344 pre_load=pre_load)
2337
2345
2338 def get_landing_commit(self):
2346 def get_landing_commit(self):
2339 """
2347 """
2340 Returns landing commit, or if that doesn't exist returns the tip
2348 Returns landing commit, or if that doesn't exist returns the tip
2341 """
2349 """
2342 _rev_type, _rev = self.landing_rev
2350 _rev_type, _rev = self.landing_rev
2343 commit = self.get_commit(_rev)
2351 commit = self.get_commit(_rev)
2344 if isinstance(commit, EmptyCommit):
2352 if isinstance(commit, EmptyCommit):
2345 return self.get_commit()
2353 return self.get_commit()
2346 return commit
2354 return commit
2347
2355
2348 def flush_commit_cache(self):
2356 def flush_commit_cache(self):
2349 self.update_commit_cache(cs_cache={'raw_id':'0'})
2357 self.update_commit_cache(cs_cache={'raw_id':'0'})
2350 self.update_commit_cache()
2358 self.update_commit_cache()
2351
2359
2352 def update_commit_cache(self, cs_cache=None, config=None):
2360 def update_commit_cache(self, cs_cache=None, config=None):
2353 """
2361 """
2354 Update cache of last commit for repository, keys should be::
2362 Update cache of last commit for repository, keys should be::
2355
2363
2356 source_repo_id
2364 source_repo_id
2357 short_id
2365 short_id
2358 raw_id
2366 raw_id
2359 revision
2367 revision
2360 parents
2368 parents
2361 message
2369 message
2362 date
2370 date
2363 author
2371 author
2364 updated_on
2372 updated_on
2365
2373
2366 """
2374 """
2367 from rhodecode.lib.vcs.backends.base import BaseChangeset
2375 from rhodecode.lib.vcs.backends.base import BaseChangeset
2368 if cs_cache is None:
2376 if cs_cache is None:
2369 # use no-cache version here
2377 # use no-cache version here
2370 scm_repo = self.scm_instance(cache=False, config=config)
2378 scm_repo = self.scm_instance(cache=False, config=config)
2371
2379
2372 empty = scm_repo is None or scm_repo.is_empty()
2380 empty = scm_repo is None or scm_repo.is_empty()
2373 if not empty:
2381 if not empty:
2374 cs_cache = scm_repo.get_commit(
2382 cs_cache = scm_repo.get_commit(
2375 pre_load=["author", "date", "message", "parents", "branch"])
2383 pre_load=["author", "date", "message", "parents", "branch"])
2376 else:
2384 else:
2377 cs_cache = EmptyCommit()
2385 cs_cache = EmptyCommit()
2378
2386
2379 if isinstance(cs_cache, BaseChangeset):
2387 if isinstance(cs_cache, BaseChangeset):
2380 cs_cache = cs_cache.__json__()
2388 cs_cache = cs_cache.__json__()
2381
2389
2382 def is_outdated(new_cs_cache):
2390 def is_outdated(new_cs_cache):
2383 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2391 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2384 new_cs_cache['revision'] != self.changeset_cache['revision']):
2392 new_cs_cache['revision'] != self.changeset_cache['revision']):
2385 return True
2393 return True
2386 return False
2394 return False
2387
2395
2388 # check if we have maybe already latest cached revision
2396 # check if we have maybe already latest cached revision
2389 if is_outdated(cs_cache) or not self.changeset_cache:
2397 if is_outdated(cs_cache) or not self.changeset_cache:
2390 _default = datetime.datetime.utcnow()
2398 _default = datetime.datetime.utcnow()
2391 last_change = cs_cache.get('date') or _default
2399 last_change = cs_cache.get('date') or _default
2392 # we check if last update is newer than the new value
2400 # we check if last update is newer than the new value
2393 # if yes, we use the current timestamp instead. Imagine you get
2401 # if yes, we use the current timestamp instead. Imagine you get
2394 # old commit pushed 1y ago, we'd set last update 1y to ago.
2402 # old commit pushed 1y ago, we'd set last update 1y to ago.
2395 last_change_timestamp = datetime_to_time(last_change)
2403 last_change_timestamp = datetime_to_time(last_change)
2396 current_timestamp = datetime_to_time(last_change)
2404 current_timestamp = datetime_to_time(last_change)
2397 if last_change_timestamp > current_timestamp:
2405 if last_change_timestamp > current_timestamp:
2398 cs_cache['date'] = _default
2406 cs_cache['date'] = _default
2399
2407
2400 cs_cache['updated_on'] = time.time()
2408 cs_cache['updated_on'] = time.time()
2401 self.changeset_cache = cs_cache
2409 self.changeset_cache = cs_cache
2402 self.updated_on = last_change
2410 self.updated_on = last_change
2403 Session().add(self)
2411 Session().add(self)
2404 Session().commit()
2412 Session().commit()
2405
2413
2406 log.debug('updated repo `%s` with new commit cache %s',
2414 log.debug('updated repo `%s` with new commit cache %s',
2407 self.repo_name, cs_cache)
2415 self.repo_name, cs_cache)
2408 else:
2416 else:
2409 cs_cache = self.changeset_cache
2417 cs_cache = self.changeset_cache
2410 cs_cache['updated_on'] = time.time()
2418 cs_cache['updated_on'] = time.time()
2411 self.changeset_cache = cs_cache
2419 self.changeset_cache = cs_cache
2412 Session().add(self)
2420 Session().add(self)
2413 Session().commit()
2421 Session().commit()
2414
2422
2415 log.debug('Skipping update_commit_cache for repo:`%s` '
2423 log.debug('Skipping update_commit_cache for repo:`%s` '
2416 'commit already with latest changes', self.repo_name)
2424 'commit already with latest changes', self.repo_name)
2417
2425
2418 @property
2426 @property
2419 def tip(self):
2427 def tip(self):
2420 return self.get_commit('tip')
2428 return self.get_commit('tip')
2421
2429
2422 @property
2430 @property
2423 def author(self):
2431 def author(self):
2424 return self.tip.author
2432 return self.tip.author
2425
2433
2426 @property
2434 @property
2427 def last_change(self):
2435 def last_change(self):
2428 return self.scm_instance().last_change
2436 return self.scm_instance().last_change
2429
2437
2430 def get_comments(self, revisions=None):
2438 def get_comments(self, revisions=None):
2431 """
2439 """
2432 Returns comments for this repository grouped by revisions
2440 Returns comments for this repository grouped by revisions
2433
2441
2434 :param revisions: filter query by revisions only
2442 :param revisions: filter query by revisions only
2435 """
2443 """
2436 cmts = ChangesetComment.query()\
2444 cmts = ChangesetComment.query()\
2437 .filter(ChangesetComment.repo == self)
2445 .filter(ChangesetComment.repo == self)
2438 if revisions:
2446 if revisions:
2439 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2447 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2440 grouped = collections.defaultdict(list)
2448 grouped = collections.defaultdict(list)
2441 for cmt in cmts.all():
2449 for cmt in cmts.all():
2442 grouped[cmt.revision].append(cmt)
2450 grouped[cmt.revision].append(cmt)
2443 return grouped
2451 return grouped
2444
2452
2445 def statuses(self, revisions=None):
2453 def statuses(self, revisions=None):
2446 """
2454 """
2447 Returns statuses for this repository
2455 Returns statuses for this repository
2448
2456
2449 :param revisions: list of revisions to get statuses for
2457 :param revisions: list of revisions to get statuses for
2450 """
2458 """
2451 statuses = ChangesetStatus.query()\
2459 statuses = ChangesetStatus.query()\
2452 .filter(ChangesetStatus.repo == self)\
2460 .filter(ChangesetStatus.repo == self)\
2453 .filter(ChangesetStatus.version == 0)
2461 .filter(ChangesetStatus.version == 0)
2454
2462
2455 if revisions:
2463 if revisions:
2456 # Try doing the filtering in chunks to avoid hitting limits
2464 # Try doing the filtering in chunks to avoid hitting limits
2457 size = 500
2465 size = 500
2458 status_results = []
2466 status_results = []
2459 for chunk in xrange(0, len(revisions), size):
2467 for chunk in xrange(0, len(revisions), size):
2460 status_results += statuses.filter(
2468 status_results += statuses.filter(
2461 ChangesetStatus.revision.in_(
2469 ChangesetStatus.revision.in_(
2462 revisions[chunk: chunk+size])
2470 revisions[chunk: chunk+size])
2463 ).all()
2471 ).all()
2464 else:
2472 else:
2465 status_results = statuses.all()
2473 status_results = statuses.all()
2466
2474
2467 grouped = {}
2475 grouped = {}
2468
2476
2469 # maybe we have open new pullrequest without a status?
2477 # maybe we have open new pullrequest without a status?
2470 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2478 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2471 status_lbl = ChangesetStatus.get_status_lbl(stat)
2479 status_lbl = ChangesetStatus.get_status_lbl(stat)
2472 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2480 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2473 for rev in pr.revisions:
2481 for rev in pr.revisions:
2474 pr_id = pr.pull_request_id
2482 pr_id = pr.pull_request_id
2475 pr_repo = pr.target_repo.repo_name
2483 pr_repo = pr.target_repo.repo_name
2476 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2484 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2477
2485
2478 for stat in status_results:
2486 for stat in status_results:
2479 pr_id = pr_repo = None
2487 pr_id = pr_repo = None
2480 if stat.pull_request:
2488 if stat.pull_request:
2481 pr_id = stat.pull_request.pull_request_id
2489 pr_id = stat.pull_request.pull_request_id
2482 pr_repo = stat.pull_request.target_repo.repo_name
2490 pr_repo = stat.pull_request.target_repo.repo_name
2483 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2491 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2484 pr_id, pr_repo]
2492 pr_id, pr_repo]
2485 return grouped
2493 return grouped
2486
2494
2487 # ==========================================================================
2495 # ==========================================================================
2488 # SCM CACHE INSTANCE
2496 # SCM CACHE INSTANCE
2489 # ==========================================================================
2497 # ==========================================================================
2490
2498
2491 def scm_instance(self, **kwargs):
2499 def scm_instance(self, **kwargs):
2492 import rhodecode
2500 import rhodecode
2493
2501
2494 # Passing a config will not hit the cache currently only used
2502 # Passing a config will not hit the cache currently only used
2495 # for repo2dbmapper
2503 # for repo2dbmapper
2496 config = kwargs.pop('config', None)
2504 config = kwargs.pop('config', None)
2497 cache = kwargs.pop('cache', None)
2505 cache = kwargs.pop('cache', None)
2498 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2506 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2499 if vcs_full_cache is not None:
2507 if vcs_full_cache is not None:
2500 # allows override global config
2508 # allows override global config
2501 full_cache = vcs_full_cache
2509 full_cache = vcs_full_cache
2502 else:
2510 else:
2503 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2511 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2504 # if cache is NOT defined use default global, else we have a full
2512 # if cache is NOT defined use default global, else we have a full
2505 # control over cache behaviour
2513 # control over cache behaviour
2506 if cache is None and full_cache and not config:
2514 if cache is None and full_cache and not config:
2507 log.debug('Initializing pure cached instance for %s', self.repo_path)
2515 log.debug('Initializing pure cached instance for %s', self.repo_path)
2508 return self._get_instance_cached()
2516 return self._get_instance_cached()
2509
2517
2510 # cache here is sent to the "vcs server"
2518 # cache here is sent to the "vcs server"
2511 return self._get_instance(cache=bool(cache), config=config)
2519 return self._get_instance(cache=bool(cache), config=config)
2512
2520
2513 def _get_instance_cached(self):
2521 def _get_instance_cached(self):
2514 from rhodecode.lib import rc_cache
2522 from rhodecode.lib import rc_cache
2515
2523
2516 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2524 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2517 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2525 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2518 repo_id=self.repo_id)
2526 repo_id=self.repo_id)
2519 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2527 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2520
2528
2521 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2529 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2522 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2530 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2523 return self._get_instance(repo_state_uid=_cache_state_uid)
2531 return self._get_instance(repo_state_uid=_cache_state_uid)
2524
2532
2525 # we must use thread scoped cache here,
2533 # we must use thread scoped cache here,
2526 # because each thread of gevent needs it's own not shared connection and cache
2534 # because each thread of gevent needs it's own not shared connection and cache
2527 # we also alter `args` so the cache key is individual for every green thread.
2535 # we also alter `args` so the cache key is individual for every green thread.
2528 inv_context_manager = rc_cache.InvalidationContext(
2536 inv_context_manager = rc_cache.InvalidationContext(
2529 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2537 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2530 thread_scoped=True)
2538 thread_scoped=True)
2531 with inv_context_manager as invalidation_context:
2539 with inv_context_manager as invalidation_context:
2532 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2540 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2533 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2541 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2534
2542
2535 # re-compute and store cache if we get invalidate signal
2543 # re-compute and store cache if we get invalidate signal
2536 if invalidation_context.should_invalidate():
2544 if invalidation_context.should_invalidate():
2537 instance = get_instance_cached.refresh(*args)
2545 instance = get_instance_cached.refresh(*args)
2538 else:
2546 else:
2539 instance = get_instance_cached(*args)
2547 instance = get_instance_cached(*args)
2540
2548
2541 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2549 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2542 return instance
2550 return instance
2543
2551
2544 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2552 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2545 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2553 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2546 self.repo_type, self.repo_path, cache)
2554 self.repo_type, self.repo_path, cache)
2547 config = config or self._config
2555 config = config or self._config
2548 custom_wire = {
2556 custom_wire = {
2549 'cache': cache, # controls the vcs.remote cache
2557 'cache': cache, # controls the vcs.remote cache
2550 'repo_state_uid': repo_state_uid
2558 'repo_state_uid': repo_state_uid
2551 }
2559 }
2552 repo = get_vcs_instance(
2560 repo = get_vcs_instance(
2553 repo_path=safe_str(self.repo_full_path),
2561 repo_path=safe_str(self.repo_full_path),
2554 config=config,
2562 config=config,
2555 with_wire=custom_wire,
2563 with_wire=custom_wire,
2556 create=False,
2564 create=False,
2557 _vcs_alias=self.repo_type)
2565 _vcs_alias=self.repo_type)
2558 if repo is not None:
2566 if repo is not None:
2559 repo.count() # cache rebuild
2567 repo.count() # cache rebuild
2560 return repo
2568 return repo
2561
2569
2562 def get_shadow_repository_path(self, workspace_id):
2570 def get_shadow_repository_path(self, workspace_id):
2563 from rhodecode.lib.vcs.backends.base import BaseRepository
2571 from rhodecode.lib.vcs.backends.base import BaseRepository
2564 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2572 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2565 self.repo_full_path, self.repo_id, workspace_id)
2573 self.repo_full_path, self.repo_id, workspace_id)
2566 return shadow_repo_path
2574 return shadow_repo_path
2567
2575
2568 def __json__(self):
2576 def __json__(self):
2569 return {'landing_rev': self.landing_rev}
2577 return {'landing_rev': self.landing_rev}
2570
2578
2571 def get_dict(self):
2579 def get_dict(self):
2572
2580
2573 # Since we transformed `repo_name` to a hybrid property, we need to
2581 # Since we transformed `repo_name` to a hybrid property, we need to
2574 # keep compatibility with the code which uses `repo_name` field.
2582 # keep compatibility with the code which uses `repo_name` field.
2575
2583
2576 result = super(Repository, self).get_dict()
2584 result = super(Repository, self).get_dict()
2577 result['repo_name'] = result.pop('_repo_name', None)
2585 result['repo_name'] = result.pop('_repo_name', None)
2578 return result
2586 return result
2579
2587
2580
2588
2581 class RepoGroup(Base, BaseModel):
2589 class RepoGroup(Base, BaseModel):
2582 __tablename__ = 'groups'
2590 __tablename__ = 'groups'
2583 __table_args__ = (
2591 __table_args__ = (
2584 UniqueConstraint('group_name', 'group_parent_id'),
2592 UniqueConstraint('group_name', 'group_parent_id'),
2585 base_table_args,
2593 base_table_args,
2586 )
2594 )
2587 __mapper_args__ = {'order_by': 'group_name'}
2595 __mapper_args__ = {'order_by': 'group_name'}
2588
2596
2589 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2597 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2590
2598
2591 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2599 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2592 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2600 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2593 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2601 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2594 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2602 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2595 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2603 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2596 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2604 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2605 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2598 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2599 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2607 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2600 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2608 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2601 _changeset_cache = Column(
2609 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2602 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2603
2610
2604 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2611 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2605 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2612 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2606 parent_group = relationship('RepoGroup', remote_side=group_id)
2613 parent_group = relationship('RepoGroup', remote_side=group_id)
2607 user = relationship('User')
2614 user = relationship('User')
2608 integrations = relationship('Integration', cascade="all, delete-orphan")
2615 integrations = relationship('Integration', cascade="all, delete-orphan")
2609
2616
2610 # no cascade, set NULL
2617 # no cascade, set NULL
2611 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2618 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2612
2619
2613 def __init__(self, group_name='', parent_group=None):
2620 def __init__(self, group_name='', parent_group=None):
2614 self.group_name = group_name
2621 self.group_name = group_name
2615 self.parent_group = parent_group
2622 self.parent_group = parent_group
2616
2623
2617 def __unicode__(self):
2624 def __unicode__(self):
2618 return u"<%s('id:%s:%s')>" % (
2625 return u"<%s('id:%s:%s')>" % (
2619 self.__class__.__name__, self.group_id, self.group_name)
2626 self.__class__.__name__, self.group_id, self.group_name)
2620
2627
2621 @hybrid_property
2628 @hybrid_property
2622 def group_name(self):
2629 def group_name(self):
2623 return self._group_name
2630 return self._group_name
2624
2631
2625 @group_name.setter
2632 @group_name.setter
2626 def group_name(self, value):
2633 def group_name(self, value):
2627 self._group_name = value
2634 self._group_name = value
2628 self.group_name_hash = self.hash_repo_group_name(value)
2635 self.group_name_hash = self.hash_repo_group_name(value)
2629
2636
2630 @hybrid_property
2637 @classmethod
2631 def changeset_cache(self):
2638 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2632 from rhodecode.lib.vcs.backends.base import EmptyCommit
2639 from rhodecode.lib.vcs.backends.base import EmptyCommit
2633 dummy = EmptyCommit().__json__()
2640 dummy = EmptyCommit().__json__()
2634 if not self._changeset_cache:
2641 if not changeset_cache_raw:
2635 dummy['source_repo_id'] = ''
2642 dummy['source_repo_id'] = repo_id
2636 return json.loads(json.dumps(dummy))
2643 return json.loads(json.dumps(dummy))
2637
2644
2638 try:
2645 try:
2639 return json.loads(self._changeset_cache)
2646 return json.loads(changeset_cache_raw)
2640 except TypeError:
2647 except TypeError:
2641 return dummy
2648 return dummy
2642 except Exception:
2649 except Exception:
2643 log.error(traceback.format_exc())
2650 log.error(traceback.format_exc())
2644 return dummy
2651 return dummy
2645
2652
2653 @hybrid_property
2654 def changeset_cache(self):
2655 return self._load_changeset_cache('', self._changeset_cache)
2656
2646 @changeset_cache.setter
2657 @changeset_cache.setter
2647 def changeset_cache(self, val):
2658 def changeset_cache(self, val):
2648 try:
2659 try:
2649 self._changeset_cache = json.dumps(val)
2660 self._changeset_cache = json.dumps(val)
2650 except Exception:
2661 except Exception:
2651 log.error(traceback.format_exc())
2662 log.error(traceback.format_exc())
2652
2663
2653 @validates('group_parent_id')
2664 @validates('group_parent_id')
2654 def validate_group_parent_id(self, key, val):
2665 def validate_group_parent_id(self, key, val):
2655 """
2666 """
2656 Check cycle references for a parent group to self
2667 Check cycle references for a parent group to self
2657 """
2668 """
2658 if self.group_id and val:
2669 if self.group_id and val:
2659 assert val != self.group_id
2670 assert val != self.group_id
2660
2671
2661 return val
2672 return val
2662
2673
2663 @hybrid_property
2674 @hybrid_property
2664 def description_safe(self):
2675 def description_safe(self):
2665 from rhodecode.lib import helpers as h
2676 from rhodecode.lib import helpers as h
2666 return h.escape(self.group_description)
2677 return h.escape(self.group_description)
2667
2678
2668 @classmethod
2679 @classmethod
2669 def hash_repo_group_name(cls, repo_group_name):
2680 def hash_repo_group_name(cls, repo_group_name):
2670 val = remove_formatting(repo_group_name)
2681 val = remove_formatting(repo_group_name)
2671 val = safe_str(val).lower()
2682 val = safe_str(val).lower()
2672 chars = []
2683 chars = []
2673 for c in val:
2684 for c in val:
2674 if c not in string.ascii_letters:
2685 if c not in string.ascii_letters:
2675 c = str(ord(c))
2686 c = str(ord(c))
2676 chars.append(c)
2687 chars.append(c)
2677
2688
2678 return ''.join(chars)
2689 return ''.join(chars)
2679
2690
2680 @classmethod
2691 @classmethod
2681 def _generate_choice(cls, repo_group):
2692 def _generate_choice(cls, repo_group):
2682 from webhelpers2.html import literal as _literal
2693 from webhelpers2.html import literal as _literal
2683 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2694 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2684 return repo_group.group_id, _name(repo_group.full_path_splitted)
2695 return repo_group.group_id, _name(repo_group.full_path_splitted)
2685
2696
2686 @classmethod
2697 @classmethod
2687 def groups_choices(cls, groups=None, show_empty_group=True):
2698 def groups_choices(cls, groups=None, show_empty_group=True):
2688 if not groups:
2699 if not groups:
2689 groups = cls.query().all()
2700 groups = cls.query().all()
2690
2701
2691 repo_groups = []
2702 repo_groups = []
2692 if show_empty_group:
2703 if show_empty_group:
2693 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2704 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2694
2705
2695 repo_groups.extend([cls._generate_choice(x) for x in groups])
2706 repo_groups.extend([cls._generate_choice(x) for x in groups])
2696
2707
2697 repo_groups = sorted(
2708 repo_groups = sorted(
2698 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2709 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2699 return repo_groups
2710 return repo_groups
2700
2711
2701 @classmethod
2712 @classmethod
2702 def url_sep(cls):
2713 def url_sep(cls):
2703 return URL_SEP
2714 return URL_SEP
2704
2715
2705 @classmethod
2716 @classmethod
2706 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2717 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2707 if case_insensitive:
2718 if case_insensitive:
2708 gr = cls.query().filter(func.lower(cls.group_name)
2719 gr = cls.query().filter(func.lower(cls.group_name)
2709 == func.lower(group_name))
2720 == func.lower(group_name))
2710 else:
2721 else:
2711 gr = cls.query().filter(cls.group_name == group_name)
2722 gr = cls.query().filter(cls.group_name == group_name)
2712 if cache:
2723 if cache:
2713 name_key = _hash_key(group_name)
2724 name_key = _hash_key(group_name)
2714 gr = gr.options(
2725 gr = gr.options(
2715 FromCache("sql_cache_short", "get_group_%s" % name_key))
2726 FromCache("sql_cache_short", "get_group_%s" % name_key))
2716 return gr.scalar()
2727 return gr.scalar()
2717
2728
2718 @classmethod
2729 @classmethod
2719 def get_user_personal_repo_group(cls, user_id):
2730 def get_user_personal_repo_group(cls, user_id):
2720 user = User.get(user_id)
2731 user = User.get(user_id)
2721 if user.username == User.DEFAULT_USER:
2732 if user.username == User.DEFAULT_USER:
2722 return None
2733 return None
2723
2734
2724 return cls.query()\
2735 return cls.query()\
2725 .filter(cls.personal == true()) \
2736 .filter(cls.personal == true()) \
2726 .filter(cls.user == user) \
2737 .filter(cls.user == user) \
2727 .order_by(cls.group_id.asc()) \
2738 .order_by(cls.group_id.asc()) \
2728 .first()
2739 .first()
2729
2740
2730 @classmethod
2741 @classmethod
2731 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2742 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2732 case_insensitive=True):
2743 case_insensitive=True):
2733 q = RepoGroup.query()
2744 q = RepoGroup.query()
2734
2745
2735 if not isinstance(user_id, Optional):
2746 if not isinstance(user_id, Optional):
2736 q = q.filter(RepoGroup.user_id == user_id)
2747 q = q.filter(RepoGroup.user_id == user_id)
2737
2748
2738 if not isinstance(group_id, Optional):
2749 if not isinstance(group_id, Optional):
2739 q = q.filter(RepoGroup.group_parent_id == group_id)
2750 q = q.filter(RepoGroup.group_parent_id == group_id)
2740
2751
2741 if case_insensitive:
2752 if case_insensitive:
2742 q = q.order_by(func.lower(RepoGroup.group_name))
2753 q = q.order_by(func.lower(RepoGroup.group_name))
2743 else:
2754 else:
2744 q = q.order_by(RepoGroup.group_name)
2755 q = q.order_by(RepoGroup.group_name)
2745 return q.all()
2756 return q.all()
2746
2757
2747 @property
2758 @property
2748 def parents(self, parents_recursion_limit = 10):
2759 def parents(self, parents_recursion_limit=10):
2749 groups = []
2760 groups = []
2750 if self.parent_group is None:
2761 if self.parent_group is None:
2751 return groups
2762 return groups
2752 cur_gr = self.parent_group
2763 cur_gr = self.parent_group
2753 groups.insert(0, cur_gr)
2764 groups.insert(0, cur_gr)
2754 cnt = 0
2765 cnt = 0
2755 while 1:
2766 while 1:
2756 cnt += 1
2767 cnt += 1
2757 gr = getattr(cur_gr, 'parent_group', None)
2768 gr = getattr(cur_gr, 'parent_group', None)
2758 cur_gr = cur_gr.parent_group
2769 cur_gr = cur_gr.parent_group
2759 if gr is None:
2770 if gr is None:
2760 break
2771 break
2761 if cnt == parents_recursion_limit:
2772 if cnt == parents_recursion_limit:
2762 # this will prevent accidental infinit loops
2773 # this will prevent accidental infinit loops
2763 log.error('more than %s parents found for group %s, stopping '
2774 log.error('more than %s parents found for group %s, stopping '
2764 'recursive parent fetching', parents_recursion_limit, self)
2775 'recursive parent fetching', parents_recursion_limit, self)
2765 break
2776 break
2766
2777
2767 groups.insert(0, gr)
2778 groups.insert(0, gr)
2768 return groups
2779 return groups
2769
2780
2770 @property
2781 @property
2771 def last_commit_cache_update_diff(self):
2782 def last_commit_cache_update_diff(self):
2772 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2783 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2773
2784
2774 @property
2785 @classmethod
2775 def last_commit_change(self):
2786 def _load_commit_change(cls, last_commit_cache):
2776 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2787 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2777 empty_date = datetime.datetime.fromtimestamp(0)
2788 empty_date = datetime.datetime.fromtimestamp(0)
2778 date_latest = self.changeset_cache.get('date', empty_date)
2789 date_latest = last_commit_cache.get('date', empty_date)
2779 try:
2790 try:
2780 return parse_datetime(date_latest)
2791 return parse_datetime(date_latest)
2781 except Exception:
2792 except Exception:
2782 return empty_date
2793 return empty_date
2783
2794
2784 @property
2795 @property
2796 def last_commit_change(self):
2797 return self._load_commit_change(self.changeset_cache)
2798
2799 @property
2785 def last_db_change(self):
2800 def last_db_change(self):
2786 return self.updated_on
2801 return self.updated_on
2787
2802
2788 @property
2803 @property
2789 def children(self):
2804 def children(self):
2790 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2805 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2791
2806
2792 @property
2807 @property
2793 def name(self):
2808 def name(self):
2794 return self.group_name.split(RepoGroup.url_sep())[-1]
2809 return self.group_name.split(RepoGroup.url_sep())[-1]
2795
2810
2796 @property
2811 @property
2797 def full_path(self):
2812 def full_path(self):
2798 return self.group_name
2813 return self.group_name
2799
2814
2800 @property
2815 @property
2801 def full_path_splitted(self):
2816 def full_path_splitted(self):
2802 return self.group_name.split(RepoGroup.url_sep())
2817 return self.group_name.split(RepoGroup.url_sep())
2803
2818
2804 @property
2819 @property
2805 def repositories(self):
2820 def repositories(self):
2806 return Repository.query()\
2821 return Repository.query()\
2807 .filter(Repository.group == self)\
2822 .filter(Repository.group == self)\
2808 .order_by(Repository.repo_name)
2823 .order_by(Repository.repo_name)
2809
2824
2810 @property
2825 @property
2811 def repositories_recursive_count(self):
2826 def repositories_recursive_count(self):
2812 cnt = self.repositories.count()
2827 cnt = self.repositories.count()
2813
2828
2814 def children_count(group):
2829 def children_count(group):
2815 cnt = 0
2830 cnt = 0
2816 for child in group.children:
2831 for child in group.children:
2817 cnt += child.repositories.count()
2832 cnt += child.repositories.count()
2818 cnt += children_count(child)
2833 cnt += children_count(child)
2819 return cnt
2834 return cnt
2820
2835
2821 return cnt + children_count(self)
2836 return cnt + children_count(self)
2822
2837
2823 def _recursive_objects(self, include_repos=True, include_groups=True):
2838 def _recursive_objects(self, include_repos=True, include_groups=True):
2824 all_ = []
2839 all_ = []
2825
2840
2826 def _get_members(root_gr):
2841 def _get_members(root_gr):
2827 if include_repos:
2842 if include_repos:
2828 for r in root_gr.repositories:
2843 for r in root_gr.repositories:
2829 all_.append(r)
2844 all_.append(r)
2830 childs = root_gr.children.all()
2845 childs = root_gr.children.all()
2831 if childs:
2846 if childs:
2832 for gr in childs:
2847 for gr in childs:
2833 if include_groups:
2848 if include_groups:
2834 all_.append(gr)
2849 all_.append(gr)
2835 _get_members(gr)
2850 _get_members(gr)
2836
2851
2837 root_group = []
2852 root_group = []
2838 if include_groups:
2853 if include_groups:
2839 root_group = [self]
2854 root_group = [self]
2840
2855
2841 _get_members(self)
2856 _get_members(self)
2842 return root_group + all_
2857 return root_group + all_
2843
2858
2844 def recursive_groups_and_repos(self):
2859 def recursive_groups_and_repos(self):
2845 """
2860 """
2846 Recursive return all groups, with repositories in those groups
2861 Recursive return all groups, with repositories in those groups
2847 """
2862 """
2848 return self._recursive_objects()
2863 return self._recursive_objects()
2849
2864
2850 def recursive_groups(self):
2865 def recursive_groups(self):
2851 """
2866 """
2852 Returns all children groups for this group including children of children
2867 Returns all children groups for this group including children of children
2853 """
2868 """
2854 return self._recursive_objects(include_repos=False)
2869 return self._recursive_objects(include_repos=False)
2855
2870
2856 def recursive_repos(self):
2871 def recursive_repos(self):
2857 """
2872 """
2858 Returns all children repositories for this group
2873 Returns all children repositories for this group
2859 """
2874 """
2860 return self._recursive_objects(include_groups=False)
2875 return self._recursive_objects(include_groups=False)
2861
2876
2862 def get_new_name(self, group_name):
2877 def get_new_name(self, group_name):
2863 """
2878 """
2864 returns new full group name based on parent and new name
2879 returns new full group name based on parent and new name
2865
2880
2866 :param group_name:
2881 :param group_name:
2867 """
2882 """
2868 path_prefix = (self.parent_group.full_path_splitted if
2883 path_prefix = (self.parent_group.full_path_splitted if
2869 self.parent_group else [])
2884 self.parent_group else [])
2870 return RepoGroup.url_sep().join(path_prefix + [group_name])
2885 return RepoGroup.url_sep().join(path_prefix + [group_name])
2871
2886
2872 def update_commit_cache(self, config=None):
2887 def update_commit_cache(self, config=None):
2873 """
2888 """
2874 Update cache of last changeset for newest repository inside this group, keys should be::
2889 Update cache of last changeset for newest repository inside this group, keys should be::
2875
2890
2876 source_repo_id
2891 source_repo_id
2877 short_id
2892 short_id
2878 raw_id
2893 raw_id
2879 revision
2894 revision
2880 parents
2895 parents
2881 message
2896 message
2882 date
2897 date
2883 author
2898 author
2884
2899
2885 """
2900 """
2886 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2901 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2887
2902
2888 def repo_groups_and_repos():
2903 def repo_groups_and_repos():
2889 all_entries = OrderedDefaultDict(list)
2904 all_entries = OrderedDefaultDict(list)
2890
2905
2891 def _get_members(root_gr, pos=0):
2906 def _get_members(root_gr, pos=0):
2892
2907
2893 for repo in root_gr.repositories:
2908 for repo in root_gr.repositories:
2894 all_entries[root_gr].append(repo)
2909 all_entries[root_gr].append(repo)
2895
2910
2896 # fill in all parent positions
2911 # fill in all parent positions
2897 for parent_group in root_gr.parents:
2912 for parent_group in root_gr.parents:
2898 all_entries[parent_group].extend(all_entries[root_gr])
2913 all_entries[parent_group].extend(all_entries[root_gr])
2899
2914
2900 children_groups = root_gr.children.all()
2915 children_groups = root_gr.children.all()
2901 if children_groups:
2916 if children_groups:
2902 for cnt, gr in enumerate(children_groups, 1):
2917 for cnt, gr in enumerate(children_groups, 1):
2903 _get_members(gr, pos=pos+cnt)
2918 _get_members(gr, pos=pos+cnt)
2904
2919
2905 _get_members(root_gr=self)
2920 _get_members(root_gr=self)
2906 return all_entries
2921 return all_entries
2907
2922
2908 empty_date = datetime.datetime.fromtimestamp(0)
2923 empty_date = datetime.datetime.fromtimestamp(0)
2909 for repo_group, repos in repo_groups_and_repos().items():
2924 for repo_group, repos in repo_groups_and_repos().items():
2910
2925
2911 latest_repo_cs_cache = {}
2926 latest_repo_cs_cache = {}
2912 _date_latest = empty_date
2927 _date_latest = empty_date
2913 for repo in repos:
2928 for repo in repos:
2914 repo_cs_cache = repo.changeset_cache
2929 repo_cs_cache = repo.changeset_cache
2915 date_latest = latest_repo_cs_cache.get('date', empty_date)
2930 date_latest = latest_repo_cs_cache.get('date', empty_date)
2916 date_current = repo_cs_cache.get('date', empty_date)
2931 date_current = repo_cs_cache.get('date', empty_date)
2917 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2932 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2918 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2933 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2919 latest_repo_cs_cache = repo_cs_cache
2934 latest_repo_cs_cache = repo_cs_cache
2920 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2935 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2921 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2936 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2922
2937
2923 latest_repo_cs_cache['updated_on'] = time.time()
2938 latest_repo_cs_cache['updated_on'] = time.time()
2924 repo_group.changeset_cache = latest_repo_cs_cache
2939 repo_group.changeset_cache = latest_repo_cs_cache
2925 repo_group.updated_on = _date_latest
2940 repo_group.updated_on = _date_latest
2926 Session().add(repo_group)
2941 Session().add(repo_group)
2927 Session().commit()
2942 Session().commit()
2928
2943
2929 log.debug('updated repo group `%s` with new commit cache %s',
2944 log.debug('updated repo group `%s` with new commit cache %s',
2930 repo_group.group_name, latest_repo_cs_cache)
2945 repo_group.group_name, latest_repo_cs_cache)
2931
2946
2932 def permissions(self, with_admins=True, with_owner=True,
2947 def permissions(self, with_admins=True, with_owner=True,
2933 expand_from_user_groups=False):
2948 expand_from_user_groups=False):
2934 """
2949 """
2935 Permissions for repository groups
2950 Permissions for repository groups
2936 """
2951 """
2937 _admin_perm = 'group.admin'
2952 _admin_perm = 'group.admin'
2938
2953
2939 owner_row = []
2954 owner_row = []
2940 if with_owner:
2955 if with_owner:
2941 usr = AttributeDict(self.user.get_dict())
2956 usr = AttributeDict(self.user.get_dict())
2942 usr.owner_row = True
2957 usr.owner_row = True
2943 usr.permission = _admin_perm
2958 usr.permission = _admin_perm
2944 owner_row.append(usr)
2959 owner_row.append(usr)
2945
2960
2946 super_admin_ids = []
2961 super_admin_ids = []
2947 super_admin_rows = []
2962 super_admin_rows = []
2948 if with_admins:
2963 if with_admins:
2949 for usr in User.get_all_super_admins():
2964 for usr in User.get_all_super_admins():
2950 super_admin_ids.append(usr.user_id)
2965 super_admin_ids.append(usr.user_id)
2951 # if this admin is also owner, don't double the record
2966 # if this admin is also owner, don't double the record
2952 if usr.user_id == owner_row[0].user_id:
2967 if usr.user_id == owner_row[0].user_id:
2953 owner_row[0].admin_row = True
2968 owner_row[0].admin_row = True
2954 else:
2969 else:
2955 usr = AttributeDict(usr.get_dict())
2970 usr = AttributeDict(usr.get_dict())
2956 usr.admin_row = True
2971 usr.admin_row = True
2957 usr.permission = _admin_perm
2972 usr.permission = _admin_perm
2958 super_admin_rows.append(usr)
2973 super_admin_rows.append(usr)
2959
2974
2960 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2975 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2961 q = q.options(joinedload(UserRepoGroupToPerm.group),
2976 q = q.options(joinedload(UserRepoGroupToPerm.group),
2962 joinedload(UserRepoGroupToPerm.user),
2977 joinedload(UserRepoGroupToPerm.user),
2963 joinedload(UserRepoGroupToPerm.permission),)
2978 joinedload(UserRepoGroupToPerm.permission),)
2964
2979
2965 # get owners and admins and permissions. We do a trick of re-writing
2980 # get owners and admins and permissions. We do a trick of re-writing
2966 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2981 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2967 # has a global reference and changing one object propagates to all
2982 # has a global reference and changing one object propagates to all
2968 # others. This means if admin is also an owner admin_row that change
2983 # others. This means if admin is also an owner admin_row that change
2969 # would propagate to both objects
2984 # would propagate to both objects
2970 perm_rows = []
2985 perm_rows = []
2971 for _usr in q.all():
2986 for _usr in q.all():
2972 usr = AttributeDict(_usr.user.get_dict())
2987 usr = AttributeDict(_usr.user.get_dict())
2973 # if this user is also owner/admin, mark as duplicate record
2988 # if this user is also owner/admin, mark as duplicate record
2974 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2989 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2975 usr.duplicate_perm = True
2990 usr.duplicate_perm = True
2976 usr.permission = _usr.permission.permission_name
2991 usr.permission = _usr.permission.permission_name
2977 perm_rows.append(usr)
2992 perm_rows.append(usr)
2978
2993
2979 # filter the perm rows by 'default' first and then sort them by
2994 # filter the perm rows by 'default' first and then sort them by
2980 # admin,write,read,none permissions sorted again alphabetically in
2995 # admin,write,read,none permissions sorted again alphabetically in
2981 # each group
2996 # each group
2982 perm_rows = sorted(perm_rows, key=display_user_sort)
2997 perm_rows = sorted(perm_rows, key=display_user_sort)
2983
2998
2984 user_groups_rows = []
2999 user_groups_rows = []
2985 if expand_from_user_groups:
3000 if expand_from_user_groups:
2986 for ug in self.permission_user_groups(with_members=True):
3001 for ug in self.permission_user_groups(with_members=True):
2987 for user_data in ug.members:
3002 for user_data in ug.members:
2988 user_groups_rows.append(user_data)
3003 user_groups_rows.append(user_data)
2989
3004
2990 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3005 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2991
3006
2992 def permission_user_groups(self, with_members=False):
3007 def permission_user_groups(self, with_members=False):
2993 q = UserGroupRepoGroupToPerm.query()\
3008 q = UserGroupRepoGroupToPerm.query()\
2994 .filter(UserGroupRepoGroupToPerm.group == self)
3009 .filter(UserGroupRepoGroupToPerm.group == self)
2995 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3010 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2996 joinedload(UserGroupRepoGroupToPerm.users_group),
3011 joinedload(UserGroupRepoGroupToPerm.users_group),
2997 joinedload(UserGroupRepoGroupToPerm.permission),)
3012 joinedload(UserGroupRepoGroupToPerm.permission),)
2998
3013
2999 perm_rows = []
3014 perm_rows = []
3000 for _user_group in q.all():
3015 for _user_group in q.all():
3001 entry = AttributeDict(_user_group.users_group.get_dict())
3016 entry = AttributeDict(_user_group.users_group.get_dict())
3002 entry.permission = _user_group.permission.permission_name
3017 entry.permission = _user_group.permission.permission_name
3003 if with_members:
3018 if with_members:
3004 entry.members = [x.user.get_dict()
3019 entry.members = [x.user.get_dict()
3005 for x in _user_group.users_group.members]
3020 for x in _user_group.users_group.members]
3006 perm_rows.append(entry)
3021 perm_rows.append(entry)
3007
3022
3008 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3023 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3009 return perm_rows
3024 return perm_rows
3010
3025
3011 def get_api_data(self):
3026 def get_api_data(self):
3012 """
3027 """
3013 Common function for generating api data
3028 Common function for generating api data
3014
3029
3015 """
3030 """
3016 group = self
3031 group = self
3017 data = {
3032 data = {
3018 'group_id': group.group_id,
3033 'group_id': group.group_id,
3019 'group_name': group.group_name,
3034 'group_name': group.group_name,
3020 'group_description': group.description_safe,
3035 'group_description': group.description_safe,
3021 'parent_group': group.parent_group.group_name if group.parent_group else None,
3036 'parent_group': group.parent_group.group_name if group.parent_group else None,
3022 'repositories': [x.repo_name for x in group.repositories],
3037 'repositories': [x.repo_name for x in group.repositories],
3023 'owner': group.user.username,
3038 'owner': group.user.username,
3024 }
3039 }
3025 return data
3040 return data
3026
3041
3027 def get_dict(self):
3042 def get_dict(self):
3028 # Since we transformed `group_name` to a hybrid property, we need to
3043 # Since we transformed `group_name` to a hybrid property, we need to
3029 # keep compatibility with the code which uses `group_name` field.
3044 # keep compatibility with the code which uses `group_name` field.
3030 result = super(RepoGroup, self).get_dict()
3045 result = super(RepoGroup, self).get_dict()
3031 result['group_name'] = result.pop('_group_name', None)
3046 result['group_name'] = result.pop('_group_name', None)
3032 return result
3047 return result
3033
3048
3034
3049
3035 class Permission(Base, BaseModel):
3050 class Permission(Base, BaseModel):
3036 __tablename__ = 'permissions'
3051 __tablename__ = 'permissions'
3037 __table_args__ = (
3052 __table_args__ = (
3038 Index('p_perm_name_idx', 'permission_name'),
3053 Index('p_perm_name_idx', 'permission_name'),
3039 base_table_args,
3054 base_table_args,
3040 )
3055 )
3041
3056
3042 PERMS = [
3057 PERMS = [
3043 ('hg.admin', _('RhodeCode Super Administrator')),
3058 ('hg.admin', _('RhodeCode Super Administrator')),
3044
3059
3045 ('repository.none', _('Repository no access')),
3060 ('repository.none', _('Repository no access')),
3046 ('repository.read', _('Repository read access')),
3061 ('repository.read', _('Repository read access')),
3047 ('repository.write', _('Repository write access')),
3062 ('repository.write', _('Repository write access')),
3048 ('repository.admin', _('Repository admin access')),
3063 ('repository.admin', _('Repository admin access')),
3049
3064
3050 ('group.none', _('Repository group no access')),
3065 ('group.none', _('Repository group no access')),
3051 ('group.read', _('Repository group read access')),
3066 ('group.read', _('Repository group read access')),
3052 ('group.write', _('Repository group write access')),
3067 ('group.write', _('Repository group write access')),
3053 ('group.admin', _('Repository group admin access')),
3068 ('group.admin', _('Repository group admin access')),
3054
3069
3055 ('usergroup.none', _('User group no access')),
3070 ('usergroup.none', _('User group no access')),
3056 ('usergroup.read', _('User group read access')),
3071 ('usergroup.read', _('User group read access')),
3057 ('usergroup.write', _('User group write access')),
3072 ('usergroup.write', _('User group write access')),
3058 ('usergroup.admin', _('User group admin access')),
3073 ('usergroup.admin', _('User group admin access')),
3059
3074
3060 ('branch.none', _('Branch no permissions')),
3075 ('branch.none', _('Branch no permissions')),
3061 ('branch.merge', _('Branch access by web merge')),
3076 ('branch.merge', _('Branch access by web merge')),
3062 ('branch.push', _('Branch access by push')),
3077 ('branch.push', _('Branch access by push')),
3063 ('branch.push_force', _('Branch access by push with force')),
3078 ('branch.push_force', _('Branch access by push with force')),
3064
3079
3065 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3080 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3066 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3081 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3067
3082
3068 ('hg.usergroup.create.false', _('User Group creation disabled')),
3083 ('hg.usergroup.create.false', _('User Group creation disabled')),
3069 ('hg.usergroup.create.true', _('User Group creation enabled')),
3084 ('hg.usergroup.create.true', _('User Group creation enabled')),
3070
3085
3071 ('hg.create.none', _('Repository creation disabled')),
3086 ('hg.create.none', _('Repository creation disabled')),
3072 ('hg.create.repository', _('Repository creation enabled')),
3087 ('hg.create.repository', _('Repository creation enabled')),
3073 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3088 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3074 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3089 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3075
3090
3076 ('hg.fork.none', _('Repository forking disabled')),
3091 ('hg.fork.none', _('Repository forking disabled')),
3077 ('hg.fork.repository', _('Repository forking enabled')),
3092 ('hg.fork.repository', _('Repository forking enabled')),
3078
3093
3079 ('hg.register.none', _('Registration disabled')),
3094 ('hg.register.none', _('Registration disabled')),
3080 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3095 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3081 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3096 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3082
3097
3083 ('hg.password_reset.enabled', _('Password reset enabled')),
3098 ('hg.password_reset.enabled', _('Password reset enabled')),
3084 ('hg.password_reset.hidden', _('Password reset hidden')),
3099 ('hg.password_reset.hidden', _('Password reset hidden')),
3085 ('hg.password_reset.disabled', _('Password reset disabled')),
3100 ('hg.password_reset.disabled', _('Password reset disabled')),
3086
3101
3087 ('hg.extern_activate.manual', _('Manual activation of external account')),
3102 ('hg.extern_activate.manual', _('Manual activation of external account')),
3088 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3103 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3089
3104
3090 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3105 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3091 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3106 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3092 ]
3107 ]
3093
3108
3094 # definition of system default permissions for DEFAULT user, created on
3109 # definition of system default permissions for DEFAULT user, created on
3095 # system setup
3110 # system setup
3096 DEFAULT_USER_PERMISSIONS = [
3111 DEFAULT_USER_PERMISSIONS = [
3097 # object perms
3112 # object perms
3098 'repository.read',
3113 'repository.read',
3099 'group.read',
3114 'group.read',
3100 'usergroup.read',
3115 'usergroup.read',
3101 # branch, for backward compat we need same value as before so forced pushed
3116 # branch, for backward compat we need same value as before so forced pushed
3102 'branch.push_force',
3117 'branch.push_force',
3103 # global
3118 # global
3104 'hg.create.repository',
3119 'hg.create.repository',
3105 'hg.repogroup.create.false',
3120 'hg.repogroup.create.false',
3106 'hg.usergroup.create.false',
3121 'hg.usergroup.create.false',
3107 'hg.create.write_on_repogroup.true',
3122 'hg.create.write_on_repogroup.true',
3108 'hg.fork.repository',
3123 'hg.fork.repository',
3109 'hg.register.manual_activate',
3124 'hg.register.manual_activate',
3110 'hg.password_reset.enabled',
3125 'hg.password_reset.enabled',
3111 'hg.extern_activate.auto',
3126 'hg.extern_activate.auto',
3112 'hg.inherit_default_perms.true',
3127 'hg.inherit_default_perms.true',
3113 ]
3128 ]
3114
3129
3115 # defines which permissions are more important higher the more important
3130 # defines which permissions are more important higher the more important
3116 # Weight defines which permissions are more important.
3131 # Weight defines which permissions are more important.
3117 # The higher number the more important.
3132 # The higher number the more important.
3118 PERM_WEIGHTS = {
3133 PERM_WEIGHTS = {
3119 'repository.none': 0,
3134 'repository.none': 0,
3120 'repository.read': 1,
3135 'repository.read': 1,
3121 'repository.write': 3,
3136 'repository.write': 3,
3122 'repository.admin': 4,
3137 'repository.admin': 4,
3123
3138
3124 'group.none': 0,
3139 'group.none': 0,
3125 'group.read': 1,
3140 'group.read': 1,
3126 'group.write': 3,
3141 'group.write': 3,
3127 'group.admin': 4,
3142 'group.admin': 4,
3128
3143
3129 'usergroup.none': 0,
3144 'usergroup.none': 0,
3130 'usergroup.read': 1,
3145 'usergroup.read': 1,
3131 'usergroup.write': 3,
3146 'usergroup.write': 3,
3132 'usergroup.admin': 4,
3147 'usergroup.admin': 4,
3133
3148
3134 'branch.none': 0,
3149 'branch.none': 0,
3135 'branch.merge': 1,
3150 'branch.merge': 1,
3136 'branch.push': 3,
3151 'branch.push': 3,
3137 'branch.push_force': 4,
3152 'branch.push_force': 4,
3138
3153
3139 'hg.repogroup.create.false': 0,
3154 'hg.repogroup.create.false': 0,
3140 'hg.repogroup.create.true': 1,
3155 'hg.repogroup.create.true': 1,
3141
3156
3142 'hg.usergroup.create.false': 0,
3157 'hg.usergroup.create.false': 0,
3143 'hg.usergroup.create.true': 1,
3158 'hg.usergroup.create.true': 1,
3144
3159
3145 'hg.fork.none': 0,
3160 'hg.fork.none': 0,
3146 'hg.fork.repository': 1,
3161 'hg.fork.repository': 1,
3147 'hg.create.none': 0,
3162 'hg.create.none': 0,
3148 'hg.create.repository': 1
3163 'hg.create.repository': 1
3149 }
3164 }
3150
3165
3151 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3166 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3152 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3167 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3153 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3168 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3154
3169
3155 def __unicode__(self):
3170 def __unicode__(self):
3156 return u"<%s('%s:%s')>" % (
3171 return u"<%s('%s:%s')>" % (
3157 self.__class__.__name__, self.permission_id, self.permission_name
3172 self.__class__.__name__, self.permission_id, self.permission_name
3158 )
3173 )
3159
3174
3160 @classmethod
3175 @classmethod
3161 def get_by_key(cls, key):
3176 def get_by_key(cls, key):
3162 return cls.query().filter(cls.permission_name == key).scalar()
3177 return cls.query().filter(cls.permission_name == key).scalar()
3163
3178
3164 @classmethod
3179 @classmethod
3165 def get_default_repo_perms(cls, user_id, repo_id=None):
3180 def get_default_repo_perms(cls, user_id, repo_id=None):
3166 q = Session().query(UserRepoToPerm, Repository, Permission)\
3181 q = Session().query(UserRepoToPerm, Repository, Permission)\
3167 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3182 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3168 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3183 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3169 .filter(UserRepoToPerm.user_id == user_id)
3184 .filter(UserRepoToPerm.user_id == user_id)
3170 if repo_id:
3185 if repo_id:
3171 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3186 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3172 return q.all()
3187 return q.all()
3173
3188
3174 @classmethod
3189 @classmethod
3175 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3190 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3176 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3191 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3177 .join(
3192 .join(
3178 Permission,
3193 Permission,
3179 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3194 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3180 .join(
3195 .join(
3181 UserRepoToPerm,
3196 UserRepoToPerm,
3182 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3197 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3183 .filter(UserRepoToPerm.user_id == user_id)
3198 .filter(UserRepoToPerm.user_id == user_id)
3184
3199
3185 if repo_id:
3200 if repo_id:
3186 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3201 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3187 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3202 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3188
3203
3189 @classmethod
3204 @classmethod
3190 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3205 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3191 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3206 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3192 .join(
3207 .join(
3193 Permission,
3208 Permission,
3194 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3209 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3195 .join(
3210 .join(
3196 Repository,
3211 Repository,
3197 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3212 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3198 .join(
3213 .join(
3199 UserGroup,
3214 UserGroup,
3200 UserGroupRepoToPerm.users_group_id ==
3215 UserGroupRepoToPerm.users_group_id ==
3201 UserGroup.users_group_id)\
3216 UserGroup.users_group_id)\
3202 .join(
3217 .join(
3203 UserGroupMember,
3218 UserGroupMember,
3204 UserGroupRepoToPerm.users_group_id ==
3219 UserGroupRepoToPerm.users_group_id ==
3205 UserGroupMember.users_group_id)\
3220 UserGroupMember.users_group_id)\
3206 .filter(
3221 .filter(
3207 UserGroupMember.user_id == user_id,
3222 UserGroupMember.user_id == user_id,
3208 UserGroup.users_group_active == true())
3223 UserGroup.users_group_active == true())
3209 if repo_id:
3224 if repo_id:
3210 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3225 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3211 return q.all()
3226 return q.all()
3212
3227
3213 @classmethod
3228 @classmethod
3214 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3229 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3215 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3230 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3216 .join(
3231 .join(
3217 Permission,
3232 Permission,
3218 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3233 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3219 .join(
3234 .join(
3220 UserGroupRepoToPerm,
3235 UserGroupRepoToPerm,
3221 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3236 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3222 .join(
3237 .join(
3223 UserGroup,
3238 UserGroup,
3224 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3239 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3225 .join(
3240 .join(
3226 UserGroupMember,
3241 UserGroupMember,
3227 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3242 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3228 .filter(
3243 .filter(
3229 UserGroupMember.user_id == user_id,
3244 UserGroupMember.user_id == user_id,
3230 UserGroup.users_group_active == true())
3245 UserGroup.users_group_active == true())
3231
3246
3232 if repo_id:
3247 if repo_id:
3233 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3248 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3234 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3249 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3235
3250
3236 @classmethod
3251 @classmethod
3237 def get_default_group_perms(cls, user_id, repo_group_id=None):
3252 def get_default_group_perms(cls, user_id, repo_group_id=None):
3238 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3253 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3239 .join(
3254 .join(
3240 Permission,
3255 Permission,
3241 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3256 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3242 .join(
3257 .join(
3243 RepoGroup,
3258 RepoGroup,
3244 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3259 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3245 .filter(UserRepoGroupToPerm.user_id == user_id)
3260 .filter(UserRepoGroupToPerm.user_id == user_id)
3246 if repo_group_id:
3261 if repo_group_id:
3247 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3262 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3248 return q.all()
3263 return q.all()
3249
3264
3250 @classmethod
3265 @classmethod
3251 def get_default_group_perms_from_user_group(
3266 def get_default_group_perms_from_user_group(
3252 cls, user_id, repo_group_id=None):
3267 cls, user_id, repo_group_id=None):
3253 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3268 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3254 .join(
3269 .join(
3255 Permission,
3270 Permission,
3256 UserGroupRepoGroupToPerm.permission_id ==
3271 UserGroupRepoGroupToPerm.permission_id ==
3257 Permission.permission_id)\
3272 Permission.permission_id)\
3258 .join(
3273 .join(
3259 RepoGroup,
3274 RepoGroup,
3260 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3275 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3261 .join(
3276 .join(
3262 UserGroup,
3277 UserGroup,
3263 UserGroupRepoGroupToPerm.users_group_id ==
3278 UserGroupRepoGroupToPerm.users_group_id ==
3264 UserGroup.users_group_id)\
3279 UserGroup.users_group_id)\
3265 .join(
3280 .join(
3266 UserGroupMember,
3281 UserGroupMember,
3267 UserGroupRepoGroupToPerm.users_group_id ==
3282 UserGroupRepoGroupToPerm.users_group_id ==
3268 UserGroupMember.users_group_id)\
3283 UserGroupMember.users_group_id)\
3269 .filter(
3284 .filter(
3270 UserGroupMember.user_id == user_id,
3285 UserGroupMember.user_id == user_id,
3271 UserGroup.users_group_active == true())
3286 UserGroup.users_group_active == true())
3272 if repo_group_id:
3287 if repo_group_id:
3273 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3288 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3274 return q.all()
3289 return q.all()
3275
3290
3276 @classmethod
3291 @classmethod
3277 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3292 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3278 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3293 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3279 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3294 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3280 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3295 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3281 .filter(UserUserGroupToPerm.user_id == user_id)
3296 .filter(UserUserGroupToPerm.user_id == user_id)
3282 if user_group_id:
3297 if user_group_id:
3283 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3298 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3284 return q.all()
3299 return q.all()
3285
3300
3286 @classmethod
3301 @classmethod
3287 def get_default_user_group_perms_from_user_group(
3302 def get_default_user_group_perms_from_user_group(
3288 cls, user_id, user_group_id=None):
3303 cls, user_id, user_group_id=None):
3289 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3304 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3290 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3305 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3291 .join(
3306 .join(
3292 Permission,
3307 Permission,
3293 UserGroupUserGroupToPerm.permission_id ==
3308 UserGroupUserGroupToPerm.permission_id ==
3294 Permission.permission_id)\
3309 Permission.permission_id)\
3295 .join(
3310 .join(
3296 TargetUserGroup,
3311 TargetUserGroup,
3297 UserGroupUserGroupToPerm.target_user_group_id ==
3312 UserGroupUserGroupToPerm.target_user_group_id ==
3298 TargetUserGroup.users_group_id)\
3313 TargetUserGroup.users_group_id)\
3299 .join(
3314 .join(
3300 UserGroup,
3315 UserGroup,
3301 UserGroupUserGroupToPerm.user_group_id ==
3316 UserGroupUserGroupToPerm.user_group_id ==
3302 UserGroup.users_group_id)\
3317 UserGroup.users_group_id)\
3303 .join(
3318 .join(
3304 UserGroupMember,
3319 UserGroupMember,
3305 UserGroupUserGroupToPerm.user_group_id ==
3320 UserGroupUserGroupToPerm.user_group_id ==
3306 UserGroupMember.users_group_id)\
3321 UserGroupMember.users_group_id)\
3307 .filter(
3322 .filter(
3308 UserGroupMember.user_id == user_id,
3323 UserGroupMember.user_id == user_id,
3309 UserGroup.users_group_active == true())
3324 UserGroup.users_group_active == true())
3310 if user_group_id:
3325 if user_group_id:
3311 q = q.filter(
3326 q = q.filter(
3312 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3327 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3313
3328
3314 return q.all()
3329 return q.all()
3315
3330
3316
3331
3317 class UserRepoToPerm(Base, BaseModel):
3332 class UserRepoToPerm(Base, BaseModel):
3318 __tablename__ = 'repo_to_perm'
3333 __tablename__ = 'repo_to_perm'
3319 __table_args__ = (
3334 __table_args__ = (
3320 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3335 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3321 base_table_args
3336 base_table_args
3322 )
3337 )
3323
3338
3324 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3339 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3341 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3342 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3328
3343
3329 user = relationship('User')
3344 user = relationship('User')
3330 repository = relationship('Repository')
3345 repository = relationship('Repository')
3331 permission = relationship('Permission')
3346 permission = relationship('Permission')
3332
3347
3333 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3348 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3334
3349
3335 @classmethod
3350 @classmethod
3336 def create(cls, user, repository, permission):
3351 def create(cls, user, repository, permission):
3337 n = cls()
3352 n = cls()
3338 n.user = user
3353 n.user = user
3339 n.repository = repository
3354 n.repository = repository
3340 n.permission = permission
3355 n.permission = permission
3341 Session().add(n)
3356 Session().add(n)
3342 return n
3357 return n
3343
3358
3344 def __unicode__(self):
3359 def __unicode__(self):
3345 return u'<%s => %s >' % (self.user, self.repository)
3360 return u'<%s => %s >' % (self.user, self.repository)
3346
3361
3347
3362
3348 class UserUserGroupToPerm(Base, BaseModel):
3363 class UserUserGroupToPerm(Base, BaseModel):
3349 __tablename__ = 'user_user_group_to_perm'
3364 __tablename__ = 'user_user_group_to_perm'
3350 __table_args__ = (
3365 __table_args__ = (
3351 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3366 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3352 base_table_args
3367 base_table_args
3353 )
3368 )
3354
3369
3355 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3370 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3356 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3357 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3372 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3358 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3373 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3359
3374
3360 user = relationship('User')
3375 user = relationship('User')
3361 user_group = relationship('UserGroup')
3376 user_group = relationship('UserGroup')
3362 permission = relationship('Permission')
3377 permission = relationship('Permission')
3363
3378
3364 @classmethod
3379 @classmethod
3365 def create(cls, user, user_group, permission):
3380 def create(cls, user, user_group, permission):
3366 n = cls()
3381 n = cls()
3367 n.user = user
3382 n.user = user
3368 n.user_group = user_group
3383 n.user_group = user_group
3369 n.permission = permission
3384 n.permission = permission
3370 Session().add(n)
3385 Session().add(n)
3371 return n
3386 return n
3372
3387
3373 def __unicode__(self):
3388 def __unicode__(self):
3374 return u'<%s => %s >' % (self.user, self.user_group)
3389 return u'<%s => %s >' % (self.user, self.user_group)
3375
3390
3376
3391
3377 class UserToPerm(Base, BaseModel):
3392 class UserToPerm(Base, BaseModel):
3378 __tablename__ = 'user_to_perm'
3393 __tablename__ = 'user_to_perm'
3379 __table_args__ = (
3394 __table_args__ = (
3380 UniqueConstraint('user_id', 'permission_id'),
3395 UniqueConstraint('user_id', 'permission_id'),
3381 base_table_args
3396 base_table_args
3382 )
3397 )
3383
3398
3384 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3399 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3400 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3386 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3401 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3387
3402
3388 user = relationship('User')
3403 user = relationship('User')
3389 permission = relationship('Permission', lazy='joined')
3404 permission = relationship('Permission', lazy='joined')
3390
3405
3391 def __unicode__(self):
3406 def __unicode__(self):
3392 return u'<%s => %s >' % (self.user, self.permission)
3407 return u'<%s => %s >' % (self.user, self.permission)
3393
3408
3394
3409
3395 class UserGroupRepoToPerm(Base, BaseModel):
3410 class UserGroupRepoToPerm(Base, BaseModel):
3396 __tablename__ = 'users_group_repo_to_perm'
3411 __tablename__ = 'users_group_repo_to_perm'
3397 __table_args__ = (
3412 __table_args__ = (
3398 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3413 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3399 base_table_args
3414 base_table_args
3400 )
3415 )
3401
3416
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3417 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3418 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3419 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3406
3421
3407 users_group = relationship('UserGroup')
3422 users_group = relationship('UserGroup')
3408 permission = relationship('Permission')
3423 permission = relationship('Permission')
3409 repository = relationship('Repository')
3424 repository = relationship('Repository')
3410 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3425 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3411
3426
3412 @classmethod
3427 @classmethod
3413 def create(cls, users_group, repository, permission):
3428 def create(cls, users_group, repository, permission):
3414 n = cls()
3429 n = cls()
3415 n.users_group = users_group
3430 n.users_group = users_group
3416 n.repository = repository
3431 n.repository = repository
3417 n.permission = permission
3432 n.permission = permission
3418 Session().add(n)
3433 Session().add(n)
3419 return n
3434 return n
3420
3435
3421 def __unicode__(self):
3436 def __unicode__(self):
3422 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3437 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3423
3438
3424
3439
3425 class UserGroupUserGroupToPerm(Base, BaseModel):
3440 class UserGroupUserGroupToPerm(Base, BaseModel):
3426 __tablename__ = 'user_group_user_group_to_perm'
3441 __tablename__ = 'user_group_user_group_to_perm'
3427 __table_args__ = (
3442 __table_args__ = (
3428 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3443 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3429 CheckConstraint('target_user_group_id != user_group_id'),
3444 CheckConstraint('target_user_group_id != user_group_id'),
3430 base_table_args
3445 base_table_args
3431 )
3446 )
3432
3447
3433 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3448 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3434 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3449 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3450 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3436 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3451 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3437
3452
3438 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3453 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3439 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3454 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3440 permission = relationship('Permission')
3455 permission = relationship('Permission')
3441
3456
3442 @classmethod
3457 @classmethod
3443 def create(cls, target_user_group, user_group, permission):
3458 def create(cls, target_user_group, user_group, permission):
3444 n = cls()
3459 n = cls()
3445 n.target_user_group = target_user_group
3460 n.target_user_group = target_user_group
3446 n.user_group = user_group
3461 n.user_group = user_group
3447 n.permission = permission
3462 n.permission = permission
3448 Session().add(n)
3463 Session().add(n)
3449 return n
3464 return n
3450
3465
3451 def __unicode__(self):
3466 def __unicode__(self):
3452 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3467 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3453
3468
3454
3469
3455 class UserGroupToPerm(Base, BaseModel):
3470 class UserGroupToPerm(Base, BaseModel):
3456 __tablename__ = 'users_group_to_perm'
3471 __tablename__ = 'users_group_to_perm'
3457 __table_args__ = (
3472 __table_args__ = (
3458 UniqueConstraint('users_group_id', 'permission_id',),
3473 UniqueConstraint('users_group_id', 'permission_id',),
3459 base_table_args
3474 base_table_args
3460 )
3475 )
3461
3476
3462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3477 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3478 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3479 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3465
3480
3466 users_group = relationship('UserGroup')
3481 users_group = relationship('UserGroup')
3467 permission = relationship('Permission')
3482 permission = relationship('Permission')
3468
3483
3469
3484
3470 class UserRepoGroupToPerm(Base, BaseModel):
3485 class UserRepoGroupToPerm(Base, BaseModel):
3471 __tablename__ = 'user_repo_group_to_perm'
3486 __tablename__ = 'user_repo_group_to_perm'
3472 __table_args__ = (
3487 __table_args__ = (
3473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3488 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3474 base_table_args
3489 base_table_args
3475 )
3490 )
3476
3491
3477 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3492 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3479 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3494 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3480 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3495 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3481
3496
3482 user = relationship('User')
3497 user = relationship('User')
3483 group = relationship('RepoGroup')
3498 group = relationship('RepoGroup')
3484 permission = relationship('Permission')
3499 permission = relationship('Permission')
3485
3500
3486 @classmethod
3501 @classmethod
3487 def create(cls, user, repository_group, permission):
3502 def create(cls, user, repository_group, permission):
3488 n = cls()
3503 n = cls()
3489 n.user = user
3504 n.user = user
3490 n.group = repository_group
3505 n.group = repository_group
3491 n.permission = permission
3506 n.permission = permission
3492 Session().add(n)
3507 Session().add(n)
3493 return n
3508 return n
3494
3509
3495
3510
3496 class UserGroupRepoGroupToPerm(Base, BaseModel):
3511 class UserGroupRepoGroupToPerm(Base, BaseModel):
3497 __tablename__ = 'users_group_repo_group_to_perm'
3512 __tablename__ = 'users_group_repo_group_to_perm'
3498 __table_args__ = (
3513 __table_args__ = (
3499 UniqueConstraint('users_group_id', 'group_id'),
3514 UniqueConstraint('users_group_id', 'group_id'),
3500 base_table_args
3515 base_table_args
3501 )
3516 )
3502
3517
3503 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3518 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3519 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3520 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3521 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507
3522
3508 users_group = relationship('UserGroup')
3523 users_group = relationship('UserGroup')
3509 permission = relationship('Permission')
3524 permission = relationship('Permission')
3510 group = relationship('RepoGroup')
3525 group = relationship('RepoGroup')
3511
3526
3512 @classmethod
3527 @classmethod
3513 def create(cls, user_group, repository_group, permission):
3528 def create(cls, user_group, repository_group, permission):
3514 n = cls()
3529 n = cls()
3515 n.users_group = user_group
3530 n.users_group = user_group
3516 n.group = repository_group
3531 n.group = repository_group
3517 n.permission = permission
3532 n.permission = permission
3518 Session().add(n)
3533 Session().add(n)
3519 return n
3534 return n
3520
3535
3521 def __unicode__(self):
3536 def __unicode__(self):
3522 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3537 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3523
3538
3524
3539
3525 class Statistics(Base, BaseModel):
3540 class Statistics(Base, BaseModel):
3526 __tablename__ = 'statistics'
3541 __tablename__ = 'statistics'
3527 __table_args__ = (
3542 __table_args__ = (
3528 base_table_args
3543 base_table_args
3529 )
3544 )
3530
3545
3531 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3546 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3547 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3533 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3548 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3534 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3549 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3535 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3550 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3536 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3551 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3537
3552
3538 repository = relationship('Repository', single_parent=True)
3553 repository = relationship('Repository', single_parent=True)
3539
3554
3540
3555
3541 class UserFollowing(Base, BaseModel):
3556 class UserFollowing(Base, BaseModel):
3542 __tablename__ = 'user_followings'
3557 __tablename__ = 'user_followings'
3543 __table_args__ = (
3558 __table_args__ = (
3544 UniqueConstraint('user_id', 'follows_repository_id'),
3559 UniqueConstraint('user_id', 'follows_repository_id'),
3545 UniqueConstraint('user_id', 'follows_user_id'),
3560 UniqueConstraint('user_id', 'follows_user_id'),
3546 base_table_args
3561 base_table_args
3547 )
3562 )
3548
3563
3549 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3564 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3551 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3566 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3552 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3567 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3553 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3568 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3554
3569
3555 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3570 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3556
3571
3557 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3572 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3558 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3573 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3559
3574
3560 @classmethod
3575 @classmethod
3561 def get_repo_followers(cls, repo_id):
3576 def get_repo_followers(cls, repo_id):
3562 return cls.query().filter(cls.follows_repo_id == repo_id)
3577 return cls.query().filter(cls.follows_repo_id == repo_id)
3563
3578
3564
3579
3565 class CacheKey(Base, BaseModel):
3580 class CacheKey(Base, BaseModel):
3566 __tablename__ = 'cache_invalidation'
3581 __tablename__ = 'cache_invalidation'
3567 __table_args__ = (
3582 __table_args__ = (
3568 UniqueConstraint('cache_key'),
3583 UniqueConstraint('cache_key'),
3569 Index('key_idx', 'cache_key'),
3584 Index('key_idx', 'cache_key'),
3570 base_table_args,
3585 base_table_args,
3571 )
3586 )
3572
3587
3573 CACHE_TYPE_FEED = 'FEED'
3588 CACHE_TYPE_FEED = 'FEED'
3574
3589
3575 # namespaces used to register process/thread aware caches
3590 # namespaces used to register process/thread aware caches
3576 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3591 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3577 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3592 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3578
3593
3579 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3594 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3580 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3595 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3581 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3596 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3582 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3597 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3583 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3598 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3584
3599
3585 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3600 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3586 self.cache_key = cache_key
3601 self.cache_key = cache_key
3587 self.cache_args = cache_args
3602 self.cache_args = cache_args
3588 self.cache_active = False
3603 self.cache_active = False
3589 # first key should be same for all entries, since all workers should share it
3604 # first key should be same for all entries, since all workers should share it
3590 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3605 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3591
3606
3592 def __unicode__(self):
3607 def __unicode__(self):
3593 return u"<%s('%s:%s[%s]')>" % (
3608 return u"<%s('%s:%s[%s]')>" % (
3594 self.__class__.__name__,
3609 self.__class__.__name__,
3595 self.cache_id, self.cache_key, self.cache_active)
3610 self.cache_id, self.cache_key, self.cache_active)
3596
3611
3597 def _cache_key_partition(self):
3612 def _cache_key_partition(self):
3598 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3613 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3599 return prefix, repo_name, suffix
3614 return prefix, repo_name, suffix
3600
3615
3601 def get_prefix(self):
3616 def get_prefix(self):
3602 """
3617 """
3603 Try to extract prefix from existing cache key. The key could consist
3618 Try to extract prefix from existing cache key. The key could consist
3604 of prefix, repo_name, suffix
3619 of prefix, repo_name, suffix
3605 """
3620 """
3606 # this returns prefix, repo_name, suffix
3621 # this returns prefix, repo_name, suffix
3607 return self._cache_key_partition()[0]
3622 return self._cache_key_partition()[0]
3608
3623
3609 def get_suffix(self):
3624 def get_suffix(self):
3610 """
3625 """
3611 get suffix that might have been used in _get_cache_key to
3626 get suffix that might have been used in _get_cache_key to
3612 generate self.cache_key. Only used for informational purposes
3627 generate self.cache_key. Only used for informational purposes
3613 in repo_edit.mako.
3628 in repo_edit.mako.
3614 """
3629 """
3615 # prefix, repo_name, suffix
3630 # prefix, repo_name, suffix
3616 return self._cache_key_partition()[2]
3631 return self._cache_key_partition()[2]
3617
3632
3618 @classmethod
3633 @classmethod
3619 def generate_new_state_uid(cls, based_on=None):
3634 def generate_new_state_uid(cls, based_on=None):
3620 if based_on:
3635 if based_on:
3621 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3636 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3622 else:
3637 else:
3623 return str(uuid.uuid4())
3638 return str(uuid.uuid4())
3624
3639
3625 @classmethod
3640 @classmethod
3626 def delete_all_cache(cls):
3641 def delete_all_cache(cls):
3627 """
3642 """
3628 Delete all cache keys from database.
3643 Delete all cache keys from database.
3629 Should only be run when all instances are down and all entries
3644 Should only be run when all instances are down and all entries
3630 thus stale.
3645 thus stale.
3631 """
3646 """
3632 cls.query().delete()
3647 cls.query().delete()
3633 Session().commit()
3648 Session().commit()
3634
3649
3635 @classmethod
3650 @classmethod
3636 def set_invalidate(cls, cache_uid, delete=False):
3651 def set_invalidate(cls, cache_uid, delete=False):
3637 """
3652 """
3638 Mark all caches of a repo as invalid in the database.
3653 Mark all caches of a repo as invalid in the database.
3639 """
3654 """
3640
3655
3641 try:
3656 try:
3642 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3657 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3643 if delete:
3658 if delete:
3644 qry.delete()
3659 qry.delete()
3645 log.debug('cache objects deleted for cache args %s',
3660 log.debug('cache objects deleted for cache args %s',
3646 safe_str(cache_uid))
3661 safe_str(cache_uid))
3647 else:
3662 else:
3648 qry.update({"cache_active": False,
3663 qry.update({"cache_active": False,
3649 "cache_state_uid": cls.generate_new_state_uid()})
3664 "cache_state_uid": cls.generate_new_state_uid()})
3650 log.debug('cache objects marked as invalid for cache args %s',
3665 log.debug('cache objects marked as invalid for cache args %s',
3651 safe_str(cache_uid))
3666 safe_str(cache_uid))
3652
3667
3653 Session().commit()
3668 Session().commit()
3654 except Exception:
3669 except Exception:
3655 log.exception(
3670 log.exception(
3656 'Cache key invalidation failed for cache args %s',
3671 'Cache key invalidation failed for cache args %s',
3657 safe_str(cache_uid))
3672 safe_str(cache_uid))
3658 Session().rollback()
3673 Session().rollback()
3659
3674
3660 @classmethod
3675 @classmethod
3661 def get_active_cache(cls, cache_key):
3676 def get_active_cache(cls, cache_key):
3662 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3677 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3663 if inv_obj:
3678 if inv_obj:
3664 return inv_obj
3679 return inv_obj
3665 return None
3680 return None
3666
3681
3667 @classmethod
3682 @classmethod
3668 def get_namespace_map(cls, namespace):
3683 def get_namespace_map(cls, namespace):
3669 return {
3684 return {
3670 x.cache_key: x
3685 x.cache_key: x
3671 for x in cls.query().filter(cls.cache_args == namespace)}
3686 for x in cls.query().filter(cls.cache_args == namespace)}
3672
3687
3673
3688
3674 class ChangesetComment(Base, BaseModel):
3689 class ChangesetComment(Base, BaseModel):
3675 __tablename__ = 'changeset_comments'
3690 __tablename__ = 'changeset_comments'
3676 __table_args__ = (
3691 __table_args__ = (
3677 Index('cc_revision_idx', 'revision'),
3692 Index('cc_revision_idx', 'revision'),
3678 base_table_args,
3693 base_table_args,
3679 )
3694 )
3680
3695
3681 COMMENT_OUTDATED = u'comment_outdated'
3696 COMMENT_OUTDATED = u'comment_outdated'
3682 COMMENT_TYPE_NOTE = u'note'
3697 COMMENT_TYPE_NOTE = u'note'
3683 COMMENT_TYPE_TODO = u'todo'
3698 COMMENT_TYPE_TODO = u'todo'
3684 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3699 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3685
3700
3686 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3701 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3687 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3702 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3688 revision = Column('revision', String(40), nullable=True)
3703 revision = Column('revision', String(40), nullable=True)
3689 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3704 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3690 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3705 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3691 line_no = Column('line_no', Unicode(10), nullable=True)
3706 line_no = Column('line_no', Unicode(10), nullable=True)
3692 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3707 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3693 f_path = Column('f_path', Unicode(1000), nullable=True)
3708 f_path = Column('f_path', Unicode(1000), nullable=True)
3694 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3709 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3695 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3710 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3711 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3697 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3712 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3698 renderer = Column('renderer', Unicode(64), nullable=True)
3713 renderer = Column('renderer', Unicode(64), nullable=True)
3699 display_state = Column('display_state', Unicode(128), nullable=True)
3714 display_state = Column('display_state', Unicode(128), nullable=True)
3700
3715
3701 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3716 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3702 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3717 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3703
3718
3704 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3719 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3705 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3720 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3706
3721
3707 author = relationship('User', lazy='joined')
3722 author = relationship('User', lazy='joined')
3708 repo = relationship('Repository')
3723 repo = relationship('Repository')
3709 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3724 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3710 pull_request = relationship('PullRequest', lazy='joined')
3725 pull_request = relationship('PullRequest', lazy='joined')
3711 pull_request_version = relationship('PullRequestVersion')
3726 pull_request_version = relationship('PullRequestVersion')
3712
3727
3713 @classmethod
3728 @classmethod
3714 def get_users(cls, revision=None, pull_request_id=None):
3729 def get_users(cls, revision=None, pull_request_id=None):
3715 """
3730 """
3716 Returns user associated with this ChangesetComment. ie those
3731 Returns user associated with this ChangesetComment. ie those
3717 who actually commented
3732 who actually commented
3718
3733
3719 :param cls:
3734 :param cls:
3720 :param revision:
3735 :param revision:
3721 """
3736 """
3722 q = Session().query(User)\
3737 q = Session().query(User)\
3723 .join(ChangesetComment.author)
3738 .join(ChangesetComment.author)
3724 if revision:
3739 if revision:
3725 q = q.filter(cls.revision == revision)
3740 q = q.filter(cls.revision == revision)
3726 elif pull_request_id:
3741 elif pull_request_id:
3727 q = q.filter(cls.pull_request_id == pull_request_id)
3742 q = q.filter(cls.pull_request_id == pull_request_id)
3728 return q.all()
3743 return q.all()
3729
3744
3730 @classmethod
3745 @classmethod
3731 def get_index_from_version(cls, pr_version, versions):
3746 def get_index_from_version(cls, pr_version, versions):
3732 num_versions = [x.pull_request_version_id for x in versions]
3747 num_versions = [x.pull_request_version_id for x in versions]
3733 try:
3748 try:
3734 return num_versions.index(pr_version) +1
3749 return num_versions.index(pr_version) +1
3735 except (IndexError, ValueError):
3750 except (IndexError, ValueError):
3736 return
3751 return
3737
3752
3738 @property
3753 @property
3739 def outdated(self):
3754 def outdated(self):
3740 return self.display_state == self.COMMENT_OUTDATED
3755 return self.display_state == self.COMMENT_OUTDATED
3741
3756
3742 def outdated_at_version(self, version):
3757 def outdated_at_version(self, version):
3743 """
3758 """
3744 Checks if comment is outdated for given pull request version
3759 Checks if comment is outdated for given pull request version
3745 """
3760 """
3746 return self.outdated and self.pull_request_version_id != version
3761 return self.outdated and self.pull_request_version_id != version
3747
3762
3748 def older_than_version(self, version):
3763 def older_than_version(self, version):
3749 """
3764 """
3750 Checks if comment is made from previous version than given
3765 Checks if comment is made from previous version than given
3751 """
3766 """
3752 if version is None:
3767 if version is None:
3753 return self.pull_request_version_id is not None
3768 return self.pull_request_version_id is not None
3754
3769
3755 return self.pull_request_version_id < version
3770 return self.pull_request_version_id < version
3756
3771
3757 @property
3772 @property
3758 def resolved(self):
3773 def resolved(self):
3759 return self.resolved_by[0] if self.resolved_by else None
3774 return self.resolved_by[0] if self.resolved_by else None
3760
3775
3761 @property
3776 @property
3762 def is_todo(self):
3777 def is_todo(self):
3763 return self.comment_type == self.COMMENT_TYPE_TODO
3778 return self.comment_type == self.COMMENT_TYPE_TODO
3764
3779
3765 @property
3780 @property
3766 def is_inline(self):
3781 def is_inline(self):
3767 return self.line_no and self.f_path
3782 return self.line_no and self.f_path
3768
3783
3769 def get_index_version(self, versions):
3784 def get_index_version(self, versions):
3770 return self.get_index_from_version(
3785 return self.get_index_from_version(
3771 self.pull_request_version_id, versions)
3786 self.pull_request_version_id, versions)
3772
3787
3773 def __repr__(self):
3788 def __repr__(self):
3774 if self.comment_id:
3789 if self.comment_id:
3775 return '<DB:Comment #%s>' % self.comment_id
3790 return '<DB:Comment #%s>' % self.comment_id
3776 else:
3791 else:
3777 return '<DB:Comment at %#x>' % id(self)
3792 return '<DB:Comment at %#x>' % id(self)
3778
3793
3779 def get_api_data(self):
3794 def get_api_data(self):
3780 comment = self
3795 comment = self
3781 data = {
3796 data = {
3782 'comment_id': comment.comment_id,
3797 'comment_id': comment.comment_id,
3783 'comment_type': comment.comment_type,
3798 'comment_type': comment.comment_type,
3784 'comment_text': comment.text,
3799 'comment_text': comment.text,
3785 'comment_status': comment.status_change,
3800 'comment_status': comment.status_change,
3786 'comment_f_path': comment.f_path,
3801 'comment_f_path': comment.f_path,
3787 'comment_lineno': comment.line_no,
3802 'comment_lineno': comment.line_no,
3788 'comment_author': comment.author,
3803 'comment_author': comment.author,
3789 'comment_created_on': comment.created_on,
3804 'comment_created_on': comment.created_on,
3790 'comment_resolved_by': self.resolved
3805 'comment_resolved_by': self.resolved
3791 }
3806 }
3792 return data
3807 return data
3793
3808
3794 def __json__(self):
3809 def __json__(self):
3795 data = dict()
3810 data = dict()
3796 data.update(self.get_api_data())
3811 data.update(self.get_api_data())
3797 return data
3812 return data
3798
3813
3799
3814
3800 class ChangesetStatus(Base, BaseModel):
3815 class ChangesetStatus(Base, BaseModel):
3801 __tablename__ = 'changeset_statuses'
3816 __tablename__ = 'changeset_statuses'
3802 __table_args__ = (
3817 __table_args__ = (
3803 Index('cs_revision_idx', 'revision'),
3818 Index('cs_revision_idx', 'revision'),
3804 Index('cs_version_idx', 'version'),
3819 Index('cs_version_idx', 'version'),
3805 UniqueConstraint('repo_id', 'revision', 'version'),
3820 UniqueConstraint('repo_id', 'revision', 'version'),
3806 base_table_args
3821 base_table_args
3807 )
3822 )
3808
3823
3809 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3824 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3810 STATUS_APPROVED = 'approved'
3825 STATUS_APPROVED = 'approved'
3811 STATUS_REJECTED = 'rejected'
3826 STATUS_REJECTED = 'rejected'
3812 STATUS_UNDER_REVIEW = 'under_review'
3827 STATUS_UNDER_REVIEW = 'under_review'
3813
3828
3814 STATUSES = [
3829 STATUSES = [
3815 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3830 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3816 (STATUS_APPROVED, _("Approved")),
3831 (STATUS_APPROVED, _("Approved")),
3817 (STATUS_REJECTED, _("Rejected")),
3832 (STATUS_REJECTED, _("Rejected")),
3818 (STATUS_UNDER_REVIEW, _("Under Review")),
3833 (STATUS_UNDER_REVIEW, _("Under Review")),
3819 ]
3834 ]
3820
3835
3821 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3836 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3837 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3823 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3838 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3824 revision = Column('revision', String(40), nullable=False)
3839 revision = Column('revision', String(40), nullable=False)
3825 status = Column('status', String(128), nullable=False, default=DEFAULT)
3840 status = Column('status', String(128), nullable=False, default=DEFAULT)
3826 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3841 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3827 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3842 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3828 version = Column('version', Integer(), nullable=False, default=0)
3843 version = Column('version', Integer(), nullable=False, default=0)
3829 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3844 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3830
3845
3831 author = relationship('User', lazy='joined')
3846 author = relationship('User', lazy='joined')
3832 repo = relationship('Repository')
3847 repo = relationship('Repository')
3833 comment = relationship('ChangesetComment', lazy='joined')
3848 comment = relationship('ChangesetComment', lazy='joined')
3834 pull_request = relationship('PullRequest', lazy='joined')
3849 pull_request = relationship('PullRequest', lazy='joined')
3835
3850
3836 def __unicode__(self):
3851 def __unicode__(self):
3837 return u"<%s('%s[v%s]:%s')>" % (
3852 return u"<%s('%s[v%s]:%s')>" % (
3838 self.__class__.__name__,
3853 self.__class__.__name__,
3839 self.status, self.version, self.author
3854 self.status, self.version, self.author
3840 )
3855 )
3841
3856
3842 @classmethod
3857 @classmethod
3843 def get_status_lbl(cls, value):
3858 def get_status_lbl(cls, value):
3844 return dict(cls.STATUSES).get(value)
3859 return dict(cls.STATUSES).get(value)
3845
3860
3846 @property
3861 @property
3847 def status_lbl(self):
3862 def status_lbl(self):
3848 return ChangesetStatus.get_status_lbl(self.status)
3863 return ChangesetStatus.get_status_lbl(self.status)
3849
3864
3850 def get_api_data(self):
3865 def get_api_data(self):
3851 status = self
3866 status = self
3852 data = {
3867 data = {
3853 'status_id': status.changeset_status_id,
3868 'status_id': status.changeset_status_id,
3854 'status': status.status,
3869 'status': status.status,
3855 }
3870 }
3856 return data
3871 return data
3857
3872
3858 def __json__(self):
3873 def __json__(self):
3859 data = dict()
3874 data = dict()
3860 data.update(self.get_api_data())
3875 data.update(self.get_api_data())
3861 return data
3876 return data
3862
3877
3863
3878
3864 class _SetState(object):
3879 class _SetState(object):
3865 """
3880 """
3866 Context processor allowing changing state for sensitive operation such as
3881 Context processor allowing changing state for sensitive operation such as
3867 pull request update or merge
3882 pull request update or merge
3868 """
3883 """
3869
3884
3870 def __init__(self, pull_request, pr_state, back_state=None):
3885 def __init__(self, pull_request, pr_state, back_state=None):
3871 self._pr = pull_request
3886 self._pr = pull_request
3872 self._org_state = back_state or pull_request.pull_request_state
3887 self._org_state = back_state or pull_request.pull_request_state
3873 self._pr_state = pr_state
3888 self._pr_state = pr_state
3874 self._current_state = None
3889 self._current_state = None
3875
3890
3876 def __enter__(self):
3891 def __enter__(self):
3877 log.debug('StateLock: entering set state context, setting state to: `%s`',
3892 log.debug('StateLock: entering set state context, setting state to: `%s`',
3878 self._pr_state)
3893 self._pr_state)
3879 self.set_pr_state(self._pr_state)
3894 self.set_pr_state(self._pr_state)
3880 return self
3895 return self
3881
3896
3882 def __exit__(self, exc_type, exc_val, exc_tb):
3897 def __exit__(self, exc_type, exc_val, exc_tb):
3883 if exc_val is not None:
3898 if exc_val is not None:
3884 log.error(traceback.format_exc(exc_tb))
3899 log.error(traceback.format_exc(exc_tb))
3885 return None
3900 return None
3886
3901
3887 self.set_pr_state(self._org_state)
3902 self.set_pr_state(self._org_state)
3888 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3903 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3889 self._org_state)
3904 self._org_state)
3890 @property
3905 @property
3891 def state(self):
3906 def state(self):
3892 return self._current_state
3907 return self._current_state
3893
3908
3894 def set_pr_state(self, pr_state):
3909 def set_pr_state(self, pr_state):
3895 try:
3910 try:
3896 self._pr.pull_request_state = pr_state
3911 self._pr.pull_request_state = pr_state
3897 Session().add(self._pr)
3912 Session().add(self._pr)
3898 Session().commit()
3913 Session().commit()
3899 self._current_state = pr_state
3914 self._current_state = pr_state
3900 except Exception:
3915 except Exception:
3901 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3916 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3902 raise
3917 raise
3903
3918
3904
3919
3905 class _PullRequestBase(BaseModel):
3920 class _PullRequestBase(BaseModel):
3906 """
3921 """
3907 Common attributes of pull request and version entries.
3922 Common attributes of pull request and version entries.
3908 """
3923 """
3909
3924
3910 # .status values
3925 # .status values
3911 STATUS_NEW = u'new'
3926 STATUS_NEW = u'new'
3912 STATUS_OPEN = u'open'
3927 STATUS_OPEN = u'open'
3913 STATUS_CLOSED = u'closed'
3928 STATUS_CLOSED = u'closed'
3914
3929
3915 # available states
3930 # available states
3916 STATE_CREATING = u'creating'
3931 STATE_CREATING = u'creating'
3917 STATE_UPDATING = u'updating'
3932 STATE_UPDATING = u'updating'
3918 STATE_MERGING = u'merging'
3933 STATE_MERGING = u'merging'
3919 STATE_CREATED = u'created'
3934 STATE_CREATED = u'created'
3920
3935
3921 title = Column('title', Unicode(255), nullable=True)
3936 title = Column('title', Unicode(255), nullable=True)
3922 description = Column(
3937 description = Column(
3923 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3938 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3924 nullable=True)
3939 nullable=True)
3925 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3940 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3926
3941
3927 # new/open/closed status of pull request (not approve/reject/etc)
3942 # new/open/closed status of pull request (not approve/reject/etc)
3928 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3943 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3929 created_on = Column(
3944 created_on = Column(
3930 'created_on', DateTime(timezone=False), nullable=False,
3945 'created_on', DateTime(timezone=False), nullable=False,
3931 default=datetime.datetime.now)
3946 default=datetime.datetime.now)
3932 updated_on = Column(
3947 updated_on = Column(
3933 'updated_on', DateTime(timezone=False), nullable=False,
3948 'updated_on', DateTime(timezone=False), nullable=False,
3934 default=datetime.datetime.now)
3949 default=datetime.datetime.now)
3935
3950
3936 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3951 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3937
3952
3938 @declared_attr
3953 @declared_attr
3939 def user_id(cls):
3954 def user_id(cls):
3940 return Column(
3955 return Column(
3941 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3956 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3942 unique=None)
3957 unique=None)
3943
3958
3944 # 500 revisions max
3959 # 500 revisions max
3945 _revisions = Column(
3960 _revisions = Column(
3946 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3961 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3947
3962
3948 @declared_attr
3963 @declared_attr
3949 def source_repo_id(cls):
3964 def source_repo_id(cls):
3950 # TODO: dan: rename column to source_repo_id
3965 # TODO: dan: rename column to source_repo_id
3951 return Column(
3966 return Column(
3952 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3967 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3953 nullable=False)
3968 nullable=False)
3954
3969
3955 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3970 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3956
3971
3957 @hybrid_property
3972 @hybrid_property
3958 def source_ref(self):
3973 def source_ref(self):
3959 return self._source_ref
3974 return self._source_ref
3960
3975
3961 @source_ref.setter
3976 @source_ref.setter
3962 def source_ref(self, val):
3977 def source_ref(self, val):
3963 parts = (val or '').split(':')
3978 parts = (val or '').split(':')
3964 if len(parts) != 3:
3979 if len(parts) != 3:
3965 raise ValueError(
3980 raise ValueError(
3966 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3981 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3967 self._source_ref = safe_unicode(val)
3982 self._source_ref = safe_unicode(val)
3968
3983
3969 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3984 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3970
3985
3971 @hybrid_property
3986 @hybrid_property
3972 def target_ref(self):
3987 def target_ref(self):
3973 return self._target_ref
3988 return self._target_ref
3974
3989
3975 @target_ref.setter
3990 @target_ref.setter
3976 def target_ref(self, val):
3991 def target_ref(self, val):
3977 parts = (val or '').split(':')
3992 parts = (val or '').split(':')
3978 if len(parts) != 3:
3993 if len(parts) != 3:
3979 raise ValueError(
3994 raise ValueError(
3980 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3981 self._target_ref = safe_unicode(val)
3996 self._target_ref = safe_unicode(val)
3982
3997
3983 @declared_attr
3998 @declared_attr
3984 def target_repo_id(cls):
3999 def target_repo_id(cls):
3985 # TODO: dan: rename column to target_repo_id
4000 # TODO: dan: rename column to target_repo_id
3986 return Column(
4001 return Column(
3987 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4002 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3988 nullable=False)
4003 nullable=False)
3989
4004
3990 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4005 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3991
4006
3992 # TODO: dan: rename column to last_merge_source_rev
4007 # TODO: dan: rename column to last_merge_source_rev
3993 _last_merge_source_rev = Column(
4008 _last_merge_source_rev = Column(
3994 'last_merge_org_rev', String(40), nullable=True)
4009 'last_merge_org_rev', String(40), nullable=True)
3995 # TODO: dan: rename column to last_merge_target_rev
4010 # TODO: dan: rename column to last_merge_target_rev
3996 _last_merge_target_rev = Column(
4011 _last_merge_target_rev = Column(
3997 'last_merge_other_rev', String(40), nullable=True)
4012 'last_merge_other_rev', String(40), nullable=True)
3998 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4013 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3999 merge_rev = Column('merge_rev', String(40), nullable=True)
4014 merge_rev = Column('merge_rev', String(40), nullable=True)
4000
4015
4001 reviewer_data = Column(
4016 reviewer_data = Column(
4002 'reviewer_data_json', MutationObj.as_mutable(
4017 'reviewer_data_json', MutationObj.as_mutable(
4003 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4018 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4004
4019
4005 @property
4020 @property
4006 def reviewer_data_json(self):
4021 def reviewer_data_json(self):
4007 return json.dumps(self.reviewer_data)
4022 return json.dumps(self.reviewer_data)
4008
4023
4009 @property
4024 @property
4010 def work_in_progress(self):
4025 def work_in_progress(self):
4011 """checks if pull request is work in progress by checking the title"""
4026 """checks if pull request is work in progress by checking the title"""
4012 title = self.title.upper()
4027 title = self.title.upper()
4013 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4028 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4014 return True
4029 return True
4015 return False
4030 return False
4016
4031
4017 @hybrid_property
4032 @hybrid_property
4018 def description_safe(self):
4033 def description_safe(self):
4019 from rhodecode.lib import helpers as h
4034 from rhodecode.lib import helpers as h
4020 return h.escape(self.description)
4035 return h.escape(self.description)
4021
4036
4022 @hybrid_property
4037 @hybrid_property
4023 def revisions(self):
4038 def revisions(self):
4024 return self._revisions.split(':') if self._revisions else []
4039 return self._revisions.split(':') if self._revisions else []
4025
4040
4026 @revisions.setter
4041 @revisions.setter
4027 def revisions(self, val):
4042 def revisions(self, val):
4028 self._revisions = u':'.join(val)
4043 self._revisions = u':'.join(val)
4029
4044
4030 @hybrid_property
4045 @hybrid_property
4031 def last_merge_status(self):
4046 def last_merge_status(self):
4032 return safe_int(self._last_merge_status)
4047 return safe_int(self._last_merge_status)
4033
4048
4034 @last_merge_status.setter
4049 @last_merge_status.setter
4035 def last_merge_status(self, val):
4050 def last_merge_status(self, val):
4036 self._last_merge_status = val
4051 self._last_merge_status = val
4037
4052
4038 @declared_attr
4053 @declared_attr
4039 def author(cls):
4054 def author(cls):
4040 return relationship('User', lazy='joined')
4055 return relationship('User', lazy='joined')
4041
4056
4042 @declared_attr
4057 @declared_attr
4043 def source_repo(cls):
4058 def source_repo(cls):
4044 return relationship(
4059 return relationship(
4045 'Repository',
4060 'Repository',
4046 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4061 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4047
4062
4048 @property
4063 @property
4049 def source_ref_parts(self):
4064 def source_ref_parts(self):
4050 return self.unicode_to_reference(self.source_ref)
4065 return self.unicode_to_reference(self.source_ref)
4051
4066
4052 @declared_attr
4067 @declared_attr
4053 def target_repo(cls):
4068 def target_repo(cls):
4054 return relationship(
4069 return relationship(
4055 'Repository',
4070 'Repository',
4056 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4071 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4057
4072
4058 @property
4073 @property
4059 def target_ref_parts(self):
4074 def target_ref_parts(self):
4060 return self.unicode_to_reference(self.target_ref)
4075 return self.unicode_to_reference(self.target_ref)
4061
4076
4062 @property
4077 @property
4063 def shadow_merge_ref(self):
4078 def shadow_merge_ref(self):
4064 return self.unicode_to_reference(self._shadow_merge_ref)
4079 return self.unicode_to_reference(self._shadow_merge_ref)
4065
4080
4066 @shadow_merge_ref.setter
4081 @shadow_merge_ref.setter
4067 def shadow_merge_ref(self, ref):
4082 def shadow_merge_ref(self, ref):
4068 self._shadow_merge_ref = self.reference_to_unicode(ref)
4083 self._shadow_merge_ref = self.reference_to_unicode(ref)
4069
4084
4070 @staticmethod
4085 @staticmethod
4071 def unicode_to_reference(raw):
4086 def unicode_to_reference(raw):
4072 """
4087 """
4073 Convert a unicode (or string) to a reference object.
4088 Convert a unicode (or string) to a reference object.
4074 If unicode evaluates to False it returns None.
4089 If unicode evaluates to False it returns None.
4075 """
4090 """
4076 if raw:
4091 if raw:
4077 refs = raw.split(':')
4092 refs = raw.split(':')
4078 return Reference(*refs)
4093 return Reference(*refs)
4079 else:
4094 else:
4080 return None
4095 return None
4081
4096
4082 @staticmethod
4097 @staticmethod
4083 def reference_to_unicode(ref):
4098 def reference_to_unicode(ref):
4084 """
4099 """
4085 Convert a reference object to unicode.
4100 Convert a reference object to unicode.
4086 If reference is None it returns None.
4101 If reference is None it returns None.
4087 """
4102 """
4088 if ref:
4103 if ref:
4089 return u':'.join(ref)
4104 return u':'.join(ref)
4090 else:
4105 else:
4091 return None
4106 return None
4092
4107
4093 def get_api_data(self, with_merge_state=True):
4108 def get_api_data(self, with_merge_state=True):
4094 from rhodecode.model.pull_request import PullRequestModel
4109 from rhodecode.model.pull_request import PullRequestModel
4095
4110
4096 pull_request = self
4111 pull_request = self
4097 if with_merge_state:
4112 if with_merge_state:
4098 merge_status = PullRequestModel().merge_status(pull_request)
4113 merge_status = PullRequestModel().merge_status(pull_request)
4099 merge_state = {
4114 merge_state = {
4100 'status': merge_status[0],
4115 'status': merge_status[0],
4101 'message': safe_unicode(merge_status[1]),
4116 'message': safe_unicode(merge_status[1]),
4102 }
4117 }
4103 else:
4118 else:
4104 merge_state = {'status': 'not_available',
4119 merge_state = {'status': 'not_available',
4105 'message': 'not_available'}
4120 'message': 'not_available'}
4106
4121
4107 merge_data = {
4122 merge_data = {
4108 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4123 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4109 'reference': (
4124 'reference': (
4110 pull_request.shadow_merge_ref._asdict()
4125 pull_request.shadow_merge_ref._asdict()
4111 if pull_request.shadow_merge_ref else None),
4126 if pull_request.shadow_merge_ref else None),
4112 }
4127 }
4113
4128
4114 data = {
4129 data = {
4115 'pull_request_id': pull_request.pull_request_id,
4130 'pull_request_id': pull_request.pull_request_id,
4116 'url': PullRequestModel().get_url(pull_request),
4131 'url': PullRequestModel().get_url(pull_request),
4117 'title': pull_request.title,
4132 'title': pull_request.title,
4118 'description': pull_request.description,
4133 'description': pull_request.description,
4119 'status': pull_request.status,
4134 'status': pull_request.status,
4120 'state': pull_request.pull_request_state,
4135 'state': pull_request.pull_request_state,
4121 'created_on': pull_request.created_on,
4136 'created_on': pull_request.created_on,
4122 'updated_on': pull_request.updated_on,
4137 'updated_on': pull_request.updated_on,
4123 'commit_ids': pull_request.revisions,
4138 'commit_ids': pull_request.revisions,
4124 'review_status': pull_request.calculated_review_status(),
4139 'review_status': pull_request.calculated_review_status(),
4125 'mergeable': merge_state,
4140 'mergeable': merge_state,
4126 'source': {
4141 'source': {
4127 'clone_url': pull_request.source_repo.clone_url(),
4142 'clone_url': pull_request.source_repo.clone_url(),
4128 'repository': pull_request.source_repo.repo_name,
4143 'repository': pull_request.source_repo.repo_name,
4129 'reference': {
4144 'reference': {
4130 'name': pull_request.source_ref_parts.name,
4145 'name': pull_request.source_ref_parts.name,
4131 'type': pull_request.source_ref_parts.type,
4146 'type': pull_request.source_ref_parts.type,
4132 'commit_id': pull_request.source_ref_parts.commit_id,
4147 'commit_id': pull_request.source_ref_parts.commit_id,
4133 },
4148 },
4134 },
4149 },
4135 'target': {
4150 'target': {
4136 'clone_url': pull_request.target_repo.clone_url(),
4151 'clone_url': pull_request.target_repo.clone_url(),
4137 'repository': pull_request.target_repo.repo_name,
4152 'repository': pull_request.target_repo.repo_name,
4138 'reference': {
4153 'reference': {
4139 'name': pull_request.target_ref_parts.name,
4154 'name': pull_request.target_ref_parts.name,
4140 'type': pull_request.target_ref_parts.type,
4155 'type': pull_request.target_ref_parts.type,
4141 'commit_id': pull_request.target_ref_parts.commit_id,
4156 'commit_id': pull_request.target_ref_parts.commit_id,
4142 },
4157 },
4143 },
4158 },
4144 'merge': merge_data,
4159 'merge': merge_data,
4145 'author': pull_request.author.get_api_data(include_secrets=False,
4160 'author': pull_request.author.get_api_data(include_secrets=False,
4146 details='basic'),
4161 details='basic'),
4147 'reviewers': [
4162 'reviewers': [
4148 {
4163 {
4149 'user': reviewer.get_api_data(include_secrets=False,
4164 'user': reviewer.get_api_data(include_secrets=False,
4150 details='basic'),
4165 details='basic'),
4151 'reasons': reasons,
4166 'reasons': reasons,
4152 'review_status': st[0][1].status if st else 'not_reviewed',
4167 'review_status': st[0][1].status if st else 'not_reviewed',
4153 }
4168 }
4154 for obj, reviewer, reasons, mandatory, st in
4169 for obj, reviewer, reasons, mandatory, st in
4155 pull_request.reviewers_statuses()
4170 pull_request.reviewers_statuses()
4156 ]
4171 ]
4157 }
4172 }
4158
4173
4159 return data
4174 return data
4160
4175
4161 def set_state(self, pull_request_state, final_state=None):
4176 def set_state(self, pull_request_state, final_state=None):
4162 """
4177 """
4163 # goes from initial state to updating to initial state.
4178 # goes from initial state to updating to initial state.
4164 # initial state can be changed by specifying back_state=
4179 # initial state can be changed by specifying back_state=
4165 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4180 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4166 pull_request.merge()
4181 pull_request.merge()
4167
4182
4168 :param pull_request_state:
4183 :param pull_request_state:
4169 :param final_state:
4184 :param final_state:
4170
4185
4171 """
4186 """
4172
4187
4173 return _SetState(self, pull_request_state, back_state=final_state)
4188 return _SetState(self, pull_request_state, back_state=final_state)
4174
4189
4175
4190
4176 class PullRequest(Base, _PullRequestBase):
4191 class PullRequest(Base, _PullRequestBase):
4177 __tablename__ = 'pull_requests'
4192 __tablename__ = 'pull_requests'
4178 __table_args__ = (
4193 __table_args__ = (
4179 base_table_args,
4194 base_table_args,
4180 )
4195 )
4181
4196
4182 pull_request_id = Column(
4197 pull_request_id = Column(
4183 'pull_request_id', Integer(), nullable=False, primary_key=True)
4198 'pull_request_id', Integer(), nullable=False, primary_key=True)
4184
4199
4185 def __repr__(self):
4200 def __repr__(self):
4186 if self.pull_request_id:
4201 if self.pull_request_id:
4187 return '<DB:PullRequest #%s>' % self.pull_request_id
4202 return '<DB:PullRequest #%s>' % self.pull_request_id
4188 else:
4203 else:
4189 return '<DB:PullRequest at %#x>' % id(self)
4204 return '<DB:PullRequest at %#x>' % id(self)
4190
4205
4191 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4206 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4192 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4207 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4193 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4208 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4194 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4209 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4195 lazy='dynamic')
4210 lazy='dynamic')
4196
4211
4197 @classmethod
4212 @classmethod
4198 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4213 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4199 internal_methods=None):
4214 internal_methods=None):
4200
4215
4201 class PullRequestDisplay(object):
4216 class PullRequestDisplay(object):
4202 """
4217 """
4203 Special object wrapper for showing PullRequest data via Versions
4218 Special object wrapper for showing PullRequest data via Versions
4204 It mimics PR object as close as possible. This is read only object
4219 It mimics PR object as close as possible. This is read only object
4205 just for display
4220 just for display
4206 """
4221 """
4207
4222
4208 def __init__(self, attrs, internal=None):
4223 def __init__(self, attrs, internal=None):
4209 self.attrs = attrs
4224 self.attrs = attrs
4210 # internal have priority over the given ones via attrs
4225 # internal have priority over the given ones via attrs
4211 self.internal = internal or ['versions']
4226 self.internal = internal or ['versions']
4212
4227
4213 def __getattr__(self, item):
4228 def __getattr__(self, item):
4214 if item in self.internal:
4229 if item in self.internal:
4215 return getattr(self, item)
4230 return getattr(self, item)
4216 try:
4231 try:
4217 return self.attrs[item]
4232 return self.attrs[item]
4218 except KeyError:
4233 except KeyError:
4219 raise AttributeError(
4234 raise AttributeError(
4220 '%s object has no attribute %s' % (self, item))
4235 '%s object has no attribute %s' % (self, item))
4221
4236
4222 def __repr__(self):
4237 def __repr__(self):
4223 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4238 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4224
4239
4225 def versions(self):
4240 def versions(self):
4226 return pull_request_obj.versions.order_by(
4241 return pull_request_obj.versions.order_by(
4227 PullRequestVersion.pull_request_version_id).all()
4242 PullRequestVersion.pull_request_version_id).all()
4228
4243
4229 def is_closed(self):
4244 def is_closed(self):
4230 return pull_request_obj.is_closed()
4245 return pull_request_obj.is_closed()
4231
4246
4232 def is_state_changing(self):
4247 def is_state_changing(self):
4233 return pull_request_obj.is_state_changing()
4248 return pull_request_obj.is_state_changing()
4234
4249
4235 @property
4250 @property
4236 def pull_request_version_id(self):
4251 def pull_request_version_id(self):
4237 return getattr(pull_request_obj, 'pull_request_version_id', None)
4252 return getattr(pull_request_obj, 'pull_request_version_id', None)
4238
4253
4239 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4254 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4240
4255
4241 attrs.author = StrictAttributeDict(
4256 attrs.author = StrictAttributeDict(
4242 pull_request_obj.author.get_api_data())
4257 pull_request_obj.author.get_api_data())
4243 if pull_request_obj.target_repo:
4258 if pull_request_obj.target_repo:
4244 attrs.target_repo = StrictAttributeDict(
4259 attrs.target_repo = StrictAttributeDict(
4245 pull_request_obj.target_repo.get_api_data())
4260 pull_request_obj.target_repo.get_api_data())
4246 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4261 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4247
4262
4248 if pull_request_obj.source_repo:
4263 if pull_request_obj.source_repo:
4249 attrs.source_repo = StrictAttributeDict(
4264 attrs.source_repo = StrictAttributeDict(
4250 pull_request_obj.source_repo.get_api_data())
4265 pull_request_obj.source_repo.get_api_data())
4251 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4266 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4252
4267
4253 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4268 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4254 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4269 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4255 attrs.revisions = pull_request_obj.revisions
4270 attrs.revisions = pull_request_obj.revisions
4256
4271
4257 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4272 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4258 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4273 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4259 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4274 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4260
4275
4261 return PullRequestDisplay(attrs, internal=internal_methods)
4276 return PullRequestDisplay(attrs, internal=internal_methods)
4262
4277
4263 def is_closed(self):
4278 def is_closed(self):
4264 return self.status == self.STATUS_CLOSED
4279 return self.status == self.STATUS_CLOSED
4265
4280
4266 def is_state_changing(self):
4281 def is_state_changing(self):
4267 return self.pull_request_state != PullRequest.STATE_CREATED
4282 return self.pull_request_state != PullRequest.STATE_CREATED
4268
4283
4269 def __json__(self):
4284 def __json__(self):
4270 return {
4285 return {
4271 'revisions': self.revisions,
4286 'revisions': self.revisions,
4272 }
4287 }
4273
4288
4274 def calculated_review_status(self):
4289 def calculated_review_status(self):
4275 from rhodecode.model.changeset_status import ChangesetStatusModel
4290 from rhodecode.model.changeset_status import ChangesetStatusModel
4276 return ChangesetStatusModel().calculated_review_status(self)
4291 return ChangesetStatusModel().calculated_review_status(self)
4277
4292
4278 def reviewers_statuses(self):
4293 def reviewers_statuses(self):
4279 from rhodecode.model.changeset_status import ChangesetStatusModel
4294 from rhodecode.model.changeset_status import ChangesetStatusModel
4280 return ChangesetStatusModel().reviewers_statuses(self)
4295 return ChangesetStatusModel().reviewers_statuses(self)
4281
4296
4282 @property
4297 @property
4283 def workspace_id(self):
4298 def workspace_id(self):
4284 from rhodecode.model.pull_request import PullRequestModel
4299 from rhodecode.model.pull_request import PullRequestModel
4285 return PullRequestModel()._workspace_id(self)
4300 return PullRequestModel()._workspace_id(self)
4286
4301
4287 def get_shadow_repo(self):
4302 def get_shadow_repo(self):
4288 workspace_id = self.workspace_id
4303 workspace_id = self.workspace_id
4289 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4304 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4290 if os.path.isdir(shadow_repository_path):
4305 if os.path.isdir(shadow_repository_path):
4291 vcs_obj = self.target_repo.scm_instance()
4306 vcs_obj = self.target_repo.scm_instance()
4292 return vcs_obj.get_shadow_instance(shadow_repository_path)
4307 return vcs_obj.get_shadow_instance(shadow_repository_path)
4293
4308
4294
4309
4295 class PullRequestVersion(Base, _PullRequestBase):
4310 class PullRequestVersion(Base, _PullRequestBase):
4296 __tablename__ = 'pull_request_versions'
4311 __tablename__ = 'pull_request_versions'
4297 __table_args__ = (
4312 __table_args__ = (
4298 base_table_args,
4313 base_table_args,
4299 )
4314 )
4300
4315
4301 pull_request_version_id = Column(
4316 pull_request_version_id = Column(
4302 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4317 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4303 pull_request_id = Column(
4318 pull_request_id = Column(
4304 'pull_request_id', Integer(),
4319 'pull_request_id', Integer(),
4305 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4320 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4306 pull_request = relationship('PullRequest')
4321 pull_request = relationship('PullRequest')
4307
4322
4308 def __repr__(self):
4323 def __repr__(self):
4309 if self.pull_request_version_id:
4324 if self.pull_request_version_id:
4310 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4325 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4311 else:
4326 else:
4312 return '<DB:PullRequestVersion at %#x>' % id(self)
4327 return '<DB:PullRequestVersion at %#x>' % id(self)
4313
4328
4314 @property
4329 @property
4315 def reviewers(self):
4330 def reviewers(self):
4316 return self.pull_request.reviewers
4331 return self.pull_request.reviewers
4317
4332
4318 @property
4333 @property
4319 def versions(self):
4334 def versions(self):
4320 return self.pull_request.versions
4335 return self.pull_request.versions
4321
4336
4322 def is_closed(self):
4337 def is_closed(self):
4323 # calculate from original
4338 # calculate from original
4324 return self.pull_request.status == self.STATUS_CLOSED
4339 return self.pull_request.status == self.STATUS_CLOSED
4325
4340
4326 def is_state_changing(self):
4341 def is_state_changing(self):
4327 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4342 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4328
4343
4329 def calculated_review_status(self):
4344 def calculated_review_status(self):
4330 return self.pull_request.calculated_review_status()
4345 return self.pull_request.calculated_review_status()
4331
4346
4332 def reviewers_statuses(self):
4347 def reviewers_statuses(self):
4333 return self.pull_request.reviewers_statuses()
4348 return self.pull_request.reviewers_statuses()
4334
4349
4335
4350
4336 class PullRequestReviewers(Base, BaseModel):
4351 class PullRequestReviewers(Base, BaseModel):
4337 __tablename__ = 'pull_request_reviewers'
4352 __tablename__ = 'pull_request_reviewers'
4338 __table_args__ = (
4353 __table_args__ = (
4339 base_table_args,
4354 base_table_args,
4340 )
4355 )
4341
4356
4342 @hybrid_property
4357 @hybrid_property
4343 def reasons(self):
4358 def reasons(self):
4344 if not self._reasons:
4359 if not self._reasons:
4345 return []
4360 return []
4346 return self._reasons
4361 return self._reasons
4347
4362
4348 @reasons.setter
4363 @reasons.setter
4349 def reasons(self, val):
4364 def reasons(self, val):
4350 val = val or []
4365 val = val or []
4351 if any(not isinstance(x, compat.string_types) for x in val):
4366 if any(not isinstance(x, compat.string_types) for x in val):
4352 raise Exception('invalid reasons type, must be list of strings')
4367 raise Exception('invalid reasons type, must be list of strings')
4353 self._reasons = val
4368 self._reasons = val
4354
4369
4355 pull_requests_reviewers_id = Column(
4370 pull_requests_reviewers_id = Column(
4356 'pull_requests_reviewers_id', Integer(), nullable=False,
4371 'pull_requests_reviewers_id', Integer(), nullable=False,
4357 primary_key=True)
4372 primary_key=True)
4358 pull_request_id = Column(
4373 pull_request_id = Column(
4359 "pull_request_id", Integer(),
4374 "pull_request_id", Integer(),
4360 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4375 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4361 user_id = Column(
4376 user_id = Column(
4362 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4377 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4363 _reasons = Column(
4378 _reasons = Column(
4364 'reason', MutationList.as_mutable(
4379 'reason', MutationList.as_mutable(
4365 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4380 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4366
4381
4367 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4382 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4368 user = relationship('User')
4383 user = relationship('User')
4369 pull_request = relationship('PullRequest')
4384 pull_request = relationship('PullRequest')
4370
4385
4371 rule_data = Column(
4386 rule_data = Column(
4372 'rule_data_json',
4387 'rule_data_json',
4373 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4388 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4374
4389
4375 def rule_user_group_data(self):
4390 def rule_user_group_data(self):
4376 """
4391 """
4377 Returns the voting user group rule data for this reviewer
4392 Returns the voting user group rule data for this reviewer
4378 """
4393 """
4379
4394
4380 if self.rule_data and 'vote_rule' in self.rule_data:
4395 if self.rule_data and 'vote_rule' in self.rule_data:
4381 user_group_data = {}
4396 user_group_data = {}
4382 if 'rule_user_group_entry_id' in self.rule_data:
4397 if 'rule_user_group_entry_id' in self.rule_data:
4383 # means a group with voting rules !
4398 # means a group with voting rules !
4384 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4399 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4385 user_group_data['name'] = self.rule_data['rule_name']
4400 user_group_data['name'] = self.rule_data['rule_name']
4386 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4401 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4387
4402
4388 return user_group_data
4403 return user_group_data
4389
4404
4390 def __unicode__(self):
4405 def __unicode__(self):
4391 return u"<%s('id:%s')>" % (self.__class__.__name__,
4406 return u"<%s('id:%s')>" % (self.__class__.__name__,
4392 self.pull_requests_reviewers_id)
4407 self.pull_requests_reviewers_id)
4393
4408
4394
4409
4395 class Notification(Base, BaseModel):
4410 class Notification(Base, BaseModel):
4396 __tablename__ = 'notifications'
4411 __tablename__ = 'notifications'
4397 __table_args__ = (
4412 __table_args__ = (
4398 Index('notification_type_idx', 'type'),
4413 Index('notification_type_idx', 'type'),
4399 base_table_args,
4414 base_table_args,
4400 )
4415 )
4401
4416
4402 TYPE_CHANGESET_COMMENT = u'cs_comment'
4417 TYPE_CHANGESET_COMMENT = u'cs_comment'
4403 TYPE_MESSAGE = u'message'
4418 TYPE_MESSAGE = u'message'
4404 TYPE_MENTION = u'mention'
4419 TYPE_MENTION = u'mention'
4405 TYPE_REGISTRATION = u'registration'
4420 TYPE_REGISTRATION = u'registration'
4406 TYPE_PULL_REQUEST = u'pull_request'
4421 TYPE_PULL_REQUEST = u'pull_request'
4407 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4422 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4408 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4423 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4409
4424
4410 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4425 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4411 subject = Column('subject', Unicode(512), nullable=True)
4426 subject = Column('subject', Unicode(512), nullable=True)
4412 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4427 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4413 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4428 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4414 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4429 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4415 type_ = Column('type', Unicode(255))
4430 type_ = Column('type', Unicode(255))
4416
4431
4417 created_by_user = relationship('User')
4432 created_by_user = relationship('User')
4418 notifications_to_users = relationship('UserNotification', lazy='joined',
4433 notifications_to_users = relationship('UserNotification', lazy='joined',
4419 cascade="all, delete-orphan")
4434 cascade="all, delete-orphan")
4420
4435
4421 @property
4436 @property
4422 def recipients(self):
4437 def recipients(self):
4423 return [x.user for x in UserNotification.query()\
4438 return [x.user for x in UserNotification.query()\
4424 .filter(UserNotification.notification == self)\
4439 .filter(UserNotification.notification == self)\
4425 .order_by(UserNotification.user_id.asc()).all()]
4440 .order_by(UserNotification.user_id.asc()).all()]
4426
4441
4427 @classmethod
4442 @classmethod
4428 def create(cls, created_by, subject, body, recipients, type_=None):
4443 def create(cls, created_by, subject, body, recipients, type_=None):
4429 if type_ is None:
4444 if type_ is None:
4430 type_ = Notification.TYPE_MESSAGE
4445 type_ = Notification.TYPE_MESSAGE
4431
4446
4432 notification = cls()
4447 notification = cls()
4433 notification.created_by_user = created_by
4448 notification.created_by_user = created_by
4434 notification.subject = subject
4449 notification.subject = subject
4435 notification.body = body
4450 notification.body = body
4436 notification.type_ = type_
4451 notification.type_ = type_
4437 notification.created_on = datetime.datetime.now()
4452 notification.created_on = datetime.datetime.now()
4438
4453
4439 # For each recipient link the created notification to his account
4454 # For each recipient link the created notification to his account
4440 for u in recipients:
4455 for u in recipients:
4441 assoc = UserNotification()
4456 assoc = UserNotification()
4442 assoc.user_id = u.user_id
4457 assoc.user_id = u.user_id
4443 assoc.notification = notification
4458 assoc.notification = notification
4444
4459
4445 # if created_by is inside recipients mark his notification
4460 # if created_by is inside recipients mark his notification
4446 # as read
4461 # as read
4447 if u.user_id == created_by.user_id:
4462 if u.user_id == created_by.user_id:
4448 assoc.read = True
4463 assoc.read = True
4449 Session().add(assoc)
4464 Session().add(assoc)
4450
4465
4451 Session().add(notification)
4466 Session().add(notification)
4452
4467
4453 return notification
4468 return notification
4454
4469
4455
4470
4456 class UserNotification(Base, BaseModel):
4471 class UserNotification(Base, BaseModel):
4457 __tablename__ = 'user_to_notification'
4472 __tablename__ = 'user_to_notification'
4458 __table_args__ = (
4473 __table_args__ = (
4459 UniqueConstraint('user_id', 'notification_id'),
4474 UniqueConstraint('user_id', 'notification_id'),
4460 base_table_args
4475 base_table_args
4461 )
4476 )
4462
4477
4463 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4478 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4464 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4479 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4465 read = Column('read', Boolean, default=False)
4480 read = Column('read', Boolean, default=False)
4466 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4481 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4467
4482
4468 user = relationship('User', lazy="joined")
4483 user = relationship('User', lazy="joined")
4469 notification = relationship('Notification', lazy="joined",
4484 notification = relationship('Notification', lazy="joined",
4470 order_by=lambda: Notification.created_on.desc(),)
4485 order_by=lambda: Notification.created_on.desc(),)
4471
4486
4472 def mark_as_read(self):
4487 def mark_as_read(self):
4473 self.read = True
4488 self.read = True
4474 Session().add(self)
4489 Session().add(self)
4475
4490
4476
4491
4477 class Gist(Base, BaseModel):
4492 class Gist(Base, BaseModel):
4478 __tablename__ = 'gists'
4493 __tablename__ = 'gists'
4479 __table_args__ = (
4494 __table_args__ = (
4480 Index('g_gist_access_id_idx', 'gist_access_id'),
4495 Index('g_gist_access_id_idx', 'gist_access_id'),
4481 Index('g_created_on_idx', 'created_on'),
4496 Index('g_created_on_idx', 'created_on'),
4482 base_table_args
4497 base_table_args
4483 )
4498 )
4484
4499
4485 GIST_PUBLIC = u'public'
4500 GIST_PUBLIC = u'public'
4486 GIST_PRIVATE = u'private'
4501 GIST_PRIVATE = u'private'
4487 DEFAULT_FILENAME = u'gistfile1.txt'
4502 DEFAULT_FILENAME = u'gistfile1.txt'
4488
4503
4489 ACL_LEVEL_PUBLIC = u'acl_public'
4504 ACL_LEVEL_PUBLIC = u'acl_public'
4490 ACL_LEVEL_PRIVATE = u'acl_private'
4505 ACL_LEVEL_PRIVATE = u'acl_private'
4491
4506
4492 gist_id = Column('gist_id', Integer(), primary_key=True)
4507 gist_id = Column('gist_id', Integer(), primary_key=True)
4493 gist_access_id = Column('gist_access_id', Unicode(250))
4508 gist_access_id = Column('gist_access_id', Unicode(250))
4494 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4509 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4495 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4510 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4496 gist_expires = Column('gist_expires', Float(53), nullable=False)
4511 gist_expires = Column('gist_expires', Float(53), nullable=False)
4497 gist_type = Column('gist_type', Unicode(128), nullable=False)
4512 gist_type = Column('gist_type', Unicode(128), nullable=False)
4498 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4513 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4499 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4514 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4500 acl_level = Column('acl_level', Unicode(128), nullable=True)
4515 acl_level = Column('acl_level', Unicode(128), nullable=True)
4501
4516
4502 owner = relationship('User')
4517 owner = relationship('User')
4503
4518
4504 def __repr__(self):
4519 def __repr__(self):
4505 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4520 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4506
4521
4507 @hybrid_property
4522 @hybrid_property
4508 def description_safe(self):
4523 def description_safe(self):
4509 from rhodecode.lib import helpers as h
4524 from rhodecode.lib import helpers as h
4510 return h.escape(self.gist_description)
4525 return h.escape(self.gist_description)
4511
4526
4512 @classmethod
4527 @classmethod
4513 def get_or_404(cls, id_):
4528 def get_or_404(cls, id_):
4514 from pyramid.httpexceptions import HTTPNotFound
4529 from pyramid.httpexceptions import HTTPNotFound
4515
4530
4516 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4531 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4517 if not res:
4532 if not res:
4518 raise HTTPNotFound()
4533 raise HTTPNotFound()
4519 return res
4534 return res
4520
4535
4521 @classmethod
4536 @classmethod
4522 def get_by_access_id(cls, gist_access_id):
4537 def get_by_access_id(cls, gist_access_id):
4523 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4538 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4524
4539
4525 def gist_url(self):
4540 def gist_url(self):
4526 from rhodecode.model.gist import GistModel
4541 from rhodecode.model.gist import GistModel
4527 return GistModel().get_url(self)
4542 return GistModel().get_url(self)
4528
4543
4529 @classmethod
4544 @classmethod
4530 def base_path(cls):
4545 def base_path(cls):
4531 """
4546 """
4532 Returns base path when all gists are stored
4547 Returns base path when all gists are stored
4533
4548
4534 :param cls:
4549 :param cls:
4535 """
4550 """
4536 from rhodecode.model.gist import GIST_STORE_LOC
4551 from rhodecode.model.gist import GIST_STORE_LOC
4537 q = Session().query(RhodeCodeUi)\
4552 q = Session().query(RhodeCodeUi)\
4538 .filter(RhodeCodeUi.ui_key == URL_SEP)
4553 .filter(RhodeCodeUi.ui_key == URL_SEP)
4539 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4554 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4540 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4555 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4541
4556
4542 def get_api_data(self):
4557 def get_api_data(self):
4543 """
4558 """
4544 Common function for generating gist related data for API
4559 Common function for generating gist related data for API
4545 """
4560 """
4546 gist = self
4561 gist = self
4547 data = {
4562 data = {
4548 'gist_id': gist.gist_id,
4563 'gist_id': gist.gist_id,
4549 'type': gist.gist_type,
4564 'type': gist.gist_type,
4550 'access_id': gist.gist_access_id,
4565 'access_id': gist.gist_access_id,
4551 'description': gist.gist_description,
4566 'description': gist.gist_description,
4552 'url': gist.gist_url(),
4567 'url': gist.gist_url(),
4553 'expires': gist.gist_expires,
4568 'expires': gist.gist_expires,
4554 'created_on': gist.created_on,
4569 'created_on': gist.created_on,
4555 'modified_at': gist.modified_at,
4570 'modified_at': gist.modified_at,
4556 'content': None,
4571 'content': None,
4557 'acl_level': gist.acl_level,
4572 'acl_level': gist.acl_level,
4558 }
4573 }
4559 return data
4574 return data
4560
4575
4561 def __json__(self):
4576 def __json__(self):
4562 data = dict(
4577 data = dict(
4563 )
4578 )
4564 data.update(self.get_api_data())
4579 data.update(self.get_api_data())
4565 return data
4580 return data
4566 # SCM functions
4581 # SCM functions
4567
4582
4568 def scm_instance(self, **kwargs):
4583 def scm_instance(self, **kwargs):
4569 """
4584 """
4570 Get an instance of VCS Repository
4585 Get an instance of VCS Repository
4571
4586
4572 :param kwargs:
4587 :param kwargs:
4573 """
4588 """
4574 from rhodecode.model.gist import GistModel
4589 from rhodecode.model.gist import GistModel
4575 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4590 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4576 return get_vcs_instance(
4591 return get_vcs_instance(
4577 repo_path=safe_str(full_repo_path), create=False,
4592 repo_path=safe_str(full_repo_path), create=False,
4578 _vcs_alias=GistModel.vcs_backend)
4593 _vcs_alias=GistModel.vcs_backend)
4579
4594
4580
4595
4581 class ExternalIdentity(Base, BaseModel):
4596 class ExternalIdentity(Base, BaseModel):
4582 __tablename__ = 'external_identities'
4597 __tablename__ = 'external_identities'
4583 __table_args__ = (
4598 __table_args__ = (
4584 Index('local_user_id_idx', 'local_user_id'),
4599 Index('local_user_id_idx', 'local_user_id'),
4585 Index('external_id_idx', 'external_id'),
4600 Index('external_id_idx', 'external_id'),
4586 base_table_args
4601 base_table_args
4587 )
4602 )
4588
4603
4589 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4604 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4590 external_username = Column('external_username', Unicode(1024), default=u'')
4605 external_username = Column('external_username', Unicode(1024), default=u'')
4591 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4606 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4592 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4607 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4593 access_token = Column('access_token', String(1024), default=u'')
4608 access_token = Column('access_token', String(1024), default=u'')
4594 alt_token = Column('alt_token', String(1024), default=u'')
4609 alt_token = Column('alt_token', String(1024), default=u'')
4595 token_secret = Column('token_secret', String(1024), default=u'')
4610 token_secret = Column('token_secret', String(1024), default=u'')
4596
4611
4597 @classmethod
4612 @classmethod
4598 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4613 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4599 """
4614 """
4600 Returns ExternalIdentity instance based on search params
4615 Returns ExternalIdentity instance based on search params
4601
4616
4602 :param external_id:
4617 :param external_id:
4603 :param provider_name:
4618 :param provider_name:
4604 :return: ExternalIdentity
4619 :return: ExternalIdentity
4605 """
4620 """
4606 query = cls.query()
4621 query = cls.query()
4607 query = query.filter(cls.external_id == external_id)
4622 query = query.filter(cls.external_id == external_id)
4608 query = query.filter(cls.provider_name == provider_name)
4623 query = query.filter(cls.provider_name == provider_name)
4609 if local_user_id:
4624 if local_user_id:
4610 query = query.filter(cls.local_user_id == local_user_id)
4625 query = query.filter(cls.local_user_id == local_user_id)
4611 return query.first()
4626 return query.first()
4612
4627
4613 @classmethod
4628 @classmethod
4614 def user_by_external_id_and_provider(cls, external_id, provider_name):
4629 def user_by_external_id_and_provider(cls, external_id, provider_name):
4615 """
4630 """
4616 Returns User instance based on search params
4631 Returns User instance based on search params
4617
4632
4618 :param external_id:
4633 :param external_id:
4619 :param provider_name:
4634 :param provider_name:
4620 :return: User
4635 :return: User
4621 """
4636 """
4622 query = User.query()
4637 query = User.query()
4623 query = query.filter(cls.external_id == external_id)
4638 query = query.filter(cls.external_id == external_id)
4624 query = query.filter(cls.provider_name == provider_name)
4639 query = query.filter(cls.provider_name == provider_name)
4625 query = query.filter(User.user_id == cls.local_user_id)
4640 query = query.filter(User.user_id == cls.local_user_id)
4626 return query.first()
4641 return query.first()
4627
4642
4628 @classmethod
4643 @classmethod
4629 def by_local_user_id(cls, local_user_id):
4644 def by_local_user_id(cls, local_user_id):
4630 """
4645 """
4631 Returns all tokens for user
4646 Returns all tokens for user
4632
4647
4633 :param local_user_id:
4648 :param local_user_id:
4634 :return: ExternalIdentity
4649 :return: ExternalIdentity
4635 """
4650 """
4636 query = cls.query()
4651 query = cls.query()
4637 query = query.filter(cls.local_user_id == local_user_id)
4652 query = query.filter(cls.local_user_id == local_user_id)
4638 return query
4653 return query
4639
4654
4640 @classmethod
4655 @classmethod
4641 def load_provider_plugin(cls, plugin_id):
4656 def load_provider_plugin(cls, plugin_id):
4642 from rhodecode.authentication.base import loadplugin
4657 from rhodecode.authentication.base import loadplugin
4643 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4658 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4644 auth_plugin = loadplugin(_plugin_id)
4659 auth_plugin = loadplugin(_plugin_id)
4645 return auth_plugin
4660 return auth_plugin
4646
4661
4647
4662
4648 class Integration(Base, BaseModel):
4663 class Integration(Base, BaseModel):
4649 __tablename__ = 'integrations'
4664 __tablename__ = 'integrations'
4650 __table_args__ = (
4665 __table_args__ = (
4651 base_table_args
4666 base_table_args
4652 )
4667 )
4653
4668
4654 integration_id = Column('integration_id', Integer(), primary_key=True)
4669 integration_id = Column('integration_id', Integer(), primary_key=True)
4655 integration_type = Column('integration_type', String(255))
4670 integration_type = Column('integration_type', String(255))
4656 enabled = Column('enabled', Boolean(), nullable=False)
4671 enabled = Column('enabled', Boolean(), nullable=False)
4657 name = Column('name', String(255), nullable=False)
4672 name = Column('name', String(255), nullable=False)
4658 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4673 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4659 default=False)
4674 default=False)
4660
4675
4661 settings = Column(
4676 settings = Column(
4662 'settings_json', MutationObj.as_mutable(
4677 'settings_json', MutationObj.as_mutable(
4663 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4678 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4664 repo_id = Column(
4679 repo_id = Column(
4665 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4680 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4666 nullable=True, unique=None, default=None)
4681 nullable=True, unique=None, default=None)
4667 repo = relationship('Repository', lazy='joined')
4682 repo = relationship('Repository', lazy='joined')
4668
4683
4669 repo_group_id = Column(
4684 repo_group_id = Column(
4670 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4685 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4671 nullable=True, unique=None, default=None)
4686 nullable=True, unique=None, default=None)
4672 repo_group = relationship('RepoGroup', lazy='joined')
4687 repo_group = relationship('RepoGroup', lazy='joined')
4673
4688
4674 @property
4689 @property
4675 def scope(self):
4690 def scope(self):
4676 if self.repo:
4691 if self.repo:
4677 return repr(self.repo)
4692 return repr(self.repo)
4678 if self.repo_group:
4693 if self.repo_group:
4679 if self.child_repos_only:
4694 if self.child_repos_only:
4680 return repr(self.repo_group) + ' (child repos only)'
4695 return repr(self.repo_group) + ' (child repos only)'
4681 else:
4696 else:
4682 return repr(self.repo_group) + ' (recursive)'
4697 return repr(self.repo_group) + ' (recursive)'
4683 if self.child_repos_only:
4698 if self.child_repos_only:
4684 return 'root_repos'
4699 return 'root_repos'
4685 return 'global'
4700 return 'global'
4686
4701
4687 def __repr__(self):
4702 def __repr__(self):
4688 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4703 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4689
4704
4690
4705
4691 class RepoReviewRuleUser(Base, BaseModel):
4706 class RepoReviewRuleUser(Base, BaseModel):
4692 __tablename__ = 'repo_review_rules_users'
4707 __tablename__ = 'repo_review_rules_users'
4693 __table_args__ = (
4708 __table_args__ = (
4694 base_table_args
4709 base_table_args
4695 )
4710 )
4696
4711
4697 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4712 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4698 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4713 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4699 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4714 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4700 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4715 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4701 user = relationship('User')
4716 user = relationship('User')
4702
4717
4703 def rule_data(self):
4718 def rule_data(self):
4704 return {
4719 return {
4705 'mandatory': self.mandatory
4720 'mandatory': self.mandatory
4706 }
4721 }
4707
4722
4708
4723
4709 class RepoReviewRuleUserGroup(Base, BaseModel):
4724 class RepoReviewRuleUserGroup(Base, BaseModel):
4710 __tablename__ = 'repo_review_rules_users_groups'
4725 __tablename__ = 'repo_review_rules_users_groups'
4711 __table_args__ = (
4726 __table_args__ = (
4712 base_table_args
4727 base_table_args
4713 )
4728 )
4714
4729
4715 VOTE_RULE_ALL = -1
4730 VOTE_RULE_ALL = -1
4716
4731
4717 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4732 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4718 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4733 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4719 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4734 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4720 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4735 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4721 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4736 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4722 users_group = relationship('UserGroup')
4737 users_group = relationship('UserGroup')
4723
4738
4724 def rule_data(self):
4739 def rule_data(self):
4725 return {
4740 return {
4726 'mandatory': self.mandatory,
4741 'mandatory': self.mandatory,
4727 'vote_rule': self.vote_rule
4742 'vote_rule': self.vote_rule
4728 }
4743 }
4729
4744
4730 @property
4745 @property
4731 def vote_rule_label(self):
4746 def vote_rule_label(self):
4732 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4747 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4733 return 'all must vote'
4748 return 'all must vote'
4734 else:
4749 else:
4735 return 'min. vote {}'.format(self.vote_rule)
4750 return 'min. vote {}'.format(self.vote_rule)
4736
4751
4737
4752
4738 class RepoReviewRule(Base, BaseModel):
4753 class RepoReviewRule(Base, BaseModel):
4739 __tablename__ = 'repo_review_rules'
4754 __tablename__ = 'repo_review_rules'
4740 __table_args__ = (
4755 __table_args__ = (
4741 base_table_args
4756 base_table_args
4742 )
4757 )
4743
4758
4744 repo_review_rule_id = Column(
4759 repo_review_rule_id = Column(
4745 'repo_review_rule_id', Integer(), primary_key=True)
4760 'repo_review_rule_id', Integer(), primary_key=True)
4746 repo_id = Column(
4761 repo_id = Column(
4747 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4762 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4748 repo = relationship('Repository', backref='review_rules')
4763 repo = relationship('Repository', backref='review_rules')
4749
4764
4750 review_rule_name = Column('review_rule_name', String(255))
4765 review_rule_name = Column('review_rule_name', String(255))
4751 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4766 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4752 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4767 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4753 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4768 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4754
4769
4755 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4770 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4756 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4771 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4757 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4772 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4758 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4773 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4759
4774
4760 rule_users = relationship('RepoReviewRuleUser')
4775 rule_users = relationship('RepoReviewRuleUser')
4761 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4776 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4762
4777
4763 def _validate_pattern(self, value):
4778 def _validate_pattern(self, value):
4764 re.compile('^' + glob2re(value) + '$')
4779 re.compile('^' + glob2re(value) + '$')
4765
4780
4766 @hybrid_property
4781 @hybrid_property
4767 def source_branch_pattern(self):
4782 def source_branch_pattern(self):
4768 return self._branch_pattern or '*'
4783 return self._branch_pattern or '*'
4769
4784
4770 @source_branch_pattern.setter
4785 @source_branch_pattern.setter
4771 def source_branch_pattern(self, value):
4786 def source_branch_pattern(self, value):
4772 self._validate_pattern(value)
4787 self._validate_pattern(value)
4773 self._branch_pattern = value or '*'
4788 self._branch_pattern = value or '*'
4774
4789
4775 @hybrid_property
4790 @hybrid_property
4776 def target_branch_pattern(self):
4791 def target_branch_pattern(self):
4777 return self._target_branch_pattern or '*'
4792 return self._target_branch_pattern or '*'
4778
4793
4779 @target_branch_pattern.setter
4794 @target_branch_pattern.setter
4780 def target_branch_pattern(self, value):
4795 def target_branch_pattern(self, value):
4781 self._validate_pattern(value)
4796 self._validate_pattern(value)
4782 self._target_branch_pattern = value or '*'
4797 self._target_branch_pattern = value or '*'
4783
4798
4784 @hybrid_property
4799 @hybrid_property
4785 def file_pattern(self):
4800 def file_pattern(self):
4786 return self._file_pattern or '*'
4801 return self._file_pattern or '*'
4787
4802
4788 @file_pattern.setter
4803 @file_pattern.setter
4789 def file_pattern(self, value):
4804 def file_pattern(self, value):
4790 self._validate_pattern(value)
4805 self._validate_pattern(value)
4791 self._file_pattern = value or '*'
4806 self._file_pattern = value or '*'
4792
4807
4793 def matches(self, source_branch, target_branch, files_changed):
4808 def matches(self, source_branch, target_branch, files_changed):
4794 """
4809 """
4795 Check if this review rule matches a branch/files in a pull request
4810 Check if this review rule matches a branch/files in a pull request
4796
4811
4797 :param source_branch: source branch name for the commit
4812 :param source_branch: source branch name for the commit
4798 :param target_branch: target branch name for the commit
4813 :param target_branch: target branch name for the commit
4799 :param files_changed: list of file paths changed in the pull request
4814 :param files_changed: list of file paths changed in the pull request
4800 """
4815 """
4801
4816
4802 source_branch = source_branch or ''
4817 source_branch = source_branch or ''
4803 target_branch = target_branch or ''
4818 target_branch = target_branch or ''
4804 files_changed = files_changed or []
4819 files_changed = files_changed or []
4805
4820
4806 branch_matches = True
4821 branch_matches = True
4807 if source_branch or target_branch:
4822 if source_branch or target_branch:
4808 if self.source_branch_pattern == '*':
4823 if self.source_branch_pattern == '*':
4809 source_branch_match = True
4824 source_branch_match = True
4810 else:
4825 else:
4811 if self.source_branch_pattern.startswith('re:'):
4826 if self.source_branch_pattern.startswith('re:'):
4812 source_pattern = self.source_branch_pattern[3:]
4827 source_pattern = self.source_branch_pattern[3:]
4813 else:
4828 else:
4814 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4829 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4815 source_branch_regex = re.compile(source_pattern)
4830 source_branch_regex = re.compile(source_pattern)
4816 source_branch_match = bool(source_branch_regex.search(source_branch))
4831 source_branch_match = bool(source_branch_regex.search(source_branch))
4817 if self.target_branch_pattern == '*':
4832 if self.target_branch_pattern == '*':
4818 target_branch_match = True
4833 target_branch_match = True
4819 else:
4834 else:
4820 if self.target_branch_pattern.startswith('re:'):
4835 if self.target_branch_pattern.startswith('re:'):
4821 target_pattern = self.target_branch_pattern[3:]
4836 target_pattern = self.target_branch_pattern[3:]
4822 else:
4837 else:
4823 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4838 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4824 target_branch_regex = re.compile(target_pattern)
4839 target_branch_regex = re.compile(target_pattern)
4825 target_branch_match = bool(target_branch_regex.search(target_branch))
4840 target_branch_match = bool(target_branch_regex.search(target_branch))
4826
4841
4827 branch_matches = source_branch_match and target_branch_match
4842 branch_matches = source_branch_match and target_branch_match
4828
4843
4829 files_matches = True
4844 files_matches = True
4830 if self.file_pattern != '*':
4845 if self.file_pattern != '*':
4831 files_matches = False
4846 files_matches = False
4832 if self.file_pattern.startswith('re:'):
4847 if self.file_pattern.startswith('re:'):
4833 file_pattern = self.file_pattern[3:]
4848 file_pattern = self.file_pattern[3:]
4834 else:
4849 else:
4835 file_pattern = glob2re(self.file_pattern)
4850 file_pattern = glob2re(self.file_pattern)
4836 file_regex = re.compile(file_pattern)
4851 file_regex = re.compile(file_pattern)
4837 for filename in files_changed:
4852 for filename in files_changed:
4838 if file_regex.search(filename):
4853 if file_regex.search(filename):
4839 files_matches = True
4854 files_matches = True
4840 break
4855 break
4841
4856
4842 return branch_matches and files_matches
4857 return branch_matches and files_matches
4843
4858
4844 @property
4859 @property
4845 def review_users(self):
4860 def review_users(self):
4846 """ Returns the users which this rule applies to """
4861 """ Returns the users which this rule applies to """
4847
4862
4848 users = collections.OrderedDict()
4863 users = collections.OrderedDict()
4849
4864
4850 for rule_user in self.rule_users:
4865 for rule_user in self.rule_users:
4851 if rule_user.user.active:
4866 if rule_user.user.active:
4852 if rule_user.user not in users:
4867 if rule_user.user not in users:
4853 users[rule_user.user.username] = {
4868 users[rule_user.user.username] = {
4854 'user': rule_user.user,
4869 'user': rule_user.user,
4855 'source': 'user',
4870 'source': 'user',
4856 'source_data': {},
4871 'source_data': {},
4857 'data': rule_user.rule_data()
4872 'data': rule_user.rule_data()
4858 }
4873 }
4859
4874
4860 for rule_user_group in self.rule_user_groups:
4875 for rule_user_group in self.rule_user_groups:
4861 source_data = {
4876 source_data = {
4862 'user_group_id': rule_user_group.users_group.users_group_id,
4877 'user_group_id': rule_user_group.users_group.users_group_id,
4863 'name': rule_user_group.users_group.users_group_name,
4878 'name': rule_user_group.users_group.users_group_name,
4864 'members': len(rule_user_group.users_group.members)
4879 'members': len(rule_user_group.users_group.members)
4865 }
4880 }
4866 for member in rule_user_group.users_group.members:
4881 for member in rule_user_group.users_group.members:
4867 if member.user.active:
4882 if member.user.active:
4868 key = member.user.username
4883 key = member.user.username
4869 if key in users:
4884 if key in users:
4870 # skip this member as we have him already
4885 # skip this member as we have him already
4871 # this prevents from override the "first" matched
4886 # this prevents from override the "first" matched
4872 # users with duplicates in multiple groups
4887 # users with duplicates in multiple groups
4873 continue
4888 continue
4874
4889
4875 users[key] = {
4890 users[key] = {
4876 'user': member.user,
4891 'user': member.user,
4877 'source': 'user_group',
4892 'source': 'user_group',
4878 'source_data': source_data,
4893 'source_data': source_data,
4879 'data': rule_user_group.rule_data()
4894 'data': rule_user_group.rule_data()
4880 }
4895 }
4881
4896
4882 return users
4897 return users
4883
4898
4884 def user_group_vote_rule(self, user_id):
4899 def user_group_vote_rule(self, user_id):
4885
4900
4886 rules = []
4901 rules = []
4887 if not self.rule_user_groups:
4902 if not self.rule_user_groups:
4888 return rules
4903 return rules
4889
4904
4890 for user_group in self.rule_user_groups:
4905 for user_group in self.rule_user_groups:
4891 user_group_members = [x.user_id for x in user_group.users_group.members]
4906 user_group_members = [x.user_id for x in user_group.users_group.members]
4892 if user_id in user_group_members:
4907 if user_id in user_group_members:
4893 rules.append(user_group)
4908 rules.append(user_group)
4894 return rules
4909 return rules
4895
4910
4896 def __repr__(self):
4911 def __repr__(self):
4897 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4912 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4898 self.repo_review_rule_id, self.repo)
4913 self.repo_review_rule_id, self.repo)
4899
4914
4900
4915
4901 class ScheduleEntry(Base, BaseModel):
4916 class ScheduleEntry(Base, BaseModel):
4902 __tablename__ = 'schedule_entries'
4917 __tablename__ = 'schedule_entries'
4903 __table_args__ = (
4918 __table_args__ = (
4904 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4919 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4905 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4920 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4906 base_table_args,
4921 base_table_args,
4907 )
4922 )
4908
4923
4909 schedule_types = ['crontab', 'timedelta', 'integer']
4924 schedule_types = ['crontab', 'timedelta', 'integer']
4910 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4925 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4911
4926
4912 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4927 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4913 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4928 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4914 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4929 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4915
4930
4916 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4931 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4917 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4932 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4918
4933
4919 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4934 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4920 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4935 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4921
4936
4922 # task
4937 # task
4923 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4938 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4924 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4939 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4925 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4940 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4926 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4941 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4927
4942
4928 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4943 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4929 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4944 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4930
4945
4931 @hybrid_property
4946 @hybrid_property
4932 def schedule_type(self):
4947 def schedule_type(self):
4933 return self._schedule_type
4948 return self._schedule_type
4934
4949
4935 @schedule_type.setter
4950 @schedule_type.setter
4936 def schedule_type(self, val):
4951 def schedule_type(self, val):
4937 if val not in self.schedule_types:
4952 if val not in self.schedule_types:
4938 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4953 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4939 val, self.schedule_type))
4954 val, self.schedule_type))
4940
4955
4941 self._schedule_type = val
4956 self._schedule_type = val
4942
4957
4943 @classmethod
4958 @classmethod
4944 def get_uid(cls, obj):
4959 def get_uid(cls, obj):
4945 args = obj.task_args
4960 args = obj.task_args
4946 kwargs = obj.task_kwargs
4961 kwargs = obj.task_kwargs
4947 if isinstance(args, JsonRaw):
4962 if isinstance(args, JsonRaw):
4948 try:
4963 try:
4949 args = json.loads(args)
4964 args = json.loads(args)
4950 except ValueError:
4965 except ValueError:
4951 args = tuple()
4966 args = tuple()
4952
4967
4953 if isinstance(kwargs, JsonRaw):
4968 if isinstance(kwargs, JsonRaw):
4954 try:
4969 try:
4955 kwargs = json.loads(kwargs)
4970 kwargs = json.loads(kwargs)
4956 except ValueError:
4971 except ValueError:
4957 kwargs = dict()
4972 kwargs = dict()
4958
4973
4959 dot_notation = obj.task_dot_notation
4974 dot_notation = obj.task_dot_notation
4960 val = '.'.join(map(safe_str, [
4975 val = '.'.join(map(safe_str, [
4961 sorted(dot_notation), args, sorted(kwargs.items())]))
4976 sorted(dot_notation), args, sorted(kwargs.items())]))
4962 return hashlib.sha1(val).hexdigest()
4977 return hashlib.sha1(val).hexdigest()
4963
4978
4964 @classmethod
4979 @classmethod
4965 def get_by_schedule_name(cls, schedule_name):
4980 def get_by_schedule_name(cls, schedule_name):
4966 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4981 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4967
4982
4968 @classmethod
4983 @classmethod
4969 def get_by_schedule_id(cls, schedule_id):
4984 def get_by_schedule_id(cls, schedule_id):
4970 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4985 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4971
4986
4972 @property
4987 @property
4973 def task(self):
4988 def task(self):
4974 return self.task_dot_notation
4989 return self.task_dot_notation
4975
4990
4976 @property
4991 @property
4977 def schedule(self):
4992 def schedule(self):
4978 from rhodecode.lib.celerylib.utils import raw_2_schedule
4993 from rhodecode.lib.celerylib.utils import raw_2_schedule
4979 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4994 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4980 return schedule
4995 return schedule
4981
4996
4982 @property
4997 @property
4983 def args(self):
4998 def args(self):
4984 try:
4999 try:
4985 return list(self.task_args or [])
5000 return list(self.task_args or [])
4986 except ValueError:
5001 except ValueError:
4987 return list()
5002 return list()
4988
5003
4989 @property
5004 @property
4990 def kwargs(self):
5005 def kwargs(self):
4991 try:
5006 try:
4992 return dict(self.task_kwargs or {})
5007 return dict(self.task_kwargs or {})
4993 except ValueError:
5008 except ValueError:
4994 return dict()
5009 return dict()
4995
5010
4996 def _as_raw(self, val):
5011 def _as_raw(self, val):
4997 if hasattr(val, 'de_coerce'):
5012 if hasattr(val, 'de_coerce'):
4998 val = val.de_coerce()
5013 val = val.de_coerce()
4999 if val:
5014 if val:
5000 val = json.dumps(val)
5015 val = json.dumps(val)
5001
5016
5002 return val
5017 return val
5003
5018
5004 @property
5019 @property
5005 def schedule_definition_raw(self):
5020 def schedule_definition_raw(self):
5006 return self._as_raw(self.schedule_definition)
5021 return self._as_raw(self.schedule_definition)
5007
5022
5008 @property
5023 @property
5009 def args_raw(self):
5024 def args_raw(self):
5010 return self._as_raw(self.task_args)
5025 return self._as_raw(self.task_args)
5011
5026
5012 @property
5027 @property
5013 def kwargs_raw(self):
5028 def kwargs_raw(self):
5014 return self._as_raw(self.task_kwargs)
5029 return self._as_raw(self.task_kwargs)
5015
5030
5016 def __repr__(self):
5031 def __repr__(self):
5017 return '<DB:ScheduleEntry({}:{})>'.format(
5032 return '<DB:ScheduleEntry({}:{})>'.format(
5018 self.schedule_entry_id, self.schedule_name)
5033 self.schedule_entry_id, self.schedule_name)
5019
5034
5020
5035
5021 @event.listens_for(ScheduleEntry, 'before_update')
5036 @event.listens_for(ScheduleEntry, 'before_update')
5022 def update_task_uid(mapper, connection, target):
5037 def update_task_uid(mapper, connection, target):
5023 target.task_uid = ScheduleEntry.get_uid(target)
5038 target.task_uid = ScheduleEntry.get_uid(target)
5024
5039
5025
5040
5026 @event.listens_for(ScheduleEntry, 'before_insert')
5041 @event.listens_for(ScheduleEntry, 'before_insert')
5027 def set_task_uid(mapper, connection, target):
5042 def set_task_uid(mapper, connection, target):
5028 target.task_uid = ScheduleEntry.get_uid(target)
5043 target.task_uid = ScheduleEntry.get_uid(target)
5029
5044
5030
5045
5031 class _BaseBranchPerms(BaseModel):
5046 class _BaseBranchPerms(BaseModel):
5032 @classmethod
5047 @classmethod
5033 def compute_hash(cls, value):
5048 def compute_hash(cls, value):
5034 return sha1_safe(value)
5049 return sha1_safe(value)
5035
5050
5036 @hybrid_property
5051 @hybrid_property
5037 def branch_pattern(self):
5052 def branch_pattern(self):
5038 return self._branch_pattern or '*'
5053 return self._branch_pattern or '*'
5039
5054
5040 @hybrid_property
5055 @hybrid_property
5041 def branch_hash(self):
5056 def branch_hash(self):
5042 return self._branch_hash
5057 return self._branch_hash
5043
5058
5044 def _validate_glob(self, value):
5059 def _validate_glob(self, value):
5045 re.compile('^' + glob2re(value) + '$')
5060 re.compile('^' + glob2re(value) + '$')
5046
5061
5047 @branch_pattern.setter
5062 @branch_pattern.setter
5048 def branch_pattern(self, value):
5063 def branch_pattern(self, value):
5049 self._validate_glob(value)
5064 self._validate_glob(value)
5050 self._branch_pattern = value or '*'
5065 self._branch_pattern = value or '*'
5051 # set the Hash when setting the branch pattern
5066 # set the Hash when setting the branch pattern
5052 self._branch_hash = self.compute_hash(self._branch_pattern)
5067 self._branch_hash = self.compute_hash(self._branch_pattern)
5053
5068
5054 def matches(self, branch):
5069 def matches(self, branch):
5055 """
5070 """
5056 Check if this the branch matches entry
5071 Check if this the branch matches entry
5057
5072
5058 :param branch: branch name for the commit
5073 :param branch: branch name for the commit
5059 """
5074 """
5060
5075
5061 branch = branch or ''
5076 branch = branch or ''
5062
5077
5063 branch_matches = True
5078 branch_matches = True
5064 if branch:
5079 if branch:
5065 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5080 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5066 branch_matches = bool(branch_regex.search(branch))
5081 branch_matches = bool(branch_regex.search(branch))
5067
5082
5068 return branch_matches
5083 return branch_matches
5069
5084
5070
5085
5071 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5086 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5072 __tablename__ = 'user_to_repo_branch_permissions'
5087 __tablename__ = 'user_to_repo_branch_permissions'
5073 __table_args__ = (
5088 __table_args__ = (
5074 base_table_args
5089 base_table_args
5075 )
5090 )
5076
5091
5077 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5092 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5078
5093
5079 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5094 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5080 repo = relationship('Repository', backref='user_branch_perms')
5095 repo = relationship('Repository', backref='user_branch_perms')
5081
5096
5082 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5097 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5083 permission = relationship('Permission')
5098 permission = relationship('Permission')
5084
5099
5085 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5100 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5086 user_repo_to_perm = relationship('UserRepoToPerm')
5101 user_repo_to_perm = relationship('UserRepoToPerm')
5087
5102
5088 rule_order = Column('rule_order', Integer(), nullable=False)
5103 rule_order = Column('rule_order', Integer(), nullable=False)
5089 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5104 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5090 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5105 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5091
5106
5092 def __unicode__(self):
5107 def __unicode__(self):
5093 return u'<UserBranchPermission(%s => %r)>' % (
5108 return u'<UserBranchPermission(%s => %r)>' % (
5094 self.user_repo_to_perm, self.branch_pattern)
5109 self.user_repo_to_perm, self.branch_pattern)
5095
5110
5096
5111
5097 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5112 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5098 __tablename__ = 'user_group_to_repo_branch_permissions'
5113 __tablename__ = 'user_group_to_repo_branch_permissions'
5099 __table_args__ = (
5114 __table_args__ = (
5100 base_table_args
5115 base_table_args
5101 )
5116 )
5102
5117
5103 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5118 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5104
5119
5105 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5120 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5106 repo = relationship('Repository', backref='user_group_branch_perms')
5121 repo = relationship('Repository', backref='user_group_branch_perms')
5107
5122
5108 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5123 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5109 permission = relationship('Permission')
5124 permission = relationship('Permission')
5110
5125
5111 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5126 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5112 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5127 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5113
5128
5114 rule_order = Column('rule_order', Integer(), nullable=False)
5129 rule_order = Column('rule_order', Integer(), nullable=False)
5115 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5130 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5116 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5131 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5117
5132
5118 def __unicode__(self):
5133 def __unicode__(self):
5119 return u'<UserBranchPermission(%s => %r)>' % (
5134 return u'<UserBranchPermission(%s => %r)>' % (
5120 self.user_group_repo_to_perm, self.branch_pattern)
5135 self.user_group_repo_to_perm, self.branch_pattern)
5121
5136
5122
5137
5123 class UserBookmark(Base, BaseModel):
5138 class UserBookmark(Base, BaseModel):
5124 __tablename__ = 'user_bookmarks'
5139 __tablename__ = 'user_bookmarks'
5125 __table_args__ = (
5140 __table_args__ = (
5126 UniqueConstraint('user_id', 'bookmark_repo_id'),
5141 UniqueConstraint('user_id', 'bookmark_repo_id'),
5127 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5142 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5128 UniqueConstraint('user_id', 'bookmark_position'),
5143 UniqueConstraint('user_id', 'bookmark_position'),
5129 base_table_args
5144 base_table_args
5130 )
5145 )
5131
5146
5132 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5147 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5133 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5148 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5134 position = Column("bookmark_position", Integer(), nullable=False)
5149 position = Column("bookmark_position", Integer(), nullable=False)
5135 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5150 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5136 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5151 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5137 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5152 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5138
5153
5139 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5154 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5140 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5155 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5141
5156
5142 user = relationship("User")
5157 user = relationship("User")
5143
5158
5144 repository = relationship("Repository")
5159 repository = relationship("Repository")
5145 repository_group = relationship("RepoGroup")
5160 repository_group = relationship("RepoGroup")
5146
5161
5147 @classmethod
5162 @classmethod
5148 def get_by_position_for_user(cls, position, user_id):
5163 def get_by_position_for_user(cls, position, user_id):
5149 return cls.query() \
5164 return cls.query() \
5150 .filter(UserBookmark.user_id == user_id) \
5165 .filter(UserBookmark.user_id == user_id) \
5151 .filter(UserBookmark.position == position).scalar()
5166 .filter(UserBookmark.position == position).scalar()
5152
5167
5153 @classmethod
5168 @classmethod
5154 def get_bookmarks_for_user(cls, user_id, cache=True):
5169 def get_bookmarks_for_user(cls, user_id, cache=True):
5155 bookmarks = cls.query() \
5170 bookmarks = cls.query() \
5156 .filter(UserBookmark.user_id == user_id) \
5171 .filter(UserBookmark.user_id == user_id) \
5157 .options(joinedload(UserBookmark.repository)) \
5172 .options(joinedload(UserBookmark.repository)) \
5158 .options(joinedload(UserBookmark.repository_group)) \
5173 .options(joinedload(UserBookmark.repository_group)) \
5159 .order_by(UserBookmark.position.asc())
5174 .order_by(UserBookmark.position.asc())
5160
5175
5161 if cache:
5176 if cache:
5162 bookmarks = bookmarks.options(
5177 bookmarks = bookmarks.options(
5163 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5178 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5164 )
5179 )
5165
5180
5166 return bookmarks.all()
5181 return bookmarks.all()
5167
5182
5168 def __unicode__(self):
5183 def __unicode__(self):
5169 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5184 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5170
5185
5171
5186
5172 class FileStore(Base, BaseModel):
5187 class FileStore(Base, BaseModel):
5173 __tablename__ = 'file_store'
5188 __tablename__ = 'file_store'
5174 __table_args__ = (
5189 __table_args__ = (
5175 base_table_args
5190 base_table_args
5176 )
5191 )
5177
5192
5178 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5193 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5179 file_uid = Column('file_uid', String(1024), nullable=False)
5194 file_uid = Column('file_uid', String(1024), nullable=False)
5180 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5195 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5181 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5196 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5182 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5197 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5183
5198
5184 # sha256 hash
5199 # sha256 hash
5185 file_hash = Column('file_hash', String(512), nullable=False)
5200 file_hash = Column('file_hash', String(512), nullable=False)
5186 file_size = Column('file_size', BigInteger(), nullable=False)
5201 file_size = Column('file_size', BigInteger(), nullable=False)
5187
5202
5188 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5203 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5189 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5204 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5190 accessed_count = Column('accessed_count', Integer(), default=0)
5205 accessed_count = Column('accessed_count', Integer(), default=0)
5191
5206
5192 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5207 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5193
5208
5194 # if repo/repo_group reference is set, check for permissions
5209 # if repo/repo_group reference is set, check for permissions
5195 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5210 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5196
5211
5197 # hidden defines an attachment that should be hidden from showing in artifact listing
5212 # hidden defines an attachment that should be hidden from showing in artifact listing
5198 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5213 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5199
5214
5200 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5215 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5201 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5216 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5202
5217
5203 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5218 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5204
5219
5205 # scope limited to user, which requester have access to
5220 # scope limited to user, which requester have access to
5206 scope_user_id = Column(
5221 scope_user_id = Column(
5207 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5222 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5208 nullable=True, unique=None, default=None)
5223 nullable=True, unique=None, default=None)
5209 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5224 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5210
5225
5211 # scope limited to user group, which requester have access to
5226 # scope limited to user group, which requester have access to
5212 scope_user_group_id = Column(
5227 scope_user_group_id = Column(
5213 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5228 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5214 nullable=True, unique=None, default=None)
5229 nullable=True, unique=None, default=None)
5215 user_group = relationship('UserGroup', lazy='joined')
5230 user_group = relationship('UserGroup', lazy='joined')
5216
5231
5217 # scope limited to repo, which requester have access to
5232 # scope limited to repo, which requester have access to
5218 scope_repo_id = Column(
5233 scope_repo_id = Column(
5219 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5234 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5220 nullable=True, unique=None, default=None)
5235 nullable=True, unique=None, default=None)
5221 repo = relationship('Repository', lazy='joined')
5236 repo = relationship('Repository', lazy='joined')
5222
5237
5223 # scope limited to repo group, which requester have access to
5238 # scope limited to repo group, which requester have access to
5224 scope_repo_group_id = Column(
5239 scope_repo_group_id = Column(
5225 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5240 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5226 nullable=True, unique=None, default=None)
5241 nullable=True, unique=None, default=None)
5227 repo_group = relationship('RepoGroup', lazy='joined')
5242 repo_group = relationship('RepoGroup', lazy='joined')
5228
5243
5229 @classmethod
5244 @classmethod
5230 def get_by_store_uid(cls, file_store_uid):
5245 def get_by_store_uid(cls, file_store_uid):
5231 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5246 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5232
5247
5233 @classmethod
5248 @classmethod
5234 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5249 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5235 file_description='', enabled=True, hidden=False, check_acl=True,
5250 file_description='', enabled=True, hidden=False, check_acl=True,
5236 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5251 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5237
5252
5238 store_entry = FileStore()
5253 store_entry = FileStore()
5239 store_entry.file_uid = file_uid
5254 store_entry.file_uid = file_uid
5240 store_entry.file_display_name = file_display_name
5255 store_entry.file_display_name = file_display_name
5241 store_entry.file_org_name = filename
5256 store_entry.file_org_name = filename
5242 store_entry.file_size = file_size
5257 store_entry.file_size = file_size
5243 store_entry.file_hash = file_hash
5258 store_entry.file_hash = file_hash
5244 store_entry.file_description = file_description
5259 store_entry.file_description = file_description
5245
5260
5246 store_entry.check_acl = check_acl
5261 store_entry.check_acl = check_acl
5247 store_entry.enabled = enabled
5262 store_entry.enabled = enabled
5248 store_entry.hidden = hidden
5263 store_entry.hidden = hidden
5249
5264
5250 store_entry.user_id = user_id
5265 store_entry.user_id = user_id
5251 store_entry.scope_user_id = scope_user_id
5266 store_entry.scope_user_id = scope_user_id
5252 store_entry.scope_repo_id = scope_repo_id
5267 store_entry.scope_repo_id = scope_repo_id
5253 store_entry.scope_repo_group_id = scope_repo_group_id
5268 store_entry.scope_repo_group_id = scope_repo_group_id
5254
5269
5255 return store_entry
5270 return store_entry
5256
5271
5257 @classmethod
5272 @classmethod
5258 def store_metadata(cls, file_store_id, args, commit=True):
5273 def store_metadata(cls, file_store_id, args, commit=True):
5259 file_store = FileStore.get(file_store_id)
5274 file_store = FileStore.get(file_store_id)
5260 if file_store is None:
5275 if file_store is None:
5261 return
5276 return
5262
5277
5263 for section, key, value, value_type in args:
5278 for section, key, value, value_type in args:
5264 has_key = FileStoreMetadata().query() \
5279 has_key = FileStoreMetadata().query() \
5265 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5280 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5266 .filter(FileStoreMetadata.file_store_meta_section == section) \
5281 .filter(FileStoreMetadata.file_store_meta_section == section) \
5267 .filter(FileStoreMetadata.file_store_meta_key == key) \
5282 .filter(FileStoreMetadata.file_store_meta_key == key) \
5268 .scalar()
5283 .scalar()
5269 if has_key:
5284 if has_key:
5270 msg = 'key `{}` already defined under section `{}` for this file.'\
5285 msg = 'key `{}` already defined under section `{}` for this file.'\
5271 .format(key, section)
5286 .format(key, section)
5272 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5287 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5273
5288
5274 # NOTE(marcink): raises ArtifactMetadataBadValueType
5289 # NOTE(marcink): raises ArtifactMetadataBadValueType
5275 FileStoreMetadata.valid_value_type(value_type)
5290 FileStoreMetadata.valid_value_type(value_type)
5276
5291
5277 meta_entry = FileStoreMetadata()
5292 meta_entry = FileStoreMetadata()
5278 meta_entry.file_store = file_store
5293 meta_entry.file_store = file_store
5279 meta_entry.file_store_meta_section = section
5294 meta_entry.file_store_meta_section = section
5280 meta_entry.file_store_meta_key = key
5295 meta_entry.file_store_meta_key = key
5281 meta_entry.file_store_meta_value_type = value_type
5296 meta_entry.file_store_meta_value_type = value_type
5282 meta_entry.file_store_meta_value = value
5297 meta_entry.file_store_meta_value = value
5283
5298
5284 Session().add(meta_entry)
5299 Session().add(meta_entry)
5285
5300
5286 try:
5301 try:
5287 if commit:
5302 if commit:
5288 Session().commit()
5303 Session().commit()
5289 except IntegrityError:
5304 except IntegrityError:
5290 Session().rollback()
5305 Session().rollback()
5291 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5306 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5292
5307
5293 @classmethod
5308 @classmethod
5294 def bump_access_counter(cls, file_uid, commit=True):
5309 def bump_access_counter(cls, file_uid, commit=True):
5295 FileStore().query()\
5310 FileStore().query()\
5296 .filter(FileStore.file_uid == file_uid)\
5311 .filter(FileStore.file_uid == file_uid)\
5297 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5312 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5298 FileStore.accessed_on: datetime.datetime.now()})
5313 FileStore.accessed_on: datetime.datetime.now()})
5299 if commit:
5314 if commit:
5300 Session().commit()
5315 Session().commit()
5301
5316
5302 def __json__(self):
5317 def __json__(self):
5303 data = {
5318 data = {
5304 'filename': self.file_display_name,
5319 'filename': self.file_display_name,
5305 'filename_org': self.file_org_name,
5320 'filename_org': self.file_org_name,
5306 'file_uid': self.file_uid,
5321 'file_uid': self.file_uid,
5307 'description': self.file_description,
5322 'description': self.file_description,
5308 'hidden': self.hidden,
5323 'hidden': self.hidden,
5309 'size': self.file_size,
5324 'size': self.file_size,
5310 'created_on': self.created_on,
5325 'created_on': self.created_on,
5311 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5326 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5312 'downloaded_times': self.accessed_count,
5327 'downloaded_times': self.accessed_count,
5313 'sha256': self.file_hash,
5328 'sha256': self.file_hash,
5314 'metadata': self.file_metadata,
5329 'metadata': self.file_metadata,
5315 }
5330 }
5316
5331
5317 return data
5332 return data
5318
5333
5319 def __repr__(self):
5334 def __repr__(self):
5320 return '<FileStore({})>'.format(self.file_store_id)
5335 return '<FileStore({})>'.format(self.file_store_id)
5321
5336
5322
5337
5323 class FileStoreMetadata(Base, BaseModel):
5338 class FileStoreMetadata(Base, BaseModel):
5324 __tablename__ = 'file_store_metadata'
5339 __tablename__ = 'file_store_metadata'
5325 __table_args__ = (
5340 __table_args__ = (
5326 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5341 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5327 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5342 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5328 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5343 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5329 base_table_args
5344 base_table_args
5330 )
5345 )
5331 SETTINGS_TYPES = {
5346 SETTINGS_TYPES = {
5332 'str': safe_str,
5347 'str': safe_str,
5333 'int': safe_int,
5348 'int': safe_int,
5334 'unicode': safe_unicode,
5349 'unicode': safe_unicode,
5335 'bool': str2bool,
5350 'bool': str2bool,
5336 'list': functools.partial(aslist, sep=',')
5351 'list': functools.partial(aslist, sep=',')
5337 }
5352 }
5338
5353
5339 file_store_meta_id = Column(
5354 file_store_meta_id = Column(
5340 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5355 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5341 primary_key=True)
5356 primary_key=True)
5342 _file_store_meta_section = Column(
5357 _file_store_meta_section = Column(
5343 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5358 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5344 nullable=True, unique=None, default=None)
5359 nullable=True, unique=None, default=None)
5345 _file_store_meta_section_hash = Column(
5360 _file_store_meta_section_hash = Column(
5346 "file_store_meta_section_hash", String(255),
5361 "file_store_meta_section_hash", String(255),
5347 nullable=True, unique=None, default=None)
5362 nullable=True, unique=None, default=None)
5348 _file_store_meta_key = Column(
5363 _file_store_meta_key = Column(
5349 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5364 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5350 nullable=True, unique=None, default=None)
5365 nullable=True, unique=None, default=None)
5351 _file_store_meta_key_hash = Column(
5366 _file_store_meta_key_hash = Column(
5352 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5367 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5353 _file_store_meta_value = Column(
5368 _file_store_meta_value = Column(
5354 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5369 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5355 nullable=True, unique=None, default=None)
5370 nullable=True, unique=None, default=None)
5356 _file_store_meta_value_type = Column(
5371 _file_store_meta_value_type = Column(
5357 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5372 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5358 default='unicode')
5373 default='unicode')
5359
5374
5360 file_store_id = Column(
5375 file_store_id = Column(
5361 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5376 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5362 nullable=True, unique=None, default=None)
5377 nullable=True, unique=None, default=None)
5363
5378
5364 file_store = relationship('FileStore', lazy='joined')
5379 file_store = relationship('FileStore', lazy='joined')
5365
5380
5366 @classmethod
5381 @classmethod
5367 def valid_value_type(cls, value):
5382 def valid_value_type(cls, value):
5368 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5383 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5369 raise ArtifactMetadataBadValueType(
5384 raise ArtifactMetadataBadValueType(
5370 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5385 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5371
5386
5372 @hybrid_property
5387 @hybrid_property
5373 def file_store_meta_section(self):
5388 def file_store_meta_section(self):
5374 return self._file_store_meta_section
5389 return self._file_store_meta_section
5375
5390
5376 @file_store_meta_section.setter
5391 @file_store_meta_section.setter
5377 def file_store_meta_section(self, value):
5392 def file_store_meta_section(self, value):
5378 self._file_store_meta_section = value
5393 self._file_store_meta_section = value
5379 self._file_store_meta_section_hash = _hash_key(value)
5394 self._file_store_meta_section_hash = _hash_key(value)
5380
5395
5381 @hybrid_property
5396 @hybrid_property
5382 def file_store_meta_key(self):
5397 def file_store_meta_key(self):
5383 return self._file_store_meta_key
5398 return self._file_store_meta_key
5384
5399
5385 @file_store_meta_key.setter
5400 @file_store_meta_key.setter
5386 def file_store_meta_key(self, value):
5401 def file_store_meta_key(self, value):
5387 self._file_store_meta_key = value
5402 self._file_store_meta_key = value
5388 self._file_store_meta_key_hash = _hash_key(value)
5403 self._file_store_meta_key_hash = _hash_key(value)
5389
5404
5390 @hybrid_property
5405 @hybrid_property
5391 def file_store_meta_value(self):
5406 def file_store_meta_value(self):
5392 val = self._file_store_meta_value
5407 val = self._file_store_meta_value
5393
5408
5394 if self._file_store_meta_value_type:
5409 if self._file_store_meta_value_type:
5395 # e.g unicode.encrypted == unicode
5410 # e.g unicode.encrypted == unicode
5396 _type = self._file_store_meta_value_type.split('.')[0]
5411 _type = self._file_store_meta_value_type.split('.')[0]
5397 # decode the encrypted value if it's encrypted field type
5412 # decode the encrypted value if it's encrypted field type
5398 if '.encrypted' in self._file_store_meta_value_type:
5413 if '.encrypted' in self._file_store_meta_value_type:
5399 cipher = EncryptedTextValue()
5414 cipher = EncryptedTextValue()
5400 val = safe_unicode(cipher.process_result_value(val, None))
5415 val = safe_unicode(cipher.process_result_value(val, None))
5401 # do final type conversion
5416 # do final type conversion
5402 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5417 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5403 val = converter(val)
5418 val = converter(val)
5404
5419
5405 return val
5420 return val
5406
5421
5407 @file_store_meta_value.setter
5422 @file_store_meta_value.setter
5408 def file_store_meta_value(self, val):
5423 def file_store_meta_value(self, val):
5409 val = safe_unicode(val)
5424 val = safe_unicode(val)
5410 # encode the encrypted value
5425 # encode the encrypted value
5411 if '.encrypted' in self.file_store_meta_value_type:
5426 if '.encrypted' in self.file_store_meta_value_type:
5412 cipher = EncryptedTextValue()
5427 cipher = EncryptedTextValue()
5413 val = safe_unicode(cipher.process_bind_param(val, None))
5428 val = safe_unicode(cipher.process_bind_param(val, None))
5414 self._file_store_meta_value = val
5429 self._file_store_meta_value = val
5415
5430
5416 @hybrid_property
5431 @hybrid_property
5417 def file_store_meta_value_type(self):
5432 def file_store_meta_value_type(self):
5418 return self._file_store_meta_value_type
5433 return self._file_store_meta_value_type
5419
5434
5420 @file_store_meta_value_type.setter
5435 @file_store_meta_value_type.setter
5421 def file_store_meta_value_type(self, val):
5436 def file_store_meta_value_type(self, val):
5422 # e.g unicode.encrypted
5437 # e.g unicode.encrypted
5423 self.valid_value_type(val)
5438 self.valid_value_type(val)
5424 self._file_store_meta_value_type = val
5439 self._file_store_meta_value_type = val
5425
5440
5426 def __json__(self):
5441 def __json__(self):
5427 data = {
5442 data = {
5428 'artifact': self.file_store.file_uid,
5443 'artifact': self.file_store.file_uid,
5429 'section': self.file_store_meta_section,
5444 'section': self.file_store_meta_section,
5430 'key': self.file_store_meta_key,
5445 'key': self.file_store_meta_key,
5431 'value': self.file_store_meta_value,
5446 'value': self.file_store_meta_value,
5432 }
5447 }
5433
5448
5434 return data
5449 return data
5435
5450
5436 def __repr__(self):
5451 def __repr__(self):
5437 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5452 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5438 self.file_store_meta_key, self.file_store_meta_value)
5453 self.file_store_meta_key, self.file_store_meta_value)
5439
5454
5440
5455
5441 class DbMigrateVersion(Base, BaseModel):
5456 class DbMigrateVersion(Base, BaseModel):
5442 __tablename__ = 'db_migrate_version'
5457 __tablename__ = 'db_migrate_version'
5443 __table_args__ = (
5458 __table_args__ = (
5444 base_table_args,
5459 base_table_args,
5445 )
5460 )
5446
5461
5447 repository_id = Column('repository_id', String(250), primary_key=True)
5462 repository_id = Column('repository_id', String(250), primary_key=True)
5448 repository_path = Column('repository_path', Text)
5463 repository_path = Column('repository_path', Text)
5449 version = Column('version', Integer)
5464 version = Column('version', Integer)
5450
5465
5451 @classmethod
5466 @classmethod
5452 def set_version(cls, version):
5467 def set_version(cls, version):
5453 """
5468 """
5454 Helper for forcing a different version, usually for debugging purposes via ishell.
5469 Helper for forcing a different version, usually for debugging purposes via ishell.
5455 """
5470 """
5456 ver = DbMigrateVersion.query().first()
5471 ver = DbMigrateVersion.query().first()
5457 ver.version = version
5472 ver.version = version
5458 Session().commit()
5473 Session().commit()
5459
5474
5460
5475
5461 class DbSession(Base, BaseModel):
5476 class DbSession(Base, BaseModel):
5462 __tablename__ = 'db_session'
5477 __tablename__ = 'db_session'
5463 __table_args__ = (
5478 __table_args__ = (
5464 base_table_args,
5479 base_table_args,
5465 )
5480 )
5466
5481
5467 def __repr__(self):
5482 def __repr__(self):
5468 return '<DB:DbSession({})>'.format(self.id)
5483 return '<DB:DbSession({})>'.format(self.id)
5469
5484
5470 id = Column('id', Integer())
5485 id = Column('id', Integer())
5471 namespace = Column('namespace', String(255), primary_key=True)
5486 namespace = Column('namespace', String(255), primary_key=True)
5472 accessed = Column('accessed', DateTime, nullable=False)
5487 accessed = Column('accessed', DateTime, nullable=False)
5473 created = Column('created', DateTime, nullable=False)
5488 created = Column('created', DateTime, nullable=False)
5474 data = Column('data', PickleType, nullable=False)
5489 data = Column('data', PickleType, nullable=False)
@@ -1,1020 +1,1019 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Scm model for RhodeCode
22 Scm model for RhodeCode
23 """
23 """
24
24
25 import os.path
25 import os.path
26 import traceback
26 import traceback
27 import logging
27 import logging
28 import cStringIO
28 import cStringIO
29
29
30 from sqlalchemy import func
30 from sqlalchemy import func
31 from zope.cachedescriptors.property import Lazy as LazyProperty
31 from zope.cachedescriptors.property import Lazy as LazyProperty
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs import get_backend
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib import helpers as h, rc_cache
38 from rhodecode.lib import helpers as h, rc_cache
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 HasUserGroupPermissionAny)
41 HasUserGroupPermissionAny)
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 from rhodecode.lib import hooks_utils
43 from rhodecode.lib import hooks_utils
44 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
45 get_filesystem_repos, make_db_config)
45 get_filesystem_repos, make_db_config)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 from rhodecode.lib.system_info import get_system_info
47 from rhodecode.lib.system_info import get_system_info
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 or_, false,
50 or_, false,
51 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
52 PullRequest, FileStore)
52 PullRequest, FileStore)
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class UserTemp(object):
59 class UserTemp(object):
60 def __init__(self, user_id):
60 def __init__(self, user_id):
61 self.user_id = user_id
61 self.user_id = user_id
62
62
63 def __repr__(self):
63 def __repr__(self):
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65
65
66
66
67 class RepoTemp(object):
67 class RepoTemp(object):
68 def __init__(self, repo_id):
68 def __init__(self, repo_id):
69 self.repo_id = repo_id
69 self.repo_id = repo_id
70
70
71 def __repr__(self):
71 def __repr__(self):
72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
73
73
74
74
75 class SimpleCachedRepoList(object):
75 class SimpleCachedRepoList(object):
76 """
76 """
77 Lighter version of of iteration of repos without the scm initialisation,
77 Lighter version of of iteration of repos without the scm initialisation,
78 and with cache usage
78 and with cache usage
79 """
79 """
80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 self.db_repo_list = db_repo_list
81 self.db_repo_list = db_repo_list
82 self.repos_path = repos_path
82 self.repos_path = repos_path
83 self.order_by = order_by
83 self.order_by = order_by
84 self.reversed = (order_by or '').startswith('-')
84 self.reversed = (order_by or '').startswith('-')
85 if not perm_set:
85 if not perm_set:
86 perm_set = ['repository.read', 'repository.write',
86 perm_set = ['repository.read', 'repository.write',
87 'repository.admin']
87 'repository.admin']
88 self.perm_set = perm_set
88 self.perm_set = perm_set
89
89
90 def __len__(self):
90 def __len__(self):
91 return len(self.db_repo_list)
91 return len(self.db_repo_list)
92
92
93 def __repr__(self):
93 def __repr__(self):
94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
95
95
96 def __iter__(self):
96 def __iter__(self):
97 for dbr in self.db_repo_list:
97 for dbr in self.db_repo_list:
98 # check permission at this level
98 # check permission at this level
99 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 has_perm = HasRepoPermissionAny(*self.perm_set)(
100 dbr.repo_name, 'SimpleCachedRepoList check')
100 dbr.repo_name, 'SimpleCachedRepoList check')
101 if not has_perm:
101 if not has_perm:
102 continue
102 continue
103
103
104 tmp_d = {
104 tmp_d = {
105 'name': dbr.repo_name,
105 'name': dbr.repo_name,
106 'dbrepo': dbr.get_dict(),
106 'dbrepo': dbr.get_dict(),
107 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
108 }
108 }
109 yield tmp_d
109 yield tmp_d
110
110
111
111
112 class _PermCheckIterator(object):
112 class _PermCheckIterator(object):
113
113
114 def __init__(
114 def __init__(
115 self, obj_list, obj_attr, perm_set, perm_checker,
115 self, obj_list, obj_attr, perm_set, perm_checker,
116 extra_kwargs=None):
116 extra_kwargs=None):
117 """
117 """
118 Creates iterator from given list of objects, additionally
118 Creates iterator from given list of objects, additionally
119 checking permission for them from perm_set var
119 checking permission for them from perm_set var
120
120
121 :param obj_list: list of db objects
121 :param obj_list: list of db objects
122 :param obj_attr: attribute of object to pass into perm_checker
122 :param obj_attr: attribute of object to pass into perm_checker
123 :param perm_set: list of permissions to check
123 :param perm_set: list of permissions to check
124 :param perm_checker: callable to check permissions against
124 :param perm_checker: callable to check permissions against
125 """
125 """
126 self.obj_list = obj_list
126 self.obj_list = obj_list
127 self.obj_attr = obj_attr
127 self.obj_attr = obj_attr
128 self.perm_set = perm_set
128 self.perm_set = perm_set
129 self.perm_checker = perm_checker(*self.perm_set)
129 self.perm_checker = perm_checker(*self.perm_set)
130 self.extra_kwargs = extra_kwargs or {}
130 self.extra_kwargs = extra_kwargs or {}
131
131
132 def __len__(self):
132 def __len__(self):
133 return len(self.obj_list)
133 return len(self.obj_list)
134
134
135 def __repr__(self):
135 def __repr__(self):
136 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
136 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
137
137
138 def __iter__(self):
138 def __iter__(self):
139 for db_obj in self.obj_list:
139 for db_obj in self.obj_list:
140 # check permission at this level
140 # check permission at this level
141 name = getattr(db_obj, self.obj_attr, None)
141 name = getattr(db_obj, self.obj_attr, None)
142 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
142 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
143 continue
143 continue
144
144
145 yield db_obj
145 yield db_obj
146
146
147
147
148 class RepoList(_PermCheckIterator):
148 class RepoList(_PermCheckIterator):
149
149
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 if not perm_set:
151 if not perm_set:
152 perm_set = [
152 perm_set = ['repository.read', 'repository.write', 'repository.admin']
153 'repository.read', 'repository.write', 'repository.admin']
154
153
155 super(RepoList, self).__init__(
154 super(RepoList, self).__init__(
156 obj_list=db_repo_list,
155 obj_list=db_repo_list,
157 obj_attr='repo_name', perm_set=perm_set,
156 obj_attr='repo_name', perm_set=perm_set,
158 perm_checker=HasRepoPermissionAny,
157 perm_checker=HasRepoPermissionAny,
159 extra_kwargs=extra_kwargs)
158 extra_kwargs=extra_kwargs)
160
159
161
160
162 class RepoGroupList(_PermCheckIterator):
161 class RepoGroupList(_PermCheckIterator):
163
162
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
163 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 if not perm_set:
164 if not perm_set:
166 perm_set = ['group.read', 'group.write', 'group.admin']
165 perm_set = ['group.read', 'group.write', 'group.admin']
167
166
168 super(RepoGroupList, self).__init__(
167 super(RepoGroupList, self).__init__(
169 obj_list=db_repo_group_list,
168 obj_list=db_repo_group_list,
170 obj_attr='group_name', perm_set=perm_set,
169 obj_attr='group_name', perm_set=perm_set,
171 perm_checker=HasRepoGroupPermissionAny,
170 perm_checker=HasRepoGroupPermissionAny,
172 extra_kwargs=extra_kwargs)
171 extra_kwargs=extra_kwargs)
173
172
174
173
175 class UserGroupList(_PermCheckIterator):
174 class UserGroupList(_PermCheckIterator):
176
175
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
176 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 if not perm_set:
177 if not perm_set:
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
178 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180
179
181 super(UserGroupList, self).__init__(
180 super(UserGroupList, self).__init__(
182 obj_list=db_user_group_list,
181 obj_list=db_user_group_list,
183 obj_attr='users_group_name', perm_set=perm_set,
182 obj_attr='users_group_name', perm_set=perm_set,
184 perm_checker=HasUserGroupPermissionAny,
183 perm_checker=HasUserGroupPermissionAny,
185 extra_kwargs=extra_kwargs)
184 extra_kwargs=extra_kwargs)
186
185
187
186
188 class ScmModel(BaseModel):
187 class ScmModel(BaseModel):
189 """
188 """
190 Generic Scm Model
189 Generic Scm Model
191 """
190 """
192
191
193 @LazyProperty
192 @LazyProperty
194 def repos_path(self):
193 def repos_path(self):
195 """
194 """
196 Gets the repositories root path from database
195 Gets the repositories root path from database
197 """
196 """
198
197
199 settings_model = VcsSettingsModel(sa=self.sa)
198 settings_model = VcsSettingsModel(sa=self.sa)
200 return settings_model.get_repos_location()
199 return settings_model.get_repos_location()
201
200
202 def repo_scan(self, repos_path=None):
201 def repo_scan(self, repos_path=None):
203 """
202 """
204 Listing of repositories in given path. This path should not be a
203 Listing of repositories in given path. This path should not be a
205 repository itself. Return a dictionary of repository objects
204 repository itself. Return a dictionary of repository objects
206
205
207 :param repos_path: path to directory containing repositories
206 :param repos_path: path to directory containing repositories
208 """
207 """
209
208
210 if repos_path is None:
209 if repos_path is None:
211 repos_path = self.repos_path
210 repos_path = self.repos_path
212
211
213 log.info('scanning for repositories in %s', repos_path)
212 log.info('scanning for repositories in %s', repos_path)
214
213
215 config = make_db_config()
214 config = make_db_config()
216 config.set('extensions', 'largefiles', '')
215 config.set('extensions', 'largefiles', '')
217 repos = {}
216 repos = {}
218
217
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
218 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 # name need to be decomposed and put back together using the /
219 # name need to be decomposed and put back together using the /
221 # since this is internal storage separator for rhodecode
220 # since this is internal storage separator for rhodecode
222 name = Repository.normalize_repo_name(name)
221 name = Repository.normalize_repo_name(name)
223
222
224 try:
223 try:
225 if name in repos:
224 if name in repos:
226 raise RepositoryError('Duplicate repository name %s '
225 raise RepositoryError('Duplicate repository name %s '
227 'found in %s' % (name, path))
226 'found in %s' % (name, path))
228 elif path[0] in rhodecode.BACKENDS:
227 elif path[0] in rhodecode.BACKENDS:
229 backend = get_backend(path[0])
228 backend = get_backend(path[0])
230 repos[name] = backend(path[1], config=config,
229 repos[name] = backend(path[1], config=config,
231 with_wire={"cache": False})
230 with_wire={"cache": False})
232 except OSError:
231 except OSError:
233 continue
232 continue
234 log.debug('found %s paths with repositories', len(repos))
233 log.debug('found %s paths with repositories', len(repos))
235 return repos
234 return repos
236
235
237 def get_repos(self, all_repos=None, sort_key=None):
236 def get_repos(self, all_repos=None, sort_key=None):
238 """
237 """
239 Get all repositories from db and for each repo create it's
238 Get all repositories from db and for each repo create it's
240 backend instance and fill that backed with information from database
239 backend instance and fill that backed with information from database
241
240
242 :param all_repos: list of repository names as strings
241 :param all_repos: list of repository names as strings
243 give specific repositories list, good for filtering
242 give specific repositories list, good for filtering
244
243
245 :param sort_key: initial sorting of repositories
244 :param sort_key: initial sorting of repositories
246 """
245 """
247 if all_repos is None:
246 if all_repos is None:
248 all_repos = self.sa.query(Repository)\
247 all_repos = self.sa.query(Repository)\
249 .filter(Repository.group_id == None)\
248 .filter(Repository.group_id == None)\
250 .order_by(func.lower(Repository.repo_name)).all()
249 .order_by(func.lower(Repository.repo_name)).all()
251 repo_iter = SimpleCachedRepoList(
250 repo_iter = SimpleCachedRepoList(
252 all_repos, repos_path=self.repos_path, order_by=sort_key)
251 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 return repo_iter
252 return repo_iter
254
253
255 def get_repo_groups(self, all_groups=None):
254 def get_repo_groups(self, all_groups=None):
256 if all_groups is None:
255 if all_groups is None:
257 all_groups = RepoGroup.query()\
256 all_groups = RepoGroup.query()\
258 .filter(RepoGroup.group_parent_id == None).all()
257 .filter(RepoGroup.group_parent_id == None).all()
259 return [x for x in RepoGroupList(all_groups)]
258 return [x for x in RepoGroupList(all_groups)]
260
259
261 def mark_for_invalidation(self, repo_name, delete=False):
260 def mark_for_invalidation(self, repo_name, delete=False):
262 """
261 """
263 Mark caches of this repo invalid in the database. `delete` flag
262 Mark caches of this repo invalid in the database. `delete` flag
264 removes the cache entries
263 removes the cache entries
265
264
266 :param repo_name: the repo_name for which caches should be marked
265 :param repo_name: the repo_name for which caches should be marked
267 invalid, or deleted
266 invalid, or deleted
268 :param delete: delete the entry keys instead of setting bool
267 :param delete: delete the entry keys instead of setting bool
269 flag on them, and also purge caches used by the dogpile
268 flag on them, and also purge caches used by the dogpile
270 """
269 """
271 repo = Repository.get_by_repo_name(repo_name)
270 repo = Repository.get_by_repo_name(repo_name)
272
271
273 if repo:
272 if repo:
274 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
273 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 repo_id=repo.repo_id)
274 repo_id=repo.repo_id)
276 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
275 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277
276
278 repo_id = repo.repo_id
277 repo_id = repo.repo_id
279 config = repo._config
278 config = repo._config
280 config.set('extensions', 'largefiles', '')
279 config.set('extensions', 'largefiles', '')
281 repo.update_commit_cache(config=config, cs_cache=None)
280 repo.update_commit_cache(config=config, cs_cache=None)
282 if delete:
281 if delete:
283 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
282 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
283 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285
284
286 def toggle_following_repo(self, follow_repo_id, user_id):
285 def toggle_following_repo(self, follow_repo_id, user_id):
287
286
288 f = self.sa.query(UserFollowing)\
287 f = self.sa.query(UserFollowing)\
289 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
288 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 .filter(UserFollowing.user_id == user_id).scalar()
289 .filter(UserFollowing.user_id == user_id).scalar()
291
290
292 if f is not None:
291 if f is not None:
293 try:
292 try:
294 self.sa.delete(f)
293 self.sa.delete(f)
295 return
294 return
296 except Exception:
295 except Exception:
297 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
298 raise
297 raise
299
298
300 try:
299 try:
301 f = UserFollowing()
300 f = UserFollowing()
302 f.user_id = user_id
301 f.user_id = user_id
303 f.follows_repo_id = follow_repo_id
302 f.follows_repo_id = follow_repo_id
304 self.sa.add(f)
303 self.sa.add(f)
305 except Exception:
304 except Exception:
306 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
307 raise
306 raise
308
307
309 def toggle_following_user(self, follow_user_id, user_id):
308 def toggle_following_user(self, follow_user_id, user_id):
310 f = self.sa.query(UserFollowing)\
309 f = self.sa.query(UserFollowing)\
311 .filter(UserFollowing.follows_user_id == follow_user_id)\
310 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 .filter(UserFollowing.user_id == user_id).scalar()
311 .filter(UserFollowing.user_id == user_id).scalar()
313
312
314 if f is not None:
313 if f is not None:
315 try:
314 try:
316 self.sa.delete(f)
315 self.sa.delete(f)
317 return
316 return
318 except Exception:
317 except Exception:
319 log.error(traceback.format_exc())
318 log.error(traceback.format_exc())
320 raise
319 raise
321
320
322 try:
321 try:
323 f = UserFollowing()
322 f = UserFollowing()
324 f.user_id = user_id
323 f.user_id = user_id
325 f.follows_user_id = follow_user_id
324 f.follows_user_id = follow_user_id
326 self.sa.add(f)
325 self.sa.add(f)
327 except Exception:
326 except Exception:
328 log.error(traceback.format_exc())
327 log.error(traceback.format_exc())
329 raise
328 raise
330
329
331 def is_following_repo(self, repo_name, user_id, cache=False):
330 def is_following_repo(self, repo_name, user_id, cache=False):
332 r = self.sa.query(Repository)\
331 r = self.sa.query(Repository)\
333 .filter(Repository.repo_name == repo_name).scalar()
332 .filter(Repository.repo_name == repo_name).scalar()
334
333
335 f = self.sa.query(UserFollowing)\
334 f = self.sa.query(UserFollowing)\
336 .filter(UserFollowing.follows_repository == r)\
335 .filter(UserFollowing.follows_repository == r)\
337 .filter(UserFollowing.user_id == user_id).scalar()
336 .filter(UserFollowing.user_id == user_id).scalar()
338
337
339 return f is not None
338 return f is not None
340
339
341 def is_following_user(self, username, user_id, cache=False):
340 def is_following_user(self, username, user_id, cache=False):
342 u = User.get_by_username(username)
341 u = User.get_by_username(username)
343
342
344 f = self.sa.query(UserFollowing)\
343 f = self.sa.query(UserFollowing)\
345 .filter(UserFollowing.follows_user == u)\
344 .filter(UserFollowing.follows_user == u)\
346 .filter(UserFollowing.user_id == user_id).scalar()
345 .filter(UserFollowing.user_id == user_id).scalar()
347
346
348 return f is not None
347 return f is not None
349
348
350 def get_followers(self, repo):
349 def get_followers(self, repo):
351 repo = self._get_repo(repo)
350 repo = self._get_repo(repo)
352
351
353 return self.sa.query(UserFollowing)\
352 return self.sa.query(UserFollowing)\
354 .filter(UserFollowing.follows_repository == repo).count()
353 .filter(UserFollowing.follows_repository == repo).count()
355
354
356 def get_forks(self, repo):
355 def get_forks(self, repo):
357 repo = self._get_repo(repo)
356 repo = self._get_repo(repo)
358 return self.sa.query(Repository)\
357 return self.sa.query(Repository)\
359 .filter(Repository.fork == repo).count()
358 .filter(Repository.fork == repo).count()
360
359
361 def get_pull_requests(self, repo):
360 def get_pull_requests(self, repo):
362 repo = self._get_repo(repo)
361 repo = self._get_repo(repo)
363 return self.sa.query(PullRequest)\
362 return self.sa.query(PullRequest)\
364 .filter(PullRequest.target_repo == repo)\
363 .filter(PullRequest.target_repo == repo)\
365 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
364 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366
365
367 def get_artifacts(self, repo):
366 def get_artifacts(self, repo):
368 repo = self._get_repo(repo)
367 repo = self._get_repo(repo)
369 return self.sa.query(FileStore)\
368 return self.sa.query(FileStore)\
370 .filter(FileStore.repo == repo)\
369 .filter(FileStore.repo == repo)\
371 .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count()
370 .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count()
372
371
373 def mark_as_fork(self, repo, fork, user):
372 def mark_as_fork(self, repo, fork, user):
374 repo = self._get_repo(repo)
373 repo = self._get_repo(repo)
375 fork = self._get_repo(fork)
374 fork = self._get_repo(fork)
376 if fork and repo.repo_id == fork.repo_id:
375 if fork and repo.repo_id == fork.repo_id:
377 raise Exception("Cannot set repository as fork of itself")
376 raise Exception("Cannot set repository as fork of itself")
378
377
379 if fork and repo.repo_type != fork.repo_type:
378 if fork and repo.repo_type != fork.repo_type:
380 raise RepositoryError(
379 raise RepositoryError(
381 "Cannot set repository as fork of repository with other type")
380 "Cannot set repository as fork of repository with other type")
382
381
383 repo.fork = fork
382 repo.fork = fork
384 self.sa.add(repo)
383 self.sa.add(repo)
385 return repo
384 return repo
386
385
387 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
386 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
388 dbrepo = self._get_repo(repo)
387 dbrepo = self._get_repo(repo)
389 remote_uri = remote_uri or dbrepo.clone_uri
388 remote_uri = remote_uri or dbrepo.clone_uri
390 if not remote_uri:
389 if not remote_uri:
391 raise Exception("This repository doesn't have a clone uri")
390 raise Exception("This repository doesn't have a clone uri")
392
391
393 repo = dbrepo.scm_instance(cache=False)
392 repo = dbrepo.scm_instance(cache=False)
394 repo.config.clear_section('hooks')
393 repo.config.clear_section('hooks')
395
394
396 try:
395 try:
397 # NOTE(marcink): add extra validation so we skip invalid urls
396 # NOTE(marcink): add extra validation so we skip invalid urls
398 # this is due this tasks can be executed via scheduler without
397 # this is due this tasks can be executed via scheduler without
399 # proper validation of remote_uri
398 # proper validation of remote_uri
400 if validate_uri:
399 if validate_uri:
401 config = make_db_config(clear_session=False)
400 config = make_db_config(clear_session=False)
402 url_validator(remote_uri, dbrepo.repo_type, config)
401 url_validator(remote_uri, dbrepo.repo_type, config)
403 except InvalidCloneUrl:
402 except InvalidCloneUrl:
404 raise
403 raise
405
404
406 repo_name = dbrepo.repo_name
405 repo_name = dbrepo.repo_name
407 try:
406 try:
408 # TODO: we need to make sure those operations call proper hooks !
407 # TODO: we need to make sure those operations call proper hooks !
409 repo.fetch(remote_uri)
408 repo.fetch(remote_uri)
410
409
411 self.mark_for_invalidation(repo_name)
410 self.mark_for_invalidation(repo_name)
412 except Exception:
411 except Exception:
413 log.error(traceback.format_exc())
412 log.error(traceback.format_exc())
414 raise
413 raise
415
414
416 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
415 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
417 dbrepo = self._get_repo(repo)
416 dbrepo = self._get_repo(repo)
418 remote_uri = remote_uri or dbrepo.push_uri
417 remote_uri = remote_uri or dbrepo.push_uri
419 if not remote_uri:
418 if not remote_uri:
420 raise Exception("This repository doesn't have a clone uri")
419 raise Exception("This repository doesn't have a clone uri")
421
420
422 repo = dbrepo.scm_instance(cache=False)
421 repo = dbrepo.scm_instance(cache=False)
423 repo.config.clear_section('hooks')
422 repo.config.clear_section('hooks')
424
423
425 try:
424 try:
426 # NOTE(marcink): add extra validation so we skip invalid urls
425 # NOTE(marcink): add extra validation so we skip invalid urls
427 # this is due this tasks can be executed via scheduler without
426 # this is due this tasks can be executed via scheduler without
428 # proper validation of remote_uri
427 # proper validation of remote_uri
429 if validate_uri:
428 if validate_uri:
430 config = make_db_config(clear_session=False)
429 config = make_db_config(clear_session=False)
431 url_validator(remote_uri, dbrepo.repo_type, config)
430 url_validator(remote_uri, dbrepo.repo_type, config)
432 except InvalidCloneUrl:
431 except InvalidCloneUrl:
433 raise
432 raise
434
433
435 try:
434 try:
436 repo.push(remote_uri)
435 repo.push(remote_uri)
437 except Exception:
436 except Exception:
438 log.error(traceback.format_exc())
437 log.error(traceback.format_exc())
439 raise
438 raise
440
439
441 def commit_change(self, repo, repo_name, commit, user, author, message,
440 def commit_change(self, repo, repo_name, commit, user, author, message,
442 content, f_path):
441 content, f_path):
443 """
442 """
444 Commits changes
443 Commits changes
445
444
446 :param repo: SCM instance
445 :param repo: SCM instance
447
446
448 """
447 """
449 user = self._get_user(user)
448 user = self._get_user(user)
450
449
451 # decoding here will force that we have proper encoded values
450 # decoding here will force that we have proper encoded values
452 # in any other case this will throw exceptions and deny commit
451 # in any other case this will throw exceptions and deny commit
453 content = safe_str(content)
452 content = safe_str(content)
454 path = safe_str(f_path)
453 path = safe_str(f_path)
455 # message and author needs to be unicode
454 # message and author needs to be unicode
456 # proper backend should then translate that into required type
455 # proper backend should then translate that into required type
457 message = safe_unicode(message)
456 message = safe_unicode(message)
458 author = safe_unicode(author)
457 author = safe_unicode(author)
459 imc = repo.in_memory_commit
458 imc = repo.in_memory_commit
460 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
459 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
461 try:
460 try:
462 # TODO: handle pre-push action !
461 # TODO: handle pre-push action !
463 tip = imc.commit(
462 tip = imc.commit(
464 message=message, author=author, parents=[commit],
463 message=message, author=author, parents=[commit],
465 branch=commit.branch)
464 branch=commit.branch)
466 except Exception as e:
465 except Exception as e:
467 log.error(traceback.format_exc())
466 log.error(traceback.format_exc())
468 raise IMCCommitError(str(e))
467 raise IMCCommitError(str(e))
469 finally:
468 finally:
470 # always clear caches, if commit fails we want fresh object also
469 # always clear caches, if commit fails we want fresh object also
471 self.mark_for_invalidation(repo_name)
470 self.mark_for_invalidation(repo_name)
472
471
473 # We trigger the post-push action
472 # We trigger the post-push action
474 hooks_utils.trigger_post_push_hook(
473 hooks_utils.trigger_post_push_hook(
475 username=user.username, action='push_local', hook_type='post_push',
474 username=user.username, action='push_local', hook_type='post_push',
476 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
475 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
477 return tip
476 return tip
478
477
479 def _sanitize_path(self, f_path):
478 def _sanitize_path(self, f_path):
480 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
479 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
481 raise NonRelativePathError('%s is not an relative path' % f_path)
480 raise NonRelativePathError('%s is not an relative path' % f_path)
482 if f_path:
481 if f_path:
483 f_path = os.path.normpath(f_path)
482 f_path = os.path.normpath(f_path)
484 return f_path
483 return f_path
485
484
486 def get_dirnode_metadata(self, request, commit, dir_node):
485 def get_dirnode_metadata(self, request, commit, dir_node):
487 if not dir_node.is_dir():
486 if not dir_node.is_dir():
488 return []
487 return []
489
488
490 data = []
489 data = []
491 for node in dir_node:
490 for node in dir_node:
492 if not node.is_file():
491 if not node.is_file():
493 # we skip file-nodes
492 # we skip file-nodes
494 continue
493 continue
495
494
496 last_commit = node.last_commit
495 last_commit = node.last_commit
497 last_commit_date = last_commit.date
496 last_commit_date = last_commit.date
498 data.append({
497 data.append({
499 'name': node.name,
498 'name': node.name,
500 'size': h.format_byte_size_binary(node.size),
499 'size': h.format_byte_size_binary(node.size),
501 'modified_at': h.format_date(last_commit_date),
500 'modified_at': h.format_date(last_commit_date),
502 'modified_ts': last_commit_date.isoformat(),
501 'modified_ts': last_commit_date.isoformat(),
503 'revision': last_commit.revision,
502 'revision': last_commit.revision,
504 'short_id': last_commit.short_id,
503 'short_id': last_commit.short_id,
505 'message': h.escape(last_commit.message),
504 'message': h.escape(last_commit.message),
506 'author': h.escape(last_commit.author),
505 'author': h.escape(last_commit.author),
507 'user_profile': h.gravatar_with_user(
506 'user_profile': h.gravatar_with_user(
508 request, last_commit.author),
507 request, last_commit.author),
509 })
508 })
510
509
511 return data
510 return data
512
511
513 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
512 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
514 extended_info=False, content=False, max_file_bytes=None):
513 extended_info=False, content=False, max_file_bytes=None):
515 """
514 """
516 recursive walk in root dir and return a set of all path in that dir
515 recursive walk in root dir and return a set of all path in that dir
517 based on repository walk function
516 based on repository walk function
518
517
519 :param repo_name: name of repository
518 :param repo_name: name of repository
520 :param commit_id: commit id for which to list nodes
519 :param commit_id: commit id for which to list nodes
521 :param root_path: root path to list
520 :param root_path: root path to list
522 :param flat: return as a list, if False returns a dict with description
521 :param flat: return as a list, if False returns a dict with description
523 :param extended_info: show additional info such as md5, binary, size etc
522 :param extended_info: show additional info such as md5, binary, size etc
524 :param content: add nodes content to the return data
523 :param content: add nodes content to the return data
525 :param max_file_bytes: will not return file contents over this limit
524 :param max_file_bytes: will not return file contents over this limit
526
525
527 """
526 """
528 _files = list()
527 _files = list()
529 _dirs = list()
528 _dirs = list()
530 try:
529 try:
531 _repo = self._get_repo(repo_name)
530 _repo = self._get_repo(repo_name)
532 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
531 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
533 root_path = root_path.lstrip('/')
532 root_path = root_path.lstrip('/')
534 for __, dirs, files in commit.walk(root_path):
533 for __, dirs, files in commit.walk(root_path):
535
534
536 for f in files:
535 for f in files:
537 _content = None
536 _content = None
538 _data = f_name = f.unicode_path
537 _data = f_name = f.unicode_path
539
538
540 if not flat:
539 if not flat:
541 _data = {
540 _data = {
542 "name": h.escape(f_name),
541 "name": h.escape(f_name),
543 "type": "file",
542 "type": "file",
544 }
543 }
545 if extended_info:
544 if extended_info:
546 _data.update({
545 _data.update({
547 "md5": f.md5,
546 "md5": f.md5,
548 "binary": f.is_binary,
547 "binary": f.is_binary,
549 "size": f.size,
548 "size": f.size,
550 "extension": f.extension,
549 "extension": f.extension,
551 "mimetype": f.mimetype,
550 "mimetype": f.mimetype,
552 "lines": f.lines()[0]
551 "lines": f.lines()[0]
553 })
552 })
554
553
555 if content:
554 if content:
556 over_size_limit = (max_file_bytes is not None
555 over_size_limit = (max_file_bytes is not None
557 and f.size > max_file_bytes)
556 and f.size > max_file_bytes)
558 full_content = None
557 full_content = None
559 if not f.is_binary and not over_size_limit:
558 if not f.is_binary and not over_size_limit:
560 full_content = safe_str(f.content)
559 full_content = safe_str(f.content)
561
560
562 _data.update({
561 _data.update({
563 "content": full_content,
562 "content": full_content,
564 })
563 })
565 _files.append(_data)
564 _files.append(_data)
566
565
567 for d in dirs:
566 for d in dirs:
568 _data = d_name = d.unicode_path
567 _data = d_name = d.unicode_path
569 if not flat:
568 if not flat:
570 _data = {
569 _data = {
571 "name": h.escape(d_name),
570 "name": h.escape(d_name),
572 "type": "dir",
571 "type": "dir",
573 }
572 }
574 if extended_info:
573 if extended_info:
575 _data.update({
574 _data.update({
576 "md5": None,
575 "md5": None,
577 "binary": None,
576 "binary": None,
578 "size": None,
577 "size": None,
579 "extension": None,
578 "extension": None,
580 })
579 })
581 if content:
580 if content:
582 _data.update({
581 _data.update({
583 "content": None
582 "content": None
584 })
583 })
585 _dirs.append(_data)
584 _dirs.append(_data)
586 except RepositoryError:
585 except RepositoryError:
587 log.exception("Exception in get_nodes")
586 log.exception("Exception in get_nodes")
588 raise
587 raise
589
588
590 return _dirs, _files
589 return _dirs, _files
591
590
592 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
591 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
593 """
592 """
594 Generate files for quick filter in files view
593 Generate files for quick filter in files view
595 """
594 """
596
595
597 _files = list()
596 _files = list()
598 _dirs = list()
597 _dirs = list()
599 try:
598 try:
600 _repo = self._get_repo(repo_name)
599 _repo = self._get_repo(repo_name)
601 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
600 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
602 root_path = root_path.lstrip('/')
601 root_path = root_path.lstrip('/')
603 for __, dirs, files in commit.walk(root_path):
602 for __, dirs, files in commit.walk(root_path):
604
603
605 for f in files:
604 for f in files:
606
605
607 _data = {
606 _data = {
608 "name": h.escape(f.unicode_path),
607 "name": h.escape(f.unicode_path),
609 "type": "file",
608 "type": "file",
610 }
609 }
611
610
612 _files.append(_data)
611 _files.append(_data)
613
612
614 for d in dirs:
613 for d in dirs:
615
614
616 _data = {
615 _data = {
617 "name": h.escape(d.unicode_path),
616 "name": h.escape(d.unicode_path),
618 "type": "dir",
617 "type": "dir",
619 }
618 }
620
619
621 _dirs.append(_data)
620 _dirs.append(_data)
622 except RepositoryError:
621 except RepositoryError:
623 log.exception("Exception in get_quick_filter_nodes")
622 log.exception("Exception in get_quick_filter_nodes")
624 raise
623 raise
625
624
626 return _dirs, _files
625 return _dirs, _files
627
626
628 def get_node(self, repo_name, commit_id, file_path,
627 def get_node(self, repo_name, commit_id, file_path,
629 extended_info=False, content=False, max_file_bytes=None, cache=True):
628 extended_info=False, content=False, max_file_bytes=None, cache=True):
630 """
629 """
631 retrieve single node from commit
630 retrieve single node from commit
632 """
631 """
633 try:
632 try:
634
633
635 _repo = self._get_repo(repo_name)
634 _repo = self._get_repo(repo_name)
636 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
635 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
637
636
638 file_node = commit.get_node(file_path)
637 file_node = commit.get_node(file_path)
639 if file_node.is_dir():
638 if file_node.is_dir():
640 raise RepositoryError('The given path is a directory')
639 raise RepositoryError('The given path is a directory')
641
640
642 _content = None
641 _content = None
643 f_name = file_node.unicode_path
642 f_name = file_node.unicode_path
644
643
645 file_data = {
644 file_data = {
646 "name": h.escape(f_name),
645 "name": h.escape(f_name),
647 "type": "file",
646 "type": "file",
648 }
647 }
649
648
650 if extended_info:
649 if extended_info:
651 file_data.update({
650 file_data.update({
652 "extension": file_node.extension,
651 "extension": file_node.extension,
653 "mimetype": file_node.mimetype,
652 "mimetype": file_node.mimetype,
654 })
653 })
655
654
656 if cache:
655 if cache:
657 md5 = file_node.md5
656 md5 = file_node.md5
658 is_binary = file_node.is_binary
657 is_binary = file_node.is_binary
659 size = file_node.size
658 size = file_node.size
660 else:
659 else:
661 is_binary, md5, size, _content = file_node.metadata_uncached()
660 is_binary, md5, size, _content = file_node.metadata_uncached()
662
661
663 file_data.update({
662 file_data.update({
664 "md5": md5,
663 "md5": md5,
665 "binary": is_binary,
664 "binary": is_binary,
666 "size": size,
665 "size": size,
667 })
666 })
668
667
669 if content and cache:
668 if content and cache:
670 # get content + cache
669 # get content + cache
671 size = file_node.size
670 size = file_node.size
672 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
671 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
673 full_content = None
672 full_content = None
674 all_lines = 0
673 all_lines = 0
675 if not file_node.is_binary and not over_size_limit:
674 if not file_node.is_binary and not over_size_limit:
676 full_content = safe_unicode(file_node.content)
675 full_content = safe_unicode(file_node.content)
677 all_lines, empty_lines = file_node.count_lines(full_content)
676 all_lines, empty_lines = file_node.count_lines(full_content)
678
677
679 file_data.update({
678 file_data.update({
680 "content": full_content,
679 "content": full_content,
681 "lines": all_lines
680 "lines": all_lines
682 })
681 })
683 elif content:
682 elif content:
684 # get content *without* cache
683 # get content *without* cache
685 if _content is None:
684 if _content is None:
686 is_binary, md5, size, _content = file_node.metadata_uncached()
685 is_binary, md5, size, _content = file_node.metadata_uncached()
687
686
688 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
687 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
689 full_content = None
688 full_content = None
690 all_lines = 0
689 all_lines = 0
691 if not is_binary and not over_size_limit:
690 if not is_binary and not over_size_limit:
692 full_content = safe_unicode(_content)
691 full_content = safe_unicode(_content)
693 all_lines, empty_lines = file_node.count_lines(full_content)
692 all_lines, empty_lines = file_node.count_lines(full_content)
694
693
695 file_data.update({
694 file_data.update({
696 "content": full_content,
695 "content": full_content,
697 "lines": all_lines
696 "lines": all_lines
698 })
697 })
699
698
700 except RepositoryError:
699 except RepositoryError:
701 log.exception("Exception in get_node")
700 log.exception("Exception in get_node")
702 raise
701 raise
703
702
704 return file_data
703 return file_data
705
704
706 def get_fts_data(self, repo_name, commit_id, root_path='/'):
705 def get_fts_data(self, repo_name, commit_id, root_path='/'):
707 """
706 """
708 Fetch node tree for usage in full text search
707 Fetch node tree for usage in full text search
709 """
708 """
710
709
711 tree_info = list()
710 tree_info = list()
712
711
713 try:
712 try:
714 _repo = self._get_repo(repo_name)
713 _repo = self._get_repo(repo_name)
715 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
714 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
716 root_path = root_path.lstrip('/')
715 root_path = root_path.lstrip('/')
717 for __, dirs, files in commit.walk(root_path):
716 for __, dirs, files in commit.walk(root_path):
718
717
719 for f in files:
718 for f in files:
720 is_binary, md5, size, _content = f.metadata_uncached()
719 is_binary, md5, size, _content = f.metadata_uncached()
721 _data = {
720 _data = {
722 "name": f.unicode_path,
721 "name": f.unicode_path,
723 "md5": md5,
722 "md5": md5,
724 "extension": f.extension,
723 "extension": f.extension,
725 "binary": is_binary,
724 "binary": is_binary,
726 "size": size
725 "size": size
727 }
726 }
728
727
729 tree_info.append(_data)
728 tree_info.append(_data)
730
729
731 except RepositoryError:
730 except RepositoryError:
732 log.exception("Exception in get_nodes")
731 log.exception("Exception in get_nodes")
733 raise
732 raise
734
733
735 return tree_info
734 return tree_info
736
735
737 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
736 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
738 author=None, trigger_push_hook=True):
737 author=None, trigger_push_hook=True):
739 """
738 """
740 Commits given multiple nodes into repo
739 Commits given multiple nodes into repo
741
740
742 :param user: RhodeCode User object or user_id, the commiter
741 :param user: RhodeCode User object or user_id, the commiter
743 :param repo: RhodeCode Repository object
742 :param repo: RhodeCode Repository object
744 :param message: commit message
743 :param message: commit message
745 :param nodes: mapping {filename:{'content':content},...}
744 :param nodes: mapping {filename:{'content':content},...}
746 :param parent_commit: parent commit, can be empty than it's
745 :param parent_commit: parent commit, can be empty than it's
747 initial commit
746 initial commit
748 :param author: author of commit, cna be different that commiter
747 :param author: author of commit, cna be different that commiter
749 only for git
748 only for git
750 :param trigger_push_hook: trigger push hooks
749 :param trigger_push_hook: trigger push hooks
751
750
752 :returns: new commited commit
751 :returns: new commited commit
753 """
752 """
754
753
755 user = self._get_user(user)
754 user = self._get_user(user)
756 scm_instance = repo.scm_instance(cache=False)
755 scm_instance = repo.scm_instance(cache=False)
757
756
758 processed_nodes = []
757 processed_nodes = []
759 for f_path in nodes:
758 for f_path in nodes:
760 f_path = self._sanitize_path(f_path)
759 f_path = self._sanitize_path(f_path)
761 content = nodes[f_path]['content']
760 content = nodes[f_path]['content']
762 f_path = safe_str(f_path)
761 f_path = safe_str(f_path)
763 # decoding here will force that we have proper encoded values
762 # decoding here will force that we have proper encoded values
764 # in any other case this will throw exceptions and deny commit
763 # in any other case this will throw exceptions and deny commit
765 if isinstance(content, (basestring,)):
764 if isinstance(content, (basestring,)):
766 content = safe_str(content)
765 content = safe_str(content)
767 elif isinstance(content, (file, cStringIO.OutputType,)):
766 elif isinstance(content, (file, cStringIO.OutputType,)):
768 content = content.read()
767 content = content.read()
769 else:
768 else:
770 raise Exception('Content is of unrecognized type %s' % (
769 raise Exception('Content is of unrecognized type %s' % (
771 type(content)
770 type(content)
772 ))
771 ))
773 processed_nodes.append((f_path, content))
772 processed_nodes.append((f_path, content))
774
773
775 message = safe_unicode(message)
774 message = safe_unicode(message)
776 commiter = user.full_contact
775 commiter = user.full_contact
777 author = safe_unicode(author) if author else commiter
776 author = safe_unicode(author) if author else commiter
778
777
779 imc = scm_instance.in_memory_commit
778 imc = scm_instance.in_memory_commit
780
779
781 if not parent_commit:
780 if not parent_commit:
782 parent_commit = EmptyCommit(alias=scm_instance.alias)
781 parent_commit = EmptyCommit(alias=scm_instance.alias)
783
782
784 if isinstance(parent_commit, EmptyCommit):
783 if isinstance(parent_commit, EmptyCommit):
785 # EmptyCommit means we we're editing empty repository
784 # EmptyCommit means we we're editing empty repository
786 parents = None
785 parents = None
787 else:
786 else:
788 parents = [parent_commit]
787 parents = [parent_commit]
789 # add multiple nodes
788 # add multiple nodes
790 for path, content in processed_nodes:
789 for path, content in processed_nodes:
791 imc.add(FileNode(path, content=content))
790 imc.add(FileNode(path, content=content))
792 # TODO: handle pre push scenario
791 # TODO: handle pre push scenario
793 tip = imc.commit(message=message,
792 tip = imc.commit(message=message,
794 author=author,
793 author=author,
795 parents=parents,
794 parents=parents,
796 branch=parent_commit.branch)
795 branch=parent_commit.branch)
797
796
798 self.mark_for_invalidation(repo.repo_name)
797 self.mark_for_invalidation(repo.repo_name)
799 if trigger_push_hook:
798 if trigger_push_hook:
800 hooks_utils.trigger_post_push_hook(
799 hooks_utils.trigger_post_push_hook(
801 username=user.username, action='push_local',
800 username=user.username, action='push_local',
802 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
801 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
803 hook_type='post_push',
802 hook_type='post_push',
804 commit_ids=[tip.raw_id])
803 commit_ids=[tip.raw_id])
805 return tip
804 return tip
806
805
807 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
806 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
808 author=None, trigger_push_hook=True):
807 author=None, trigger_push_hook=True):
809 user = self._get_user(user)
808 user = self._get_user(user)
810 scm_instance = repo.scm_instance(cache=False)
809 scm_instance = repo.scm_instance(cache=False)
811
810
812 message = safe_unicode(message)
811 message = safe_unicode(message)
813 commiter = user.full_contact
812 commiter = user.full_contact
814 author = safe_unicode(author) if author else commiter
813 author = safe_unicode(author) if author else commiter
815
814
816 imc = scm_instance.in_memory_commit
815 imc = scm_instance.in_memory_commit
817
816
818 if not parent_commit:
817 if not parent_commit:
819 parent_commit = EmptyCommit(alias=scm_instance.alias)
818 parent_commit = EmptyCommit(alias=scm_instance.alias)
820
819
821 if isinstance(parent_commit, EmptyCommit):
820 if isinstance(parent_commit, EmptyCommit):
822 # EmptyCommit means we we're editing empty repository
821 # EmptyCommit means we we're editing empty repository
823 parents = None
822 parents = None
824 else:
823 else:
825 parents = [parent_commit]
824 parents = [parent_commit]
826
825
827 # add multiple nodes
826 # add multiple nodes
828 for _filename, data in nodes.items():
827 for _filename, data in nodes.items():
829 # new filename, can be renamed from the old one, also sanitaze
828 # new filename, can be renamed from the old one, also sanitaze
830 # the path for any hack around relative paths like ../../ etc.
829 # the path for any hack around relative paths like ../../ etc.
831 filename = self._sanitize_path(data['filename'])
830 filename = self._sanitize_path(data['filename'])
832 old_filename = self._sanitize_path(_filename)
831 old_filename = self._sanitize_path(_filename)
833 content = data['content']
832 content = data['content']
834 file_mode = data.get('mode')
833 file_mode = data.get('mode')
835 filenode = FileNode(old_filename, content=content, mode=file_mode)
834 filenode = FileNode(old_filename, content=content, mode=file_mode)
836 op = data['op']
835 op = data['op']
837 if op == 'add':
836 if op == 'add':
838 imc.add(filenode)
837 imc.add(filenode)
839 elif op == 'del':
838 elif op == 'del':
840 imc.remove(filenode)
839 imc.remove(filenode)
841 elif op == 'mod':
840 elif op == 'mod':
842 if filename != old_filename:
841 if filename != old_filename:
843 # TODO: handle renames more efficient, needs vcs lib changes
842 # TODO: handle renames more efficient, needs vcs lib changes
844 imc.remove(filenode)
843 imc.remove(filenode)
845 imc.add(FileNode(filename, content=content, mode=file_mode))
844 imc.add(FileNode(filename, content=content, mode=file_mode))
846 else:
845 else:
847 imc.change(filenode)
846 imc.change(filenode)
848
847
849 try:
848 try:
850 # TODO: handle pre push scenario commit changes
849 # TODO: handle pre push scenario commit changes
851 tip = imc.commit(message=message,
850 tip = imc.commit(message=message,
852 author=author,
851 author=author,
853 parents=parents,
852 parents=parents,
854 branch=parent_commit.branch)
853 branch=parent_commit.branch)
855 except NodeNotChangedError:
854 except NodeNotChangedError:
856 raise
855 raise
857 except Exception as e:
856 except Exception as e:
858 log.exception("Unexpected exception during call to imc.commit")
857 log.exception("Unexpected exception during call to imc.commit")
859 raise IMCCommitError(str(e))
858 raise IMCCommitError(str(e))
860 finally:
859 finally:
861 # always clear caches, if commit fails we want fresh object also
860 # always clear caches, if commit fails we want fresh object also
862 self.mark_for_invalidation(repo.repo_name)
861 self.mark_for_invalidation(repo.repo_name)
863
862
864 if trigger_push_hook:
863 if trigger_push_hook:
865 hooks_utils.trigger_post_push_hook(
864 hooks_utils.trigger_post_push_hook(
866 username=user.username, action='push_local', hook_type='post_push',
865 username=user.username, action='push_local', hook_type='post_push',
867 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
866 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
868 commit_ids=[tip.raw_id])
867 commit_ids=[tip.raw_id])
869
868
870 return tip
869 return tip
871
870
872 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
871 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
873 author=None, trigger_push_hook=True):
872 author=None, trigger_push_hook=True):
874 """
873 """
875 Deletes given multiple nodes into `repo`
874 Deletes given multiple nodes into `repo`
876
875
877 :param user: RhodeCode User object or user_id, the committer
876 :param user: RhodeCode User object or user_id, the committer
878 :param repo: RhodeCode Repository object
877 :param repo: RhodeCode Repository object
879 :param message: commit message
878 :param message: commit message
880 :param nodes: mapping {filename:{'content':content},...}
879 :param nodes: mapping {filename:{'content':content},...}
881 :param parent_commit: parent commit, can be empty than it's initial
880 :param parent_commit: parent commit, can be empty than it's initial
882 commit
881 commit
883 :param author: author of commit, cna be different that commiter only
882 :param author: author of commit, cna be different that commiter only
884 for git
883 for git
885 :param trigger_push_hook: trigger push hooks
884 :param trigger_push_hook: trigger push hooks
886
885
887 :returns: new commit after deletion
886 :returns: new commit after deletion
888 """
887 """
889
888
890 user = self._get_user(user)
889 user = self._get_user(user)
891 scm_instance = repo.scm_instance(cache=False)
890 scm_instance = repo.scm_instance(cache=False)
892
891
893 processed_nodes = []
892 processed_nodes = []
894 for f_path in nodes:
893 for f_path in nodes:
895 f_path = self._sanitize_path(f_path)
894 f_path = self._sanitize_path(f_path)
896 # content can be empty but for compatabilty it allows same dicts
895 # content can be empty but for compatabilty it allows same dicts
897 # structure as add_nodes
896 # structure as add_nodes
898 content = nodes[f_path].get('content')
897 content = nodes[f_path].get('content')
899 processed_nodes.append((f_path, content))
898 processed_nodes.append((f_path, content))
900
899
901 message = safe_unicode(message)
900 message = safe_unicode(message)
902 commiter = user.full_contact
901 commiter = user.full_contact
903 author = safe_unicode(author) if author else commiter
902 author = safe_unicode(author) if author else commiter
904
903
905 imc = scm_instance.in_memory_commit
904 imc = scm_instance.in_memory_commit
906
905
907 if not parent_commit:
906 if not parent_commit:
908 parent_commit = EmptyCommit(alias=scm_instance.alias)
907 parent_commit = EmptyCommit(alias=scm_instance.alias)
909
908
910 if isinstance(parent_commit, EmptyCommit):
909 if isinstance(parent_commit, EmptyCommit):
911 # EmptyCommit means we we're editing empty repository
910 # EmptyCommit means we we're editing empty repository
912 parents = None
911 parents = None
913 else:
912 else:
914 parents = [parent_commit]
913 parents = [parent_commit]
915 # add multiple nodes
914 # add multiple nodes
916 for path, content in processed_nodes:
915 for path, content in processed_nodes:
917 imc.remove(FileNode(path, content=content))
916 imc.remove(FileNode(path, content=content))
918
917
919 # TODO: handle pre push scenario
918 # TODO: handle pre push scenario
920 tip = imc.commit(message=message,
919 tip = imc.commit(message=message,
921 author=author,
920 author=author,
922 parents=parents,
921 parents=parents,
923 branch=parent_commit.branch)
922 branch=parent_commit.branch)
924
923
925 self.mark_for_invalidation(repo.repo_name)
924 self.mark_for_invalidation(repo.repo_name)
926 if trigger_push_hook:
925 if trigger_push_hook:
927 hooks_utils.trigger_post_push_hook(
926 hooks_utils.trigger_post_push_hook(
928 username=user.username, action='push_local', hook_type='post_push',
927 username=user.username, action='push_local', hook_type='post_push',
929 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
928 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
930 commit_ids=[tip.raw_id])
929 commit_ids=[tip.raw_id])
931 return tip
930 return tip
932
931
933 def strip(self, repo, commit_id, branch):
932 def strip(self, repo, commit_id, branch):
934 scm_instance = repo.scm_instance(cache=False)
933 scm_instance = repo.scm_instance(cache=False)
935 scm_instance.config.clear_section('hooks')
934 scm_instance.config.clear_section('hooks')
936 scm_instance.strip(commit_id, branch)
935 scm_instance.strip(commit_id, branch)
937 self.mark_for_invalidation(repo.repo_name)
936 self.mark_for_invalidation(repo.repo_name)
938
937
939 def get_unread_journal(self):
938 def get_unread_journal(self):
940 return self.sa.query(UserLog).count()
939 return self.sa.query(UserLog).count()
941
940
942 @classmethod
941 @classmethod
943 def backend_landing_ref(cls, repo_type):
942 def backend_landing_ref(cls, repo_type):
944 """
943 """
945 Return a default landing ref based on a repository type.
944 Return a default landing ref based on a repository type.
946 """
945 """
947
946
948 landing_ref = {
947 landing_ref = {
949 'hg': ('branch:default', 'default'),
948 'hg': ('branch:default', 'default'),
950 'git': ('branch:master', 'master'),
949 'git': ('branch:master', 'master'),
951 'svn': ('rev:tip', 'latest tip'),
950 'svn': ('rev:tip', 'latest tip'),
952 'default': ('rev:tip', 'latest tip'),
951 'default': ('rev:tip', 'latest tip'),
953 }
952 }
954
953
955 return landing_ref.get(repo_type) or landing_ref['default']
954 return landing_ref.get(repo_type) or landing_ref['default']
956
955
957 def get_repo_landing_revs(self, translator, repo=None):
956 def get_repo_landing_revs(self, translator, repo=None):
958 """
957 """
959 Generates select option with tags branches and bookmarks (for hg only)
958 Generates select option with tags branches and bookmarks (for hg only)
960 grouped by type
959 grouped by type
961
960
962 :param repo:
961 :param repo:
963 """
962 """
964 _ = translator
963 _ = translator
965 repo = self._get_repo(repo)
964 repo = self._get_repo(repo)
966
965
967 if repo:
966 if repo:
968 repo_type = repo.repo_type
967 repo_type = repo.repo_type
969 else:
968 else:
970 repo_type = 'default'
969 repo_type = 'default'
971
970
972 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
971 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
973
972
974 default_ref_options = [
973 default_ref_options = [
975 [default_landing_ref, landing_ref_lbl]
974 [default_landing_ref, landing_ref_lbl]
976 ]
975 ]
977 default_choices = [
976 default_choices = [
978 default_landing_ref
977 default_landing_ref
979 ]
978 ]
980
979
981 if not repo:
980 if not repo:
982 return default_choices, default_ref_options
981 return default_choices, default_ref_options
983
982
984 repo = repo.scm_instance()
983 repo = repo.scm_instance()
985
984
986 ref_options = [('rev:tip', 'latest tip')]
985 ref_options = [('rev:tip', 'latest tip')]
987 choices = ['rev:tip']
986 choices = ['rev:tip']
988
987
989 # branches
988 # branches
990 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
989 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
991 if not branch_group:
990 if not branch_group:
992 # new repo, or without maybe a branch?
991 # new repo, or without maybe a branch?
993 branch_group = default_ref_options
992 branch_group = default_ref_options
994
993
995 branches_group = (branch_group, _("Branches"))
994 branches_group = (branch_group, _("Branches"))
996 ref_options.append(branches_group)
995 ref_options.append(branches_group)
997 choices.extend([x[0] for x in branches_group[0]])
996 choices.extend([x[0] for x in branches_group[0]])
998
997
999 # bookmarks for HG
998 # bookmarks for HG
1000 if repo.alias == 'hg':
999 if repo.alias == 'hg':
1001 bookmarks_group = (
1000 bookmarks_group = (
1002 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
1001 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
1003 for b in repo.bookmarks],
1002 for b in repo.bookmarks],
1004 _("Bookmarks"))
1003 _("Bookmarks"))
1005 ref_options.append(bookmarks_group)
1004 ref_options.append(bookmarks_group)
1006 choices.extend([x[0] for x in bookmarks_group[0]])
1005 choices.extend([x[0] for x in bookmarks_group[0]])
1007
1006
1008 # tags
1007 # tags
1009 tags_group = (
1008 tags_group = (
1010 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1009 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1011 for t in repo.tags],
1010 for t in repo.tags],
1012 _("Tags"))
1011 _("Tags"))
1013 ref_options.append(tags_group)
1012 ref_options.append(tags_group)
1014 choices.extend([x[0] for x in tags_group[0]])
1013 choices.extend([x[0] for x in tags_group[0]])
1015
1014
1016 return choices, ref_options
1015 return choices, ref_options
1017
1016
1018 def get_server_info(self, environ=None):
1017 def get_server_info(self, environ=None):
1019 server_info = get_system_info(environ)
1018 server_info = get_system_info(environ)
1020 return server_info
1019 return server_info
@@ -1,389 +1,390 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
35 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
36 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
36 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
37 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
37 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
38 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
38 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
39 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
39 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
40 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
40 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
41 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
41 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
42 pyroutes.register('admin_home', '/_admin', []);
42 pyroutes.register('admin_home', '/_admin', []);
43 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
43 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
44 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
44 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
45 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
45 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
48 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
48 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
49 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
49 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
50 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
50 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
51 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
51 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
52 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
52 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
53 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
53 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
54 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
54 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
55 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
55 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
56 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
56 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
57 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
57 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
58 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
58 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
59 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
59 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
60 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
60 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
62 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
62 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
63 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
63 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
64 pyroutes.register('admin_settings', '/_admin/settings', []);
64 pyroutes.register('admin_settings', '/_admin/settings', []);
65 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
65 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
66 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
66 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
67 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
67 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
68 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
68 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
69 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
69 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
70 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
70 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
71 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
71 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
72 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
72 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
73 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
73 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
74 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
74 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
75 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
75 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
76 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
76 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
77 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
77 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
78 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
78 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
79 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
79 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
80 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
80 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
81 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
81 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
82 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
82 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
83 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
83 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
84 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
84 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
85 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
85 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
86 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
86 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
87 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
87 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
88 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
88 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
89 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
89 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
90 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
90 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
91 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
91 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
92 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
92 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
93 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
93 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
94 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
94 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
95 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
95 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
96 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
96 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
97 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
97 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
98 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
98 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
99 pyroutes.register('users', '/_admin/users', []);
99 pyroutes.register('users', '/_admin/users', []);
100 pyroutes.register('users_data', '/_admin/users_data', []);
100 pyroutes.register('users_data', '/_admin/users_data', []);
101 pyroutes.register('users_create', '/_admin/users/create', []);
101 pyroutes.register('users_create', '/_admin/users/create', []);
102 pyroutes.register('users_new', '/_admin/users/new', []);
102 pyroutes.register('users_new', '/_admin/users/new', []);
103 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
103 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
104 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
104 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
105 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
105 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
106 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
106 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
107 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
107 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
108 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
108 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
109 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
109 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
110 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
110 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
111 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
112 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
112 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
113 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
117 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
117 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
118 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
118 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
119 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
119 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
120 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
120 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
121 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
121 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
122 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
122 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
123 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
123 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
124 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
124 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
125 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
125 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
126 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
126 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
127 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
127 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
128 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
128 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
129 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
129 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
130 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
130 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
131 pyroutes.register('user_groups', '/_admin/user_groups', []);
131 pyroutes.register('user_groups', '/_admin/user_groups', []);
132 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
132 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
133 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
133 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
134 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
134 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
135 pyroutes.register('repos', '/_admin/repos', []);
135 pyroutes.register('repos', '/_admin/repos', []);
136 pyroutes.register('repos_data', '/_admin/repos_data', []);
136 pyroutes.register('repo_new', '/_admin/repos/new', []);
137 pyroutes.register('repo_new', '/_admin/repos/new', []);
137 pyroutes.register('repo_create', '/_admin/repos/create', []);
138 pyroutes.register('repo_create', '/_admin/repos/create', []);
138 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
139 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
139 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
140 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
140 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
141 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
141 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
142 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
142 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
143 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
143 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
144 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
144 pyroutes.register('channelstream_proxy', '/_channelstream', []);
145 pyroutes.register('channelstream_proxy', '/_channelstream', []);
145 pyroutes.register('upload_file', '/_file_store/upload', []);
146 pyroutes.register('upload_file', '/_file_store/upload', []);
146 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
147 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
147 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
148 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
148 pyroutes.register('logout', '/_admin/logout', []);
149 pyroutes.register('logout', '/_admin/logout', []);
149 pyroutes.register('reset_password', '/_admin/password_reset', []);
150 pyroutes.register('reset_password', '/_admin/password_reset', []);
150 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
151 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
151 pyroutes.register('home', '/', []);
152 pyroutes.register('home', '/', []);
152 pyroutes.register('user_autocomplete_data', '/_users', []);
153 pyroutes.register('user_autocomplete_data', '/_users', []);
153 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
154 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
154 pyroutes.register('repo_list_data', '/_repos', []);
155 pyroutes.register('repo_list_data', '/_repos', []);
155 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
156 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
156 pyroutes.register('goto_switcher_data', '/_goto_data', []);
157 pyroutes.register('goto_switcher_data', '/_goto_data', []);
157 pyroutes.register('markup_preview', '/_markup_preview', []);
158 pyroutes.register('markup_preview', '/_markup_preview', []);
158 pyroutes.register('file_preview', '/_file_preview', []);
159 pyroutes.register('file_preview', '/_file_preview', []);
159 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
160 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
160 pyroutes.register('journal', '/_admin/journal', []);
161 pyroutes.register('journal', '/_admin/journal', []);
161 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
162 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
162 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
163 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
163 pyroutes.register('journal_public', '/_admin/public_journal', []);
164 pyroutes.register('journal_public', '/_admin/public_journal', []);
164 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
165 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
165 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
166 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
166 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
167 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
167 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
168 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
168 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
169 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
169 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
170 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
170 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
171 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
171 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
172 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
172 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
173 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
173 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
177 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
178 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
184 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
184 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
186 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
186 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
187 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
187 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
188 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
188 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
190 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
190 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
191 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
191 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
196 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
196 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
210 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
210 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
211 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
211 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
212 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
212 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
213 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
213 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
215 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
215 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
217 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
217 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
219 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
219 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
220 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
220 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
221 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
221 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
222 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
222 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
223 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
223 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
224 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
224 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
225 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
225 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
226 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
226 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
227 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
227 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
228 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
229 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
229 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
230 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
230 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
231 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
231 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
232 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
232 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
233 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
233 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
234 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
234 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
235 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
237 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
237 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
238 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
238 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
239 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
239 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
240 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
240 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
241 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
241 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
242 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
243 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
244 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
245 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
246 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
246 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
247 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
247 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
248 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
248 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
249 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
249 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
250 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
250 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
251 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
251 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
252 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
252 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
253 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
253 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
254 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
254 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
255 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
255 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
256 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
256 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
257 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
257 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
258 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
258 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
259 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
259 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
260 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
260 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
261 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
262 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
263 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
263 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
264 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
264 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
265 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
265 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
266 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
266 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
267 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
267 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
268 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
268 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
269 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
269 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
270 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
270 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
271 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
271 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
272 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
272 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
273 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
273 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
274 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
274 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
275 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
275 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
276 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
276 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
277 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
277 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
278 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
278 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
279 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
279 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
280 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
280 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
281 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
282 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
283 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
283 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
284 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
284 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
285 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
285 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
286 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
286 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
287 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
287 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
288 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
288 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
289 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
289 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
290 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
290 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
291 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
291 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
292 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
292 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
293 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
293 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
294 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
294 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
295 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
295 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
296 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
296 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
297 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
297 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
298 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
298 pyroutes.register('search', '/_admin/search', []);
299 pyroutes.register('search', '/_admin/search', []);
299 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
300 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
300 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
301 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
301 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
302 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
302 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
303 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
303 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
304 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
304 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
305 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
305 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
306 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
306 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
307 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
307 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
308 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
308 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
309 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
309 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
310 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
310 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
311 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
311 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
312 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
312 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
313 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
313 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
314 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
314 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
315 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
315 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
316 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
316 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
317 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
317 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
318 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
318 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
319 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
319 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
320 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
320 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
321 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
321 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
322 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
322 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
323 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
323 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
324 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
324 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
325 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
325 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
326 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
326 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
327 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
327 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
328 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
328 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
329 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
329 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
330 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
330 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
331 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
331 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
332 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
332 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
333 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
333 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
334 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
334 pyroutes.register('gists_show', '/_admin/gists', []);
335 pyroutes.register('gists_show', '/_admin/gists', []);
335 pyroutes.register('gists_new', '/_admin/gists/new', []);
336 pyroutes.register('gists_new', '/_admin/gists/new', []);
336 pyroutes.register('gists_create', '/_admin/gists/create', []);
337 pyroutes.register('gists_create', '/_admin/gists/create', []);
337 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
338 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
338 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
339 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
339 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
340 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
340 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
341 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
341 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
342 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
342 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
343 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
343 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
344 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
344 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
345 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
345 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
346 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
346 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
347 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
347 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
348 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
348 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
349 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
349 pyroutes.register('apiv2', '/_admin/api', []);
350 pyroutes.register('apiv2', '/_admin/api', []);
350 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
351 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
351 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
352 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
352 pyroutes.register('login', '/_admin/login', []);
353 pyroutes.register('login', '/_admin/login', []);
353 pyroutes.register('register', '/_admin/register', []);
354 pyroutes.register('register', '/_admin/register', []);
354 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
355 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
355 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
356 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
356 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
357 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
357 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
358 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
358 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
359 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
359 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
360 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
360 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
361 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
361 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
362 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
362 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
363 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
363 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
364 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
364 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
365 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
365 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
366 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
366 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
367 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
367 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
368 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
368 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
369 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
369 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
370 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
370 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
371 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
371 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
372 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
372 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
373 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
373 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
374 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
374 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
375 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
375 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
376 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
376 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
377 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
377 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
378 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
378 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
379 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
379 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
380 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
380 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
381 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
381 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
382 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
382 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
383 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
383 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
384 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
384 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
385 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
385 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
386 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
386 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
387 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
387 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
388 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
388 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
389 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
389 }
390 }
@@ -1,116 +1,117 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repository groups administration')}
5 ${_('Repository groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()"></%def>
11 <%def name="breadcrumbs_links()"></%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='admin')}
14 ${self.menu_items(active='admin')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.admin_menu(active='repository_groups')}
18 ${self.admin_menu(active='repository_groups')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23
23
24 <div class="title">
24 <div class="title">
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
26 <span id="repo_group_count">0</span> ${_('repository groups')}
26 <span id="repo_group_count"></span>
27
27
28 <ul class="links">
28 <ul class="links">
29 %if c.can_create_repo_group:
29 %if c.can_create_repo_group:
30 <li>
30 <li>
31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
32 </li>
32 </li>
33 %endif
33 %endif
34 </ul>
34 </ul>
35 </div>
35 </div>
36 <div id="repos_list_wrap">
36 <div id="repos_list_wrap">
37 <table id="group_list_table" class="display"></table>
37 <table id="group_list_table" class="display"></table>
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 <script>
41 <script>
42 $(document).ready(function() {
42 $(document).ready(function() {
43 var $repoGroupsListTable = $('#group_list_table');
43 var $repoGroupsListTable = $('#group_list_table');
44
44
45 // repo group list
45 // repo group list
46 $repoGroupsListTable.DataTable({
46 $repoGroupsListTable.DataTable({
47 processing: true,
47 processing: true,
48 serverSide: true,
48 serverSide: true,
49 ajax: {
49 ajax: {
50 "url": "${h.route_path('repo_groups_data')}",
50 "url": "${h.route_path('repo_groups_data')}",
51 "dataSrc": function (json) {
51 "dataSrc": function (json) {
52 var filteredCount = json.recordsFiltered;
52 var filteredCount = json.recordsFiltered;
53 var filteredInactiveCount = json.recordsFilteredInactive;
53 var filteredInactiveCount = json.recordsFilteredInactive;
54 var totalInactive = json.recordsTotalInactive;
54 var totalInactive = json.recordsTotalInactive;
55 var total = json.recordsTotal;
55 var total = json.recordsTotal;
56
56
57 var _text = _gettext(
57 var _text = _gettext(
58 "{0} of {1} repository groups").format(
58 "{0} of {1} repository groups").format(
59 filteredCount, total);
59 filteredCount, total);
60
60
61 if (total === filteredCount) {
61 if (total === filteredCount) {
62 _text = _gettext("{0} repository groups").format(total);
62 _text = _gettext("{0} repository groups").format(total);
63 }
63 }
64 $('#repo_group_count').text(_text);
64 $('#repo_group_count').text(_text);
65 return json.data;
65 return json.data;
66 },
66 },
67 },
67 },
68
68
69 dom: 'rtp',
69 dom: 'rtp',
70 pageLength: ${c.visual.admin_grid_items},
70 pageLength: ${c.visual.admin_grid_items},
71 order: [[ 0, "asc" ]],
71 order: [[ 0, "asc" ]],
72 columns: [
72 columns: [
73 { data: {"_": "name",
73 { data: {"_": "name",
74 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
74 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
75 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
75 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
76 { data: {"_": "desc",
76 { data: {"_": "desc",
77 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
77 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
78 { data: {"_": "last_change",
78 { data: {"_": "last_change",
79 "sort": "last_change_raw",
79 "sort": "last_change_raw",
80 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
80 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
81 { data: {"_": "top_level_repos",
81 { data: {"_": "top_level_repos",
82 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
82 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
83 { data: {"_": "owner",
83 { data: {"_": "owner",
84 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
84 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
85 { data: {"_": "action",
85 { data: {"_": "action",
86 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
86 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
87 ],
87 ],
88 language: {
88 language: {
89 paginate: DEFAULT_GRID_PAGINATION,
89 paginate: DEFAULT_GRID_PAGINATION,
90 sProcessing: _gettext('loading...'),
90 sProcessing: _gettext('loading...'),
91 emptyTable: _gettext("No repository groups available yet.")
91 emptyTable: _gettext("No repository groups available yet.")
92 },
92 },
93 });
93 });
94
94
95 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
95 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
96 $repoGroupsListTable.css('opacity', 1);
96 $repoGroupsListTable.css('opacity', 1);
97 });
97 });
98
98
99 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
99 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
100 $repoGroupsListTable.css('opacity', 0.3);
100 $repoGroupsListTable.css('opacity', 0.3);
101 });
101 });
102
102
103 // filter
103 // filter
104 $('#q_filter').on('keyup',
104 $('#q_filter').on('keyup',
105 $.debounce(250, function() {
105 $.debounce(250, function() {
106 $repoGroupsListTable.DataTable().search(
106 $repoGroupsListTable.DataTable().search(
107 $('#q_filter').val()
107 $('#q_filter').val()
108 ).draw();
108 ).draw();
109 })
109 })
110 );
110 );
111
111 });
112 });
112
113
113 </script>
114 </script>
114
115
115 </%def>
116 </%def>
116
117
@@ -1,105 +1,149 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')}
5 ${_('Repositories administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()"></%def>
11 <%def name="breadcrumbs_links()"></%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='admin')}
14 ${self.menu_items(active='admin')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.admin_menu(active='repositories')}
18 ${self.admin_menu(active='repositories')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23
23
24 <div class="title">
24 <div class="title">
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
25 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
26 <span id="repo_count">0</span> ${_('repositories')}
26 <span id="repo_count"></span>
27
27
28 <ul class="links">
28 <ul class="links">
29 %if c.can_create_repo:
29 %if c.can_create_repo:
30 <li>
30 <li>
31 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
31 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
32 </li>
32 </li>
33 %endif
33 %endif
34 </ul>
34 </ul>
35 </div>
35 </div>
36 <div id="repos_list_wrap">
36 <div id="repos_list_wrap">
37 <table id="repo_list_table" class="display"></table>
37 <table id="repo_list_table" class="display"></table>
38 </div>
38 </div>
39
39 </div>
40 </div>
40
41
41 <script>
42 <script>
42 $(document).ready(function() {
43 $(document).ready(function() {
43
44 var $repoListTable = $('#repo_list_table');
44 var get_datatable_count = function(){
45 var api = $('#repo_list_table').dataTable().api();
46 $('#repo_count').text(api.page.info().recordsDisplay);
47 };
48
49
45
50 // repo list
46 // repo list
51 $('#repo_list_table').DataTable({
47 $repoListTable.DataTable({
52 data: ${c.data|n},
48 processing: true,
53 dom: 'rtp',
49 serverSide: true,
54 pageLength: ${c.visual.admin_grid_items},
50 ajax: {
55 order: [[ 0, "asc" ]],
51 "url": "${h.route_path('repos_data')}",
56 columns: [
52 "dataSrc": function (json) {
57 { data: {"_": "name",
53 var filteredCount = json.recordsFiltered;
58 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 var total = json.recordsTotal;
59 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55
60 { data: {"_": "desc",
56 var _text = _gettext(
61 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 "{0} of {1} repositories").format(
62 { data: {"_": "last_change",
58 filteredCount, total);
63 "sort": "last_change_raw",
59
64 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
60 if (total === filteredCount) {
65 { data: {"_": "last_changeset",
61 _text = _gettext("{0} repositories").format(total);
66 "sort": "last_changeset_raw",
62 }
67 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
63 $('#repo_count').text(_text);
68 { data: {"_": "owner",
64
69 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
65 return json.data;
70 { data: {"_": "state",
66 },
71 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
67 },
72 { data: {"_": "action",
68 dom: 'rtp',
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
69 pageLength: ${c.visual.admin_grid_items},
74 ],
70 order: [[ 0, "asc" ]],
75 language: {
71 columns: [
72 {
73 data: {
74 "_": "name",
75 "sort": "name_raw"
76 }, title: "${_('Name')}", className: "td-componentname"
77 },
78 {
79 data: 'menu', "bSortable": false, className: "quick_repo_menu"},
80 {
81 data: {
82 "_": "desc",
83 "sort": "desc"
84 }, title: "${_('Description')}", className: "td-description"
85 },
86 {
87 data: {
88 "_": "last_change",
89 "sort": "last_change_raw",
90 "type": Number
91 }, title: "${_('Last Change')}", className: "td-time"
92 },
93 {
94 data: {
95 "_": "last_changeset",
96 "sort": "last_changeset_raw",
97 "type": Number
98 }, title: "${_('Commit')}", className: "td-commit", orderable: false
99 },
100 {
101 data: {
102 "_": "owner",
103 "sort": "owner"
104 }, title: "${_('Owner')}", className: "td-user"
105 },
106 {
107 data: {
108 "_": "state",
109 "sort": "state"
110 }, title: "${_('State')}", className: "td-tags td-state"
111 },
112 {
113 data: {
114 "_": "action",
115 "sort": "action"
116 }, title: "${_('Action')}", className: "td-action", orderable: false
117 }
118 ],
119 language: {
76 paginate: DEFAULT_GRID_PAGINATION,
120 paginate: DEFAULT_GRID_PAGINATION,
77 emptyTable:_gettext("No repositories available yet.")
121 sProcessing: _gettext('loading...'),
78 },
122 emptyTable:_gettext("No repositories present.")
79 "initComplete": function( settings, json ) {
123 },
80 get_datatable_count();
124 "initComplete": function( settings, json ) {
81 quick_repo_menu();
125 quick_repo_menu();
82 }
126 }
83 });
127 });
84
128
85 // update the counter when doing search
129 $repoListTable.on('xhr.dt', function(e, settings, json, xhr){
86 $('#repo_list_table').on( 'search.dt', function (e,settings) {
130 $repoListTable.css('opacity', 1);
87 get_datatable_count();
131 });
132
133 $repoListTable.on('preXhr.dt', function(e, settings, data){
134 $repoListTable.css('opacity', 0.3);
88 });
135 });
89
136
90 // filter, filter both grids
137 $('#q_filter').on('keyup',
91 $('#q_filter').on( 'keyup', function () {
138 $.debounce(250, function() {
92 var repo_api = $('#repo_list_table').dataTable().api();
139 $repoListTable.DataTable().search(
93 repo_api
140 $('#q_filter').val()
94 .columns(0)
141 ).draw();
95 .search(this.value)
142 })
96 .draw();
143 );
97 });
98
144
99 // refilter table if page load via back button
100 $("#q_filter").trigger('keyup');
101 });
145 });
102
146
103 </script>
147 </script>
104
148
105 </%def>
149 </%def>
@@ -1,372 +1,372 b''
1 ## snippet for displaying permissions overview for users
1 ## snippet for displaying permissions overview for users
2 ## usage:
2 ## usage:
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 ## ${p.perms_summary(c.perm_user.permissions)}
4 ## ${p.perms_summary(c.perm_user.permissions)}
5
5
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 <% section_to_label = {
7 <% section_to_label = {
8 'global': 'Global Permissions',
8 'global': 'Global Permissions',
9 'repository_branches': 'Repository Branch Rules',
9 'repository_branches': 'Repository Branch Rules',
10 'repositories': 'Repository Access Permissions',
10 'repositories': 'Repository Access Permissions',
11 'user_groups': 'User Group Permissions',
11 'user_groups': 'User Group Permissions',
12 'repositories_groups': 'Repository Group Permissions',
12 'repositories_groups': 'Repository Group Permissions',
13 } %>
13 } %>
14
14
15 <div id="perms" class="table fields">
15 <div id="perms" class="table fields">
16 %for section in sorted(permissions.keys(), key=lambda item: {'global': 0, 'repository_branches': 1}.get(item, 1000)):
16 %for section in sorted(permissions.keys(), key=lambda item: {'global': 0, 'repository_branches': 1}.get(item, 1000)):
17 <% total_counter = 0 %>
17 <% total_counter = 0 %>
18
18
19 <div class="panel panel-default">
19 <div class="panel panel-default">
20 <div class="panel-heading" id="${section.replace("_","-")}-permissions">
20 <div class="panel-heading" id="${section.replace("_","-")}-permissions">
21 <h3 class="panel-title">${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
21 <h3 class="panel-title">${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
22 <a class="permalink" href="#${section.replace("_","-")}-permissions"> ΒΆ</a>
22 <a class="permalink" href="#${section.replace("_","-")}-permissions"> ΒΆ</a>
23 </h3>
23 </h3>
24 % if side_link:
24 % if side_link:
25 <div class="pull-right">
25 <div class="pull-right">
26 <a href="${side_link}">${_('in JSON format')}</a>
26 <a href="${side_link}">${_('in JSON format')}</a>
27 </div>
27 </div>
28 % endif
28 % endif
29 </div>
29 </div>
30 <div class="panel-body">
30 <div class="panel-body">
31 <div class="perms_section_head field">
31 <div class="perms_section_head field">
32 <div class="radios">
32 <div class="radios">
33 % if section == 'repository_branches':
33 % if section == 'repository_branches':
34 <span class="permissions_boxes">
34 <span class="permissions_boxes">
35 <span class="desc">${_('show')}: </span>
35 <span class="desc">${_('show')}: </span>
36 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
36 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
37 ${h.checkbox('perms_filter_merge_%s' % section, 'merge', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='merge')} <label for="${'perms_filter_merge_{}'.format(section)}"><span class="perm_tag merge">${_('merge')}</span></label>
37 ${h.checkbox('perms_filter_merge_%s' % section, 'merge', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='merge')} <label for="${'perms_filter_merge_{}'.format(section)}"><span class="perm_tag merge">${_('merge')}</span></label>
38 ${h.checkbox('perms_filter_push_%s' % section, 'push', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push')} <label for="${'perms_filter_push_{}'.format(section)}"> <span class="perm_tag push">${_('push')}</span></label>
38 ${h.checkbox('perms_filter_push_%s' % section, 'push', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push')} <label for="${'perms_filter_push_{}'.format(section)}"> <span class="perm_tag push">${_('push')}</span></label>
39 ${h.checkbox('perms_filter_push_force_%s' % section, 'push_force', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push_force')} <label for="${'perms_filter_push_force_{}'.format(section)}"><span class="perm_tag push_force">${_('push force')}</span></label>
39 ${h.checkbox('perms_filter_push_force_%s' % section, 'push_force', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push_force')} <label for="${'perms_filter_push_force_{}'.format(section)}"><span class="perm_tag push_force">${_('push force')}</span></label>
40 </span>
40 </span>
41 % elif section != 'global':
41 % elif section != 'global':
42 <span class="permissions_boxes">
42 <span class="permissions_boxes">
43 <span class="desc">${_('show')}: </span>
43 <span class="desc">${_('show')}: </span>
44 ${h.checkbox('perms_filter_none_%s' % section, 'none', '', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
44 ${h.checkbox('perms_filter_none_%s' % section, 'none', '', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
45 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_{}'.format(section)}"><span class="perm_tag read">${_('read')}</span></label>
45 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_{}'.format(section)}"><span class="perm_tag read">${_('read')}</span></label>
46 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_{}'.format(section)}"> <span class="perm_tag write">${_('write')}</span></label>
46 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_{}'.format(section)}"> <span class="perm_tag write">${_('write')}</span></label>
47 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_{}'.format(section)}"><span class="perm_tag admin">${_('admin')}</span></label>
47 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_{}'.format(section)}"><span class="perm_tag admin">${_('admin')}</span></label>
48 </span>
48 </span>
49 % endif
49 % endif
50
50
51 </div>
51 </div>
52 </div>
52 </div>
53 <div class="field">
53 <div class="field">
54 %if not permissions[section]:
54 %if not permissions[section]:
55 <p class="empty_data help-block">${_('No permissions defined')}</p>
55 <p class="empty_data help-block">${_('No permissions defined')}</p>
56 %else:
56 %else:
57 <div id='tbl_list_wrap_${section}'>
57 <div id='tbl_list_wrap_${section}'>
58 <table id="tbl_list_${section}" class="rctable">
58 <table id="tbl_list_${section}" class="rctable">
59 ## global permission box
59 ## global permission box
60 %if section == 'global':
60 %if section == 'global':
61 <thead>
61 <thead>
62 <tr>
62 <tr>
63 <th colspan="2" class="left">${_('Permission')}</th>
63 <th colspan="2" class="left">${_('Permission')}</th>
64 %if actions:
64 %if actions:
65 <th colspan="2">${_('Edit Permission')}</th>
65 <th colspan="2">${_('Edit Permission')}</th>
66 %endif
66 %endif
67 </thead>
67 </thead>
68 <tbody>
68 <tbody>
69
69
70 <%
70 <%
71 def get_section_perms(prefix, opts):
71 def get_section_perms(prefix, opts):
72 _selected = []
72 _selected = []
73 for op in opts:
73 for op in opts:
74 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
74 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
75 _selected.append(op)
75 _selected.append(op)
76 admin = 'hg.admin' in opts
76 admin = 'hg.admin' in opts
77 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
77 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
78 return admin, _selected_vals, _selected
78 return admin, _selected_vals, _selected
79 %>
79 %>
80
80
81 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
81 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
82 <tr>
82 <tr>
83 <td class="td-tags">
83 <td class="td-tags">
84 ${lbl}
84 ${lbl}
85 </td>
85 </td>
86 <td class="td-tags">
86 <td class="td-tags">
87 %if val[0]:
87 %if val[0]:
88 %if not val_lbl:
88 %if not val_lbl:
89 ## super-admin case
89 ## super-admin case
90 True
90 True
91 %else:
91 %else:
92 <span class="perm_tag admin">${val_lbl}.admin</span>
92 <span class="perm_tag admin">${val_lbl}.admin</span>
93 %endif
93 %endif
94 %else:
94 %else:
95 %if not val_lbl:
95 %if not val_lbl:
96 ${{'false': False,
96 ${{'false': False,
97 'true': True,
97 'true': True,
98 'none': False,
98 'none': False,
99 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')}
99 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')}
100 %else:
100 %else:
101 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
101 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
102 %endif
102 %endif
103 %endif
103 %endif
104 </td>
104 </td>
105 %if actions:
105 %if actions:
106
106
107 % if edit_url or edit_global_url:
107 % if edit_url or edit_global_url:
108
108
109 <td class="td-action">
109 <td class="td-action">
110 % if edit_url:
110 % if edit_url:
111 <a href="${edit_url}">${_('edit')}</a>
111 <a href="${edit_url}">${_('edit')}</a>
112 % else:
112 % else:
113 -
113 -
114 % endif
114 % endif
115 </td>
115 </td>
116
116
117 <td class="td-action">
117 <td class="td-action">
118 % if edit_global_url:
118 % if edit_global_url:
119 <a href="${edit_global_url}">${_('edit global')}</a>
119 <a href="${edit_global_url}">${_('edit global')}</a>
120 % else:
120 % else:
121 -
121 -
122 % endif
122 % endif
123 </td>
123 </td>
124
124
125 % else:
125 % else:
126 <td class="td-action"></td>
126 <td class="td-action"></td>
127 <td class="td-action">
127 <td class="td-action">
128 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
128 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
129 <td class="td-action">
129 <td class="td-action">
130 % endif
130 % endif
131
131
132 %endif
132 %endif
133 </tr>
133 </tr>
134 </%def>
134 </%def>
135
135
136 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
136 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
137 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
137 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
138
138
139 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
139 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
140 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
140 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
141
141
142 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
142 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
143 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
143 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
144
144
145 ${glob(_('Super-admin'), get_section_perms('hg.admin', permissions[section]),
145 ${glob(_('Super-admin'), get_section_perms('hg.admin', permissions[section]),
146 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
146 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
147
147
148 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
148 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
149 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
149 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
150
150
151 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
151 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
152 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
152 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
153
153
154 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
154 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
155 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
155 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
156
156
157 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
157 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
158 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
158 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
159
159
160 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
160 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
161 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
161 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
162
162
163 </tbody>
163 </tbody>
164 ## Branch perms
164 ## Branch perms
165 %elif section == 'repository_branches':
165 %elif section == 'repository_branches':
166 <thead>
166 <thead>
167 <tr>
167 <tr>
168 <th>${_('Name')}</th>
168 <th>${_('Name')}</th>
169 <th>${_('Pattern')}</th>
169 <th>${_('Pattern')}</th>
170 <th>${_('Permission')}</th>
170 <th>${_('Permission')}</th>
171 %if actions:
171 %if actions:
172 <th>${_('Edit Branch Permission')}</th>
172 <th>${_('Edit Branch Permission')}</th>
173 %endif
173 %endif
174 </thead>
174 </thead>
175 <tbody class="section_${section}">
175 <tbody class="section_${section}">
176 <%
176 <%
177 def name_sorter(permissions):
177 def name_sorter(permissions):
178 def custom_sorter(item):
178 def custom_sorter(item):
179 return item[0]
179 return item[0]
180 return sorted(permissions, key=custom_sorter)
180 return sorted(permissions, key=custom_sorter)
181
181
182 def branch_sorter(permissions):
182 def branch_sorter(permissions):
183 def custom_sorter(item):
183 def custom_sorter(item):
184 ## none, merge, push, push_force
184 ## none, merge, push, push_force
185 section = item[1].split('.')[-1]
185 section = item[1].split('.')[-1]
186 section_importance = {'none': u'0',
186 section_importance = {'none': u'0',
187 'merge': u'1',
187 'merge': u'1',
188 'push': u'2',
188 'push': u'2',
189 'push_force': u'3'}.get(section)
189 'push_force': u'3'}.get(section)
190 ## sort by importance + name
190 ## sort by importance + name
191 return section_importance + item[0]
191 return section_importance + item[0]
192 return sorted(permissions, key=custom_sorter)
192 return sorted(permissions, key=custom_sorter)
193 %>
193 %>
194 %for k, section_perms in name_sorter(permissions[section].items()):
194 %for k, section_perms in name_sorter(permissions[section].items()):
195 ## for display purposes, for non super-admins we need to check if shown
195 ## for display purposes, for non super-admins we need to check if shown
196 ## repository is actually accessible for user
196 ## repository is actually accessible for user
197 <% repo_perm = permissions['repositories'][k] %>
197 <% repo_perm = permissions['repositories'][k] %>
198 % if repo_perm == 'repository.none' and not c.rhodecode_user.is_admin:
198 % if repo_perm == 'repository.none' and not c.rhodecode_user.is_admin:
199 ## skip this entry
199 ## skip this entry
200 <% continue %>
200 <% continue %>
201 % endif
201 % endif
202
202
203 <% total_counter +=1 %>
203 <% total_counter +=1 %>
204 % for pattern, perm in branch_sorter(section_perms.items()):
204 % for pattern, perm in branch_sorter(section_perms.items()):
205 <tr class="perm_row ${'{}_{}'.format(section, perm.split('.')[-1])}">
205 <tr class="perm_row ${'{}_{}'.format(section, perm.split('.')[-1])}">
206 <td class="td-name">
206 <td class="td-name">
207 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
207 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
208 </td>
208 </td>
209 <td>${pattern}</td>
209 <td>${pattern}</td>
210 <td class="td-tags">
210 <td class="td-tags">
211 ## TODO: calculate origin somehow
211 ## TODO: calculate origin somehow
212 ## % for i, ((_pat, perm), origin) in enumerate((permissions[section].perm_origin_stack[k])):
212 ## % for i, ((_pat, perm), origin) in enumerate((permissions[section].perm_origin_stack[k])):
213
213
214 <div>
214 <div>
215 <% i = 0 %>
215 <% i = 0 %>
216 <% origin = 'unknown' %>
216 <% origin = 'unknown' %>
217 <% _css_class = i > 0 and 'perm_overriden' or '' %>
217 <% _css_class = i > 0 and 'perm_overriden' or '' %>
218
218
219 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
219 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
220 ${perm}
220 ${perm}
221 ##(${origin})
221 ##(${origin})
222 </span>
222 </span>
223 </div>
223 </div>
224 ## % endfor
224 ## % endfor
225 </td>
225 </td>
226 %if actions:
226 %if actions:
227 <td class="td-action">
227 <td class="td-action">
228 <a href="${h.route_path('edit_repo_perms_branch',repo_name=k)}">${_('edit')}</a>
228 <a href="${h.route_path('edit_repo_perms_branch',repo_name=k)}">${_('edit')}</a>
229 </td>
229 </td>
230 %endif
230 %endif
231 </tr>
231 </tr>
232 % endfor
232 % endfor
233 %endfor
233 %endfor
234 </tbody>
234 </tbody>
235
235
236 ## Repos/Repo Groups/users groups perms
236 ## Repos/Repo Groups/users groups perms
237 %else:
237 %else:
238
238
239 ## none/read/write/admin permissions on groups/repos etc
239 ## none/read/write/admin permissions on groups/repos etc
240 <thead>
240 <thead>
241 <tr>
241 <tr>
242 <th>${_('Name')}</th>
242 <th>${_('Name')}</th>
243 <th>${_('Permission')}</th>
243 <th>${_('Permission')}</th>
244 %if actions:
244 %if actions:
245 <th>${_('Edit Permission')}</th>
245 <th>${_('Edit Permission')}</th>
246 %endif
246 %endif
247 </thead>
247 </thead>
248 <tbody class="section_${section}">
248 <tbody class="section_${section}">
249 <%
249 <%
250 def sorter(permissions):
250 def sorter(permissions):
251 def custom_sorter(item):
251 def custom_sorter(item):
252 ## read/write/admin
252 ## read/write/admin
253 section = item[1].split('.')[-1]
253 section = item[1].split('.')[-1]
254 section_importance = {'none': u'0',
254 section_importance = {'none': u'0',
255 'read': u'1',
255 'read': u'1',
256 'write':u'2',
256 'write':u'2',
257 'admin':u'3'}.get(section)
257 'admin':u'3'}.get(section)
258 ## sort by group importance+name
258 ## sort by group importance+name
259 return section_importance+item[0]
259 return section_importance+item[0]
260 return sorted(permissions, key=custom_sorter)
260 return sorted(permissions, key=custom_sorter)
261 %>
261 %>
262 %for k, section_perm in sorter(permissions[section].items()):
262 %for k, section_perm in sorter(permissions[section].items()):
263 <% perm_value = section_perm.split('.')[-1] %>
263 <% perm_value = section_perm.split('.')[-1] %>
264 <% _css_class = 'display:none' if perm_value in ['none'] else '' %>
264 <% _css_class = 'display:none' if perm_value in ['none'] else '' %>
265
265
266 %if perm_value != 'none' or show_all:
266 %if perm_value != 'none' or show_all:
267 <tr class="perm_row ${'{}_{}'.format(section, section_perm.split('.')[-1])}" style="${_css_class}">
267 <tr class="perm_row ${'{}_{}'.format(section, section_perm.split('.')[-1])}" style="${_css_class}">
268 <td class="td-name">
268 <td class="td-name">
269 %if section == 'repositories':
269 %if section == 'repositories':
270 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
270 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
271 %elif section == 'repositories_groups':
271 %elif section == 'repositories_groups':
272 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
272 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
273 %elif section == 'user_groups':
273 %elif section == 'user_groups':
274 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
274 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
275 ${k}
275 ${k}
276 %endif
276 %endif
277 </td>
277 </td>
278 <td class="td-tags">
278 <td class="td-tags">
279 %if hasattr(permissions[section], 'perm_origin_stack'):
279 %if hasattr(permissions[section], 'perm_origin_stack'):
280 <div>
280 <div>
281 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
281 %for i, (perm, origin, obj_id) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
282 <% _css_class = i > 0 and 'perm_overriden' or '' %>
282 <% _css_class = i > 0 and 'perm_overriden' or '' %>
283 % if i > 0:
283 % if i > 0:
284 <div style="color: #979797">
284 <div style="color: #979797">
285 <i class="icon-arrow_up"></i>
285 <i class="icon-arrow_up"></i>
286 ${_('overridden by')}
286 ${_('overridden by')}
287 <i class="icon-arrow_up"></i>
287 <i class="icon-arrow_up"></i>
288 </div>
288 </div>
289 % endif
289 % endif
290
290
291 <div>
291 <div>
292 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
292 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
293 ${perm} (${origin})
293 ${perm} (${origin})
294 </span>
294 </span>
295 </div>
295 </div>
296
296
297 %endfor
297 %endfor
298 </div>
298 </div>
299 %else:
299 %else:
300 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
300 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
301 %endif
301 %endif
302 </td>
302 </td>
303 %if actions:
303 %if actions:
304 <td class="td-action">
304 <td class="td-action">
305 %if section == 'repositories':
305 %if section == 'repositories':
306 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
306 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
307 %elif section == 'repositories_groups':
307 %elif section == 'repositories_groups':
308 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
308 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
309 %elif section == 'user_groups':
309 %elif section == 'user_groups':
310 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
310 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
311 %endif
311 %endif
312 </td>
312 </td>
313 %endif
313 %endif
314 </tr>
314 </tr>
315 <% total_counter +=1 %>
315 <% total_counter +=1 %>
316 %endif
316 %endif
317
317
318 %endfor
318 %endfor
319
319
320 <tr id="empty_${section}" class="noborder" style="display:none;">
320 <tr id="empty_${section}" class="noborder" style="display:none;">
321 <td colspan="6">${_('No matching permission defined')}</td>
321 <td colspan="6">${_('No matching permission defined')}</td>
322 </tr>
322 </tr>
323
323
324 </tbody>
324 </tbody>
325 %endif
325 %endif
326 </table>
326 </table>
327 </div>
327 </div>
328 %endif
328 %endif
329 </div>
329 </div>
330 </div>
330 </div>
331 </div>
331 </div>
332
332
333 <script>
333 <script>
334 $('#total_count_${section}').html(${total_counter})
334 $('#total_count_${section}').html(${total_counter})
335 </script>
335 </script>
336
336
337 %endfor
337 %endfor
338 </div>
338 </div>
339
339
340 <script>
340 <script>
341 $(document).ready(function(){
341 $(document).ready(function(){
342 var showEmpty = function(section){
342 var showEmpty = function(section){
343 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
343 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
344 if(visible === 0){
344 if(visible === 0){
345 $('#empty_{0}'.format(section)).show();
345 $('#empty_{0}'.format(section)).show();
346 }
346 }
347 else{
347 else{
348 $('#empty_{0}'.format(section)).hide();
348 $('#empty_{0}'.format(section)).hide();
349 }
349 }
350 };
350 };
351
351
352 $('.perm_filter').on('change', function(e){
352 $('.perm_filter').on('change', function(e){
353 var self = this;
353 var self = this;
354 var section = $(this).attr('section');
354 var section = $(this).attr('section');
355
355
356 var opts = {};
356 var opts = {};
357 var elems = $('.filter_' + section).each(function(el){
357 var elems = $('.filter_' + section).each(function(el){
358 var perm_type = $(this).attr('perm_type');
358 var perm_type = $(this).attr('perm_type');
359 var checked = this.checked;
359 var checked = this.checked;
360 opts[perm_type] = checked;
360 opts[perm_type] = checked;
361 if(checked){
361 if(checked){
362 $('.'+section+'_'+perm_type).show();
362 $('.'+section+'_'+perm_type).show();
363 }
363 }
364 else{
364 else{
365 $('.'+section+'_'+perm_type).hide();
365 $('.'+section+'_'+perm_type).hide();
366 }
366 }
367 });
367 });
368 showEmpty(section);
368 showEmpty(section);
369 })
369 })
370 })
370 })
371 </script>
371 </script>
372 </%def>
372 </%def>
@@ -1,632 +1,632 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from hashlib import sha1
22 from hashlib import sha1
23
23
24 import pytest
24 import pytest
25 from mock import patch
25 from mock import patch
26
26
27 from rhodecode.lib import auth
27 from rhodecode.lib import auth
28 from rhodecode.lib.utils2 import md5
28 from rhodecode.lib.utils2 import md5
29 from rhodecode.model.auth_token import AuthTokenModel
29 from rhodecode.model.auth_token import AuthTokenModel
30 from rhodecode.model.db import Session, User
30 from rhodecode.model.db import Session, User
31 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
34
34
35
35
36 def test_perm_origin_dict():
36 def test_perm_origin_dict():
37 pod = auth.PermOriginDict()
37 pod = auth.PermOriginDict()
38 pod['thing'] = 'read', 'default'
38 pod['thing'] = 'read', 'default', 1
39 assert pod['thing'] == 'read'
39 assert pod['thing'] == 'read'
40
40
41 assert pod.perm_origin_stack == {
41 assert pod.perm_origin_stack == {
42 'thing': [('read', 'default')]}
42 'thing': [('read', 'default', 1)]}
43
43
44 pod['thing'] = 'write', 'admin'
44 pod['thing'] = 'write', 'admin', 1
45 assert pod['thing'] == 'write'
45 assert pod['thing'] == 'write'
46
46
47 assert pod.perm_origin_stack == {
47 assert pod.perm_origin_stack == {
48 'thing': [('read', 'default'), ('write', 'admin')]}
48 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
49
49
50 pod['other'] = 'write', 'default'
50 pod['other'] = 'write', 'default', 8
51
51
52 assert pod.perm_origin_stack == {
52 assert pod.perm_origin_stack == {
53 'other': [('write', 'default')],
53 'other': [('write', 'default', 8)],
54 'thing': [('read', 'default'), ('write', 'admin')]}
54 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
55
55
56 pod['other'] = 'none', 'override'
56 pod['other'] = 'none', 'override', 8
57
57
58 assert pod.perm_origin_stack == {
58 assert pod.perm_origin_stack == {
59 'other': [('write', 'default'), ('none', 'override')],
59 'other': [('write', 'default', 8), ('none', 'override', 8)],
60 'thing': [('read', 'default'), ('write', 'admin')]}
60 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
61
61
62 with pytest.raises(ValueError):
62 with pytest.raises(ValueError):
63 pod['thing'] = 'read'
63 pod['thing'] = 'read'
64
64
65
65
66 def test_cached_perms_data(user_regular, backend_random):
66 def test_cached_perms_data(user_regular, backend_random):
67 permissions = get_permissions(user_regular)
67 permissions = get_permissions(user_regular)
68 repo_name = backend_random.repo.repo_name
68 repo_name = backend_random.repo.repo_name
69 expected_global_permissions = {
69 expected_global_permissions = {
70 'repository.read', 'group.read', 'usergroup.read'}
70 'repository.read', 'group.read', 'usergroup.read'}
71 assert expected_global_permissions.issubset(permissions['global'])
71 assert expected_global_permissions.issubset(permissions['global'])
72 assert permissions['repositories'][repo_name] == 'repository.read'
72 assert permissions['repositories'][repo_name] == 'repository.read'
73
73
74
74
75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
76 permissions = get_permissions(user_regular, user_is_admin=True)
76 permissions = get_permissions(user_regular, user_is_admin=True)
77 repo_name = backend_random.repo.repo_name
77 repo_name = backend_random.repo.repo_name
78 assert 'hg.admin' in permissions['global']
78 assert 'hg.admin' in permissions['global']
79 assert permissions['repositories'][repo_name] == 'repository.admin'
79 assert permissions['repositories'][repo_name] == 'repository.admin'
80
80
81
81
82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
83 permissions = get_permissions(user_regular, user_is_admin=True,
83 permissions = get_permissions(user_regular, user_is_admin=True,
84 calculate_super_admin=True)
84 calculate_super_admin=True)
85 repo_name = backend_random.repo.repo_name
85 repo_name = backend_random.repo.repo_name
86 assert 'hg.admin' in permissions['global']
86 assert 'hg.admin' in permissions['global']
87 assert permissions['repositories'][repo_name] == 'repository.admin'
87 assert permissions['repositories'][repo_name] == 'repository.admin'
88
88
89
89
90 def test_cached_perms_data_user_group_global_permissions(user_util):
90 def test_cached_perms_data_user_group_global_permissions(user_util):
91 user, user_group = user_util.create_user_with_group()
91 user, user_group = user_util.create_user_with_group()
92 user_group.inherit_default_permissions = False
92 user_group.inherit_default_permissions = False
93
93
94 granted_permission = 'repository.write'
94 granted_permission = 'repository.write'
95 UserGroupModel().grant_perm(user_group, granted_permission)
95 UserGroupModel().grant_perm(user_group, granted_permission)
96 Session().commit()
96 Session().commit()
97
97
98 permissions = get_permissions(user)
98 permissions = get_permissions(user)
99 assert granted_permission in permissions['global']
99 assert granted_permission in permissions['global']
100
100
101
101
102 @pytest.mark.xfail(reason="Not implemented, see TODO note")
102 @pytest.mark.xfail(reason="Not implemented, see TODO note")
103 def test_cached_perms_data_user_group_global_permissions_(user_util):
103 def test_cached_perms_data_user_group_global_permissions_(user_util):
104 user, user_group = user_util.create_user_with_group()
104 user, user_group = user_util.create_user_with_group()
105
105
106 granted_permission = 'repository.write'
106 granted_permission = 'repository.write'
107 UserGroupModel().grant_perm(user_group, granted_permission)
107 UserGroupModel().grant_perm(user_group, granted_permission)
108 Session().commit()
108 Session().commit()
109
109
110 permissions = get_permissions(user)
110 permissions = get_permissions(user)
111 assert granted_permission in permissions['global']
111 assert granted_permission in permissions['global']
112
112
113
113
114 def test_cached_perms_data_user_global_permissions(user_util):
114 def test_cached_perms_data_user_global_permissions(user_util):
115 user = user_util.create_user()
115 user = user_util.create_user()
116 UserModel().grant_perm(user, 'repository.none')
116 UserModel().grant_perm(user, 'repository.none')
117 Session().commit()
117 Session().commit()
118
118
119 permissions = get_permissions(user, user_inherit_default_permissions=True)
119 permissions = get_permissions(user, user_inherit_default_permissions=True)
120 assert 'repository.read' in permissions['global']
120 assert 'repository.read' in permissions['global']
121
121
122
122
123 def test_cached_perms_data_repository_permissions_on_private_repository(
123 def test_cached_perms_data_repository_permissions_on_private_repository(
124 backend_random, user_util):
124 backend_random, user_util):
125 user, user_group = user_util.create_user_with_group()
125 user, user_group = user_util.create_user_with_group()
126
126
127 repo = backend_random.create_repo()
127 repo = backend_random.create_repo()
128 repo.private = True
128 repo.private = True
129
129
130 granted_permission = 'repository.write'
130 granted_permission = 'repository.write'
131 RepoModel().grant_user_group_permission(
131 RepoModel().grant_user_group_permission(
132 repo, user_group.users_group_name, granted_permission)
132 repo, user_group.users_group_name, granted_permission)
133 Session().commit()
133 Session().commit()
134
134
135 permissions = get_permissions(user)
135 permissions = get_permissions(user)
136 assert permissions['repositories'][repo.repo_name] == granted_permission
136 assert permissions['repositories'][repo.repo_name] == granted_permission
137
137
138
138
139 def test_cached_perms_data_repository_permissions_for_owner(
139 def test_cached_perms_data_repository_permissions_for_owner(
140 backend_random, user_util):
140 backend_random, user_util):
141 user = user_util.create_user()
141 user = user_util.create_user()
142
142
143 repo = backend_random.create_repo()
143 repo = backend_random.create_repo()
144 repo.user_id = user.user_id
144 repo.user_id = user.user_id
145
145
146 permissions = get_permissions(user)
146 permissions = get_permissions(user)
147 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
147 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
148
148
149 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
149 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
150 repo.user_id = User.get_default_user().user_id
150 repo.user_id = User.get_default_user().user_id
151
151
152
152
153 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
153 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
154 backend_random, user_util):
154 backend_random, user_util):
155 user = user_util.create_user()
155 user = user_util.create_user()
156 repo = backend_random.create_repo()
156 repo = backend_random.create_repo()
157
157
158 # Don't inherit default object permissions
158 # Don't inherit default object permissions
159 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
159 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
160 Session().commit()
160 Session().commit()
161
161
162 permissions = get_permissions(user)
162 permissions = get_permissions(user)
163 assert permissions['repositories'][repo.repo_name] == 'repository.none'
163 assert permissions['repositories'][repo.repo_name] == 'repository.none'
164
164
165
165
166 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
166 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
167 # Have a repository group with default permissions set
167 # Have a repository group with default permissions set
168 repo_group = user_util.create_repo_group()
168 repo_group = user_util.create_repo_group()
169 default_user = User.get_default_user()
169 default_user = User.get_default_user()
170 user_util.grant_user_permission_to_repo_group(
170 user_util.grant_user_permission_to_repo_group(
171 repo_group, default_user, 'repository.write')
171 repo_group, default_user, 'repository.write')
172 user = user_util.create_user()
172 user = user_util.create_user()
173
173
174 permissions = get_permissions(user)
174 permissions = get_permissions(user)
175 assert permissions['repositories_groups'][repo_group.group_name] == \
175 assert permissions['repositories_groups'][repo_group.group_name] == \
176 'repository.write'
176 'repository.write'
177
177
178
178
179 def test_cached_perms_data_default_permissions_on_repository_group_owner(
179 def test_cached_perms_data_default_permissions_on_repository_group_owner(
180 user_util):
180 user_util):
181 # Have a repository group
181 # Have a repository group
182 repo_group = user_util.create_repo_group()
182 repo_group = user_util.create_repo_group()
183 default_user = User.get_default_user()
183 default_user = User.get_default_user()
184
184
185 # Add a permission for the default user to hit the code path
185 # Add a permission for the default user to hit the code path
186 user_util.grant_user_permission_to_repo_group(
186 user_util.grant_user_permission_to_repo_group(
187 repo_group, default_user, 'repository.write')
187 repo_group, default_user, 'repository.write')
188
188
189 # Have an owner of the group
189 # Have an owner of the group
190 user = user_util.create_user()
190 user = user_util.create_user()
191 repo_group.user_id = user.user_id
191 repo_group.user_id = user.user_id
192
192
193 permissions = get_permissions(user)
193 permissions = get_permissions(user)
194 assert permissions['repositories_groups'][repo_group.group_name] == \
194 assert permissions['repositories_groups'][repo_group.group_name] == \
195 'group.admin'
195 'group.admin'
196
196
197
197
198 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
198 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
199 user_util):
199 user_util):
200 # Have a repository group
200 # Have a repository group
201 repo_group = user_util.create_repo_group()
201 repo_group = user_util.create_repo_group()
202 default_user = User.get_default_user()
202 default_user = User.get_default_user()
203
203
204 # Add a permission for the default user to hit the code path
204 # Add a permission for the default user to hit the code path
205 user_util.grant_user_permission_to_repo_group(
205 user_util.grant_user_permission_to_repo_group(
206 repo_group, default_user, 'repository.write')
206 repo_group, default_user, 'repository.write')
207
207
208 # Don't inherit default object permissions
208 # Don't inherit default object permissions
209 user = user_util.create_user()
209 user = user_util.create_user()
210 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
210 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
211 Session().commit()
211 Session().commit()
212
212
213 permissions = get_permissions(user)
213 permissions = get_permissions(user)
214 assert permissions['repositories_groups'][repo_group.group_name] == \
214 assert permissions['repositories_groups'][repo_group.group_name] == \
215 'group.none'
215 'group.none'
216
216
217
217
218 def test_cached_perms_data_repository_permissions_from_user_group(
218 def test_cached_perms_data_repository_permissions_from_user_group(
219 user_util, backend_random):
219 user_util, backend_random):
220 user, user_group = user_util.create_user_with_group()
220 user, user_group = user_util.create_user_with_group()
221
221
222 # Needs a second user group to make sure that we select the right
222 # Needs a second user group to make sure that we select the right
223 # permissions.
223 # permissions.
224 user_group2 = user_util.create_user_group()
224 user_group2 = user_util.create_user_group()
225 UserGroupModel().add_user_to_group(user_group2, user)
225 UserGroupModel().add_user_to_group(user_group2, user)
226
226
227 repo = backend_random.create_repo()
227 repo = backend_random.create_repo()
228
228
229 RepoModel().grant_user_group_permission(
229 RepoModel().grant_user_group_permission(
230 repo, user_group.users_group_name, 'repository.read')
230 repo, user_group.users_group_name, 'repository.read')
231 RepoModel().grant_user_group_permission(
231 RepoModel().grant_user_group_permission(
232 repo, user_group2.users_group_name, 'repository.write')
232 repo, user_group2.users_group_name, 'repository.write')
233 Session().commit()
233 Session().commit()
234
234
235 permissions = get_permissions(user)
235 permissions = get_permissions(user)
236 assert permissions['repositories'][repo.repo_name] == 'repository.write'
236 assert permissions['repositories'][repo.repo_name] == 'repository.write'
237
237
238
238
239 def test_cached_perms_data_repository_permissions_from_user_group_owner(
239 def test_cached_perms_data_repository_permissions_from_user_group_owner(
240 user_util, backend_random):
240 user_util, backend_random):
241 user, user_group = user_util.create_user_with_group()
241 user, user_group = user_util.create_user_with_group()
242
242
243 repo = backend_random.create_repo()
243 repo = backend_random.create_repo()
244 repo.user_id = user.user_id
244 repo.user_id = user.user_id
245
245
246 RepoModel().grant_user_group_permission(
246 RepoModel().grant_user_group_permission(
247 repo, user_group.users_group_name, 'repository.write')
247 repo, user_group.users_group_name, 'repository.write')
248 Session().commit()
248 Session().commit()
249
249
250 permissions = get_permissions(user)
250 permissions = get_permissions(user)
251 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
251 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
252
252
253
253
254 def test_cached_perms_data_user_repository_permissions(
254 def test_cached_perms_data_user_repository_permissions(
255 user_util, backend_random):
255 user_util, backend_random):
256 user = user_util.create_user()
256 user = user_util.create_user()
257 repo = backend_random.create_repo()
257 repo = backend_random.create_repo()
258 granted_permission = 'repository.write'
258 granted_permission = 'repository.write'
259 RepoModel().grant_user_permission(repo, user, granted_permission)
259 RepoModel().grant_user_permission(repo, user, granted_permission)
260 Session().commit()
260 Session().commit()
261
261
262 permissions = get_permissions(user)
262 permissions = get_permissions(user)
263 assert permissions['repositories'][repo.repo_name] == granted_permission
263 assert permissions['repositories'][repo.repo_name] == granted_permission
264
264
265
265
266 def test_cached_perms_data_user_repository_permissions_explicit(
266 def test_cached_perms_data_user_repository_permissions_explicit(
267 user_util, backend_random):
267 user_util, backend_random):
268 user = user_util.create_user()
268 user = user_util.create_user()
269 repo = backend_random.create_repo()
269 repo = backend_random.create_repo()
270 granted_permission = 'repository.none'
270 granted_permission = 'repository.none'
271 RepoModel().grant_user_permission(repo, user, granted_permission)
271 RepoModel().grant_user_permission(repo, user, granted_permission)
272 Session().commit()
272 Session().commit()
273
273
274 permissions = get_permissions(user, explicit=True)
274 permissions = get_permissions(user, explicit=True)
275 assert permissions['repositories'][repo.repo_name] == granted_permission
275 assert permissions['repositories'][repo.repo_name] == granted_permission
276
276
277
277
278 def test_cached_perms_data_user_repository_permissions_owner(
278 def test_cached_perms_data_user_repository_permissions_owner(
279 user_util, backend_random):
279 user_util, backend_random):
280 user = user_util.create_user()
280 user = user_util.create_user()
281 repo = backend_random.create_repo()
281 repo = backend_random.create_repo()
282 repo.user_id = user.user_id
282 repo.user_id = user.user_id
283 RepoModel().grant_user_permission(repo, user, 'repository.write')
283 RepoModel().grant_user_permission(repo, user, 'repository.write')
284 Session().commit()
284 Session().commit()
285
285
286 permissions = get_permissions(user)
286 permissions = get_permissions(user)
287 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
287 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
288
288
289
289
290 def test_cached_perms_data_repository_groups_permissions_inherited(
290 def test_cached_perms_data_repository_groups_permissions_inherited(
291 user_util, backend_random):
291 user_util, backend_random):
292 user, user_group = user_util.create_user_with_group()
292 user, user_group = user_util.create_user_with_group()
293
293
294 # Needs a second group to hit the last condition
294 # Needs a second group to hit the last condition
295 user_group2 = user_util.create_user_group()
295 user_group2 = user_util.create_user_group()
296 UserGroupModel().add_user_to_group(user_group2, user)
296 UserGroupModel().add_user_to_group(user_group2, user)
297
297
298 repo_group = user_util.create_repo_group()
298 repo_group = user_util.create_repo_group()
299
299
300 user_util.grant_user_group_permission_to_repo_group(
300 user_util.grant_user_group_permission_to_repo_group(
301 repo_group, user_group, 'group.read')
301 repo_group, user_group, 'group.read')
302 user_util.grant_user_group_permission_to_repo_group(
302 user_util.grant_user_group_permission_to_repo_group(
303 repo_group, user_group2, 'group.write')
303 repo_group, user_group2, 'group.write')
304
304
305 permissions = get_permissions(user)
305 permissions = get_permissions(user)
306 assert permissions['repositories_groups'][repo_group.group_name] == \
306 assert permissions['repositories_groups'][repo_group.group_name] == \
307 'group.write'
307 'group.write'
308
308
309
309
310 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
310 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
311 user_util, backend_random):
311 user_util, backend_random):
312 user, user_group = user_util.create_user_with_group()
312 user, user_group = user_util.create_user_with_group()
313 repo_group = user_util.create_repo_group()
313 repo_group = user_util.create_repo_group()
314 repo_group.user_id = user.user_id
314 repo_group.user_id = user.user_id
315
315
316 granted_permission = 'group.write'
316 granted_permission = 'group.write'
317 user_util.grant_user_group_permission_to_repo_group(
317 user_util.grant_user_group_permission_to_repo_group(
318 repo_group, user_group, granted_permission)
318 repo_group, user_group, granted_permission)
319
319
320 permissions = get_permissions(user)
320 permissions = get_permissions(user)
321 assert permissions['repositories_groups'][repo_group.group_name] == \
321 assert permissions['repositories_groups'][repo_group.group_name] == \
322 'group.admin'
322 'group.admin'
323
323
324
324
325 def test_cached_perms_data_repository_groups_permissions(
325 def test_cached_perms_data_repository_groups_permissions(
326 user_util, backend_random):
326 user_util, backend_random):
327 user = user_util.create_user()
327 user = user_util.create_user()
328
328
329 repo_group = user_util.create_repo_group()
329 repo_group = user_util.create_repo_group()
330
330
331 granted_permission = 'group.write'
331 granted_permission = 'group.write'
332 user_util.grant_user_permission_to_repo_group(
332 user_util.grant_user_permission_to_repo_group(
333 repo_group, user, granted_permission)
333 repo_group, user, granted_permission)
334
334
335 permissions = get_permissions(user)
335 permissions = get_permissions(user)
336 assert permissions['repositories_groups'][repo_group.group_name] == \
336 assert permissions['repositories_groups'][repo_group.group_name] == \
337 'group.write'
337 'group.write'
338
338
339
339
340 def test_cached_perms_data_repository_groups_permissions_explicit(
340 def test_cached_perms_data_repository_groups_permissions_explicit(
341 user_util, backend_random):
341 user_util, backend_random):
342 user = user_util.create_user()
342 user = user_util.create_user()
343
343
344 repo_group = user_util.create_repo_group()
344 repo_group = user_util.create_repo_group()
345
345
346 granted_permission = 'group.none'
346 granted_permission = 'group.none'
347 user_util.grant_user_permission_to_repo_group(
347 user_util.grant_user_permission_to_repo_group(
348 repo_group, user, granted_permission)
348 repo_group, user, granted_permission)
349
349
350 permissions = get_permissions(user, explicit=True)
350 permissions = get_permissions(user, explicit=True)
351 assert permissions['repositories_groups'][repo_group.group_name] == \
351 assert permissions['repositories_groups'][repo_group.group_name] == \
352 'group.none'
352 'group.none'
353
353
354
354
355 def test_cached_perms_data_repository_groups_permissions_owner(
355 def test_cached_perms_data_repository_groups_permissions_owner(
356 user_util, backend_random):
356 user_util, backend_random):
357 user = user_util.create_user()
357 user = user_util.create_user()
358
358
359 repo_group = user_util.create_repo_group()
359 repo_group = user_util.create_repo_group()
360 repo_group.user_id = user.user_id
360 repo_group.user_id = user.user_id
361
361
362 granted_permission = 'group.write'
362 granted_permission = 'group.write'
363 user_util.grant_user_permission_to_repo_group(
363 user_util.grant_user_permission_to_repo_group(
364 repo_group, user, granted_permission)
364 repo_group, user, granted_permission)
365
365
366 permissions = get_permissions(user)
366 permissions = get_permissions(user)
367 assert permissions['repositories_groups'][repo_group.group_name] == \
367 assert permissions['repositories_groups'][repo_group.group_name] == \
368 'group.admin'
368 'group.admin'
369
369
370
370
371 def test_cached_perms_data_user_group_permissions_inherited(
371 def test_cached_perms_data_user_group_permissions_inherited(
372 user_util, backend_random):
372 user_util, backend_random):
373 user, user_group = user_util.create_user_with_group()
373 user, user_group = user_util.create_user_with_group()
374 user_group2 = user_util.create_user_group()
374 user_group2 = user_util.create_user_group()
375 UserGroupModel().add_user_to_group(user_group2, user)
375 UserGroupModel().add_user_to_group(user_group2, user)
376
376
377 target_user_group = user_util.create_user_group()
377 target_user_group = user_util.create_user_group()
378
378
379 user_util.grant_user_group_permission_to_user_group(
379 user_util.grant_user_group_permission_to_user_group(
380 target_user_group, user_group, 'usergroup.read')
380 target_user_group, user_group, 'usergroup.read')
381 user_util.grant_user_group_permission_to_user_group(
381 user_util.grant_user_group_permission_to_user_group(
382 target_user_group, user_group2, 'usergroup.write')
382 target_user_group, user_group2, 'usergroup.write')
383
383
384 permissions = get_permissions(user)
384 permissions = get_permissions(user)
385 assert permissions['user_groups'][target_user_group.users_group_name] == \
385 assert permissions['user_groups'][target_user_group.users_group_name] == \
386 'usergroup.write'
386 'usergroup.write'
387
387
388
388
389 def test_cached_perms_data_user_group_permissions(
389 def test_cached_perms_data_user_group_permissions(
390 user_util, backend_random):
390 user_util, backend_random):
391 user = user_util.create_user()
391 user = user_util.create_user()
392 user_group = user_util.create_user_group()
392 user_group = user_util.create_user_group()
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
394 Session().commit()
394 Session().commit()
395
395
396 permissions = get_permissions(user)
396 permissions = get_permissions(user)
397 assert permissions['user_groups'][user_group.users_group_name] == \
397 assert permissions['user_groups'][user_group.users_group_name] == \
398 'usergroup.write'
398 'usergroup.write'
399
399
400
400
401 def test_cached_perms_data_user_group_permissions_explicit(
401 def test_cached_perms_data_user_group_permissions_explicit(
402 user_util, backend_random):
402 user_util, backend_random):
403 user = user_util.create_user()
403 user = user_util.create_user()
404 user_group = user_util.create_user_group()
404 user_group = user_util.create_user_group()
405 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
405 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
406 Session().commit()
406 Session().commit()
407
407
408 permissions = get_permissions(user, explicit=True)
408 permissions = get_permissions(user, explicit=True)
409 assert permissions['user_groups'][user_group.users_group_name] == \
409 assert permissions['user_groups'][user_group.users_group_name] == \
410 'usergroup.none'
410 'usergroup.none'
411
411
412
412
413 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
413 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
414 user_util, backend_random):
414 user_util, backend_random):
415 user = user_util.create_user()
415 user = user_util.create_user()
416 user_group = user_util.create_user_group()
416 user_group = user_util.create_user_group()
417
417
418 # Don't inherit default object permissions
418 # Don't inherit default object permissions
419 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
419 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
420 Session().commit()
420 Session().commit()
421
421
422 permissions = get_permissions(user)
422 permissions = get_permissions(user)
423 assert permissions['user_groups'][user_group.users_group_name] == \
423 assert permissions['user_groups'][user_group.users_group_name] == \
424 'usergroup.none'
424 'usergroup.none'
425
425
426
426
427 def test_permission_calculator_admin_permissions(
427 def test_permission_calculator_admin_permissions(
428 user_util, backend_random):
428 user_util, backend_random):
429 user = user_util.create_user()
429 user = user_util.create_user()
430 user_group = user_util.create_user_group()
430 user_group = user_util.create_user_group()
431 repo = backend_random.repo
431 repo = backend_random.repo
432 repo_group = user_util.create_repo_group()
432 repo_group = user_util.create_repo_group()
433
433
434 calculator = auth.PermissionCalculator(
434 calculator = auth.PermissionCalculator(
435 user.user_id, {}, False, False, True, 'higherwin')
435 user.user_id, {}, False, False, True, 'higherwin')
436 permissions = calculator._calculate_admin_permissions()
436 permissions = calculator._calculate_admin_permissions()
437
437
438 assert permissions['repositories_groups'][repo_group.group_name] == \
438 assert permissions['repositories_groups'][repo_group.group_name] == \
439 'group.admin'
439 'group.admin'
440 assert permissions['user_groups'][user_group.users_group_name] == \
440 assert permissions['user_groups'][user_group.users_group_name] == \
441 'usergroup.admin'
441 'usergroup.admin'
442 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
442 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
443 assert 'hg.admin' in permissions['global']
443 assert 'hg.admin' in permissions['global']
444
444
445
445
446 def test_permission_calculator_repository_permissions_robustness_from_group(
446 def test_permission_calculator_repository_permissions_robustness_from_group(
447 user_util, backend_random):
447 user_util, backend_random):
448 user, user_group = user_util.create_user_with_group()
448 user, user_group = user_util.create_user_with_group()
449
449
450 RepoModel().grant_user_group_permission(
450 RepoModel().grant_user_group_permission(
451 backend_random.repo, user_group.users_group_name, 'repository.write')
451 backend_random.repo, user_group.users_group_name, 'repository.write')
452
452
453 calculator = auth.PermissionCalculator(
453 calculator = auth.PermissionCalculator(
454 user.user_id, {}, False, False, False, 'higherwin')
454 user.user_id, {}, False, False, False, 'higherwin')
455 calculator._calculate_repository_permissions()
455 calculator._calculate_repository_permissions()
456
456
457
457
458 def test_permission_calculator_repository_permissions_robustness_from_user(
458 def test_permission_calculator_repository_permissions_robustness_from_user(
459 user_util, backend_random):
459 user_util, backend_random):
460 user = user_util.create_user()
460 user = user_util.create_user()
461
461
462 RepoModel().grant_user_permission(
462 RepoModel().grant_user_permission(
463 backend_random.repo, user, 'repository.write')
463 backend_random.repo, user, 'repository.write')
464 Session().commit()
464 Session().commit()
465
465
466 calculator = auth.PermissionCalculator(
466 calculator = auth.PermissionCalculator(
467 user.user_id, {}, False, False, False, 'higherwin')
467 user.user_id, {}, False, False, False, 'higherwin')
468 calculator._calculate_repository_permissions()
468 calculator._calculate_repository_permissions()
469
469
470
470
471 def test_permission_calculator_repo_group_permissions_robustness_from_group(
471 def test_permission_calculator_repo_group_permissions_robustness_from_group(
472 user_util, backend_random):
472 user_util, backend_random):
473 user, user_group = user_util.create_user_with_group()
473 user, user_group = user_util.create_user_with_group()
474 repo_group = user_util.create_repo_group()
474 repo_group = user_util.create_repo_group()
475
475
476 user_util.grant_user_group_permission_to_repo_group(
476 user_util.grant_user_group_permission_to_repo_group(
477 repo_group, user_group, 'group.write')
477 repo_group, user_group, 'group.write')
478
478
479 calculator = auth.PermissionCalculator(
479 calculator = auth.PermissionCalculator(
480 user.user_id, {}, False, False, False, 'higherwin')
480 user.user_id, {}, False, False, False, 'higherwin')
481 calculator._calculate_repository_group_permissions()
481 calculator._calculate_repository_group_permissions()
482
482
483
483
484 def test_permission_calculator_repo_group_permissions_robustness_from_user(
484 def test_permission_calculator_repo_group_permissions_robustness_from_user(
485 user_util, backend_random):
485 user_util, backend_random):
486 user = user_util.create_user()
486 user = user_util.create_user()
487 repo_group = user_util.create_repo_group()
487 repo_group = user_util.create_repo_group()
488
488
489 user_util.grant_user_permission_to_repo_group(
489 user_util.grant_user_permission_to_repo_group(
490 repo_group, user, 'group.write')
490 repo_group, user, 'group.write')
491
491
492 calculator = auth.PermissionCalculator(
492 calculator = auth.PermissionCalculator(
493 user.user_id, {}, False, False, False, 'higherwin')
493 user.user_id, {}, False, False, False, 'higherwin')
494 calculator._calculate_repository_group_permissions()
494 calculator._calculate_repository_group_permissions()
495
495
496
496
497 def test_permission_calculator_user_group_permissions_robustness_from_group(
497 def test_permission_calculator_user_group_permissions_robustness_from_group(
498 user_util, backend_random):
498 user_util, backend_random):
499 user, user_group = user_util.create_user_with_group()
499 user, user_group = user_util.create_user_with_group()
500 target_user_group = user_util.create_user_group()
500 target_user_group = user_util.create_user_group()
501
501
502 user_util.grant_user_group_permission_to_user_group(
502 user_util.grant_user_group_permission_to_user_group(
503 target_user_group, user_group, 'usergroup.write')
503 target_user_group, user_group, 'usergroup.write')
504
504
505 calculator = auth.PermissionCalculator(
505 calculator = auth.PermissionCalculator(
506 user.user_id, {}, False, False, False, 'higherwin')
506 user.user_id, {}, False, False, False, 'higherwin')
507 calculator._calculate_user_group_permissions()
507 calculator._calculate_user_group_permissions()
508
508
509
509
510 def test_permission_calculator_user_group_permissions_robustness_from_user(
510 def test_permission_calculator_user_group_permissions_robustness_from_user(
511 user_util, backend_random):
511 user_util, backend_random):
512 user = user_util.create_user()
512 user = user_util.create_user()
513 target_user_group = user_util.create_user_group()
513 target_user_group = user_util.create_user_group()
514
514
515 user_util.grant_user_permission_to_user_group(
515 user_util.grant_user_permission_to_user_group(
516 target_user_group, user, 'usergroup.write')
516 target_user_group, user, 'usergroup.write')
517
517
518 calculator = auth.PermissionCalculator(
518 calculator = auth.PermissionCalculator(
519 user.user_id, {}, False, False, False, 'higherwin')
519 user.user_id, {}, False, False, False, 'higherwin')
520 calculator._calculate_user_group_permissions()
520 calculator._calculate_user_group_permissions()
521
521
522
522
523 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
523 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
524 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
524 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
525 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
525 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
526 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
526 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
527 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
527 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
528 ])
528 ])
529 def test_permission_calculator_choose_permission(
529 def test_permission_calculator_choose_permission(
530 user_regular, algo, new_permission, old_permission, expected):
530 user_regular, algo, new_permission, old_permission, expected):
531 calculator = auth.PermissionCalculator(
531 calculator = auth.PermissionCalculator(
532 user_regular.user_id, {}, False, False, False, algo)
532 user_regular.user_id, {}, False, False, False, algo)
533 result = calculator._choose_permission(new_permission, old_permission)
533 result = calculator._choose_permission(new_permission, old_permission)
534 assert result == expected
534 assert result == expected
535
535
536
536
537 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
537 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
538 user_regular):
538 user_regular):
539 calculator = auth.PermissionCalculator(
539 calculator = auth.PermissionCalculator(
540 user_regular.user_id, {}, False, False, False, 'invalid')
540 user_regular.user_id, {}, False, False, False, 'invalid')
541 result = calculator._choose_permission(
541 result = calculator._choose_permission(
542 'repository.read', 'repository.read')
542 'repository.read', 'repository.read')
543 # TODO: johbo: This documents the existing behavior. Think of an
543 # TODO: johbo: This documents the existing behavior. Think of an
544 # improvement.
544 # improvement.
545 assert result is None
545 assert result is None
546
546
547
547
548 def test_auth_user_get_cookie_store_for_normal_user(user_util):
548 def test_auth_user_get_cookie_store_for_normal_user(user_util):
549 user = user_util.create_user()
549 user = user_util.create_user()
550 auth_user = auth.AuthUser(user_id=user.user_id)
550 auth_user = auth.AuthUser(user_id=user.user_id)
551 expected_data = {
551 expected_data = {
552 'username': user.username,
552 'username': user.username,
553 'user_id': user.user_id,
553 'user_id': user.user_id,
554 'password': md5(user.password),
554 'password': md5(user.password),
555 'is_authenticated': False
555 'is_authenticated': False
556 }
556 }
557 assert auth_user.get_cookie_store() == expected_data
557 assert auth_user.get_cookie_store() == expected_data
558
558
559
559
560 def test_auth_user_get_cookie_store_for_default_user():
560 def test_auth_user_get_cookie_store_for_default_user():
561 default_user = User.get_default_user()
561 default_user = User.get_default_user()
562 auth_user = auth.AuthUser()
562 auth_user = auth.AuthUser()
563 expected_data = {
563 expected_data = {
564 'username': User.DEFAULT_USER,
564 'username': User.DEFAULT_USER,
565 'user_id': default_user.user_id,
565 'user_id': default_user.user_id,
566 'password': md5(default_user.password),
566 'password': md5(default_user.password),
567 'is_authenticated': True
567 'is_authenticated': True
568 }
568 }
569 assert auth_user.get_cookie_store() == expected_data
569 assert auth_user.get_cookie_store() == expected_data
570
570
571
571
572 def get_permissions(user, **kwargs):
572 def get_permissions(user, **kwargs):
573 """
573 """
574 Utility filling in useful defaults into the call to `_cached_perms_data`.
574 Utility filling in useful defaults into the call to `_cached_perms_data`.
575
575
576 Fill in `**kwargs` if specific values are needed for a test.
576 Fill in `**kwargs` if specific values are needed for a test.
577 """
577 """
578 call_args = {
578 call_args = {
579 'user_id': user.user_id,
579 'user_id': user.user_id,
580 'scope': {},
580 'scope': {},
581 'user_is_admin': False,
581 'user_is_admin': False,
582 'user_inherit_default_permissions': False,
582 'user_inherit_default_permissions': False,
583 'explicit': False,
583 'explicit': False,
584 'algo': 'higherwin',
584 'algo': 'higherwin',
585 'calculate_super_admin': False,
585 'calculate_super_admin': False,
586 }
586 }
587 call_args.update(kwargs)
587 call_args.update(kwargs)
588 permissions = auth._cached_perms_data(**call_args)
588 permissions = auth._cached_perms_data(**call_args)
589 return permissions
589 return permissions
590
590
591
591
592 class TestGenerateAuthToken(object):
592 class TestGenerateAuthToken(object):
593 def test_salt_is_used_when_specified(self):
593 def test_salt_is_used_when_specified(self):
594 salt = 'abcde'
594 salt = 'abcde'
595 user_name = 'test_user'
595 user_name = 'test_user'
596 result = auth.generate_auth_token(user_name, salt)
596 result = auth.generate_auth_token(user_name, salt)
597 expected_result = sha1(user_name + salt).hexdigest()
597 expected_result = sha1(user_name + salt).hexdigest()
598 assert result == expected_result
598 assert result == expected_result
599
599
600 def test_salt_is_geneated_when_not_specified(self):
600 def test_salt_is_geneated_when_not_specified(self):
601 user_name = 'test_user'
601 user_name = 'test_user'
602 random_salt = os.urandom(16)
602 random_salt = os.urandom(16)
603 with patch.object(auth, 'os') as os_mock:
603 with patch.object(auth, 'os') as os_mock:
604 os_mock.urandom.return_value = random_salt
604 os_mock.urandom.return_value = random_salt
605 result = auth.generate_auth_token(user_name)
605 result = auth.generate_auth_token(user_name)
606 expected_result = sha1(user_name + random_salt).hexdigest()
606 expected_result = sha1(user_name + random_salt).hexdigest()
607 assert result == expected_result
607 assert result == expected_result
608
608
609
609
610 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
610 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
611 ('', None, False,
611 ('', None, False,
612 []),
612 []),
613 ('wrongtoken', None, False,
613 ('wrongtoken', None, False,
614 []),
614 []),
615 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
615 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
616 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
616 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
617 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
617 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
618 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
618 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
619 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
619 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
620 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
620 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
621 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
621 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
622 ])
622 ])
623 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
623 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
624 user_util):
624 user_util):
625 user = user_util.create_user()
625 user = user_util.create_user()
626 user_id = user.user_id
626 user_id = user.user_id
627 for token, role, expires in expected_tokens:
627 for token, role, expires in expected_tokens:
628 new_token = AuthTokenModel().create(user_id, u'test-token', expires, role)
628 new_token = AuthTokenModel().create(user_id, u'test-token', expires, role)
629 new_token.api_key = token # inject known name for testing...
629 new_token.api_key = token # inject known name for testing...
630
630
631 assert auth_result == user.authenticate_by_token(
631 assert auth_result == user.authenticate_by_token(
632 test_token, roles=test_roles)
632 test_token, roles=test_roles)
General Comments 0
You need to be logged in to leave comments. Login now