##// 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 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 from rhodecode.apps._base import ADMIN_PREFIX
23 23
24 24
25 25 def admin_routes(config):
26 26 """
27 27 Admin prefixed routes
28 28 """
29 29
30 30 config.add_route(
31 31 name='admin_audit_logs',
32 32 pattern='/audit_logs')
33 33
34 34 config.add_route(
35 35 name='admin_audit_log_entry',
36 36 pattern='/audit_logs/{audit_log_id}')
37 37
38 38 config.add_route(
39 39 name='pull_requests_global_0', # backward compat
40 40 pattern='/pull_requests/{pull_request_id:\d+}')
41 41 config.add_route(
42 42 name='pull_requests_global_1', # backward compat
43 43 pattern='/pull-requests/{pull_request_id:\d+}')
44 44 config.add_route(
45 45 name='pull_requests_global',
46 46 pattern='/pull-request/{pull_request_id:\d+}')
47 47
48 48 config.add_route(
49 49 name='admin_settings_open_source',
50 50 pattern='/settings/open_source')
51 51 config.add_route(
52 52 name='admin_settings_vcs_svn_generate_cfg',
53 53 pattern='/settings/vcs/svn_generate_cfg')
54 54
55 55 config.add_route(
56 56 name='admin_settings_system',
57 57 pattern='/settings/system')
58 58 config.add_route(
59 59 name='admin_settings_system_update',
60 60 pattern='/settings/system/updates')
61 61
62 62 config.add_route(
63 63 name='admin_settings_exception_tracker',
64 64 pattern='/settings/exceptions')
65 65 config.add_route(
66 66 name='admin_settings_exception_tracker_delete_all',
67 67 pattern='/settings/exceptions/delete')
68 68 config.add_route(
69 69 name='admin_settings_exception_tracker_show',
70 70 pattern='/settings/exceptions/{exception_id}')
71 71 config.add_route(
72 72 name='admin_settings_exception_tracker_delete',
73 73 pattern='/settings/exceptions/{exception_id}/delete')
74 74
75 75 config.add_route(
76 76 name='admin_settings_sessions',
77 77 pattern='/settings/sessions')
78 78 config.add_route(
79 79 name='admin_settings_sessions_cleanup',
80 80 pattern='/settings/sessions/cleanup')
81 81
82 82 config.add_route(
83 83 name='admin_settings_process_management',
84 84 pattern='/settings/process_management')
85 85 config.add_route(
86 86 name='admin_settings_process_management_data',
87 87 pattern='/settings/process_management/data')
88 88 config.add_route(
89 89 name='admin_settings_process_management_signal',
90 90 pattern='/settings/process_management/signal')
91 91 config.add_route(
92 92 name='admin_settings_process_management_master_signal',
93 93 pattern='/settings/process_management/master_signal')
94 94
95 95 # default settings
96 96 config.add_route(
97 97 name='admin_defaults_repositories',
98 98 pattern='/defaults/repositories')
99 99 config.add_route(
100 100 name='admin_defaults_repositories_update',
101 101 pattern='/defaults/repositories/update')
102 102
103 103 # admin settings
104 104
105 105 config.add_route(
106 106 name='admin_settings',
107 107 pattern='/settings')
108 108 config.add_route(
109 109 name='admin_settings_update',
110 110 pattern='/settings/update')
111 111
112 112 config.add_route(
113 113 name='admin_settings_global',
114 114 pattern='/settings/global')
115 115 config.add_route(
116 116 name='admin_settings_global_update',
117 117 pattern='/settings/global/update')
118 118
119 119 config.add_route(
120 120 name='admin_settings_vcs',
121 121 pattern='/settings/vcs')
122 122 config.add_route(
123 123 name='admin_settings_vcs_update',
124 124 pattern='/settings/vcs/update')
125 125 config.add_route(
126 126 name='admin_settings_vcs_svn_pattern_delete',
127 127 pattern='/settings/vcs/svn_pattern_delete')
128 128
129 129 config.add_route(
130 130 name='admin_settings_mapping',
131 131 pattern='/settings/mapping')
132 132 config.add_route(
133 133 name='admin_settings_mapping_update',
134 134 pattern='/settings/mapping/update')
135 135
136 136 config.add_route(
137 137 name='admin_settings_visual',
138 138 pattern='/settings/visual')
139 139 config.add_route(
140 140 name='admin_settings_visual_update',
141 141 pattern='/settings/visual/update')
142 142
143
144 143 config.add_route(
145 144 name='admin_settings_issuetracker',
146 145 pattern='/settings/issue-tracker')
147 146 config.add_route(
148 147 name='admin_settings_issuetracker_update',
149 148 pattern='/settings/issue-tracker/update')
150 149 config.add_route(
151 150 name='admin_settings_issuetracker_test',
152 151 pattern='/settings/issue-tracker/test')
153 152 config.add_route(
154 153 name='admin_settings_issuetracker_delete',
155 154 pattern='/settings/issue-tracker/delete')
156 155
157 156 config.add_route(
158 157 name='admin_settings_email',
159 158 pattern='/settings/email')
160 159 config.add_route(
161 160 name='admin_settings_email_update',
162 161 pattern='/settings/email/update')
163 162
164 163 config.add_route(
165 164 name='admin_settings_hooks',
166 165 pattern='/settings/hooks')
167 166 config.add_route(
168 167 name='admin_settings_hooks_update',
169 168 pattern='/settings/hooks/update')
170 169 config.add_route(
171 170 name='admin_settings_hooks_delete',
172 171 pattern='/settings/hooks/delete')
173 172
174 173 config.add_route(
175 174 name='admin_settings_search',
176 175 pattern='/settings/search')
177 176
178 177 config.add_route(
179 178 name='admin_settings_labs',
180 179 pattern='/settings/labs')
181 180 config.add_route(
182 181 name='admin_settings_labs_update',
183 182 pattern='/settings/labs/update')
184 183
185 184 # Automation EE feature
186 185 config.add_route(
187 186 'admin_settings_automation',
188 187 pattern=ADMIN_PREFIX + '/settings/automation')
189 188
190 189 # global permissions
191 190
192 191 config.add_route(
193 192 name='admin_permissions_application',
194 193 pattern='/permissions/application')
195 194 config.add_route(
196 195 name='admin_permissions_application_update',
197 196 pattern='/permissions/application/update')
198 197
199 198 config.add_route(
200 199 name='admin_permissions_global',
201 200 pattern='/permissions/global')
202 201 config.add_route(
203 202 name='admin_permissions_global_update',
204 203 pattern='/permissions/global/update')
205 204
206 205 config.add_route(
207 206 name='admin_permissions_object',
208 207 pattern='/permissions/object')
209 208 config.add_route(
210 209 name='admin_permissions_object_update',
211 210 pattern='/permissions/object/update')
212 211
213 212 # Branch perms EE feature
214 213 config.add_route(
215 214 name='admin_permissions_branch',
216 215 pattern='/permissions/branch')
217 216
218 217 config.add_route(
219 218 name='admin_permissions_ips',
220 219 pattern='/permissions/ips')
221 220
222 221 config.add_route(
223 222 name='admin_permissions_overview',
224 223 pattern='/permissions/overview')
225 224
226 225 config.add_route(
227 226 name='admin_permissions_auth_token_access',
228 227 pattern='/permissions/auth_token_access')
229 228
230 229 config.add_route(
231 230 name='admin_permissions_ssh_keys',
232 231 pattern='/permissions/ssh_keys')
233 232 config.add_route(
234 233 name='admin_permissions_ssh_keys_data',
235 234 pattern='/permissions/ssh_keys/data')
236 235 config.add_route(
237 236 name='admin_permissions_ssh_keys_update',
238 237 pattern='/permissions/ssh_keys/update')
239 238
240 239 # users admin
241 240 config.add_route(
242 241 name='users',
243 242 pattern='/users')
244 243
245 244 config.add_route(
246 245 name='users_data',
247 246 pattern='/users_data')
248 247
249 248 config.add_route(
250 249 name='users_create',
251 250 pattern='/users/create')
252 251
253 252 config.add_route(
254 253 name='users_new',
255 254 pattern='/users/new')
256 255
257 256 # user management
258 257 config.add_route(
259 258 name='user_edit',
260 259 pattern='/users/{user_id:\d+}/edit',
261 260 user_route=True)
262 261 config.add_route(
263 262 name='user_edit_advanced',
264 263 pattern='/users/{user_id:\d+}/edit/advanced',
265 264 user_route=True)
266 265 config.add_route(
267 266 name='user_edit_global_perms',
268 267 pattern='/users/{user_id:\d+}/edit/global_permissions',
269 268 user_route=True)
270 269 config.add_route(
271 270 name='user_edit_global_perms_update',
272 271 pattern='/users/{user_id:\d+}/edit/global_permissions/update',
273 272 user_route=True)
274 273 config.add_route(
275 274 name='user_update',
276 275 pattern='/users/{user_id:\d+}/update',
277 276 user_route=True)
278 277 config.add_route(
279 278 name='user_delete',
280 279 pattern='/users/{user_id:\d+}/delete',
281 280 user_route=True)
282 281 config.add_route(
283 282 name='user_enable_force_password_reset',
284 283 pattern='/users/{user_id:\d+}/password_reset_enable',
285 284 user_route=True)
286 285 config.add_route(
287 286 name='user_disable_force_password_reset',
288 287 pattern='/users/{user_id:\d+}/password_reset_disable',
289 288 user_route=True)
290 289 config.add_route(
291 290 name='user_create_personal_repo_group',
292 291 pattern='/users/{user_id:\d+}/create_repo_group',
293 292 user_route=True)
294 293
295 294 # user auth tokens
296 295 config.add_route(
297 296 name='edit_user_auth_tokens',
298 297 pattern='/users/{user_id:\d+}/edit/auth_tokens',
299 298 user_route=True)
300 299 config.add_route(
301 300 name='edit_user_auth_tokens_add',
302 301 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
303 302 user_route=True)
304 303 config.add_route(
305 304 name='edit_user_auth_tokens_delete',
306 305 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete',
307 306 user_route=True)
308 307
309 308 # user ssh keys
310 309 config.add_route(
311 310 name='edit_user_ssh_keys',
312 311 pattern='/users/{user_id:\d+}/edit/ssh_keys',
313 312 user_route=True)
314 313 config.add_route(
315 314 name='edit_user_ssh_keys_generate_keypair',
316 315 pattern='/users/{user_id:\d+}/edit/ssh_keys/generate',
317 316 user_route=True)
318 317 config.add_route(
319 318 name='edit_user_ssh_keys_add',
320 319 pattern='/users/{user_id:\d+}/edit/ssh_keys/new',
321 320 user_route=True)
322 321 config.add_route(
323 322 name='edit_user_ssh_keys_delete',
324 323 pattern='/users/{user_id:\d+}/edit/ssh_keys/delete',
325 324 user_route=True)
326 325
327 326 # user emails
328 327 config.add_route(
329 328 name='edit_user_emails',
330 329 pattern='/users/{user_id:\d+}/edit/emails',
331 330 user_route=True)
332 331 config.add_route(
333 332 name='edit_user_emails_add',
334 333 pattern='/users/{user_id:\d+}/edit/emails/new',
335 334 user_route=True)
336 335 config.add_route(
337 336 name='edit_user_emails_delete',
338 337 pattern='/users/{user_id:\d+}/edit/emails/delete',
339 338 user_route=True)
340 339
341 340 # user IPs
342 341 config.add_route(
343 342 name='edit_user_ips',
344 343 pattern='/users/{user_id:\d+}/edit/ips',
345 344 user_route=True)
346 345 config.add_route(
347 346 name='edit_user_ips_add',
348 347 pattern='/users/{user_id:\d+}/edit/ips/new',
349 348 user_route_with_default=True) # enabled for default user too
350 349 config.add_route(
351 350 name='edit_user_ips_delete',
352 351 pattern='/users/{user_id:\d+}/edit/ips/delete',
353 352 user_route_with_default=True) # enabled for default user too
354 353
355 354 # user perms
356 355 config.add_route(
357 356 name='edit_user_perms_summary',
358 357 pattern='/users/{user_id:\d+}/edit/permissions_summary',
359 358 user_route=True)
360 359 config.add_route(
361 360 name='edit_user_perms_summary_json',
362 361 pattern='/users/{user_id:\d+}/edit/permissions_summary/json',
363 362 user_route=True)
364 363
365 364 # user user groups management
366 365 config.add_route(
367 366 name='edit_user_groups_management',
368 367 pattern='/users/{user_id:\d+}/edit/groups_management',
369 368 user_route=True)
370 369
371 370 config.add_route(
372 371 name='edit_user_groups_management_updates',
373 372 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates',
374 373 user_route=True)
375 374
376 375 # user audit logs
377 376 config.add_route(
378 377 name='edit_user_audit_logs',
379 378 pattern='/users/{user_id:\d+}/edit/audit', user_route=True)
380 379
381 380 config.add_route(
382 381 name='edit_user_audit_logs_download',
383 382 pattern='/users/{user_id:\d+}/edit/audit/download', user_route=True)
384 383
385 384 # user caches
386 385 config.add_route(
387 386 name='edit_user_caches',
388 387 pattern='/users/{user_id:\d+}/edit/caches',
389 388 user_route=True)
390 389 config.add_route(
391 390 name='edit_user_caches_update',
392 391 pattern='/users/{user_id:\d+}/edit/caches/update',
393 392 user_route=True)
394 393
395 394 # user-groups admin
396 395 config.add_route(
397 396 name='user_groups',
398 397 pattern='/user_groups')
399 398
400 399 config.add_route(
401 400 name='user_groups_data',
402 401 pattern='/user_groups_data')
403 402
404 403 config.add_route(
405 404 name='user_groups_new',
406 405 pattern='/user_groups/new')
407 406
408 407 config.add_route(
409 408 name='user_groups_create',
410 409 pattern='/user_groups/create')
411 410
412 411 # repos admin
413 412 config.add_route(
414 413 name='repos',
415 414 pattern='/repos')
416 415
417 416 config.add_route(
417 name='repos_data',
418 pattern='/repos_data')
419
420 config.add_route(
418 421 name='repo_new',
419 422 pattern='/repos/new')
420 423
421 424 config.add_route(
422 425 name='repo_create',
423 426 pattern='/repos/create')
424 427
425 428 # repo groups admin
426 429 config.add_route(
427 430 name='repo_groups',
428 431 pattern='/repo_groups')
429 432
430 433 config.add_route(
431 434 name='repo_groups_data',
432 435 pattern='/repo_groups_data')
433 436
434 437 config.add_route(
435 438 name='repo_group_new',
436 439 pattern='/repo_group/new')
437 440
438 441 config.add_route(
439 442 name='repo_group_create',
440 443 pattern='/repo_group/create')
441 444
442 445
443 446 def includeme(config):
444 447 from rhodecode.apps._base.navigation import includeme as nav_includeme
445 448
446 449 # Create admin navigation registry and add it to the pyramid registry.
447 450 nav_includeme(config)
448 451
449 452 # main admin routes
450 453 config.add_route(name='admin_home', pattern=ADMIN_PREFIX)
451 454 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
452 455
453 456 # Scan module for configuration decorators.
454 457 config.scan('.views', ignore='.tests')
@@ -1,365 +1,361 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import datetime
21 21 import logging
22 22 import time
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26
27 27 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 28 from pyramid.view import view_config
29 29 from pyramid.renderers import render
30 30 from pyramid.response import Response
31 31
32 32 from rhodecode import events
33 33 from rhodecode.apps._base import BaseAppView, DataGridAppView
34 34
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, CSRFRequired, NotAnonymous,
37 37 HasPermissionAny, HasRepoGroupPermissionAny)
38 38 from rhodecode.lib import helpers as h, audit_logger
39 39 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
40 40 from rhodecode.model.forms import RepoGroupForm
41 41 from rhodecode.model.permission import PermissionModel
42 42 from rhodecode.model.repo_group import RepoGroupModel
43 43 from rhodecode.model.scm import RepoGroupList
44 44 from rhodecode.model.db import (
45 45 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminRepoGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 return c
56 56
57 57 def _load_form_data(self, c):
58 58 allow_empty_group = False
59 59
60 60 if self._can_create_repo_group():
61 61 # we're global admin, we're ok and we can create TOP level groups
62 62 allow_empty_group = True
63 63
64 64 # override the choices for this form, we need to filter choices
65 65 # and display only those we have ADMIN right
66 66 groups_with_admin_rights = RepoGroupList(
67 67 RepoGroup.query().all(),
68 perm_set=['group.admin'])
68 perm_set=['group.admin'], extra_kwargs=dict(user=self._rhodecode_user))
69 69 c.repo_groups = RepoGroup.groups_choices(
70 70 groups=groups_with_admin_rights,
71 71 show_empty_group=allow_empty_group)
72 72
73 73 def _can_create_repo_group(self, parent_group_id=None):
74 74 is_admin = HasPermissionAny('hg.admin')('group create controller')
75 75 create_repo_group = HasPermissionAny(
76 76 'hg.repogroup.create.true')('group create controller')
77 77 if is_admin or (create_repo_group and not parent_group_id):
78 78 # we're global admin, or we have global repo group create
79 79 # permission
80 80 # we're ok and we can create TOP level groups
81 81 return True
82 82 elif parent_group_id:
83 83 # we check the permission if we can write to parent group
84 84 group = RepoGroup.get(parent_group_id)
85 85 group_name = group.group_name if group else None
86 86 if HasRepoGroupPermissionAny('group.admin')(
87 87 group_name, 'check if user is an admin of group'):
88 88 # we're an admin of passed in group, we're ok.
89 89 return True
90 90 else:
91 91 return False
92 92 return False
93 93
94 94 # permission check in data loading of
95 95 # `repo_group_list_data` via RepoGroupList
96 96 @LoginRequired()
97 97 @NotAnonymous()
98 98 @view_config(
99 99 route_name='repo_groups', request_method='GET',
100 100 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
101 101 def repo_group_list(self):
102 102 c = self.load_default_context()
103 103 return self._get_template_context(c)
104 104
105 105 # permission check inside
106 106 @LoginRequired()
107 107 @NotAnonymous()
108 108 @view_config(
109 109 route_name='repo_groups_data', request_method='GET',
110 110 renderer='json_ext', xhr=True)
111 111 def repo_group_list_data(self):
112 112 self.load_default_context()
113 113 column_map = {
114 114 'name_raw': 'group_name_hash',
115 115 'desc': 'group_description',
116 116 'last_change_raw': 'updated_on',
117 117 'top_level_repos': 'repos_total',
118 118 'owner': 'user_username',
119 119 }
120 120 draw, start, limit = self._extract_chunk(self.request)
121 121 search_q, order_by, order_dir = self._extract_ordering(
122 122 self.request, column_map=column_map)
123 123
124 124 _render = self.request.get_partial_renderer(
125 125 'rhodecode:templates/data_table/_dt_elements.mako')
126 126 c = _render.get_call_context()
127 127
128 128 def quick_menu(repo_group_name):
129 129 return _render('quick_repo_group_menu', repo_group_name)
130 130
131 131 def repo_group_lnk(repo_group_name):
132 132 return _render('repo_group_name', repo_group_name)
133 133
134 134 def last_change(last_change):
135 135 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
136 136 ts = time.time()
137 137 utc_offset = (datetime.datetime.fromtimestamp(ts)
138 138 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
139 139 last_change = last_change + datetime.timedelta(seconds=utc_offset)
140 140 return _render("last_change", last_change)
141 141
142 142 def desc(desc, personal):
143 143 return _render(
144 144 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
145 145
146 146 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
147 147 return _render(
148 148 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
149 149
150 150 def user_profile(username):
151 151 return _render('user_profile', username)
152 152
153 auth_repo_group_list = RepoGroupList(
154 RepoGroup.query().all(), perm_set=['group.admin'])
155
156 allowed_ids = [-1]
157 for repo_group in auth_repo_group_list:
158 allowed_ids.append(repo_group.group_id)
153 _perms = ['group.admin']
154 allowed_ids = [-1] + self._rhodecode_user.repo_group_acl_ids_from_stack(_perms)
159 155
160 156 repo_groups_data_total_count = RepoGroup.query()\
161 157 .filter(or_(
162 158 # generate multiple IN to fix limitation problems
163 159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
164 160 )) \
165 161 .count()
166 162
167 163 repo_groups_data_total_inactive_count = RepoGroup.query()\
168 164 .filter(RepoGroup.group_id.in_(allowed_ids))\
169 165 .count()
170 166
171 167 repo_count = count(Repository.repo_id)
172 168 base_q = Session.query(
173 169 RepoGroup.group_name,
174 170 RepoGroup.group_name_hash,
175 171 RepoGroup.group_description,
176 172 RepoGroup.group_id,
177 173 RepoGroup.personal,
178 174 RepoGroup.updated_on,
179 175 User,
180 176 repo_count.label('repos_count')
181 177 ) \
182 178 .filter(or_(
183 179 # generate multiple IN to fix limitation problems
184 180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
185 181 )) \
186 182 .outerjoin(Repository, Repository.group_id == RepoGroup.group_id) \
187 183 .join(User, User.user_id == RepoGroup.user_id) \
188 184 .group_by(RepoGroup, User)
189 185
190 186 if search_q:
191 187 like_expression = u'%{}%'.format(safe_unicode(search_q))
192 188 base_q = base_q.filter(or_(
193 189 RepoGroup.group_name.ilike(like_expression),
194 190 ))
195 191
196 192 repo_groups_data_total_filtered_count = base_q.count()
197 193 # the inactive isn't really used, but we still make it same as other data grids
198 194 # which use inactive (users,user groups)
199 195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
200 196
201 197 sort_defined = False
202 198 if order_by == 'group_name':
203 199 sort_col = func.lower(RepoGroup.group_name)
204 200 sort_defined = True
205 201 elif order_by == 'repos_total':
206 202 sort_col = repo_count
207 203 sort_defined = True
208 204 elif order_by == 'user_username':
209 205 sort_col = User.username
210 206 else:
211 207 sort_col = getattr(RepoGroup, order_by, None)
212 208
213 209 if sort_defined or sort_col:
214 210 if order_dir == 'asc':
215 211 sort_col = sort_col.asc()
216 212 else:
217 213 sort_col = sort_col.desc()
218 214
219 215 base_q = base_q.order_by(sort_col)
220 216 base_q = base_q.offset(start).limit(limit)
221 217
222 218 # authenticated access to user groups
223 219 auth_repo_group_list = base_q.all()
224 220
225 221 repo_groups_data = []
226 222 for repo_gr in auth_repo_group_list:
227 223 row = {
228 224 "menu": quick_menu(repo_gr.group_name),
229 225 "name": repo_group_lnk(repo_gr.group_name),
230 226 "name_raw": repo_gr.group_name,
231 227 "last_change": last_change(repo_gr.updated_on),
232 228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
233 229
234 230 "last_changeset": "",
235 231 "last_changeset_raw": "",
236 232
237 233 "desc": desc(repo_gr.group_description, repo_gr.personal),
238 234 "owner": user_profile(repo_gr.User.username),
239 235 "top_level_repos": repo_gr.repos_count,
240 236 "action": repo_group_actions(
241 237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
242 238
243 239 }
244 240
245 241 repo_groups_data.append(row)
246 242
247 243 data = ({
248 244 'draw': draw,
249 245 'data': repo_groups_data,
250 246 'recordsTotal': repo_groups_data_total_count,
251 247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
252 248 'recordsFiltered': repo_groups_data_total_filtered_count,
253 249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
254 250 })
255 251
256 252 return data
257 253
258 254 @LoginRequired()
259 255 @NotAnonymous()
260 256 # perm checks inside
261 257 @view_config(
262 258 route_name='repo_group_new', request_method='GET',
263 259 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
264 260 def repo_group_new(self):
265 261 c = self.load_default_context()
266 262
267 263 # perm check for admin, create_group perm or admin of parent_group
268 264 parent_group_id = safe_int(self.request.GET.get('parent_group'))
269 265 if not self._can_create_repo_group(parent_group_id):
270 266 raise HTTPForbidden()
271 267
272 268 self._load_form_data(c)
273 269
274 270 defaults = {} # Future proof for default of repo group
275 271 data = render(
276 272 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
277 273 self._get_template_context(c), self.request)
278 274 html = formencode.htmlfill.render(
279 275 data,
280 276 defaults=defaults,
281 277 encoding="UTF-8",
282 278 force_defaults=False
283 279 )
284 280 return Response(html)
285 281
286 282 @LoginRequired()
287 283 @NotAnonymous()
288 284 @CSRFRequired()
289 285 # perm checks inside
290 286 @view_config(
291 287 route_name='repo_group_create', request_method='POST',
292 288 renderer='rhodecode:templates/admin/repo_groups/repo_group_add.mako')
293 289 def repo_group_create(self):
294 290 c = self.load_default_context()
295 291 _ = self.request.translate
296 292
297 293 parent_group_id = safe_int(self.request.POST.get('group_parent_id'))
298 294 can_create = self._can_create_repo_group(parent_group_id)
299 295
300 296 self._load_form_data(c)
301 297 # permissions for can create group based on parent_id are checked
302 298 # here in the Form
303 299 available_groups = map(lambda k: safe_unicode(k[0]), c.repo_groups)
304 300 repo_group_form = RepoGroupForm(
305 301 self.request.translate, available_groups=available_groups,
306 302 can_create_in_root=can_create)()
307 303
308 304 repo_group_name = self.request.POST.get('group_name')
309 305 try:
310 306 owner = self._rhodecode_user
311 307 form_result = repo_group_form.to_python(dict(self.request.POST))
312 308 copy_permissions = form_result.get('group_copy_permissions')
313 309 repo_group = RepoGroupModel().create(
314 310 group_name=form_result['group_name_full'],
315 311 group_description=form_result['group_description'],
316 312 owner=owner.user_id,
317 313 copy_permissions=form_result['group_copy_permissions']
318 314 )
319 315 Session().flush()
320 316
321 317 repo_group_data = repo_group.get_api_data()
322 318 audit_logger.store_web(
323 319 'repo_group.create', action_data={'data': repo_group_data},
324 320 user=self._rhodecode_user)
325 321
326 322 Session().commit()
327 323
328 324 _new_group_name = form_result['group_name_full']
329 325
330 326 repo_group_url = h.link_to(
331 327 _new_group_name,
332 328 h.route_path('repo_group_home', repo_group_name=_new_group_name))
333 329 h.flash(h.literal(_('Created repository group %s')
334 330 % repo_group_url), category='success')
335 331
336 332 except formencode.Invalid as errors:
337 333 data = render(
338 334 'rhodecode:templates/admin/repo_groups/repo_group_add.mako',
339 335 self._get_template_context(c), self.request)
340 336 html = formencode.htmlfill.render(
341 337 data,
342 338 defaults=errors.value,
343 339 errors=errors.error_dict or {},
344 340 prefix_error=False,
345 341 encoding="UTF-8",
346 342 force_defaults=False
347 343 )
348 344 return Response(html)
349 345 except Exception:
350 346 log.exception("Exception during creation of repository group")
351 347 h.flash(_('Error occurred during creation of repository group %s')
352 348 % repo_group_name, category='error')
353 349 raise HTTPFound(h.route_path('home'))
354 350
355 351 affected_user_ids = [self._rhodecode_user.user_id]
356 352 if copy_permissions:
357 353 user_group_perms = repo_group.permissions(expand_from_user_groups=True)
358 354 copy_perms = [perm['user_id'] for perm in user_group_perms]
359 355 # also include those newly created by copy
360 356 affected_user_ids.extend(copy_perms)
361 357 PermissionModel().trigger_permission_flush(affected_user_ids)
362 358
363 359 raise HTTPFound(
364 360 h.route_path('repo_group_home',
365 361 repo_group_name=form_result['group_name_full']))
@@ -1,187 +1,266 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import formencode
23 23 import formencode.htmlfill
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode import events
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.celerylib.utils import get_task_id
33 33
34 from rhodecode.lib.ext_json import json
35 34 from rhodecode.lib.auth import (
36 35 LoginRequired, CSRFRequired, NotAnonymous,
37 36 HasPermissionAny, HasRepoGroupPermissionAny)
38 37 from rhodecode.lib import helpers as h
39 38 from rhodecode.lib.utils import repo_name_slug
40 39 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 40 from rhodecode.model.forms import RepoForm
42 41 from rhodecode.model.permission import PermissionModel
43 42 from rhodecode.model.repo import RepoModel
44 43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
45 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 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class AdminReposView(BaseAppView, DataGridAppView):
52 52
53 53 def load_default_context(self):
54 54 c = self._get_local_tmpl_context()
55 55
56 56 return c
57 57
58 58 def _load_form_data(self, c):
59 59 acl_groups = RepoGroupList(RepoGroup.query().all(),
60 60 perm_set=['group.write', 'group.admin'])
61 61 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
62 62 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
63 63 c.personal_repo_group = self._rhodecode_user.personal_repo_group
64 64
65 65 @LoginRequired()
66 66 @NotAnonymous()
67 67 # perms check inside
68 68 @view_config(
69 69 route_name='repos', request_method='GET',
70 70 renderer='rhodecode:templates/admin/repos/repos.mako')
71 71 def repository_list(self):
72 72 c = self.load_default_context()
73 return self._get_template_context(c)
73 74
74 repo_list = Repository.get_all_repos()
75 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
75 @LoginRequired()
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 151 repos_data = RepoModel().get_repos_as_dict(
77 repo_list=c.repo_list, admin=True, super_user_actions=True)
78 # json used to render the grid
79 c.data = json.dumps(repos_data)
152 repo_list=repos_list, admin=True, super_user_actions=True)
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 162 @LoginRequired()
84 163 @NotAnonymous()
85 164 # perms check inside
86 165 @view_config(
87 166 route_name='repo_new', request_method='GET',
88 167 renderer='rhodecode:templates/admin/repos/repo_add.mako')
89 168 def repository_new(self):
90 169 c = self.load_default_context()
91 170
92 171 new_repo = self.request.GET.get('repo', '')
93 172 parent_group = safe_int(self.request.GET.get('parent_group'))
94 173 _gr = RepoGroup.get(parent_group)
95 174
96 175 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
97 176 # you're not super admin nor have global create permissions,
98 177 # but maybe you have at least write permission to a parent group ?
99 178
100 179 gr_name = _gr.group_name if _gr else None
101 180 # create repositories with write permission on group is set to true
102 181 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
103 182 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
104 183 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
105 184 if not (group_admin or (group_write and create_on_write)):
106 185 raise HTTPForbidden()
107 186
108 187 self._load_form_data(c)
109 188 c.new_repo = repo_name_slug(new_repo)
110 189
111 190 # apply the defaults from defaults page
112 191 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
113 192 # set checkbox to autochecked
114 193 defaults['repo_copy_permissions'] = True
115 194
116 195 parent_group_choice = '-1'
117 196 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
118 197 parent_group_choice = self._rhodecode_user.personal_repo_group
119 198
120 199 if parent_group and _gr:
121 200 if parent_group in [x[0] for x in c.repo_groups]:
122 201 parent_group_choice = safe_unicode(parent_group)
123 202
124 203 defaults.update({'repo_group': parent_group_choice})
125 204
126 205 data = render('rhodecode:templates/admin/repos/repo_add.mako',
127 206 self._get_template_context(c), self.request)
128 207 html = formencode.htmlfill.render(
129 208 data,
130 209 defaults=defaults,
131 210 encoding="UTF-8",
132 211 force_defaults=False
133 212 )
134 213 return Response(html)
135 214
136 215 @LoginRequired()
137 216 @NotAnonymous()
138 217 @CSRFRequired()
139 218 # perms check inside
140 219 @view_config(
141 220 route_name='repo_create', request_method='POST',
142 221 renderer='rhodecode:templates/admin/repos/repos.mako')
143 222 def repository_create(self):
144 223 c = self.load_default_context()
145 224
146 225 form_result = {}
147 226 self._load_form_data(c)
148 227
149 228 try:
150 229 # CanWriteToGroup validators checks permissions of this POST
151 230 form = RepoForm(
152 231 self.request.translate, repo_groups=c.repo_groups_choices)()
153 232 form_result = form.to_python(dict(self.request.POST))
154 233 copy_permissions = form_result.get('repo_copy_permissions')
155 234 # create is done sometimes async on celery, db transaction
156 235 # management is handled there.
157 236 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
158 237 task_id = get_task_id(task)
159 238 except formencode.Invalid as errors:
160 239 data = render('rhodecode:templates/admin/repos/repo_add.mako',
161 240 self._get_template_context(c), self.request)
162 241 html = formencode.htmlfill.render(
163 242 data,
164 243 defaults=errors.value,
165 244 errors=errors.error_dict or {},
166 245 prefix_error=False,
167 246 encoding="UTF-8",
168 247 force_defaults=False
169 248 )
170 249 return Response(html)
171 250
172 251 except Exception as e:
173 252 msg = self._log_creation_exception(e, form_result.get('repo_name'))
174 253 h.flash(msg, category='error')
175 254 raise HTTPFound(h.route_path('home'))
176 255
177 256 repo_name = form_result.get('repo_name_full')
178 257
179 258 affected_user_ids = [self._rhodecode_user.user_id]
180 259 if copy_permissions:
181 260 # permission flush is done in repo creating
182 261 pass
183 262 PermissionModel().trigger_permission_flush(affected_user_ids)
184 263
185 264 raise HTTPFound(
186 265 h.route_path('repo_creating', repo_name=repo_name,
187 266 _query=dict(task_id=task_id)))
@@ -1,273 +1,269 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView
33 33 from rhodecode.lib.auth import (
34 34 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
35 35 from rhodecode.lib import helpers as h, audit_logger
36 36 from rhodecode.lib.utils2 import safe_unicode
37 37
38 38 from rhodecode.model.forms import UserGroupForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.scm import UserGroupList
41 41 from rhodecode.model.db import (
42 42 or_, count, User, UserGroup, UserGroupMember, in_filter_generator)
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.user_group import UserGroupModel
45 45 from rhodecode.model.db import true
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class AdminUserGroupsView(BaseAppView, DataGridAppView):
51 51
52 52 def load_default_context(self):
53 53 c = self._get_local_tmpl_context()
54 54
55 55 PermissionModel().set_global_permission_choices(
56 56 c, gettext_translator=self.request.translate)
57 57
58 58 return c
59 59
60 60 # permission check in data loading of
61 61 # `user_groups_list_data` via UserGroupList
62 62 @LoginRequired()
63 63 @NotAnonymous()
64 64 @view_config(
65 65 route_name='user_groups', request_method='GET',
66 66 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
67 67 def user_groups_list(self):
68 68 c = self.load_default_context()
69 69 return self._get_template_context(c)
70 70
71 71 # permission check inside
72 72 @LoginRequired()
73 73 @NotAnonymous()
74 74 @view_config(
75 75 route_name='user_groups_data', request_method='GET',
76 76 renderer='json_ext', xhr=True)
77 77 def user_groups_list_data(self):
78 78 self.load_default_context()
79 79 column_map = {
80 80 'active': 'users_group_active',
81 81 'description': 'user_group_description',
82 82 'members': 'members_total',
83 83 'owner': 'user_username',
84 84 'sync': 'group_data'
85 85 }
86 86 draw, start, limit = self._extract_chunk(self.request)
87 87 search_q, order_by, order_dir = self._extract_ordering(
88 88 self.request, column_map=column_map)
89 89
90 90 _render = self.request.get_partial_renderer(
91 91 'rhodecode:templates/data_table/_dt_elements.mako')
92 92
93 93 def user_group_name(user_group_name):
94 94 return _render("user_group_name", user_group_name)
95 95
96 96 def user_group_actions(user_group_id, user_group_name):
97 97 return _render("user_group_actions", user_group_id, user_group_name)
98 98
99 99 def user_profile(username):
100 100 return _render('user_profile', username)
101 101
102 auth_user_group_list = UserGroupList(
103 UserGroup.query().all(), perm_set=['usergroup.admin'])
104
105 allowed_ids = [-1]
106 for user_group in auth_user_group_list:
107 allowed_ids.append(user_group.users_group_id)
102 _perms = ['usergroup.admin']
103 allowed_ids = [-1] + self._rhodecode_user.user_group_acl_ids_from_stack(_perms)
108 104
109 105 user_groups_data_total_count = UserGroup.query()\
110 106 .filter(or_(
111 107 # generate multiple IN to fix limitation problems
112 108 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
113 109 ))\
114 110 .count()
115 111
116 112 user_groups_data_total_inactive_count = UserGroup.query()\
117 113 .filter(or_(
118 114 # generate multiple IN to fix limitation problems
119 115 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
120 116 ))\
121 117 .filter(UserGroup.users_group_active != true()).count()
122 118
123 119 member_count = count(UserGroupMember.user_id)
124 120 base_q = Session.query(
125 121 UserGroup.users_group_name,
126 122 UserGroup.user_group_description,
127 123 UserGroup.users_group_active,
128 124 UserGroup.users_group_id,
129 125 UserGroup.group_data,
130 126 User,
131 127 member_count.label('member_count')
132 128 ) \
133 129 .filter(or_(
134 130 # generate multiple IN to fix limitation problems
135 131 *in_filter_generator(UserGroup.users_group_id, allowed_ids)
136 132 )) \
137 133 .outerjoin(UserGroupMember, UserGroupMember.users_group_id == UserGroup.users_group_id) \
138 134 .join(User, User.user_id == UserGroup.user_id) \
139 135 .group_by(UserGroup, User)
140 136
141 137 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
142 138
143 139 if search_q:
144 140 like_expression = u'%{}%'.format(safe_unicode(search_q))
145 141 base_q = base_q.filter(or_(
146 142 UserGroup.users_group_name.ilike(like_expression),
147 143 ))
148 144 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
149 145
150 146 user_groups_data_total_filtered_count = base_q.count()
151 147 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
152 148
153 149 sort_defined = False
154 150 if order_by == 'members_total':
155 151 sort_col = member_count
156 152 sort_defined = True
157 153 elif order_by == 'user_username':
158 154 sort_col = User.username
159 155 else:
160 156 sort_col = getattr(UserGroup, order_by, None)
161 157
162 158 if sort_defined or sort_col:
163 159 if order_dir == 'asc':
164 160 sort_col = sort_col.asc()
165 161 else:
166 162 sort_col = sort_col.desc()
167 163
168 164 base_q = base_q.order_by(sort_col)
169 165 base_q = base_q.offset(start).limit(limit)
170 166
171 167 # authenticated access to user groups
172 168 auth_user_group_list = base_q.all()
173 169
174 170 user_groups_data = []
175 171 for user_gr in auth_user_group_list:
176 172 row = {
177 173 "users_group_name": user_group_name(user_gr.users_group_name),
178 174 "name_raw": h.escape(user_gr.users_group_name),
179 175 "description": h.escape(user_gr.user_group_description),
180 176 "members": user_gr.member_count,
181 177 # NOTE(marcink): because of advanced query we
182 178 # need to load it like that
183 179 "sync": UserGroup._load_sync(
184 180 UserGroup._load_group_data(user_gr.group_data)),
185 181 "active": h.bool2icon(user_gr.users_group_active),
186 182 "owner": user_profile(user_gr.User.username),
187 183 "action": user_group_actions(
188 184 user_gr.users_group_id, user_gr.users_group_name)
189 185 }
190 186 user_groups_data.append(row)
191 187
192 188 data = ({
193 189 'draw': draw,
194 190 'data': user_groups_data,
195 191 'recordsTotal': user_groups_data_total_count,
196 192 'recordsTotalInactive': user_groups_data_total_inactive_count,
197 193 'recordsFiltered': user_groups_data_total_filtered_count,
198 194 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
199 195 })
200 196
201 197 return data
202 198
203 199 @LoginRequired()
204 200 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
205 201 @view_config(
206 202 route_name='user_groups_new', request_method='GET',
207 203 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
208 204 def user_groups_new(self):
209 205 c = self.load_default_context()
210 206 return self._get_template_context(c)
211 207
212 208 @LoginRequired()
213 209 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
214 210 @CSRFRequired()
215 211 @view_config(
216 212 route_name='user_groups_create', request_method='POST',
217 213 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
218 214 def user_groups_create(self):
219 215 _ = self.request.translate
220 216 c = self.load_default_context()
221 217 users_group_form = UserGroupForm(self.request.translate)()
222 218
223 219 user_group_name = self.request.POST.get('users_group_name')
224 220 try:
225 221 form_result = users_group_form.to_python(dict(self.request.POST))
226 222 user_group = UserGroupModel().create(
227 223 name=form_result['users_group_name'],
228 224 description=form_result['user_group_description'],
229 225 owner=self._rhodecode_user.user_id,
230 226 active=form_result['users_group_active'])
231 227 Session().flush()
232 228 creation_data = user_group.get_api_data()
233 229 user_group_name = form_result['users_group_name']
234 230
235 231 audit_logger.store_web(
236 232 'user_group.create', action_data={'data': creation_data},
237 233 user=self._rhodecode_user)
238 234
239 235 user_group_link = h.link_to(
240 236 h.escape(user_group_name),
241 237 h.route_path(
242 238 'edit_user_group', user_group_id=user_group.users_group_id))
243 239 h.flash(h.literal(_('Created user group %(user_group_link)s')
244 240 % {'user_group_link': user_group_link}),
245 241 category='success')
246 242 Session().commit()
247 243 user_group_id = user_group.users_group_id
248 244 except formencode.Invalid as errors:
249 245
250 246 data = render(
251 247 'rhodecode:templates/admin/user_groups/user_group_add.mako',
252 248 self._get_template_context(c), self.request)
253 249 html = formencode.htmlfill.render(
254 250 data,
255 251 defaults=errors.value,
256 252 errors=errors.error_dict or {},
257 253 prefix_error=False,
258 254 encoding="UTF-8",
259 255 force_defaults=False
260 256 )
261 257 return Response(html)
262 258
263 259 except Exception:
264 260 log.exception("Exception creating user group")
265 261 h.flash(_('Error occurred during creation of user group %s') \
266 262 % user_group_name, category='error')
267 263 raise HTTPFound(h.route_path('user_groups_new'))
268 264
269 265 affected_user_ids = [self._rhodecode_user.user_id]
270 266 PermissionModel().trigger_permission_flush(affected_user_ids)
271 267
272 268 raise HTTPFound(
273 269 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,2368 +1,2410 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default', 1
356 356 >>> perms['resource']
357 357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin', 2
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 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 370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
371 (perm, origin, obj_id))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, (pattern, perm, origin)):
390 390
391 391 self.perm_origin_stack.setdefault(key, {}) \
392 392 .setdefault(pattern, []).append((perm, origin))
393 393
394 394 if key in self:
395 395 self[key].__setitem__(pattern, perm)
396 396 else:
397 397 patterns = collections.OrderedDict()
398 398 patterns[pattern] = perm
399 399 dict.__setitem__(self, key, patterns)
400 400
401 401
402 402 class PermissionCalculator(object):
403 403
404 404 def __init__(
405 405 self, user_id, scope, user_is_admin,
406 406 user_inherit_default_permissions, explicit, algo,
407 407 calculate_super_admin_as_user=False):
408 408
409 409 self.user_id = user_id
410 410 self.user_is_admin = user_is_admin
411 411 self.inherit_default_permissions = user_inherit_default_permissions
412 412 self.explicit = explicit
413 413 self.algo = algo
414 414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 415
416 416 scope = scope or {}
417 417 self.scope_repo_id = scope.get('repo_id')
418 418 self.scope_repo_group_id = scope.get('repo_group_id')
419 419 self.scope_user_group_id = scope.get('user_group_id')
420 420
421 421 self.default_user_id = User.get_default_user(cache=True).user_id
422 422
423 423 self.permissions_repositories = PermOriginDict()
424 424 self.permissions_repository_groups = PermOriginDict()
425 425 self.permissions_user_groups = PermOriginDict()
426 426 self.permissions_repository_branches = BranchPermOriginDict()
427 427 self.permissions_global = set()
428 428
429 429 self.default_repo_perms = Permission.get_default_repo_perms(
430 430 self.default_user_id, self.scope_repo_id)
431 431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 432 self.default_user_id, self.scope_repo_group_id)
433 433 self.default_user_group_perms = \
434 434 Permission.get_default_user_group_perms(
435 435 self.default_user_id, self.scope_user_group_id)
436 436
437 437 # default branch perms
438 438 self.default_branch_repo_perms = \
439 439 Permission.get_default_repo_branch_perms(
440 440 self.default_user_id, self.scope_repo_id)
441 441
442 442 def calculate(self):
443 443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 444 return self._calculate_admin_permissions()
445 445
446 446 self._calculate_global_default_permissions()
447 447 self._calculate_global_permissions()
448 448 self._calculate_default_permissions()
449 449 self._calculate_repository_permissions()
450 450 self._calculate_repository_branch_permissions()
451 451 self._calculate_repository_group_permissions()
452 452 self._calculate_user_group_permissions()
453 453 return self._permission_structure()
454 454
455 455 def _calculate_admin_permissions(self):
456 456 """
457 457 admin user have all default rights for repositories
458 458 and groups set to admin
459 459 """
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 # repositories
464 464 for perm in self.default_repo_perms:
465 465 r_k = perm.UserRepoToPerm.repository.repo_name
466 obj_id = perm.UserRepoToPerm.repository.repo_id
466 467 archived = perm.UserRepoToPerm.repository.archived
467 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 470 # special case for archived repositories, which we block still even for
470 471 # super admins
471 472 if archived:
472 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 476 # repository groups
476 477 for perm in self.default_repo_groups_perms:
477 478 rg_k = perm.UserRepoGroupToPerm.group.group_name
479 obj_id = perm.UserRepoGroupToPerm.group.group_id
478 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 483 # user groups
482 484 for perm in self.default_user_group_perms:
483 485 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
486 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
484 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 490 # branch permissions
488 491 # since super-admin also can have custom rule permissions
489 492 # we *always* need to calculate those inherited from default, and also explicit
490 493 self._calculate_default_permissions_repository_branches(
491 494 user_inherit_object_permissions=False)
492 495 self._calculate_repository_branch_permissions()
493 496
494 497 return self._permission_structure()
495 498
496 499 def _calculate_global_default_permissions(self):
497 500 """
498 501 global permissions taken from the default user
499 502 """
500 503 default_global_perms = UserToPerm.query()\
501 504 .filter(UserToPerm.user_id == self.default_user_id)\
502 505 .options(joinedload(UserToPerm.permission))
503 506
504 507 for perm in default_global_perms:
505 508 self.permissions_global.add(perm.permission.permission_name)
506 509
507 510 if self.user_is_admin:
508 511 self.permissions_global.add('hg.admin')
509 512 self.permissions_global.add('hg.create.write_on_repogroup.true')
510 513
511 514 def _calculate_global_permissions(self):
512 515 """
513 516 Set global system permissions with user permissions or permissions
514 517 taken from the user groups of the current user.
515 518
516 519 The permissions include repo creating, repo group creating, forking
517 520 etc.
518 521 """
519 522
520 523 # now we read the defined permissions and overwrite what we have set
521 524 # before those can be configured from groups or users explicitly.
522 525
523 526 # In case we want to extend this list we should make sure
524 527 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 528 _configurable = frozenset([
526 529 'hg.fork.none', 'hg.fork.repository',
527 530 'hg.create.none', 'hg.create.repository',
528 531 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 532 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 533 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 534 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 535 ])
533 536
534 537 # USER GROUPS comes first user group global permissions
535 538 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 539 .options(joinedload(UserGroupToPerm.permission))\
537 540 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 541 UserGroupMember.users_group_id))\
539 542 .filter(UserGroupMember.user_id == self.user_id)\
540 543 .order_by(UserGroupToPerm.users_group_id)\
541 544 .all()
542 545
543 546 # need to group here by groups since user can be in more than
544 547 # one group, so we get all groups
545 548 _explicit_grouped_perms = [
546 549 [x, list(y)] for x, y in
547 550 itertools.groupby(user_perms_from_users_groups,
548 551 lambda _x: _x.users_group)]
549 552
550 553 for gr, perms in _explicit_grouped_perms:
551 554 # since user can be in multiple groups iterate over them and
552 555 # select the lowest permissions first (more explicit)
553 556 # TODO(marcink): do this^^
554 557
555 558 # group doesn't inherit default permissions so we actually set them
556 559 if not gr.inherit_default_permissions:
557 560 # NEED TO IGNORE all previously set configurable permissions
558 561 # and replace them with explicitly set from this user
559 562 # group permissions
560 563 self.permissions_global = self.permissions_global.difference(
561 564 _configurable)
562 565 for perm in perms:
563 566 self.permissions_global.add(perm.permission.permission_name)
564 567
565 568 # user explicit global permissions
566 569 user_perms = Session().query(UserToPerm)\
567 570 .options(joinedload(UserToPerm.permission))\
568 571 .filter(UserToPerm.user_id == self.user_id).all()
569 572
570 573 if not self.inherit_default_permissions:
571 574 # NEED TO IGNORE all configurable permissions and
572 575 # replace them with explicitly set from this user permissions
573 576 self.permissions_global = self.permissions_global.difference(
574 577 _configurable)
575 578 for perm in user_perms:
576 579 self.permissions_global.add(perm.permission.permission_name)
577 580
578 581 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 582 for perm in self.default_repo_perms:
580 583 r_k = perm.UserRepoToPerm.repository.repo_name
584 obj_id = perm.UserRepoToPerm.repository.repo_id
581 585 archived = perm.UserRepoToPerm.repository.archived
582 586 p = perm.Permission.permission_name
583 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 590 # if we decide this user isn't inheriting permissions from
587 591 # default user we set him to .none so only explicit
588 592 # permissions work
589 593 if not user_inherit_object_permissions:
590 594 p = 'repository.none'
591 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 598 if perm.Repository.private and not (
595 599 perm.Repository.user_id == self.user_id):
596 600 # disable defaults for private repos,
597 601 p = 'repository.none'
598 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 605 elif perm.Repository.user_id == self.user_id:
602 606 # set admin if owner
603 607 p = 'repository.admin'
604 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 611 if self.user_is_admin:
608 612 p = 'repository.admin'
609 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 616 # finally in case of archived repositories, we downgrade higher
613 617 # permissions to read
614 618 if archived:
615 619 current_perm = self.permissions_repositories[r_k]
616 620 if current_perm in ['repository.write', 'repository.admin']:
617 621 p = 'repository.read'
618 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 625 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 626 for perm in self.default_branch_repo_perms:
623 627
624 628 r_k = perm.UserRepoToPerm.repository.repo_name
625 629 p = perm.Permission.permission_name
626 630 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 631 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628 632
629 633 if not self.explicit:
630 634 cur_perm = self.permissions_repository_branches.get(r_k)
631 635 if cur_perm:
632 636 cur_perm = cur_perm[pattern]
633 637 cur_perm = cur_perm or 'branch.none'
634 638
635 639 p = self._choose_permission(p, cur_perm)
636 640
637 641 # NOTE(marcink): register all pattern/perm instances in this
638 642 # special dict that aggregates entries
639 643 self.permissions_repository_branches[r_k] = pattern, p, o
640 644
641 645 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 646 for perm in self.default_repo_groups_perms:
643 647 rg_k = perm.UserRepoGroupToPerm.group.group_name
648 obj_id = perm.UserRepoGroupToPerm.group.group_id
644 649 p = perm.Permission.permission_name
645 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 653 # if we decide this user isn't inheriting permissions from default
649 654 # user we set him to .none so only explicit permissions work
650 655 if not user_inherit_object_permissions:
651 656 p = 'group.none'
652 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 660 if perm.RepoGroup.user_id == self.user_id:
656 661 # set admin if owner
657 662 p = 'group.admin'
658 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 666 if self.user_is_admin:
662 667 p = 'group.admin'
663 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 671 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 672 for perm in self.default_user_group_perms:
668 673 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
674 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
669 675 p = perm.Permission.permission_name
670 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 679 # if we decide this user isn't inheriting permissions from default
674 680 # user we set him to .none so only explicit permissions work
675 681 if not user_inherit_object_permissions:
676 682 p = 'usergroup.none'
677 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 686 if perm.UserGroup.user_id == self.user_id:
681 687 # set admin if owner
682 688 p = 'usergroup.admin'
683 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 692 if self.user_is_admin:
687 693 p = 'usergroup.admin'
688 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 697 def _calculate_default_permissions(self):
692 698 """
693 699 Set default user permissions for repositories, repository branches,
694 700 repository groups, user groups taken from the default user.
695 701
696 702 Calculate inheritance of object permissions based on what we have now
697 703 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 704 explicitly set. Inherit is the opposite of .false being there.
699 705
700 706 .. note::
701 707
702 708 the syntax is little bit odd but what we need to check here is
703 709 the opposite of .false permission being in the list so even for
704 710 inconsistent state when both .true/.false is there
705 711 .false is more important
706 712
707 713 """
708 714 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 715 in self.permissions_global)
710 716
711 717 # default permissions inherited from `default` user permissions
712 718 self._calculate_default_permissions_repositories(
713 719 user_inherit_object_permissions)
714 720
715 721 self._calculate_default_permissions_repository_branches(
716 722 user_inherit_object_permissions)
717 723
718 724 self._calculate_default_permissions_repository_groups(
719 725 user_inherit_object_permissions)
720 726
721 727 self._calculate_default_permissions_user_groups(
722 728 user_inherit_object_permissions)
723 729
724 730 def _calculate_repository_permissions(self):
725 731 """
726 732 Repository access permissions for the current user.
727 733
728 734 Check if the user is part of user groups for this repository and
729 735 fill in the permission from it. `_choose_permission` decides of which
730 736 permission should be selected based on selected method.
731 737 """
732 738
733 739 # user group for repositories permissions
734 740 user_repo_perms_from_user_group = Permission\
735 741 .get_default_repo_perms_from_user_group(
736 742 self.user_id, self.scope_repo_id)
737 743
738 744 multiple_counter = collections.defaultdict(int)
739 745 for perm in user_repo_perms_from_user_group:
740 746 r_k = perm.UserGroupRepoToPerm.repository.repo_name
747 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
741 748 multiple_counter[r_k] += 1
742 749 p = perm.Permission.permission_name
743 750 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 751 .users_group.users_group_name
745 752
746 753 if multiple_counter[r_k] > 1:
747 754 cur_perm = self.permissions_repositories[r_k]
748 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 759 if perm.Repository.user_id == self.user_id:
753 760 # set admin if owner
754 761 p = 'repository.admin'
755 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 765 if self.user_is_admin:
759 766 p = 'repository.admin'
760 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 770 # user explicit permissions for repositories, overrides any specified
764 771 # by the group permission
765 772 user_repo_perms = Permission.get_default_repo_perms(
766 773 self.user_id, self.scope_repo_id)
767 774 for perm in user_repo_perms:
768 775 r_k = perm.UserRepoToPerm.repository.repo_name
776 obj_id = perm.UserRepoToPerm.repository.repo_id
769 777 p = perm.Permission.permission_name
770 778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771 779
772 780 if not self.explicit:
773 781 cur_perm = self.permissions_repositories.get(
774 782 r_k, 'repository.none')
775 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 787 if perm.Repository.user_id == self.user_id:
780 788 # set admin if owner
781 789 p = 'repository.admin'
782 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 793 if self.user_is_admin:
786 794 p = 'repository.admin'
787 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 798 def _calculate_repository_branch_permissions(self):
791 799 # user group for repositories permissions
792 800 user_repo_branch_perms_from_user_group = Permission\
793 801 .get_default_repo_branch_perms_from_user_group(
794 802 self.user_id, self.scope_repo_id)
795 803
796 804 multiple_counter = collections.defaultdict(int)
797 805 for perm in user_repo_branch_perms_from_user_group:
798 806 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 807 p = perm.Permission.permission_name
800 808 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 809 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 810 .users_group.users_group_name
803 811
804 812 multiple_counter[r_k] += 1
805 813 if multiple_counter[r_k] > 1:
806 814 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 815 p = self._choose_permission(p, cur_perm)
808 816
809 817 self.permissions_repository_branches[r_k] = pattern, p, o
810 818
811 819 # user explicit branch permissions for repositories, overrides
812 820 # any specified by the group permission
813 821 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 822 self.user_id, self.scope_repo_id)
815 823
816 824 for perm in user_repo_branch_perms:
817 825
818 826 r_k = perm.UserRepoToPerm.repository.repo_name
819 827 p = perm.Permission.permission_name
820 828 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 829 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822 830
823 831 if not self.explicit:
824 832 cur_perm = self.permissions_repository_branches.get(r_k)
825 833 if cur_perm:
826 834 cur_perm = cur_perm[pattern]
827 835 cur_perm = cur_perm or 'branch.none'
828 836 p = self._choose_permission(p, cur_perm)
829 837
830 838 # NOTE(marcink): register all pattern/perm instances in this
831 839 # special dict that aggregates entries
832 840 self.permissions_repository_branches[r_k] = pattern, p, o
833 841
834 842 def _calculate_repository_group_permissions(self):
835 843 """
836 844 Repository group permissions for the current user.
837 845
838 846 Check if the user is part of user groups for repository groups and
839 847 fill in the permissions from it. `_choose_permission` decides of which
840 848 permission should be selected based on selected method.
841 849 """
842 850 # user group for repo groups permissions
843 851 user_repo_group_perms_from_user_group = Permission\
844 852 .get_default_group_perms_from_user_group(
845 853 self.user_id, self.scope_repo_group_id)
846 854
847 855 multiple_counter = collections.defaultdict(int)
848 856 for perm in user_repo_group_perms_from_user_group:
849 857 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
858 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
850 859 multiple_counter[rg_k] += 1
851 860 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 861 .users_group.users_group_name
853 862 p = perm.Permission.permission_name
854 863
855 864 if multiple_counter[rg_k] > 1:
856 865 cur_perm = self.permissions_repository_groups[rg_k]
857 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 869 if perm.RepoGroup.user_id == self.user_id:
861 870 # set admin if owner, even for member of other user group
862 871 p = 'group.admin'
863 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 875 if self.user_is_admin:
867 876 p = 'group.admin'
868 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 880 # user explicit permissions for repository groups
872 881 user_repo_groups_perms = Permission.get_default_group_perms(
873 882 self.user_id, self.scope_repo_group_id)
874 883 for perm in user_repo_groups_perms:
875 884 rg_k = perm.UserRepoGroupToPerm.group.group_name
885 obj_id = perm.UserRepoGroupToPerm.group.group_id
876 886 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 887 .user.username
878 888 p = perm.Permission.permission_name
879 889
880 890 if not self.explicit:
881 891 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 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 896 if perm.RepoGroup.user_id == self.user_id:
887 897 # set admin if owner
888 898 p = 'group.admin'
889 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 902 if self.user_is_admin:
893 903 p = 'group.admin'
894 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 907 def _calculate_user_group_permissions(self):
898 908 """
899 909 User group permissions for the current user.
900 910 """
901 911 # user group for user group permissions
902 912 user_group_from_user_group = Permission\
903 913 .get_default_user_group_perms_from_user_group(
904 914 self.user_id, self.scope_user_group_id)
905 915
906 916 multiple_counter = collections.defaultdict(int)
907 917 for perm in user_group_from_user_group:
908 ug_k = perm.UserGroupUserGroupToPerm\
909 .target_user_group.users_group_name
918 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
919 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
910 920 multiple_counter[ug_k] += 1
911 921 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 922 .user_group.users_group_name
913 923 p = perm.Permission.permission_name
914 924
915 925 if multiple_counter[ug_k] > 1:
916 926 cur_perm = self.permissions_user_groups[ug_k]
917 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 931 if perm.UserGroup.user_id == self.user_id:
922 932 # set admin if owner, even for member of other user group
923 933 p = 'usergroup.admin'
924 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 937 if self.user_is_admin:
928 938 p = 'usergroup.admin'
929 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 942 # user explicit permission for user groups
933 943 user_user_groups_perms = Permission.get_default_user_group_perms(
934 944 self.user_id, self.scope_user_group_id)
935 945 for perm in user_user_groups_perms:
936 946 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
947 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
937 948 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 949 .user.username
939 950 p = perm.Permission.permission_name
940 951
941 952 if not self.explicit:
942 953 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 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 958 if perm.UserGroup.user_id == self.user_id:
948 959 # set admin if owner
949 960 p = 'usergroup.admin'
950 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 964 if self.user_is_admin:
954 965 p = 'usergroup.admin'
955 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 969 def _choose_permission(self, new_perm, cur_perm):
959 970 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 971 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 972 if self.algo == 'higherwin':
962 973 if new_perm_val > cur_perm_val:
963 974 return new_perm
964 975 return cur_perm
965 976 elif self.algo == 'lowerwin':
966 977 if new_perm_val < cur_perm_val:
967 978 return new_perm
968 979 return cur_perm
969 980
970 981 def _permission_structure(self):
971 982 return {
972 983 'global': self.permissions_global,
973 984 'repositories': self.permissions_repositories,
974 985 'repository_branches': self.permissions_repository_branches,
975 986 'repositories_groups': self.permissions_repository_groups,
976 987 'user_groups': self.permissions_user_groups,
977 988 }
978 989
979 990
980 991 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 992 """
982 993 Check if given controller_name is in whitelist of auth token access
983 994 """
984 995 if not whitelist:
985 996 from rhodecode import CONFIG
986 997 whitelist = aslist(
987 998 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 999 # backward compat translation
989 1000 compat = {
990 1001 # old controller, new VIEW
991 1002 'ChangesetController:*': 'RepoCommitsView:*',
992 1003 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 1004 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 1005 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 1006 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 1007 'GistsController:*': 'GistView:*',
997 1008 }
998 1009
999 1010 log.debug(
1000 1011 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 1012 auth_token_access_valid = False
1002 1013
1003 1014 for entry in whitelist:
1004 1015 token_match = True
1005 1016 if entry in compat:
1006 1017 # translate from old Controllers to Pyramid Views
1007 1018 entry = compat[entry]
1008 1019
1009 1020 if '@' in entry:
1010 1021 # specific AuthToken
1011 1022 entry, allowed_token = entry.split('@', 1)
1012 1023 token_match = auth_token == allowed_token
1013 1024
1014 1025 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 1026 auth_token_access_valid = True
1016 1027 break
1017 1028
1018 1029 if auth_token_access_valid:
1019 1030 log.debug('view: `%s` matches entry in whitelist: %s',
1020 1031 view_name, whitelist)
1021 1032
1022 1033 else:
1023 1034 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 1035 % (view_name, whitelist))
1025 1036 if auth_token:
1026 1037 # if we use auth token key and don't have access it's a warning
1027 1038 log.warning(msg)
1028 1039 else:
1029 1040 log.debug(msg)
1030 1041
1031 1042 return auth_token_access_valid
1032 1043
1033 1044
1034 1045 class AuthUser(object):
1035 1046 """
1036 1047 A simple object that handles all attributes of user in RhodeCode
1037 1048
1038 1049 It does lookup based on API key,given user, or user present in session
1039 1050 Then it fills all required information for such user. It also checks if
1040 1051 anonymous access is enabled and if so, it returns default user as logged in
1041 1052 """
1042 1053 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043 1054
1044 1055 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045 1056
1046 1057 self.user_id = user_id
1047 1058 self._api_key = api_key
1048 1059
1049 1060 self.api_key = None
1050 1061 self.username = username
1051 1062 self.ip_addr = ip_addr
1052 1063 self.name = ''
1053 1064 self.lastname = ''
1054 1065 self.first_name = ''
1055 1066 self.last_name = ''
1056 1067 self.email = ''
1057 1068 self.is_authenticated = False
1058 1069 self.admin = False
1059 1070 self.inherit_default_permissions = False
1060 1071 self.password = ''
1061 1072
1062 1073 self.anonymous_user = None # propagated on propagate_data
1063 1074 self.propagate_data()
1064 1075 self._instance = None
1065 1076 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066 1077
1067 1078 @LazyProperty
1068 1079 def permissions(self):
1069 1080 return self.get_perms(user=self, cache=None)
1070 1081
1071 1082 @LazyProperty
1072 1083 def permissions_safe(self):
1073 1084 """
1074 1085 Filtered permissions excluding not allowed repositories
1075 1086 """
1076 1087 perms = self.get_perms(user=self, cache=None)
1077 1088
1078 1089 perms['repositories'] = {
1079 1090 k: v for k, v in perms['repositories'].items()
1080 1091 if v != 'repository.none'}
1081 1092 perms['repositories_groups'] = {
1082 1093 k: v for k, v in perms['repositories_groups'].items()
1083 1094 if v != 'group.none'}
1084 1095 perms['user_groups'] = {
1085 1096 k: v for k, v in perms['user_groups'].items()
1086 1097 if v != 'usergroup.none'}
1087 1098 perms['repository_branches'] = {
1088 1099 k: v for k, v in perms['repository_branches'].iteritems()
1089 1100 if v != 'branch.none'}
1090 1101 return perms
1091 1102
1092 1103 @LazyProperty
1093 1104 def permissions_full_details(self):
1094 1105 return self.get_perms(
1095 1106 user=self, cache=None, calculate_super_admin=True)
1096 1107
1097 1108 def permissions_with_scope(self, scope):
1098 1109 """
1099 1110 Call the get_perms function with scoped data. The scope in that function
1100 1111 narrows the SQL calls to the given ID of objects resulting in fetching
1101 1112 Just particular permission we want to obtain. If scope is an empty dict
1102 1113 then it basically narrows the scope to GLOBAL permissions only.
1103 1114
1104 1115 :param scope: dict
1105 1116 """
1106 1117 if 'repo_name' in scope:
1107 1118 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 1119 if obj:
1109 1120 scope['repo_id'] = obj.repo_id
1110 1121 _scope = collections.OrderedDict()
1111 1122 _scope['repo_id'] = -1
1112 1123 _scope['user_group_id'] = -1
1113 1124 _scope['repo_group_id'] = -1
1114 1125
1115 1126 for k in sorted(scope.keys()):
1116 1127 _scope[k] = scope[k]
1117 1128
1118 1129 # store in cache to mimic how the @LazyProperty works,
1119 1130 # the difference here is that we use the unique key calculated
1120 1131 # from params and values
1121 1132 return self.get_perms(user=self, cache=None, scope=_scope)
1122 1133
1123 1134 def get_instance(self):
1124 1135 return User.get(self.user_id)
1125 1136
1126 1137 def propagate_data(self):
1127 1138 """
1128 1139 Fills in user data and propagates values to this instance. Maps fetched
1129 1140 user attributes to this class instance attributes
1130 1141 """
1131 1142 log.debug('AuthUser: starting data propagation for new potential user')
1132 1143 user_model = UserModel()
1133 1144 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 1145 is_user_loaded = False
1135 1146
1136 1147 # lookup by userid
1137 1148 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 1149 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 1150 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140 1151
1141 1152 # try go get user by api key
1142 1153 elif self._api_key and self._api_key != anon_user.api_key:
1143 1154 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1144 1155 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145 1156
1146 1157 # lookup by username
1147 1158 elif self.username:
1148 1159 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 1160 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 1161 else:
1151 1162 log.debug('No data in %s that could been used to log in', self)
1152 1163
1153 1164 if not is_user_loaded:
1154 1165 log.debug(
1155 1166 'Failed to load user. Fallback to default user %s', anon_user)
1156 1167 # if we cannot authenticate user try anonymous
1157 1168 if anon_user.active:
1158 1169 log.debug('default user is active, using it as a session user')
1159 1170 user_model.fill_data(self, user_id=anon_user.user_id)
1160 1171 # then we set this user is logged in
1161 1172 self.is_authenticated = True
1162 1173 else:
1163 1174 log.debug('default user is NOT active')
1164 1175 # in case of disabled anonymous user we reset some of the
1165 1176 # parameters so such user is "corrupted", skipping the fill_data
1166 1177 for attr in ['user_id', 'username', 'admin', 'active']:
1167 1178 setattr(self, attr, None)
1168 1179 self.is_authenticated = False
1169 1180
1170 1181 if not self.username:
1171 1182 self.username = 'None'
1172 1183
1173 1184 log.debug('AuthUser: propagated user is now %s', self)
1174 1185
1175 1186 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 1187 calculate_super_admin=False, cache=None):
1177 1188 """
1178 1189 Fills user permission attribute with permissions taken from database
1179 1190 works for permissions given for repositories, and for permissions that
1180 1191 are granted to groups
1181 1192
1182 1193 :param user: instance of User object from database
1183 1194 :param explicit: In case there are permissions both for user and a group
1184 1195 that user is part of, explicit flag will defiine if user will
1185 1196 explicitly override permissions from group, if it's False it will
1186 1197 make decision based on the algo
1187 1198 :param algo: algorithm to decide what permission should be choose if
1188 1199 it's multiple defined, eg user in two different groups. It also
1189 1200 decides if explicit flag is turned off how to specify the permission
1190 1201 for case when user is in a group + have defined separate permission
1191 1202 :param calculate_super_admin: calculate permissions for super-admin in the
1192 1203 same way as for regular user without speedups
1193 1204 :param cache: Use caching for calculation, None = let the cache backend decide
1194 1205 """
1195 1206 user_id = user.user_id
1196 1207 user_is_admin = user.is_admin
1197 1208
1198 1209 # inheritance of global permissions like create repo/fork repo etc
1199 1210 user_inherit_default_permissions = user.inherit_default_permissions
1200 1211
1201 1212 cache_seconds = safe_int(
1202 1213 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203 1214
1204 1215 if cache is None:
1205 1216 # let the backend cache decide
1206 1217 cache_on = cache_seconds > 0
1207 1218 else:
1208 1219 cache_on = cache
1209 1220
1210 1221 log.debug(
1211 1222 'Computing PERMISSION tree for user %s scope `%s` '
1212 1223 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213 1224
1214 1225 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 1226 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216 1227
1217 1228 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 1229 condition=cache_on)
1219 1230 def compute_perm_tree(cache_name,
1220 1231 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 1232 explicit, algo, calculate_super_admin):
1222 1233 return _cached_perms_data(
1223 1234 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 1235 explicit, algo, calculate_super_admin)
1225 1236
1226 1237 start = time.time()
1227 1238 result = compute_perm_tree(
1228 1239 'permissions', user_id, scope, user_is_admin,
1229 1240 user_inherit_default_permissions, explicit, algo,
1230 1241 calculate_super_admin)
1231 1242
1232 1243 result_repr = []
1233 1244 for k in result:
1234 1245 result_repr.append((k, len(result[k])))
1235 1246 total = time.time() - start
1236 1247 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1237 1248 user, total, result_repr)
1238 1249
1239 1250 return result
1240 1251
1241 1252 @property
1242 1253 def is_default(self):
1243 1254 return self.username == User.DEFAULT_USER
1244 1255
1245 1256 @property
1246 1257 def is_admin(self):
1247 1258 return self.admin
1248 1259
1249 1260 @property
1250 1261 def is_user_object(self):
1251 1262 return self.user_id is not None
1252 1263
1253 1264 @property
1254 1265 def repositories_admin(self):
1255 1266 """
1256 1267 Returns list of repositories you're an admin of
1257 1268 """
1258 1269 return [
1259 1270 x[0] for x in self.permissions['repositories'].items()
1260 1271 if x[1] == 'repository.admin']
1261 1272
1262 1273 @property
1263 1274 def repository_groups_admin(self):
1264 1275 """
1265 1276 Returns list of repository groups you're an admin of
1266 1277 """
1267 1278 return [
1268 1279 x[0] for x in self.permissions['repositories_groups'].items()
1269 1280 if x[1] == 'group.admin']
1270 1281
1271 1282 @property
1272 1283 def user_groups_admin(self):
1273 1284 """
1274 1285 Returns list of user groups you're an admin of
1275 1286 """
1276 1287 return [
1277 1288 x[0] for x in self.permissions['user_groups'].items()
1278 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 1303 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 1304 """
1282 1305 Returns list of repository ids that user have access to based on given
1283 1306 perms. The cache flag should be only used in cases that are used for
1284 1307 display purposes, NOT IN ANY CASE for permission checks.
1285 1308 """
1286 1309 from rhodecode.model.scm import RepoList
1287 1310 if not perms:
1288 perms = [
1289 'repository.read', 'repository.write', 'repository.admin']
1311 perms = ['repository.read', 'repository.write', 'repository.admin']
1290 1312
1291 1313 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 1314 qry = Repository.query()
1293 1315 if _name_filter:
1294 1316 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 1317 qry = qry.filter(
1296 1318 Repository.repo_name.ilike(ilike_expression))
1297 1319
1298 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 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 1337 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 1338 """
1305 1339 Returns list of repository group ids that user have access to based on given
1306 1340 perms. The cache flag should be only used in cases that are used for
1307 1341 display purposes, NOT IN ANY CASE for permission checks.
1308 1342 """
1309 1343 from rhodecode.model.scm import RepoGroupList
1310 1344 if not perms:
1311 perms = [
1312 'group.read', 'group.write', 'group.admin']
1345 perms = ['group.read', 'group.write', 'group.admin']
1313 1346
1314 1347 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 1348 qry = RepoGroup.query()
1316 1349 if _name_filter:
1317 1350 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 1351 qry = qry.filter(
1319 1352 RepoGroup.group_name.ilike(ilike_expression))
1320 1353
1321 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 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 1369 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 1370 """
1328 1371 Returns list of user group ids that user have access to based on given
1329 1372 perms. The cache flag should be only used in cases that are used for
1330 1373 display purposes, NOT IN ANY CASE for permission checks.
1331 1374 """
1332 1375 from rhodecode.model.scm import UserGroupList
1333 1376 if not perms:
1334 perms = [
1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1377 perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1336 1378
1337 1379 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 1380 qry = UserGroup.query()
1339 1381 if name_filter:
1340 1382 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 1383 qry = qry.filter(
1342 1384 UserGroup.users_group_name.ilike(ilike_expression))
1343 1385
1344 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 1389 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348 1390
1349 1391 @property
1350 1392 def ip_allowed(self):
1351 1393 """
1352 1394 Checks if ip_addr used in constructor is allowed from defined list of
1353 1395 allowed ip_addresses for user
1354 1396
1355 1397 :returns: boolean, True if ip is in allowed ip range
1356 1398 """
1357 1399 # check IP
1358 1400 inherit = self.inherit_default_permissions
1359 1401 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 1402 inherit_from_default=inherit)
1361 1403 @property
1362 1404 def personal_repo_group(self):
1363 1405 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364 1406
1365 1407 @LazyProperty
1366 1408 def feed_token(self):
1367 1409 return self.get_instance().feed_token
1368 1410
1369 1411 @LazyProperty
1370 1412 def artifact_token(self):
1371 1413 return self.get_instance().artifact_token
1372 1414
1373 1415 @classmethod
1374 1416 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1375 1417 allowed_ips = AuthUser.get_allowed_ips(
1376 1418 user_id, cache=True, inherit_from_default=inherit_from_default)
1377 1419 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1378 1420 log.debug('IP:%s for user %s is in range of %s',
1379 1421 ip_addr, user_id, allowed_ips)
1380 1422 return True
1381 1423 else:
1382 1424 log.info('Access for IP:%s forbidden for user %s, '
1383 1425 'not in %s', ip_addr, user_id, allowed_ips)
1384 1426 return False
1385 1427
1386 1428 def get_branch_permissions(self, repo_name, perms=None):
1387 1429 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1388 1430 branch_perms = perms.get('repository_branches', {})
1389 1431 if not branch_perms:
1390 1432 return {}
1391 1433 repo_branch_perms = branch_perms.get(repo_name)
1392 1434 return repo_branch_perms or {}
1393 1435
1394 1436 def get_rule_and_branch_permission(self, repo_name, branch_name):
1395 1437 """
1396 1438 Check if this AuthUser has defined any permissions for branches. If any of
1397 1439 the rules match in order, we return the matching permissions
1398 1440 """
1399 1441
1400 1442 rule = default_perm = ''
1401 1443
1402 1444 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1403 1445 if not repo_branch_perms:
1404 1446 return rule, default_perm
1405 1447
1406 1448 # now calculate the permissions
1407 1449 for pattern, branch_perm in repo_branch_perms.items():
1408 1450 if fnmatch.fnmatch(branch_name, pattern):
1409 1451 rule = '`{}`=>{}'.format(pattern, branch_perm)
1410 1452 return rule, branch_perm
1411 1453
1412 1454 return rule, default_perm
1413 1455
1414 1456 def __repr__(self):
1415 1457 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1416 1458 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1417 1459
1418 1460 def set_authenticated(self, authenticated=True):
1419 1461 if self.user_id != self.anonymous_user.user_id:
1420 1462 self.is_authenticated = authenticated
1421 1463
1422 1464 def get_cookie_store(self):
1423 1465 return {
1424 1466 'username': self.username,
1425 1467 'password': md5(self.password or ''),
1426 1468 'user_id': self.user_id,
1427 1469 'is_authenticated': self.is_authenticated
1428 1470 }
1429 1471
1430 1472 @classmethod
1431 1473 def from_cookie_store(cls, cookie_store):
1432 1474 """
1433 1475 Creates AuthUser from a cookie store
1434 1476
1435 1477 :param cls:
1436 1478 :param cookie_store:
1437 1479 """
1438 1480 user_id = cookie_store.get('user_id')
1439 1481 username = cookie_store.get('username')
1440 1482 api_key = cookie_store.get('api_key')
1441 1483 return AuthUser(user_id, api_key, username)
1442 1484
1443 1485 @classmethod
1444 1486 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1445 1487 _set = set()
1446 1488
1447 1489 if inherit_from_default:
1448 1490 def_user_id = User.get_default_user(cache=True).user_id
1449 1491 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1450 1492 if cache:
1451 1493 default_ips = default_ips.options(
1452 1494 FromCache("sql_cache_short", "get_user_ips_default"))
1453 1495
1454 1496 # populate from default user
1455 1497 for ip in default_ips:
1456 1498 try:
1457 1499 _set.add(ip.ip_addr)
1458 1500 except ObjectDeletedError:
1459 1501 # since we use heavy caching sometimes it happens that
1460 1502 # we get deleted objects here, we just skip them
1461 1503 pass
1462 1504
1463 1505 # NOTE:(marcink) we don't want to load any rules for empty
1464 1506 # user_id which is the case of access of non logged users when anonymous
1465 1507 # access is disabled
1466 1508 user_ips = []
1467 1509 if user_id:
1468 1510 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1469 1511 if cache:
1470 1512 user_ips = user_ips.options(
1471 1513 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1472 1514
1473 1515 for ip in user_ips:
1474 1516 try:
1475 1517 _set.add(ip.ip_addr)
1476 1518 except ObjectDeletedError:
1477 1519 # since we use heavy caching sometimes it happens that we get
1478 1520 # deleted objects here, we just skip them
1479 1521 pass
1480 1522 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1481 1523
1482 1524
1483 1525 def set_available_permissions(settings):
1484 1526 """
1485 1527 This function will propagate pyramid settings with all available defined
1486 1528 permission given in db. We don't want to check each time from db for new
1487 1529 permissions since adding a new permission also requires application restart
1488 1530 ie. to decorate new views with the newly created permission
1489 1531
1490 1532 :param settings: current pyramid registry.settings
1491 1533
1492 1534 """
1493 1535 log.debug('auth: getting information about all available permissions')
1494 1536 try:
1495 1537 sa = meta.Session
1496 1538 all_perms = sa.query(Permission).all()
1497 1539 settings.setdefault('available_permissions',
1498 1540 [x.permission_name for x in all_perms])
1499 1541 log.debug('auth: set available permissions')
1500 1542 except Exception:
1501 1543 log.exception('Failed to fetch permissions from the database.')
1502 1544 raise
1503 1545
1504 1546
1505 1547 def get_csrf_token(session, force_new=False, save_if_missing=True):
1506 1548 """
1507 1549 Return the current authentication token, creating one if one doesn't
1508 1550 already exist and the save_if_missing flag is present.
1509 1551
1510 1552 :param session: pass in the pyramid session, else we use the global ones
1511 1553 :param force_new: force to re-generate the token and store it in session
1512 1554 :param save_if_missing: save the newly generated token if it's missing in
1513 1555 session
1514 1556 """
1515 1557 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1516 1558 # from pyramid.csrf import get_csrf_token
1517 1559
1518 1560 if (csrf_token_key not in session and save_if_missing) or force_new:
1519 1561 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1520 1562 session[csrf_token_key] = token
1521 1563 if hasattr(session, 'save'):
1522 1564 session.save()
1523 1565 return session.get(csrf_token_key)
1524 1566
1525 1567
1526 1568 def get_request(perm_class_instance):
1527 1569 from pyramid.threadlocal import get_current_request
1528 1570 pyramid_request = get_current_request()
1529 1571 return pyramid_request
1530 1572
1531 1573
1532 1574 # CHECK DECORATORS
1533 1575 class CSRFRequired(object):
1534 1576 """
1535 1577 Decorator for authenticating a form
1536 1578
1537 1579 This decorator uses an authorization token stored in the client's
1538 1580 session for prevention of certain Cross-site request forgery (CSRF)
1539 1581 attacks (See
1540 1582 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1541 1583 information).
1542 1584
1543 1585 For use with the ``secure_form`` helper functions.
1544 1586
1545 1587 """
1546 1588 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1547 1589 self.token = token
1548 1590 self.header = header
1549 1591 self.except_methods = except_methods or []
1550 1592
1551 1593 def __call__(self, func):
1552 1594 return get_cython_compat_decorator(self.__wrapper, func)
1553 1595
1554 1596 def _get_csrf(self, _request):
1555 1597 return _request.POST.get(self.token, _request.headers.get(self.header))
1556 1598
1557 1599 def check_csrf(self, _request, cur_token):
1558 1600 supplied_token = self._get_csrf(_request)
1559 1601 return supplied_token and supplied_token == cur_token
1560 1602
1561 1603 def _get_request(self):
1562 1604 return get_request(self)
1563 1605
1564 1606 def __wrapper(self, func, *fargs, **fkwargs):
1565 1607 request = self._get_request()
1566 1608
1567 1609 if request.method in self.except_methods:
1568 1610 return func(*fargs, **fkwargs)
1569 1611
1570 1612 cur_token = get_csrf_token(request.session, save_if_missing=False)
1571 1613 if self.check_csrf(request, cur_token):
1572 1614 if request.POST.get(self.token):
1573 1615 del request.POST[self.token]
1574 1616 return func(*fargs, **fkwargs)
1575 1617 else:
1576 1618 reason = 'token-missing'
1577 1619 supplied_token = self._get_csrf(request)
1578 1620 if supplied_token and cur_token != supplied_token:
1579 1621 reason = 'token-mismatch [%s:%s]' % (
1580 1622 cur_token or ''[:6], supplied_token or ''[:6])
1581 1623
1582 1624 csrf_message = \
1583 1625 ("Cross-site request forgery detected, request denied. See "
1584 1626 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1585 1627 "more information.")
1586 1628 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1587 1629 'REMOTE_ADDR:%s, HEADERS:%s' % (
1588 1630 request, reason, request.remote_addr, request.headers))
1589 1631
1590 1632 raise HTTPForbidden(explanation=csrf_message)
1591 1633
1592 1634
1593 1635 class LoginRequired(object):
1594 1636 """
1595 1637 Must be logged in to execute this function else
1596 1638 redirect to login page
1597 1639
1598 1640 :param api_access: if enabled this checks only for valid auth token
1599 1641 and grants access based on valid token
1600 1642 """
1601 1643 def __init__(self, auth_token_access=None):
1602 1644 self.auth_token_access = auth_token_access
1603 1645 if self.auth_token_access:
1604 1646 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1605 1647 if not valid_type:
1606 1648 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1607 1649 UserApiKeys.ROLES, auth_token_access))
1608 1650
1609 1651 def __call__(self, func):
1610 1652 return get_cython_compat_decorator(self.__wrapper, func)
1611 1653
1612 1654 def _get_request(self):
1613 1655 return get_request(self)
1614 1656
1615 1657 def __wrapper(self, func, *fargs, **fkwargs):
1616 1658 from rhodecode.lib import helpers as h
1617 1659 cls = fargs[0]
1618 1660 user = cls._rhodecode_user
1619 1661 request = self._get_request()
1620 1662 _ = request.translate
1621 1663
1622 1664 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1623 1665 log.debug('Starting login restriction checks for user: %s', user)
1624 1666 # check if our IP is allowed
1625 1667 ip_access_valid = True
1626 1668 if not user.ip_allowed:
1627 1669 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1628 1670 category='warning')
1629 1671 ip_access_valid = False
1630 1672
1631 1673 # we used stored token that is extract from GET or URL param (if any)
1632 1674 _auth_token = request.user_auth_token
1633 1675
1634 1676 # check if we used an AUTH_TOKEN and it's a valid one
1635 1677 # defined white-list of controllers which API access will be enabled
1636 1678 whitelist = None
1637 1679 if self.auth_token_access:
1638 1680 # since this location is allowed by @LoginRequired decorator it's our
1639 1681 # only whitelist
1640 1682 whitelist = [loc]
1641 1683 auth_token_access_valid = allowed_auth_token_access(
1642 1684 loc, whitelist=whitelist, auth_token=_auth_token)
1643 1685
1644 1686 # explicit controller is enabled or API is in our whitelist
1645 1687 if auth_token_access_valid:
1646 1688 log.debug('Checking AUTH TOKEN access for %s', cls)
1647 1689 db_user = user.get_instance()
1648 1690
1649 1691 if db_user:
1650 1692 if self.auth_token_access:
1651 1693 roles = self.auth_token_access
1652 1694 else:
1653 1695 roles = [UserApiKeys.ROLE_HTTP]
1654 1696 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1655 1697 db_user, roles)
1656 1698 token_match = db_user.authenticate_by_token(
1657 1699 _auth_token, roles=roles)
1658 1700 else:
1659 1701 log.debug('Unable to fetch db instance for auth user: %s', user)
1660 1702 token_match = False
1661 1703
1662 1704 if _auth_token and token_match:
1663 1705 auth_token_access_valid = True
1664 1706 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1665 1707 else:
1666 1708 auth_token_access_valid = False
1667 1709 if not _auth_token:
1668 1710 log.debug("AUTH TOKEN *NOT* present in request")
1669 1711 else:
1670 1712 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1671 1713
1672 1714 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1673 1715 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1674 1716 else 'AUTH_TOKEN_AUTH'
1675 1717
1676 1718 if ip_access_valid and (
1677 1719 user.is_authenticated or auth_token_access_valid):
1678 1720 log.info('user %s authenticating with:%s IS authenticated on func %s',
1679 1721 user, reason, loc)
1680 1722
1681 1723 return func(*fargs, **fkwargs)
1682 1724 else:
1683 1725 log.warning(
1684 1726 'user %s authenticating with:%s NOT authenticated on '
1685 1727 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1686 1728 user, reason, loc, ip_access_valid, auth_token_access_valid)
1687 1729 # we preserve the get PARAM
1688 1730 came_from = get_came_from(request)
1689 1731
1690 1732 log.debug('redirecting to login page with %s', came_from)
1691 1733 raise HTTPFound(
1692 1734 h.route_path('login', _query={'came_from': came_from}))
1693 1735
1694 1736
1695 1737 class NotAnonymous(object):
1696 1738 """
1697 1739 Must be logged in to execute this function else
1698 1740 redirect to login page
1699 1741 """
1700 1742
1701 1743 def __call__(self, func):
1702 1744 return get_cython_compat_decorator(self.__wrapper, func)
1703 1745
1704 1746 def _get_request(self):
1705 1747 return get_request(self)
1706 1748
1707 1749 def __wrapper(self, func, *fargs, **fkwargs):
1708 1750 import rhodecode.lib.helpers as h
1709 1751 cls = fargs[0]
1710 1752 self.user = cls._rhodecode_user
1711 1753 request = self._get_request()
1712 1754 _ = request.translate
1713 1755 log.debug('Checking if user is not anonymous @%s', cls)
1714 1756
1715 1757 anonymous = self.user.username == User.DEFAULT_USER
1716 1758
1717 1759 if anonymous:
1718 1760 came_from = get_came_from(request)
1719 1761 h.flash(_('You need to be a registered user to '
1720 1762 'perform this action'),
1721 1763 category='warning')
1722 1764 raise HTTPFound(
1723 1765 h.route_path('login', _query={'came_from': came_from}))
1724 1766 else:
1725 1767 return func(*fargs, **fkwargs)
1726 1768
1727 1769
1728 1770 class PermsDecorator(object):
1729 1771 """
1730 1772 Base class for controller decorators, we extract the current user from
1731 1773 the class itself, which has it stored in base controllers
1732 1774 """
1733 1775
1734 1776 def __init__(self, *required_perms):
1735 1777 self.required_perms = set(required_perms)
1736 1778
1737 1779 def __call__(self, func):
1738 1780 return get_cython_compat_decorator(self.__wrapper, func)
1739 1781
1740 1782 def _get_request(self):
1741 1783 return get_request(self)
1742 1784
1743 1785 def __wrapper(self, func, *fargs, **fkwargs):
1744 1786 import rhodecode.lib.helpers as h
1745 1787 cls = fargs[0]
1746 1788 _user = cls._rhodecode_user
1747 1789 request = self._get_request()
1748 1790 _ = request.translate
1749 1791
1750 1792 log.debug('checking %s permissions %s for %s %s',
1751 1793 self.__class__.__name__, self.required_perms, cls, _user)
1752 1794
1753 1795 if self.check_permissions(_user):
1754 1796 log.debug('Permission granted for %s %s', cls, _user)
1755 1797 return func(*fargs, **fkwargs)
1756 1798
1757 1799 else:
1758 1800 log.debug('Permission denied for %s %s', cls, _user)
1759 1801 anonymous = _user.username == User.DEFAULT_USER
1760 1802
1761 1803 if anonymous:
1762 1804 came_from = get_came_from(self._get_request())
1763 1805 h.flash(_('You need to be signed in to view this page'),
1764 1806 category='warning')
1765 1807 raise HTTPFound(
1766 1808 h.route_path('login', _query={'came_from': came_from}))
1767 1809
1768 1810 else:
1769 1811 # redirect with 404 to prevent resource discovery
1770 1812 raise HTTPNotFound()
1771 1813
1772 1814 def check_permissions(self, user):
1773 1815 """Dummy function for overriding"""
1774 1816 raise NotImplementedError(
1775 1817 'You have to write this function in child class')
1776 1818
1777 1819
1778 1820 class HasPermissionAllDecorator(PermsDecorator):
1779 1821 """
1780 1822 Checks for access permission for all given predicates. All of them
1781 1823 have to be meet in order to fulfill the request
1782 1824 """
1783 1825
1784 1826 def check_permissions(self, user):
1785 1827 perms = user.permissions_with_scope({})
1786 1828 if self.required_perms.issubset(perms['global']):
1787 1829 return True
1788 1830 return False
1789 1831
1790 1832
1791 1833 class HasPermissionAnyDecorator(PermsDecorator):
1792 1834 """
1793 1835 Checks for access permission for any of given predicates. In order to
1794 1836 fulfill the request any of predicates must be meet
1795 1837 """
1796 1838
1797 1839 def check_permissions(self, user):
1798 1840 perms = user.permissions_with_scope({})
1799 1841 if self.required_perms.intersection(perms['global']):
1800 1842 return True
1801 1843 return False
1802 1844
1803 1845
1804 1846 class HasRepoPermissionAllDecorator(PermsDecorator):
1805 1847 """
1806 1848 Checks for access permission for all given predicates for specific
1807 1849 repository. All of them have to be meet in order to fulfill the request
1808 1850 """
1809 1851 def _get_repo_name(self):
1810 1852 _request = self._get_request()
1811 1853 return get_repo_slug(_request)
1812 1854
1813 1855 def check_permissions(self, user):
1814 1856 perms = user.permissions
1815 1857 repo_name = self._get_repo_name()
1816 1858
1817 1859 try:
1818 1860 user_perms = {perms['repositories'][repo_name]}
1819 1861 except KeyError:
1820 1862 log.debug('cannot locate repo with name: `%s` in permissions defs',
1821 1863 repo_name)
1822 1864 return False
1823 1865
1824 1866 log.debug('checking `%s` permissions for repo `%s`',
1825 1867 user_perms, repo_name)
1826 1868 if self.required_perms.issubset(user_perms):
1827 1869 return True
1828 1870 return False
1829 1871
1830 1872
1831 1873 class HasRepoPermissionAnyDecorator(PermsDecorator):
1832 1874 """
1833 1875 Checks for access permission for any of given predicates for specific
1834 1876 repository. In order to fulfill the request any of predicates must be meet
1835 1877 """
1836 1878 def _get_repo_name(self):
1837 1879 _request = self._get_request()
1838 1880 return get_repo_slug(_request)
1839 1881
1840 1882 def check_permissions(self, user):
1841 1883 perms = user.permissions
1842 1884 repo_name = self._get_repo_name()
1843 1885
1844 1886 try:
1845 1887 user_perms = {perms['repositories'][repo_name]}
1846 1888 except KeyError:
1847 1889 log.debug(
1848 1890 'cannot locate repo with name: `%s` in permissions defs',
1849 1891 repo_name)
1850 1892 return False
1851 1893
1852 1894 log.debug('checking `%s` permissions for repo `%s`',
1853 1895 user_perms, repo_name)
1854 1896 if self.required_perms.intersection(user_perms):
1855 1897 return True
1856 1898 return False
1857 1899
1858 1900
1859 1901 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1860 1902 """
1861 1903 Checks for access permission for all given predicates for specific
1862 1904 repository group. All of them have to be meet in order to
1863 1905 fulfill the request
1864 1906 """
1865 1907 def _get_repo_group_name(self):
1866 1908 _request = self._get_request()
1867 1909 return get_repo_group_slug(_request)
1868 1910
1869 1911 def check_permissions(self, user):
1870 1912 perms = user.permissions
1871 1913 group_name = self._get_repo_group_name()
1872 1914 try:
1873 1915 user_perms = {perms['repositories_groups'][group_name]}
1874 1916 except KeyError:
1875 1917 log.debug(
1876 1918 'cannot locate repo group with name: `%s` in permissions defs',
1877 1919 group_name)
1878 1920 return False
1879 1921
1880 1922 log.debug('checking `%s` permissions for repo group `%s`',
1881 1923 user_perms, group_name)
1882 1924 if self.required_perms.issubset(user_perms):
1883 1925 return True
1884 1926 return False
1885 1927
1886 1928
1887 1929 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1888 1930 """
1889 1931 Checks for access permission for any of given predicates for specific
1890 1932 repository group. In order to fulfill the request any
1891 1933 of predicates must be met
1892 1934 """
1893 1935 def _get_repo_group_name(self):
1894 1936 _request = self._get_request()
1895 1937 return get_repo_group_slug(_request)
1896 1938
1897 1939 def check_permissions(self, user):
1898 1940 perms = user.permissions
1899 1941 group_name = self._get_repo_group_name()
1900 1942
1901 1943 try:
1902 1944 user_perms = {perms['repositories_groups'][group_name]}
1903 1945 except KeyError:
1904 1946 log.debug(
1905 1947 'cannot locate repo group with name: `%s` in permissions defs',
1906 1948 group_name)
1907 1949 return False
1908 1950
1909 1951 log.debug('checking `%s` permissions for repo group `%s`',
1910 1952 user_perms, group_name)
1911 1953 if self.required_perms.intersection(user_perms):
1912 1954 return True
1913 1955 return False
1914 1956
1915 1957
1916 1958 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1917 1959 """
1918 1960 Checks for access permission for all given predicates for specific
1919 1961 user group. All of them have to be meet in order to fulfill the request
1920 1962 """
1921 1963 def _get_user_group_name(self):
1922 1964 _request = self._get_request()
1923 1965 return get_user_group_slug(_request)
1924 1966
1925 1967 def check_permissions(self, user):
1926 1968 perms = user.permissions
1927 1969 group_name = self._get_user_group_name()
1928 1970 try:
1929 1971 user_perms = {perms['user_groups'][group_name]}
1930 1972 except KeyError:
1931 1973 return False
1932 1974
1933 1975 if self.required_perms.issubset(user_perms):
1934 1976 return True
1935 1977 return False
1936 1978
1937 1979
1938 1980 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1939 1981 """
1940 1982 Checks for access permission for any of given predicates for specific
1941 1983 user group. In order to fulfill the request any of predicates must be meet
1942 1984 """
1943 1985 def _get_user_group_name(self):
1944 1986 _request = self._get_request()
1945 1987 return get_user_group_slug(_request)
1946 1988
1947 1989 def check_permissions(self, user):
1948 1990 perms = user.permissions
1949 1991 group_name = self._get_user_group_name()
1950 1992 try:
1951 1993 user_perms = {perms['user_groups'][group_name]}
1952 1994 except KeyError:
1953 1995 return False
1954 1996
1955 1997 if self.required_perms.intersection(user_perms):
1956 1998 return True
1957 1999 return False
1958 2000
1959 2001
1960 2002 # CHECK FUNCTIONS
1961 2003 class PermsFunction(object):
1962 2004 """Base function for other check functions"""
1963 2005
1964 2006 def __init__(self, *perms):
1965 2007 self.required_perms = set(perms)
1966 2008 self.repo_name = None
1967 2009 self.repo_group_name = None
1968 2010 self.user_group_name = None
1969 2011
1970 2012 def __bool__(self):
1971 2013 frame = inspect.currentframe()
1972 2014 stack_trace = traceback.format_stack(frame)
1973 2015 log.error('Checking bool value on a class instance of perm '
1974 2016 'function is not allowed: %s', ''.join(stack_trace))
1975 2017 # rather than throwing errors, here we always return False so if by
1976 2018 # accident someone checks truth for just an instance it will always end
1977 2019 # up in returning False
1978 2020 return False
1979 2021 __nonzero__ = __bool__
1980 2022
1981 2023 def __call__(self, check_location='', user=None):
1982 2024 if not user:
1983 2025 log.debug('Using user attribute from global request')
1984 2026 request = self._get_request()
1985 2027 user = request.user
1986 2028
1987 2029 # init auth user if not already given
1988 2030 if not isinstance(user, AuthUser):
1989 2031 log.debug('Wrapping user %s into AuthUser', user)
1990 2032 user = AuthUser(user.user_id)
1991 2033
1992 2034 cls_name = self.__class__.__name__
1993 2035 check_scope = self._get_check_scope(cls_name)
1994 2036 check_location = check_location or 'unspecified location'
1995 2037
1996 2038 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1997 2039 self.required_perms, user, check_scope, check_location)
1998 2040 if not user:
1999 2041 log.warning('Empty user given for permission check')
2000 2042 return False
2001 2043
2002 2044 if self.check_permissions(user):
2003 2045 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2004 2046 check_scope, user, check_location)
2005 2047 return True
2006 2048
2007 2049 else:
2008 2050 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2009 2051 check_scope, user, check_location)
2010 2052 return False
2011 2053
2012 2054 def _get_request(self):
2013 2055 return get_request(self)
2014 2056
2015 2057 def _get_check_scope(self, cls_name):
2016 2058 return {
2017 2059 'HasPermissionAll': 'GLOBAL',
2018 2060 'HasPermissionAny': 'GLOBAL',
2019 2061 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2020 2062 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2021 2063 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2022 2064 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2023 2065 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2024 2066 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2025 2067 }.get(cls_name, '?:%s' % cls_name)
2026 2068
2027 2069 def check_permissions(self, user):
2028 2070 """Dummy function for overriding"""
2029 2071 raise Exception('You have to write this function in child class')
2030 2072
2031 2073
2032 2074 class HasPermissionAll(PermsFunction):
2033 2075 def check_permissions(self, user):
2034 2076 perms = user.permissions_with_scope({})
2035 2077 if self.required_perms.issubset(perms.get('global')):
2036 2078 return True
2037 2079 return False
2038 2080
2039 2081
2040 2082 class HasPermissionAny(PermsFunction):
2041 2083 def check_permissions(self, user):
2042 2084 perms = user.permissions_with_scope({})
2043 2085 if self.required_perms.intersection(perms.get('global')):
2044 2086 return True
2045 2087 return False
2046 2088
2047 2089
2048 2090 class HasRepoPermissionAll(PermsFunction):
2049 2091 def __call__(self, repo_name=None, check_location='', user=None):
2050 2092 self.repo_name = repo_name
2051 2093 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2052 2094
2053 2095 def _get_repo_name(self):
2054 2096 if not self.repo_name:
2055 2097 _request = self._get_request()
2056 2098 self.repo_name = get_repo_slug(_request)
2057 2099 return self.repo_name
2058 2100
2059 2101 def check_permissions(self, user):
2060 2102 self.repo_name = self._get_repo_name()
2061 2103 perms = user.permissions
2062 2104 try:
2063 2105 user_perms = {perms['repositories'][self.repo_name]}
2064 2106 except KeyError:
2065 2107 return False
2066 2108 if self.required_perms.issubset(user_perms):
2067 2109 return True
2068 2110 return False
2069 2111
2070 2112
2071 2113 class HasRepoPermissionAny(PermsFunction):
2072 2114 def __call__(self, repo_name=None, check_location='', user=None):
2073 2115 self.repo_name = repo_name
2074 2116 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2075 2117
2076 2118 def _get_repo_name(self):
2077 2119 if not self.repo_name:
2078 2120 _request = self._get_request()
2079 2121 self.repo_name = get_repo_slug(_request)
2080 2122 return self.repo_name
2081 2123
2082 2124 def check_permissions(self, user):
2083 2125 self.repo_name = self._get_repo_name()
2084 2126 perms = user.permissions
2085 2127 try:
2086 2128 user_perms = {perms['repositories'][self.repo_name]}
2087 2129 except KeyError:
2088 2130 return False
2089 2131 if self.required_perms.intersection(user_perms):
2090 2132 return True
2091 2133 return False
2092 2134
2093 2135
2094 2136 class HasRepoGroupPermissionAny(PermsFunction):
2095 2137 def __call__(self, group_name=None, check_location='', user=None):
2096 2138 self.repo_group_name = group_name
2097 2139 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2098 2140
2099 2141 def check_permissions(self, user):
2100 2142 perms = user.permissions
2101 2143 try:
2102 2144 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2103 2145 except KeyError:
2104 2146 return False
2105 2147 if self.required_perms.intersection(user_perms):
2106 2148 return True
2107 2149 return False
2108 2150
2109 2151
2110 2152 class HasRepoGroupPermissionAll(PermsFunction):
2111 2153 def __call__(self, group_name=None, check_location='', user=None):
2112 2154 self.repo_group_name = group_name
2113 2155 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2114 2156
2115 2157 def check_permissions(self, user):
2116 2158 perms = user.permissions
2117 2159 try:
2118 2160 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2119 2161 except KeyError:
2120 2162 return False
2121 2163 if self.required_perms.issubset(user_perms):
2122 2164 return True
2123 2165 return False
2124 2166
2125 2167
2126 2168 class HasUserGroupPermissionAny(PermsFunction):
2127 2169 def __call__(self, user_group_name=None, check_location='', user=None):
2128 2170 self.user_group_name = user_group_name
2129 2171 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2130 2172
2131 2173 def check_permissions(self, user):
2132 2174 perms = user.permissions
2133 2175 try:
2134 2176 user_perms = {perms['user_groups'][self.user_group_name]}
2135 2177 except KeyError:
2136 2178 return False
2137 2179 if self.required_perms.intersection(user_perms):
2138 2180 return True
2139 2181 return False
2140 2182
2141 2183
2142 2184 class HasUserGroupPermissionAll(PermsFunction):
2143 2185 def __call__(self, user_group_name=None, check_location='', user=None):
2144 2186 self.user_group_name = user_group_name
2145 2187 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2146 2188
2147 2189 def check_permissions(self, user):
2148 2190 perms = user.permissions
2149 2191 try:
2150 2192 user_perms = {perms['user_groups'][self.user_group_name]}
2151 2193 except KeyError:
2152 2194 return False
2153 2195 if self.required_perms.issubset(user_perms):
2154 2196 return True
2155 2197 return False
2156 2198
2157 2199
2158 2200 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2159 2201 class HasPermissionAnyMiddleware(object):
2160 2202 def __init__(self, *perms):
2161 2203 self.required_perms = set(perms)
2162 2204
2163 2205 def __call__(self, auth_user, repo_name):
2164 2206 # repo_name MUST be unicode, since we handle keys in permission
2165 2207 # dict by unicode
2166 2208 repo_name = safe_unicode(repo_name)
2167 2209 log.debug(
2168 2210 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2169 2211 self.required_perms, auth_user, repo_name)
2170 2212
2171 2213 if self.check_permissions(auth_user, repo_name):
2172 2214 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2173 2215 repo_name, auth_user, 'PermissionMiddleware')
2174 2216 return True
2175 2217
2176 2218 else:
2177 2219 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2178 2220 repo_name, auth_user, 'PermissionMiddleware')
2179 2221 return False
2180 2222
2181 2223 def check_permissions(self, user, repo_name):
2182 2224 perms = user.permissions_with_scope({'repo_name': repo_name})
2183 2225
2184 2226 try:
2185 2227 user_perms = {perms['repositories'][repo_name]}
2186 2228 except Exception:
2187 2229 log.exception('Error while accessing user permissions')
2188 2230 return False
2189 2231
2190 2232 if self.required_perms.intersection(user_perms):
2191 2233 return True
2192 2234 return False
2193 2235
2194 2236
2195 2237 # SPECIAL VERSION TO HANDLE API AUTH
2196 2238 class _BaseApiPerm(object):
2197 2239 def __init__(self, *perms):
2198 2240 self.required_perms = set(perms)
2199 2241
2200 2242 def __call__(self, check_location=None, user=None, repo_name=None,
2201 2243 group_name=None, user_group_name=None):
2202 2244 cls_name = self.__class__.__name__
2203 2245 check_scope = 'global:%s' % (self.required_perms,)
2204 2246 if repo_name:
2205 2247 check_scope += ', repo_name:%s' % (repo_name,)
2206 2248
2207 2249 if group_name:
2208 2250 check_scope += ', repo_group_name:%s' % (group_name,)
2209 2251
2210 2252 if user_group_name:
2211 2253 check_scope += ', user_group_name:%s' % (user_group_name,)
2212 2254
2213 2255 log.debug('checking cls:%s %s %s @ %s',
2214 2256 cls_name, self.required_perms, check_scope, check_location)
2215 2257 if not user:
2216 2258 log.debug('Empty User passed into arguments')
2217 2259 return False
2218 2260
2219 2261 # process user
2220 2262 if not isinstance(user, AuthUser):
2221 2263 user = AuthUser(user.user_id)
2222 2264 if not check_location:
2223 2265 check_location = 'unspecified'
2224 2266 if self.check_permissions(user.permissions, repo_name, group_name,
2225 2267 user_group_name):
2226 2268 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2227 2269 check_scope, user, check_location)
2228 2270 return True
2229 2271
2230 2272 else:
2231 2273 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2232 2274 check_scope, user, check_location)
2233 2275 return False
2234 2276
2235 2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2236 2278 user_group_name=None):
2237 2279 """
2238 2280 implement in child class should return True if permissions are ok,
2239 2281 False otherwise
2240 2282
2241 2283 :param perm_defs: dict with permission definitions
2242 2284 :param repo_name: repo name
2243 2285 """
2244 2286 raise NotImplementedError()
2245 2287
2246 2288
2247 2289 class HasPermissionAllApi(_BaseApiPerm):
2248 2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2249 2291 user_group_name=None):
2250 2292 if self.required_perms.issubset(perm_defs.get('global')):
2251 2293 return True
2252 2294 return False
2253 2295
2254 2296
2255 2297 class HasPermissionAnyApi(_BaseApiPerm):
2256 2298 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2257 2299 user_group_name=None):
2258 2300 if self.required_perms.intersection(perm_defs.get('global')):
2259 2301 return True
2260 2302 return False
2261 2303
2262 2304
2263 2305 class HasRepoPermissionAllApi(_BaseApiPerm):
2264 2306 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2265 2307 user_group_name=None):
2266 2308 try:
2267 2309 _user_perms = {perm_defs['repositories'][repo_name]}
2268 2310 except KeyError:
2269 2311 log.warning(traceback.format_exc())
2270 2312 return False
2271 2313 if self.required_perms.issubset(_user_perms):
2272 2314 return True
2273 2315 return False
2274 2316
2275 2317
2276 2318 class HasRepoPermissionAnyApi(_BaseApiPerm):
2277 2319 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2278 2320 user_group_name=None):
2279 2321 try:
2280 2322 _user_perms = {perm_defs['repositories'][repo_name]}
2281 2323 except KeyError:
2282 2324 log.warning(traceback.format_exc())
2283 2325 return False
2284 2326 if self.required_perms.intersection(_user_perms):
2285 2327 return True
2286 2328 return False
2287 2329
2288 2330
2289 2331 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2290 2332 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2291 2333 user_group_name=None):
2292 2334 try:
2293 2335 _user_perms = {perm_defs['repositories_groups'][group_name]}
2294 2336 except KeyError:
2295 2337 log.warning(traceback.format_exc())
2296 2338 return False
2297 2339 if self.required_perms.intersection(_user_perms):
2298 2340 return True
2299 2341 return False
2300 2342
2301 2343
2302 2344 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2303 2345 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2304 2346 user_group_name=None):
2305 2347 try:
2306 2348 _user_perms = {perm_defs['repositories_groups'][group_name]}
2307 2349 except KeyError:
2308 2350 log.warning(traceback.format_exc())
2309 2351 return False
2310 2352 if self.required_perms.issubset(_user_perms):
2311 2353 return True
2312 2354 return False
2313 2355
2314 2356
2315 2357 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2316 2358 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2317 2359 user_group_name=None):
2318 2360 try:
2319 2361 _user_perms = {perm_defs['user_groups'][user_group_name]}
2320 2362 except KeyError:
2321 2363 log.warning(traceback.format_exc())
2322 2364 return False
2323 2365 if self.required_perms.intersection(_user_perms):
2324 2366 return True
2325 2367 return False
2326 2368
2327 2369
2328 2370 def check_ip_access(source_ip, allowed_ips=None):
2329 2371 """
2330 2372 Checks if source_ip is a subnet of any of allowed_ips.
2331 2373
2332 2374 :param source_ip:
2333 2375 :param allowed_ips: list of allowed ips together with mask
2334 2376 """
2335 2377 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2336 2378 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2337 2379 if isinstance(allowed_ips, (tuple, list, set)):
2338 2380 for ip in allowed_ips:
2339 2381 ip = safe_unicode(ip)
2340 2382 try:
2341 2383 network_address = ipaddress.ip_network(ip, strict=False)
2342 2384 if source_ip_address in network_address:
2343 2385 log.debug('IP %s is network %s', source_ip_address, network_address)
2344 2386 return True
2345 2387 # for any case we cannot determine the IP, don't crash just
2346 2388 # skip it and log as error, we want to say forbidden still when
2347 2389 # sending bad IP
2348 2390 except Exception:
2349 2391 log.error(traceback.format_exc())
2350 2392 continue
2351 2393 return False
2352 2394
2353 2395
2354 2396 def get_cython_compat_decorator(wrapper, func):
2355 2397 """
2356 2398 Creates a cython compatible decorator. The previously used
2357 2399 decorator.decorator() function seems to be incompatible with cython.
2358 2400
2359 2401 :param wrapper: __wrapper method of the decorator class
2360 2402 :param func: decorated function
2361 2403 """
2362 2404 @wraps(func)
2363 2405 def local_wrapper(*args, **kwds):
2364 2406 return wrapper(func, *args, **kwds)
2365 2407 local_wrapper.__wrapped__ = func
2366 2408 return local_wrapper
2367 2409
2368 2410
@@ -1,5474 +1,5489 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Database Models for RhodeCode Enterprise
23 23 """
24 24
25 25 import re
26 26 import os
27 27 import time
28 28 import string
29 29 import hashlib
30 30 import logging
31 31 import datetime
32 32 import uuid
33 33 import warnings
34 34 import ipaddress
35 35 import functools
36 36 import traceback
37 37 import collections
38 38
39 39 from sqlalchemy import (
40 40 or_, and_, not_, func, cast, TypeDecorator, event,
41 41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 43 Text, Float, PickleType, BigInteger)
44 44 from sqlalchemy.sql.expression import true, false, case
45 45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 46 from sqlalchemy.orm import (
47 47 relationship, joinedload, class_mapper, validates, aliased)
48 48 from sqlalchemy.ext.declarative import declared_attr
49 49 from sqlalchemy.ext.hybrid import hybrid_property
50 50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 51 from sqlalchemy.dialects.mysql import LONGTEXT
52 52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 53 from pyramid import compat
54 54 from pyramid.threadlocal import get_current_request
55 55 from webhelpers2.text import remove_formatting
56 56
57 57 from rhodecode.translation import _
58 58 from rhodecode.lib.vcs import get_vcs_instance
59 59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 60 from rhodecode.lib.utils2 import (
61 61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 65 JsonRaw
66 66 from rhodecode.lib.ext_json import json
67 67 from rhodecode.lib.caching_query import FromCache
68 68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 69 from rhodecode.lib.encrypt2 import Encryptor
70 70 from rhodecode.lib.exceptions import (
71 71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 72 from rhodecode.model.meta import Base, Session
73 73
74 74 URL_SEP = '/'
75 75 log = logging.getLogger(__name__)
76 76
77 77 # =============================================================================
78 78 # BASE CLASSES
79 79 # =============================================================================
80 80
81 81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 82 # beaker.session.secret if first is not set.
83 83 # and initialized at environment.py
84 84 ENCRYPTION_KEY = None
85 85
86 86 # used to sort permissions by types, '#' used here is not allowed to be in
87 87 # usernames, and it's very early in sorted string.printable table.
88 88 PERMISSION_TYPE_SORT = {
89 89 'admin': '####',
90 90 'write': '###',
91 91 'read': '##',
92 92 'none': '#',
93 93 }
94 94
95 95
96 96 def display_user_sort(obj):
97 97 """
98 98 Sort function used to sort permissions in .permissions() function of
99 99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 100 of all other resources
101 101 """
102 102
103 103 if obj.username == User.DEFAULT_USER:
104 104 return '#####'
105 105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 106 return prefix + obj.username
107 107
108 108
109 109 def display_user_group_sort(obj):
110 110 """
111 111 Sort function used to sort permissions in .permissions() function of
112 112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 113 of all other resources
114 114 """
115 115
116 116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 117 return prefix + obj.users_group_name
118 118
119 119
120 120 def _hash_key(k):
121 121 return sha1_safe(k)
122 122
123 123
124 124 def in_filter_generator(qry, items, limit=500):
125 125 """
126 126 Splits IN() into multiple with OR
127 127 e.g.::
128 128 cnt = Repository.query().filter(
129 129 or_(
130 130 *in_filter_generator(Repository.repo_id, range(100000))
131 131 )).count()
132 132 """
133 133 if not items:
134 134 # empty list will cause empty query which might cause security issues
135 135 # this can lead to hidden unpleasant results
136 136 items = [-1]
137 137
138 138 parts = []
139 139 for chunk in xrange(0, len(items), limit):
140 140 parts.append(
141 141 qry.in_(items[chunk: chunk + limit])
142 142 )
143 143
144 144 return parts
145 145
146 146
147 147 base_table_args = {
148 148 'extend_existing': True,
149 149 'mysql_engine': 'InnoDB',
150 150 'mysql_charset': 'utf8',
151 151 'sqlite_autoincrement': True
152 152 }
153 153
154 154
155 155 class EncryptedTextValue(TypeDecorator):
156 156 """
157 157 Special column for encrypted long text data, use like::
158 158
159 159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160 160
161 161 This column is intelligent so if value is in unencrypted form it return
162 162 unencrypted form, but on save it always encrypts
163 163 """
164 164 impl = Text
165 165
166 166 def process_bind_param(self, value, dialect):
167 167 """
168 168 Setter for storing value
169 169 """
170 170 import rhodecode
171 171 if not value:
172 172 return value
173 173
174 174 # protect against double encrypting if values is already encrypted
175 175 if value.startswith('enc$aes$') \
176 176 or value.startswith('enc$aes_hmac$') \
177 177 or value.startswith('enc2$'):
178 178 raise ValueError('value needs to be in unencrypted format, '
179 179 'ie. not starting with enc$ or enc2$')
180 180
181 181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 182 if algo == 'aes':
183 183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 184 elif algo == 'fernet':
185 185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 186 else:
187 187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188 188
189 189 def process_result_value(self, value, dialect):
190 190 """
191 191 Getter for retrieving value
192 192 """
193 193
194 194 import rhodecode
195 195 if not value:
196 196 return value
197 197
198 198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 200 if algo == 'aes':
201 201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 202 elif algo == 'fernet':
203 203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 204 else:
205 205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 206 return decrypted_data
207 207
208 208
209 209 class BaseModel(object):
210 210 """
211 211 Base Model for all classes
212 212 """
213 213
214 214 @classmethod
215 215 def _get_keys(cls):
216 216 """return column names for this model """
217 217 return class_mapper(cls).c.keys()
218 218
219 219 def get_dict(self):
220 220 """
221 221 return dict with keys and values corresponding
222 222 to this model data """
223 223
224 224 d = {}
225 225 for k in self._get_keys():
226 226 d[k] = getattr(self, k)
227 227
228 228 # also use __json__() if present to get additional fields
229 229 _json_attr = getattr(self, '__json__', None)
230 230 if _json_attr:
231 231 # update with attributes from __json__
232 232 if callable(_json_attr):
233 233 _json_attr = _json_attr()
234 234 for k, val in _json_attr.iteritems():
235 235 d[k] = val
236 236 return d
237 237
238 238 def get_appstruct(self):
239 239 """return list with keys and values tuples corresponding
240 240 to this model data """
241 241
242 242 lst = []
243 243 for k in self._get_keys():
244 244 lst.append((k, getattr(self, k),))
245 245 return lst
246 246
247 247 def populate_obj(self, populate_dict):
248 248 """populate model with data from given populate_dict"""
249 249
250 250 for k in self._get_keys():
251 251 if k in populate_dict:
252 252 setattr(self, k, populate_dict[k])
253 253
254 254 @classmethod
255 255 def query(cls):
256 256 return Session().query(cls)
257 257
258 258 @classmethod
259 259 def get(cls, id_):
260 260 if id_:
261 261 return cls.query().get(id_)
262 262
263 263 @classmethod
264 264 def get_or_404(cls, id_):
265 265 from pyramid.httpexceptions import HTTPNotFound
266 266
267 267 try:
268 268 id_ = int(id_)
269 269 except (TypeError, ValueError):
270 270 raise HTTPNotFound()
271 271
272 272 res = cls.query().get(id_)
273 273 if not res:
274 274 raise HTTPNotFound()
275 275 return res
276 276
277 277 @classmethod
278 278 def getAll(cls):
279 279 # deprecated and left for backward compatibility
280 280 return cls.get_all()
281 281
282 282 @classmethod
283 283 def get_all(cls):
284 284 return cls.query().all()
285 285
286 286 @classmethod
287 287 def delete(cls, id_):
288 288 obj = cls.query().get(id_)
289 289 Session().delete(obj)
290 290
291 291 @classmethod
292 292 def identity_cache(cls, session, attr_name, value):
293 293 exist_in_session = []
294 294 for (item_cls, pkey), instance in session.identity_map.items():
295 295 if cls == item_cls and getattr(instance, attr_name) == value:
296 296 exist_in_session.append(instance)
297 297 if exist_in_session:
298 298 if len(exist_in_session) == 1:
299 299 return exist_in_session[0]
300 300 log.exception(
301 301 'multiple objects with attr %s and '
302 302 'value %s found with same name: %r',
303 303 attr_name, value, exist_in_session)
304 304
305 305 def __repr__(self):
306 306 if hasattr(self, '__unicode__'):
307 307 # python repr needs to return str
308 308 try:
309 309 return safe_str(self.__unicode__())
310 310 except UnicodeDecodeError:
311 311 pass
312 312 return '<DB:%s>' % (self.__class__.__name__)
313 313
314 314
315 315 class RhodeCodeSetting(Base, BaseModel):
316 316 __tablename__ = 'rhodecode_settings'
317 317 __table_args__ = (
318 318 UniqueConstraint('app_settings_name'),
319 319 base_table_args
320 320 )
321 321
322 322 SETTINGS_TYPES = {
323 323 'str': safe_str,
324 324 'int': safe_int,
325 325 'unicode': safe_unicode,
326 326 'bool': str2bool,
327 327 'list': functools.partial(aslist, sep=',')
328 328 }
329 329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 330 GLOBAL_CONF_KEY = 'app_settings'
331 331
332 332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336 336
337 337 def __init__(self, key='', val='', type='unicode'):
338 338 self.app_settings_name = key
339 339 self.app_settings_type = type
340 340 self.app_settings_value = val
341 341
342 342 @validates('_app_settings_value')
343 343 def validate_settings_value(self, key, val):
344 344 assert type(val) == unicode
345 345 return val
346 346
347 347 @hybrid_property
348 348 def app_settings_value(self):
349 349 v = self._app_settings_value
350 350 _type = self.app_settings_type
351 351 if _type:
352 352 _type = self.app_settings_type.split('.')[0]
353 353 # decode the encrypted value
354 354 if 'encrypted' in self.app_settings_type:
355 355 cipher = EncryptedTextValue()
356 356 v = safe_unicode(cipher.process_result_value(v, None))
357 357
358 358 converter = self.SETTINGS_TYPES.get(_type) or \
359 359 self.SETTINGS_TYPES['unicode']
360 360 return converter(v)
361 361
362 362 @app_settings_value.setter
363 363 def app_settings_value(self, val):
364 364 """
365 365 Setter that will always make sure we use unicode in app_settings_value
366 366
367 367 :param val:
368 368 """
369 369 val = safe_unicode(val)
370 370 # encode the encrypted value
371 371 if 'encrypted' in self.app_settings_type:
372 372 cipher = EncryptedTextValue()
373 373 val = safe_unicode(cipher.process_bind_param(val, None))
374 374 self._app_settings_value = val
375 375
376 376 @hybrid_property
377 377 def app_settings_type(self):
378 378 return self._app_settings_type
379 379
380 380 @app_settings_type.setter
381 381 def app_settings_type(self, val):
382 382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 383 raise Exception('type must be one of %s got %s'
384 384 % (self.SETTINGS_TYPES.keys(), val))
385 385 self._app_settings_type = val
386 386
387 387 @classmethod
388 388 def get_by_prefix(cls, prefix):
389 389 return RhodeCodeSetting.query()\
390 390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 391 .all()
392 392
393 393 def __unicode__(self):
394 394 return u"<%s('%s:%s[%s]')>" % (
395 395 self.__class__.__name__,
396 396 self.app_settings_name, self.app_settings_value,
397 397 self.app_settings_type
398 398 )
399 399
400 400
401 401 class RhodeCodeUi(Base, BaseModel):
402 402 __tablename__ = 'rhodecode_ui'
403 403 __table_args__ = (
404 404 UniqueConstraint('ui_key'),
405 405 base_table_args
406 406 )
407 407
408 408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 409 # HG
410 410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 411 HOOK_PULL = 'outgoing.pull_logger'
412 412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 414 HOOK_PUSH = 'changegroup.push_logger'
415 415 HOOK_PUSH_KEY = 'pushkey.key_push'
416 416
417 417 HOOKS_BUILTIN = [
418 418 HOOK_PRE_PULL,
419 419 HOOK_PULL,
420 420 HOOK_PRE_PUSH,
421 421 HOOK_PRETX_PUSH,
422 422 HOOK_PUSH,
423 423 HOOK_PUSH_KEY,
424 424 ]
425 425
426 426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 427 # git part is currently hardcoded.
428 428
429 429 # SVN PATTERNS
430 430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 431 SVN_TAG_ID = 'vcs_svn_tag'
432 432
433 433 ui_id = Column(
434 434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 435 primary_key=True)
436 436 ui_section = Column(
437 437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 438 ui_key = Column(
439 439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 440 ui_value = Column(
441 441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 442 ui_active = Column(
443 443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444 444
445 445 def __repr__(self):
446 446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 447 self.ui_key, self.ui_value)
448 448
449 449
450 450 class RepoRhodeCodeSetting(Base, BaseModel):
451 451 __tablename__ = 'repo_rhodecode_settings'
452 452 __table_args__ = (
453 453 UniqueConstraint(
454 454 'app_settings_name', 'repository_id',
455 455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 456 base_table_args
457 457 )
458 458
459 459 repository_id = Column(
460 460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 461 nullable=False)
462 462 app_settings_id = Column(
463 463 "app_settings_id", Integer(), nullable=False, unique=True,
464 464 default=None, primary_key=True)
465 465 app_settings_name = Column(
466 466 "app_settings_name", String(255), nullable=True, unique=None,
467 467 default=None)
468 468 _app_settings_value = Column(
469 469 "app_settings_value", String(4096), nullable=True, unique=None,
470 470 default=None)
471 471 _app_settings_type = Column(
472 472 "app_settings_type", String(255), nullable=True, unique=None,
473 473 default=None)
474 474
475 475 repository = relationship('Repository')
476 476
477 477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 478 self.repository_id = repository_id
479 479 self.app_settings_name = key
480 480 self.app_settings_type = type
481 481 self.app_settings_value = val
482 482
483 483 @validates('_app_settings_value')
484 484 def validate_settings_value(self, key, val):
485 485 assert type(val) == unicode
486 486 return val
487 487
488 488 @hybrid_property
489 489 def app_settings_value(self):
490 490 v = self._app_settings_value
491 491 type_ = self.app_settings_type
492 492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 494 return converter(v)
495 495
496 496 @app_settings_value.setter
497 497 def app_settings_value(self, val):
498 498 """
499 499 Setter that will always make sure we use unicode in app_settings_value
500 500
501 501 :param val:
502 502 """
503 503 self._app_settings_value = safe_unicode(val)
504 504
505 505 @hybrid_property
506 506 def app_settings_type(self):
507 507 return self._app_settings_type
508 508
509 509 @app_settings_type.setter
510 510 def app_settings_type(self, val):
511 511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 512 if val not in SETTINGS_TYPES:
513 513 raise Exception('type must be one of %s got %s'
514 514 % (SETTINGS_TYPES.keys(), val))
515 515 self._app_settings_type = val
516 516
517 517 def __unicode__(self):
518 518 return u"<%s('%s:%s:%s[%s]')>" % (
519 519 self.__class__.__name__, self.repository.repo_name,
520 520 self.app_settings_name, self.app_settings_value,
521 521 self.app_settings_type
522 522 )
523 523
524 524
525 525 class RepoRhodeCodeUi(Base, BaseModel):
526 526 __tablename__ = 'repo_rhodecode_ui'
527 527 __table_args__ = (
528 528 UniqueConstraint(
529 529 'repository_id', 'ui_section', 'ui_key',
530 530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 531 base_table_args
532 532 )
533 533
534 534 repository_id = Column(
535 535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 536 nullable=False)
537 537 ui_id = Column(
538 538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 539 primary_key=True)
540 540 ui_section = Column(
541 541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 542 ui_key = Column(
543 543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 544 ui_value = Column(
545 545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 546 ui_active = Column(
547 547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548 548
549 549 repository = relationship('Repository')
550 550
551 551 def __repr__(self):
552 552 return '<%s[%s:%s]%s=>%s]>' % (
553 553 self.__class__.__name__, self.repository.repo_name,
554 554 self.ui_section, self.ui_key, self.ui_value)
555 555
556 556
557 557 class User(Base, BaseModel):
558 558 __tablename__ = 'users'
559 559 __table_args__ = (
560 560 UniqueConstraint('username'), UniqueConstraint('email'),
561 561 Index('u_username_idx', 'username'),
562 562 Index('u_email_idx', 'email'),
563 563 base_table_args
564 564 )
565 565
566 566 DEFAULT_USER = 'default'
567 567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569 569
570 570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581 581
582 582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588 588
589 589 user_log = relationship('UserLog')
590 590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591 591
592 592 repositories = relationship('Repository')
593 593 repository_groups = relationship('RepoGroup')
594 594 user_groups = relationship('UserGroup')
595 595
596 596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598 598
599 599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602 602
603 603 group_member = relationship('UserGroupMember', cascade='all')
604 604
605 605 notifications = relationship('UserNotification', cascade='all')
606 606 # notifications assigned to this user
607 607 user_created_notifications = relationship('Notification', cascade='all')
608 608 # comments created by this user
609 609 user_comments = relationship('ChangesetComment', cascade='all')
610 610 # user profile extra info
611 611 user_emails = relationship('UserEmailMap', cascade='all')
612 612 user_ip_map = relationship('UserIpMap', cascade='all')
613 613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615 615
616 616 # gists
617 617 user_gists = relationship('Gist', cascade='all')
618 618 # user pull requests
619 619 user_pull_requests = relationship('PullRequest', cascade='all')
620 620 # external identities
621 621 external_identities = relationship(
622 622 'ExternalIdentity',
623 623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 624 cascade='all')
625 625 # review rules
626 626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627 627
628 628 # artifacts owned
629 629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630 630
631 631 # no cascade, set NULL
632 632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633 633
634 634 def __unicode__(self):
635 635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 636 self.user_id, self.username)
637 637
638 638 @hybrid_property
639 639 def email(self):
640 640 return self._email
641 641
642 642 @email.setter
643 643 def email(self, val):
644 644 self._email = val.lower() if val else None
645 645
646 646 @hybrid_property
647 647 def first_name(self):
648 648 from rhodecode.lib import helpers as h
649 649 if self.name:
650 650 return h.escape(self.name)
651 651 return self.name
652 652
653 653 @hybrid_property
654 654 def last_name(self):
655 655 from rhodecode.lib import helpers as h
656 656 if self.lastname:
657 657 return h.escape(self.lastname)
658 658 return self.lastname
659 659
660 660 @hybrid_property
661 661 def api_key(self):
662 662 """
663 663 Fetch if exist an auth-token with role ALL connected to this user
664 664 """
665 665 user_auth_token = UserApiKeys.query()\
666 666 .filter(UserApiKeys.user_id == self.user_id)\
667 667 .filter(or_(UserApiKeys.expires == -1,
668 668 UserApiKeys.expires >= time.time()))\
669 669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 670 if user_auth_token:
671 671 user_auth_token = user_auth_token.api_key
672 672
673 673 return user_auth_token
674 674
675 675 @api_key.setter
676 676 def api_key(self, val):
677 677 # don't allow to set API key this is deprecated for now
678 678 self._api_key = None
679 679
680 680 @property
681 681 def reviewer_pull_requests(self):
682 682 return PullRequestReviewers.query() \
683 683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 685 .all()
686 686
687 687 @property
688 688 def firstname(self):
689 689 # alias for future
690 690 return self.name
691 691
692 692 @property
693 693 def emails(self):
694 694 other = UserEmailMap.query()\
695 695 .filter(UserEmailMap.user == self) \
696 696 .order_by(UserEmailMap.email_id.asc()) \
697 697 .all()
698 698 return [self.email] + [x.email for x in other]
699 699
700 700 def emails_cached(self):
701 701 emails = UserEmailMap.query()\
702 702 .filter(UserEmailMap.user == self) \
703 703 .order_by(UserEmailMap.email_id.asc())
704 704
705 705 emails = emails.options(
706 706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 707 )
708 708
709 709 return [self.email] + [x.email for x in emails]
710 710
711 711 @property
712 712 def auth_tokens(self):
713 713 auth_tokens = self.get_auth_tokens()
714 714 return [x.api_key for x in auth_tokens]
715 715
716 716 def get_auth_tokens(self):
717 717 return UserApiKeys.query()\
718 718 .filter(UserApiKeys.user == self)\
719 719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 720 .all()
721 721
722 722 @LazyProperty
723 723 def feed_token(self):
724 724 return self.get_feed_token()
725 725
726 726 def get_feed_token(self, cache=True):
727 727 feed_tokens = UserApiKeys.query()\
728 728 .filter(UserApiKeys.user == self)\
729 729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 730 if cache:
731 731 feed_tokens = feed_tokens.options(
732 732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733 733
734 734 feed_tokens = feed_tokens.all()
735 735 if feed_tokens:
736 736 return feed_tokens[0].api_key
737 737 return 'NO_FEED_TOKEN_AVAILABLE'
738 738
739 739 @LazyProperty
740 740 def artifact_token(self):
741 741 return self.get_artifact_token()
742 742
743 743 def get_artifact_token(self, cache=True):
744 744 artifacts_tokens = UserApiKeys.query()\
745 745 .filter(UserApiKeys.user == self)\
746 746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 747 if cache:
748 748 artifacts_tokens = artifacts_tokens.options(
749 749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750 750
751 751 artifacts_tokens = artifacts_tokens.all()
752 752 if artifacts_tokens:
753 753 return artifacts_tokens[0].api_key
754 754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755 755
756 756 @classmethod
757 757 def get(cls, user_id, cache=False):
758 758 if not user_id:
759 759 return
760 760
761 761 user = cls.query()
762 762 if cache:
763 763 user = user.options(
764 764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 765 return user.get(user_id)
766 766
767 767 @classmethod
768 768 def extra_valid_auth_tokens(cls, user, role=None):
769 769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 770 .filter(or_(UserApiKeys.expires == -1,
771 771 UserApiKeys.expires >= time.time()))
772 772 if role:
773 773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 775 return tokens.all()
776 776
777 777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 778 from rhodecode.lib import auth
779 779
780 780 log.debug('Trying to authenticate user: %s via auth-token, '
781 781 'and roles: %s', self, roles)
782 782
783 783 if not auth_token:
784 784 return False
785 785
786 786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 787 tokens_q = UserApiKeys.query()\
788 788 .filter(UserApiKeys.user_id == self.user_id)\
789 789 .filter(or_(UserApiKeys.expires == -1,
790 790 UserApiKeys.expires >= time.time()))
791 791
792 792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793 793
794 794 crypto_backend = auth.crypto_backend()
795 795 enc_token_map = {}
796 796 plain_token_map = {}
797 797 for token in tokens_q:
798 798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 799 enc_token_map[token.api_key] = token
800 800 else:
801 801 plain_token_map[token.api_key] = token
802 802 log.debug(
803 803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 804 len(plain_token_map), len(enc_token_map))
805 805
806 806 # plain token match comes first
807 807 match = plain_token_map.get(auth_token)
808 808
809 809 # check encrypted tokens now
810 810 if not match:
811 811 for token_hash, token in enc_token_map.items():
812 812 # NOTE(marcink): this is expensive to calculate, but most secure
813 813 if crypto_backend.hash_check(auth_token, token_hash):
814 814 match = token
815 815 break
816 816
817 817 if match:
818 818 log.debug('Found matching token %s', match)
819 819 if match.repo_id:
820 820 log.debug('Found scope, checking for scope match of token %s', match)
821 821 if match.repo_id == scope_repo_id:
822 822 return True
823 823 else:
824 824 log.debug(
825 825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 826 'and calling scope is:%s, skipping further checks',
827 827 match.repo, scope_repo_id)
828 828 return False
829 829 else:
830 830 return True
831 831
832 832 return False
833 833
834 834 @property
835 835 def ip_addresses(self):
836 836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 837 return [x.ip_addr for x in ret]
838 838
839 839 @property
840 840 def username_and_name(self):
841 841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842 842
843 843 @property
844 844 def username_or_name_or_email(self):
845 845 full_name = self.full_name if self.full_name is not ' ' else None
846 846 return self.username or full_name or self.email
847 847
848 848 @property
849 849 def full_name(self):
850 850 return '%s %s' % (self.first_name, self.last_name)
851 851
852 852 @property
853 853 def full_name_or_username(self):
854 854 return ('%s %s' % (self.first_name, self.last_name)
855 855 if (self.first_name and self.last_name) else self.username)
856 856
857 857 @property
858 858 def full_contact(self):
859 859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860 860
861 861 @property
862 862 def short_contact(self):
863 863 return '%s %s' % (self.first_name, self.last_name)
864 864
865 865 @property
866 866 def is_admin(self):
867 867 return self.admin
868 868
869 869 @property
870 870 def language(self):
871 871 return self.user_data.get('language')
872 872
873 873 def AuthUser(self, **kwargs):
874 874 """
875 875 Returns instance of AuthUser for this user
876 876 """
877 877 from rhodecode.lib.auth import AuthUser
878 878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879 879
880 880 @hybrid_property
881 881 def user_data(self):
882 882 if not self._user_data:
883 883 return {}
884 884
885 885 try:
886 886 return json.loads(self._user_data)
887 887 except TypeError:
888 888 return {}
889 889
890 890 @user_data.setter
891 891 def user_data(self, val):
892 892 if not isinstance(val, dict):
893 893 raise Exception('user_data must be dict, got %s' % type(val))
894 894 try:
895 895 self._user_data = json.dumps(val)
896 896 except Exception:
897 897 log.error(traceback.format_exc())
898 898
899 899 @classmethod
900 900 def get_by_username(cls, username, case_insensitive=False,
901 901 cache=False, identity_cache=False):
902 902 session = Session()
903 903
904 904 if case_insensitive:
905 905 q = cls.query().filter(
906 906 func.lower(cls.username) == func.lower(username))
907 907 else:
908 908 q = cls.query().filter(cls.username == username)
909 909
910 910 if cache:
911 911 if identity_cache:
912 912 val = cls.identity_cache(session, 'username', username)
913 913 if val:
914 914 return val
915 915 else:
916 916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 917 q = q.options(
918 918 FromCache("sql_cache_short", cache_key))
919 919
920 920 return q.scalar()
921 921
922 922 @classmethod
923 923 def get_by_auth_token(cls, auth_token, cache=False):
924 924 q = UserApiKeys.query()\
925 925 .filter(UserApiKeys.api_key == auth_token)\
926 926 .filter(or_(UserApiKeys.expires == -1,
927 927 UserApiKeys.expires >= time.time()))
928 928 if cache:
929 929 q = q.options(
930 930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931 931
932 932 match = q.first()
933 933 if match:
934 934 return match.user
935 935
936 936 @classmethod
937 937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938 938
939 939 if case_insensitive:
940 940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941 941
942 942 else:
943 943 q = cls.query().filter(cls.email == email)
944 944
945 945 email_key = _hash_key(email)
946 946 if cache:
947 947 q = q.options(
948 948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949 949
950 950 ret = q.scalar()
951 951 if ret is None:
952 952 q = UserEmailMap.query()
953 953 # try fetching in alternate email map
954 954 if case_insensitive:
955 955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 956 else:
957 957 q = q.filter(UserEmailMap.email == email)
958 958 q = q.options(joinedload(UserEmailMap.user))
959 959 if cache:
960 960 q = q.options(
961 961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 962 ret = getattr(q.scalar(), 'user', None)
963 963
964 964 return ret
965 965
966 966 @classmethod
967 967 def get_from_cs_author(cls, author):
968 968 """
969 969 Tries to get User objects out of commit author string
970 970
971 971 :param author:
972 972 """
973 973 from rhodecode.lib.helpers import email, author_name
974 974 # Valid email in the attribute passed, see if they're in the system
975 975 _email = email(author)
976 976 if _email:
977 977 user = cls.get_by_email(_email, case_insensitive=True)
978 978 if user:
979 979 return user
980 980 # Maybe we can match by username?
981 981 _author = author_name(author)
982 982 user = cls.get_by_username(_author, case_insensitive=True)
983 983 if user:
984 984 return user
985 985
986 986 def update_userdata(self, **kwargs):
987 987 usr = self
988 988 old = usr.user_data
989 989 old.update(**kwargs)
990 990 usr.user_data = old
991 991 Session().add(usr)
992 992 log.debug('updated userdata with %s', kwargs)
993 993
994 994 def update_lastlogin(self):
995 995 """Update user lastlogin"""
996 996 self.last_login = datetime.datetime.now()
997 997 Session().add(self)
998 998 log.debug('updated user %s lastlogin', self.username)
999 999
1000 1000 def update_password(self, new_password):
1001 1001 from rhodecode.lib.auth import get_crypt_password
1002 1002
1003 1003 self.password = get_crypt_password(new_password)
1004 1004 Session().add(self)
1005 1005
1006 1006 @classmethod
1007 1007 def get_first_super_admin(cls):
1008 1008 user = User.query()\
1009 1009 .filter(User.admin == true()) \
1010 1010 .order_by(User.user_id.asc()) \
1011 1011 .first()
1012 1012
1013 1013 if user is None:
1014 1014 raise Exception('FATAL: Missing administrative account!')
1015 1015 return user
1016 1016
1017 1017 @classmethod
1018 1018 def get_all_super_admins(cls, only_active=False):
1019 1019 """
1020 1020 Returns all admin accounts sorted by username
1021 1021 """
1022 1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 1023 if only_active:
1024 1024 qry = qry.filter(User.active == true())
1025 1025 return qry.all()
1026 1026
1027 1027 @classmethod
1028 1028 def get_default_user(cls, cache=False, refresh=False):
1029 1029 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1030 1030 if user is None:
1031 1031 raise Exception('FATAL: Missing default account!')
1032 1032 if refresh:
1033 1033 # The default user might be based on outdated state which
1034 1034 # has been loaded from the cache.
1035 1035 # A call to refresh() ensures that the
1036 1036 # latest state from the database is used.
1037 1037 Session().refresh(user)
1038 1038 return user
1039 1039
1040 1040 def _get_default_perms(self, user, suffix=''):
1041 1041 from rhodecode.model.permission import PermissionModel
1042 1042 return PermissionModel().get_default_perms(user.user_perms, suffix)
1043 1043
1044 1044 def get_default_perms(self, suffix=''):
1045 1045 return self._get_default_perms(self, suffix)
1046 1046
1047 1047 def get_api_data(self, include_secrets=False, details='full'):
1048 1048 """
1049 1049 Common function for generating user related data for API
1050 1050
1051 1051 :param include_secrets: By default secrets in the API data will be replaced
1052 1052 by a placeholder value to prevent exposing this data by accident. In case
1053 1053 this data shall be exposed, set this flag to ``True``.
1054 1054
1055 1055 :param details: details can be 'basic|full' basic gives only a subset of
1056 1056 the available user information that includes user_id, name and emails.
1057 1057 """
1058 1058 user = self
1059 1059 user_data = self.user_data
1060 1060 data = {
1061 1061 'user_id': user.user_id,
1062 1062 'username': user.username,
1063 1063 'firstname': user.name,
1064 1064 'lastname': user.lastname,
1065 1065 'description': user.description,
1066 1066 'email': user.email,
1067 1067 'emails': user.emails,
1068 1068 }
1069 1069 if details == 'basic':
1070 1070 return data
1071 1071
1072 1072 auth_token_length = 40
1073 1073 auth_token_replacement = '*' * auth_token_length
1074 1074
1075 1075 extras = {
1076 1076 'auth_tokens': [auth_token_replacement],
1077 1077 'active': user.active,
1078 1078 'admin': user.admin,
1079 1079 'extern_type': user.extern_type,
1080 1080 'extern_name': user.extern_name,
1081 1081 'last_login': user.last_login,
1082 1082 'last_activity': user.last_activity,
1083 1083 'ip_addresses': user.ip_addresses,
1084 1084 'language': user_data.get('language')
1085 1085 }
1086 1086 data.update(extras)
1087 1087
1088 1088 if include_secrets:
1089 1089 data['auth_tokens'] = user.auth_tokens
1090 1090 return data
1091 1091
1092 1092 def __json__(self):
1093 1093 data = {
1094 1094 'full_name': self.full_name,
1095 1095 'full_name_or_username': self.full_name_or_username,
1096 1096 'short_contact': self.short_contact,
1097 1097 'full_contact': self.full_contact,
1098 1098 }
1099 1099 data.update(self.get_api_data())
1100 1100 return data
1101 1101
1102 1102
1103 1103 class UserApiKeys(Base, BaseModel):
1104 1104 __tablename__ = 'user_api_keys'
1105 1105 __table_args__ = (
1106 1106 Index('uak_api_key_idx', 'api_key'),
1107 1107 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1108 1108 base_table_args
1109 1109 )
1110 1110 __mapper_args__ = {}
1111 1111
1112 1112 # ApiKey role
1113 1113 ROLE_ALL = 'token_role_all'
1114 1114 ROLE_HTTP = 'token_role_http'
1115 1115 ROLE_VCS = 'token_role_vcs'
1116 1116 ROLE_API = 'token_role_api'
1117 1117 ROLE_FEED = 'token_role_feed'
1118 1118 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1119 1119 ROLE_PASSWORD_RESET = 'token_password_reset'
1120 1120
1121 1121 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1122 1122
1123 1123 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1125 1125 api_key = Column("api_key", String(255), nullable=False, unique=True)
1126 1126 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1127 1127 expires = Column('expires', Float(53), nullable=False)
1128 1128 role = Column('role', String(255), nullable=True)
1129 1129 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1130 1130
1131 1131 # scope columns
1132 1132 repo_id = Column(
1133 1133 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1134 1134 nullable=True, unique=None, default=None)
1135 1135 repo = relationship('Repository', lazy='joined')
1136 1136
1137 1137 repo_group_id = Column(
1138 1138 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1139 1139 nullable=True, unique=None, default=None)
1140 1140 repo_group = relationship('RepoGroup', lazy='joined')
1141 1141
1142 1142 user = relationship('User', lazy='joined')
1143 1143
1144 1144 def __unicode__(self):
1145 1145 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1146 1146
1147 1147 def __json__(self):
1148 1148 data = {
1149 1149 'auth_token': self.api_key,
1150 1150 'role': self.role,
1151 1151 'scope': self.scope_humanized,
1152 1152 'expired': self.expired
1153 1153 }
1154 1154 return data
1155 1155
1156 1156 def get_api_data(self, include_secrets=False):
1157 1157 data = self.__json__()
1158 1158 if include_secrets:
1159 1159 return data
1160 1160 else:
1161 1161 data['auth_token'] = self.token_obfuscated
1162 1162 return data
1163 1163
1164 1164 @hybrid_property
1165 1165 def description_safe(self):
1166 1166 from rhodecode.lib import helpers as h
1167 1167 return h.escape(self.description)
1168 1168
1169 1169 @property
1170 1170 def expired(self):
1171 1171 if self.expires == -1:
1172 1172 return False
1173 1173 return time.time() > self.expires
1174 1174
1175 1175 @classmethod
1176 1176 def _get_role_name(cls, role):
1177 1177 return {
1178 1178 cls.ROLE_ALL: _('all'),
1179 1179 cls.ROLE_HTTP: _('http/web interface'),
1180 1180 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1181 1181 cls.ROLE_API: _('api calls'),
1182 1182 cls.ROLE_FEED: _('feed access'),
1183 1183 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1184 1184 }.get(role, role)
1185 1185
1186 1186 @property
1187 1187 def role_humanized(self):
1188 1188 return self._get_role_name(self.role)
1189 1189
1190 1190 def _get_scope(self):
1191 1191 if self.repo:
1192 1192 return 'Repository: {}'.format(self.repo.repo_name)
1193 1193 if self.repo_group:
1194 1194 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1195 1195 return 'Global'
1196 1196
1197 1197 @property
1198 1198 def scope_humanized(self):
1199 1199 return self._get_scope()
1200 1200
1201 1201 @property
1202 1202 def token_obfuscated(self):
1203 1203 if self.api_key:
1204 1204 return self.api_key[:4] + "****"
1205 1205
1206 1206
1207 1207 class UserEmailMap(Base, BaseModel):
1208 1208 __tablename__ = 'user_email_map'
1209 1209 __table_args__ = (
1210 1210 Index('uem_email_idx', 'email'),
1211 1211 UniqueConstraint('email'),
1212 1212 base_table_args
1213 1213 )
1214 1214 __mapper_args__ = {}
1215 1215
1216 1216 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1217 1217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1218 1218 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1219 1219 user = relationship('User', lazy='joined')
1220 1220
1221 1221 @validates('_email')
1222 1222 def validate_email(self, key, email):
1223 1223 # check if this email is not main one
1224 1224 main_email = Session().query(User).filter(User.email == email).scalar()
1225 1225 if main_email is not None:
1226 1226 raise AttributeError('email %s is present is user table' % email)
1227 1227 return email
1228 1228
1229 1229 @hybrid_property
1230 1230 def email(self):
1231 1231 return self._email
1232 1232
1233 1233 @email.setter
1234 1234 def email(self, val):
1235 1235 self._email = val.lower() if val else None
1236 1236
1237 1237
1238 1238 class UserIpMap(Base, BaseModel):
1239 1239 __tablename__ = 'user_ip_map'
1240 1240 __table_args__ = (
1241 1241 UniqueConstraint('user_id', 'ip_addr'),
1242 1242 base_table_args
1243 1243 )
1244 1244 __mapper_args__ = {}
1245 1245
1246 1246 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 1247 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 1248 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1249 1249 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1250 1250 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1251 1251 user = relationship('User', lazy='joined')
1252 1252
1253 1253 @hybrid_property
1254 1254 def description_safe(self):
1255 1255 from rhodecode.lib import helpers as h
1256 1256 return h.escape(self.description)
1257 1257
1258 1258 @classmethod
1259 1259 def _get_ip_range(cls, ip_addr):
1260 1260 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1261 1261 return [str(net.network_address), str(net.broadcast_address)]
1262 1262
1263 1263 def __json__(self):
1264 1264 return {
1265 1265 'ip_addr': self.ip_addr,
1266 1266 'ip_range': self._get_ip_range(self.ip_addr),
1267 1267 }
1268 1268
1269 1269 def __unicode__(self):
1270 1270 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1271 1271 self.user_id, self.ip_addr)
1272 1272
1273 1273
1274 1274 class UserSshKeys(Base, BaseModel):
1275 1275 __tablename__ = 'user_ssh_keys'
1276 1276 __table_args__ = (
1277 1277 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1278 1278
1279 1279 UniqueConstraint('ssh_key_fingerprint'),
1280 1280
1281 1281 base_table_args
1282 1282 )
1283 1283 __mapper_args__ = {}
1284 1284
1285 1285 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1286 1286 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1287 1287 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1288 1288
1289 1289 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1290 1290
1291 1291 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1292 1292 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1293 1293 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1294 1294
1295 1295 user = relationship('User', lazy='joined')
1296 1296
1297 1297 def __json__(self):
1298 1298 data = {
1299 1299 'ssh_fingerprint': self.ssh_key_fingerprint,
1300 1300 'description': self.description,
1301 1301 'created_on': self.created_on
1302 1302 }
1303 1303 return data
1304 1304
1305 1305 def get_api_data(self):
1306 1306 data = self.__json__()
1307 1307 return data
1308 1308
1309 1309
1310 1310 class UserLog(Base, BaseModel):
1311 1311 __tablename__ = 'user_logs'
1312 1312 __table_args__ = (
1313 1313 base_table_args,
1314 1314 )
1315 1315
1316 1316 VERSION_1 = 'v1'
1317 1317 VERSION_2 = 'v2'
1318 1318 VERSIONS = [VERSION_1, VERSION_2]
1319 1319
1320 1320 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 1321 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1322 1322 username = Column("username", String(255), nullable=True, unique=None, default=None)
1323 1323 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1324 1324 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1325 1325 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1326 1326 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1327 1327 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1328 1328
1329 1329 version = Column("version", String(255), nullable=True, default=VERSION_1)
1330 1330 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1331 1331 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1332 1332
1333 1333 def __unicode__(self):
1334 1334 return u"<%s('id:%s:%s')>" % (
1335 1335 self.__class__.__name__, self.repository_name, self.action)
1336 1336
1337 1337 def __json__(self):
1338 1338 return {
1339 1339 'user_id': self.user_id,
1340 1340 'username': self.username,
1341 1341 'repository_id': self.repository_id,
1342 1342 'repository_name': self.repository_name,
1343 1343 'user_ip': self.user_ip,
1344 1344 'action_date': self.action_date,
1345 1345 'action': self.action,
1346 1346 }
1347 1347
1348 1348 @hybrid_property
1349 1349 def entry_id(self):
1350 1350 return self.user_log_id
1351 1351
1352 1352 @property
1353 1353 def action_as_day(self):
1354 1354 return datetime.date(*self.action_date.timetuple()[:3])
1355 1355
1356 1356 user = relationship('User')
1357 1357 repository = relationship('Repository', cascade='')
1358 1358
1359 1359
1360 1360 class UserGroup(Base, BaseModel):
1361 1361 __tablename__ = 'users_groups'
1362 1362 __table_args__ = (
1363 1363 base_table_args,
1364 1364 )
1365 1365
1366 1366 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 1367 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1368 1368 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1369 1369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1370 1370 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1371 1371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1372 1372 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1373 1373 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1374 1374
1375 1375 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1376 1376 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1377 1377 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1378 1378 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1379 1379 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1380 1380 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1381 1381
1382 1382 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1383 1383 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1384 1384
1385 1385 @classmethod
1386 1386 def _load_group_data(cls, column):
1387 1387 if not column:
1388 1388 return {}
1389 1389
1390 1390 try:
1391 1391 return json.loads(column) or {}
1392 1392 except TypeError:
1393 1393 return {}
1394 1394
1395 1395 @hybrid_property
1396 1396 def description_safe(self):
1397 1397 from rhodecode.lib import helpers as h
1398 1398 return h.escape(self.user_group_description)
1399 1399
1400 1400 @hybrid_property
1401 1401 def group_data(self):
1402 1402 return self._load_group_data(self._group_data)
1403 1403
1404 1404 @group_data.expression
1405 1405 def group_data(self, **kwargs):
1406 1406 return self._group_data
1407 1407
1408 1408 @group_data.setter
1409 1409 def group_data(self, val):
1410 1410 try:
1411 1411 self._group_data = json.dumps(val)
1412 1412 except Exception:
1413 1413 log.error(traceback.format_exc())
1414 1414
1415 1415 @classmethod
1416 1416 def _load_sync(cls, group_data):
1417 1417 if group_data:
1418 1418 return group_data.get('extern_type')
1419 1419
1420 1420 @property
1421 1421 def sync(self):
1422 1422 return self._load_sync(self.group_data)
1423 1423
1424 1424 def __unicode__(self):
1425 1425 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1426 1426 self.users_group_id,
1427 1427 self.users_group_name)
1428 1428
1429 1429 @classmethod
1430 1430 def get_by_group_name(cls, group_name, cache=False,
1431 1431 case_insensitive=False):
1432 1432 if case_insensitive:
1433 1433 q = cls.query().filter(func.lower(cls.users_group_name) ==
1434 1434 func.lower(group_name))
1435 1435
1436 1436 else:
1437 1437 q = cls.query().filter(cls.users_group_name == group_name)
1438 1438 if cache:
1439 1439 q = q.options(
1440 1440 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1441 1441 return q.scalar()
1442 1442
1443 1443 @classmethod
1444 1444 def get(cls, user_group_id, cache=False):
1445 1445 if not user_group_id:
1446 1446 return
1447 1447
1448 1448 user_group = cls.query()
1449 1449 if cache:
1450 1450 user_group = user_group.options(
1451 1451 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1452 1452 return user_group.get(user_group_id)
1453 1453
1454 1454 def permissions(self, with_admins=True, with_owner=True,
1455 1455 expand_from_user_groups=False):
1456 1456 """
1457 1457 Permissions for user groups
1458 1458 """
1459 1459 _admin_perm = 'usergroup.admin'
1460 1460
1461 1461 owner_row = []
1462 1462 if with_owner:
1463 1463 usr = AttributeDict(self.user.get_dict())
1464 1464 usr.owner_row = True
1465 1465 usr.permission = _admin_perm
1466 1466 owner_row.append(usr)
1467 1467
1468 1468 super_admin_ids = []
1469 1469 super_admin_rows = []
1470 1470 if with_admins:
1471 1471 for usr in User.get_all_super_admins():
1472 1472 super_admin_ids.append(usr.user_id)
1473 1473 # if this admin is also owner, don't double the record
1474 1474 if usr.user_id == owner_row[0].user_id:
1475 1475 owner_row[0].admin_row = True
1476 1476 else:
1477 1477 usr = AttributeDict(usr.get_dict())
1478 1478 usr.admin_row = True
1479 1479 usr.permission = _admin_perm
1480 1480 super_admin_rows.append(usr)
1481 1481
1482 1482 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1483 1483 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1484 1484 joinedload(UserUserGroupToPerm.user),
1485 1485 joinedload(UserUserGroupToPerm.permission),)
1486 1486
1487 1487 # get owners and admins and permissions. We do a trick of re-writing
1488 1488 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1489 1489 # has a global reference and changing one object propagates to all
1490 1490 # others. This means if admin is also an owner admin_row that change
1491 1491 # would propagate to both objects
1492 1492 perm_rows = []
1493 1493 for _usr in q.all():
1494 1494 usr = AttributeDict(_usr.user.get_dict())
1495 1495 # if this user is also owner/admin, mark as duplicate record
1496 1496 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1497 1497 usr.duplicate_perm = True
1498 1498 usr.permission = _usr.permission.permission_name
1499 1499 perm_rows.append(usr)
1500 1500
1501 1501 # filter the perm rows by 'default' first and then sort them by
1502 1502 # admin,write,read,none permissions sorted again alphabetically in
1503 1503 # each group
1504 1504 perm_rows = sorted(perm_rows, key=display_user_sort)
1505 1505
1506 1506 user_groups_rows = []
1507 1507 if expand_from_user_groups:
1508 1508 for ug in self.permission_user_groups(with_members=True):
1509 1509 for user_data in ug.members:
1510 1510 user_groups_rows.append(user_data)
1511 1511
1512 1512 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1513 1513
1514 1514 def permission_user_groups(self, with_members=False):
1515 1515 q = UserGroupUserGroupToPerm.query()\
1516 1516 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1517 1517 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1518 1518 joinedload(UserGroupUserGroupToPerm.target_user_group),
1519 1519 joinedload(UserGroupUserGroupToPerm.permission),)
1520 1520
1521 1521 perm_rows = []
1522 1522 for _user_group in q.all():
1523 1523 entry = AttributeDict(_user_group.user_group.get_dict())
1524 1524 entry.permission = _user_group.permission.permission_name
1525 1525 if with_members:
1526 1526 entry.members = [x.user.get_dict()
1527 1527 for x in _user_group.user_group.members]
1528 1528 perm_rows.append(entry)
1529 1529
1530 1530 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1531 1531 return perm_rows
1532 1532
1533 1533 def _get_default_perms(self, user_group, suffix=''):
1534 1534 from rhodecode.model.permission import PermissionModel
1535 1535 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1536 1536
1537 1537 def get_default_perms(self, suffix=''):
1538 1538 return self._get_default_perms(self, suffix)
1539 1539
1540 1540 def get_api_data(self, with_group_members=True, include_secrets=False):
1541 1541 """
1542 1542 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1543 1543 basically forwarded.
1544 1544
1545 1545 """
1546 1546 user_group = self
1547 1547 data = {
1548 1548 'users_group_id': user_group.users_group_id,
1549 1549 'group_name': user_group.users_group_name,
1550 1550 'group_description': user_group.user_group_description,
1551 1551 'active': user_group.users_group_active,
1552 1552 'owner': user_group.user.username,
1553 1553 'sync': user_group.sync,
1554 1554 'owner_email': user_group.user.email,
1555 1555 }
1556 1556
1557 1557 if with_group_members:
1558 1558 users = []
1559 1559 for user in user_group.members:
1560 1560 user = user.user
1561 1561 users.append(user.get_api_data(include_secrets=include_secrets))
1562 1562 data['users'] = users
1563 1563
1564 1564 return data
1565 1565
1566 1566
1567 1567 class UserGroupMember(Base, BaseModel):
1568 1568 __tablename__ = 'users_groups_members'
1569 1569 __table_args__ = (
1570 1570 base_table_args,
1571 1571 )
1572 1572
1573 1573 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 1575 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1576 1576
1577 1577 user = relationship('User', lazy='joined')
1578 1578 users_group = relationship('UserGroup')
1579 1579
1580 1580 def __init__(self, gr_id='', u_id=''):
1581 1581 self.users_group_id = gr_id
1582 1582 self.user_id = u_id
1583 1583
1584 1584
1585 1585 class RepositoryField(Base, BaseModel):
1586 1586 __tablename__ = 'repositories_fields'
1587 1587 __table_args__ = (
1588 1588 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1589 1589 base_table_args,
1590 1590 )
1591 1591
1592 1592 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1593 1593
1594 1594 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1595 1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1596 1596 field_key = Column("field_key", String(250))
1597 1597 field_label = Column("field_label", String(1024), nullable=False)
1598 1598 field_value = Column("field_value", String(10000), nullable=False)
1599 1599 field_desc = Column("field_desc", String(1024), nullable=False)
1600 1600 field_type = Column("field_type", String(255), nullable=False, unique=None)
1601 1601 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1602 1602
1603 1603 repository = relationship('Repository')
1604 1604
1605 1605 @property
1606 1606 def field_key_prefixed(self):
1607 1607 return 'ex_%s' % self.field_key
1608 1608
1609 1609 @classmethod
1610 1610 def un_prefix_key(cls, key):
1611 1611 if key.startswith(cls.PREFIX):
1612 1612 return key[len(cls.PREFIX):]
1613 1613 return key
1614 1614
1615 1615 @classmethod
1616 1616 def get_by_key_name(cls, key, repo):
1617 1617 row = cls.query()\
1618 1618 .filter(cls.repository == repo)\
1619 1619 .filter(cls.field_key == key).scalar()
1620 1620 return row
1621 1621
1622 1622
1623 1623 class Repository(Base, BaseModel):
1624 1624 __tablename__ = 'repositories'
1625 1625 __table_args__ = (
1626 1626 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1627 1627 base_table_args,
1628 1628 )
1629 1629 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1630 1630 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1631 1631 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1632 1632
1633 1633 STATE_CREATED = 'repo_state_created'
1634 1634 STATE_PENDING = 'repo_state_pending'
1635 1635 STATE_ERROR = 'repo_state_error'
1636 1636
1637 1637 LOCK_AUTOMATIC = 'lock_auto'
1638 1638 LOCK_API = 'lock_api'
1639 1639 LOCK_WEB = 'lock_web'
1640 1640 LOCK_PULL = 'lock_pull'
1641 1641
1642 1642 NAME_SEP = URL_SEP
1643 1643
1644 1644 repo_id = Column(
1645 1645 "repo_id", Integer(), nullable=False, unique=True, default=None,
1646 1646 primary_key=True)
1647 1647 _repo_name = Column(
1648 1648 "repo_name", Text(), nullable=False, default=None)
1649 _repo_name_hash = Column(
1649 repo_name_hash = Column(
1650 1650 "repo_name_hash", String(255), nullable=False, unique=True)
1651 1651 repo_state = Column("repo_state", String(255), nullable=True)
1652 1652
1653 1653 clone_uri = Column(
1654 1654 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1655 1655 default=None)
1656 1656 push_uri = Column(
1657 1657 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1658 1658 default=None)
1659 1659 repo_type = Column(
1660 1660 "repo_type", String(255), nullable=False, unique=False, default=None)
1661 1661 user_id = Column(
1662 1662 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1663 1663 unique=False, default=None)
1664 1664 private = Column(
1665 1665 "private", Boolean(), nullable=True, unique=None, default=None)
1666 1666 archived = Column(
1667 1667 "archived", Boolean(), nullable=True, unique=None, default=None)
1668 1668 enable_statistics = Column(
1669 1669 "statistics", Boolean(), nullable=True, unique=None, default=True)
1670 1670 enable_downloads = Column(
1671 1671 "downloads", Boolean(), nullable=True, unique=None, default=True)
1672 1672 description = Column(
1673 1673 "description", String(10000), nullable=True, unique=None, default=None)
1674 1674 created_on = Column(
1675 1675 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1676 1676 default=datetime.datetime.now)
1677 1677 updated_on = Column(
1678 1678 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1679 1679 default=datetime.datetime.now)
1680 1680 _landing_revision = Column(
1681 1681 "landing_revision", String(255), nullable=False, unique=False,
1682 1682 default=None)
1683 1683 enable_locking = Column(
1684 1684 "enable_locking", Boolean(), nullable=False, unique=None,
1685 1685 default=False)
1686 1686 _locked = Column(
1687 1687 "locked", String(255), nullable=True, unique=False, default=None)
1688 1688 _changeset_cache = Column(
1689 1689 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1690 1690
1691 1691 fork_id = Column(
1692 1692 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1693 1693 nullable=True, unique=False, default=None)
1694 1694 group_id = Column(
1695 1695 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1696 1696 unique=False, default=None)
1697 1697
1698 1698 user = relationship('User', lazy='joined')
1699 1699 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1700 1700 group = relationship('RepoGroup', lazy='joined')
1701 1701 repo_to_perm = relationship(
1702 1702 'UserRepoToPerm', cascade='all',
1703 1703 order_by='UserRepoToPerm.repo_to_perm_id')
1704 1704 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1705 1705 stats = relationship('Statistics', cascade='all', uselist=False)
1706 1706
1707 1707 followers = relationship(
1708 1708 'UserFollowing',
1709 1709 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1710 1710 cascade='all')
1711 1711 extra_fields = relationship(
1712 1712 'RepositoryField', cascade="all, delete-orphan")
1713 1713 logs = relationship('UserLog')
1714 1714 comments = relationship(
1715 1715 'ChangesetComment', cascade="all, delete-orphan")
1716 1716 pull_requests_source = relationship(
1717 1717 'PullRequest',
1718 1718 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1719 1719 cascade="all, delete-orphan")
1720 1720 pull_requests_target = relationship(
1721 1721 'PullRequest',
1722 1722 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1723 1723 cascade="all, delete-orphan")
1724 1724 ui = relationship('RepoRhodeCodeUi', cascade="all")
1725 1725 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1726 1726 integrations = relationship('Integration', cascade="all, delete-orphan")
1727 1727
1728 1728 scoped_tokens = relationship('UserApiKeys', cascade="all")
1729 1729
1730 1730 # no cascade, set NULL
1731 1731 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1732 1732
1733 1733 def __unicode__(self):
1734 1734 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1735 1735 safe_unicode(self.repo_name))
1736 1736
1737 1737 @hybrid_property
1738 1738 def description_safe(self):
1739 1739 from rhodecode.lib import helpers as h
1740 1740 return h.escape(self.description)
1741 1741
1742 1742 @hybrid_property
1743 1743 def landing_rev(self):
1744 1744 # always should return [rev_type, rev]
1745 1745 if self._landing_revision:
1746 1746 _rev_info = self._landing_revision.split(':')
1747 1747 if len(_rev_info) < 2:
1748 1748 _rev_info.insert(0, 'rev')
1749 1749 return [_rev_info[0], _rev_info[1]]
1750 1750 return [None, None]
1751 1751
1752 1752 @landing_rev.setter
1753 1753 def landing_rev(self, val):
1754 1754 if ':' not in val:
1755 1755 raise ValueError('value must be delimited with `:` and consist '
1756 1756 'of <rev_type>:<rev>, got %s instead' % val)
1757 1757 self._landing_revision = val
1758 1758
1759 1759 @hybrid_property
1760 1760 def locked(self):
1761 1761 if self._locked:
1762 1762 user_id, timelocked, reason = self._locked.split(':')
1763 1763 lock_values = int(user_id), timelocked, reason
1764 1764 else:
1765 1765 lock_values = [None, None, None]
1766 1766 return lock_values
1767 1767
1768 1768 @locked.setter
1769 1769 def locked(self, val):
1770 1770 if val and isinstance(val, (list, tuple)):
1771 1771 self._locked = ':'.join(map(str, val))
1772 1772 else:
1773 1773 self._locked = None
1774 1774
1775 @hybrid_property
1776 def changeset_cache(self):
1775 @classmethod
1776 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1777 1777 from rhodecode.lib.vcs.backends.base import EmptyCommit
1778 1778 dummy = EmptyCommit().__json__()
1779 if not self._changeset_cache:
1780 dummy['source_repo_id'] = self.repo_id
1779 if not changeset_cache_raw:
1780 dummy['source_repo_id'] = repo_id
1781 1781 return json.loads(json.dumps(dummy))
1782 1782
1783 1783 try:
1784 return json.loads(self._changeset_cache)
1784 return json.loads(changeset_cache_raw)
1785 1785 except TypeError:
1786 1786 return dummy
1787 1787 except Exception:
1788 1788 log.error(traceback.format_exc())
1789 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 1795 @changeset_cache.setter
1792 1796 def changeset_cache(self, val):
1793 1797 try:
1794 1798 self._changeset_cache = json.dumps(val)
1795 1799 except Exception:
1796 1800 log.error(traceback.format_exc())
1797 1801
1798 1802 @hybrid_property
1799 1803 def repo_name(self):
1800 1804 return self._repo_name
1801 1805
1802 1806 @repo_name.setter
1803 1807 def repo_name(self, value):
1804 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 1811 @classmethod
1808 1812 def normalize_repo_name(cls, repo_name):
1809 1813 """
1810 1814 Normalizes os specific repo_name to the format internally stored inside
1811 1815 database using URL_SEP
1812 1816
1813 1817 :param cls:
1814 1818 :param repo_name:
1815 1819 """
1816 1820 return cls.NAME_SEP.join(repo_name.split(os.sep))
1817 1821
1818 1822 @classmethod
1819 1823 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1820 1824 session = Session()
1821 1825 q = session.query(cls).filter(cls.repo_name == repo_name)
1822 1826
1823 1827 if cache:
1824 1828 if identity_cache:
1825 1829 val = cls.identity_cache(session, 'repo_name', repo_name)
1826 1830 if val:
1827 1831 return val
1828 1832 else:
1829 1833 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1830 1834 q = q.options(
1831 1835 FromCache("sql_cache_short", cache_key))
1832 1836
1833 1837 return q.scalar()
1834 1838
1835 1839 @classmethod
1836 1840 def get_by_id_or_repo_name(cls, repoid):
1837 1841 if isinstance(repoid, (int, long)):
1838 1842 try:
1839 1843 repo = cls.get(repoid)
1840 1844 except ValueError:
1841 1845 repo = None
1842 1846 else:
1843 1847 repo = cls.get_by_repo_name(repoid)
1844 1848 return repo
1845 1849
1846 1850 @classmethod
1847 1851 def get_by_full_path(cls, repo_full_path):
1848 1852 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1849 1853 repo_name = cls.normalize_repo_name(repo_name)
1850 1854 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1851 1855
1852 1856 @classmethod
1853 1857 def get_repo_forks(cls, repo_id):
1854 1858 return cls.query().filter(Repository.fork_id == repo_id)
1855 1859
1856 1860 @classmethod
1857 1861 def base_path(cls):
1858 1862 """
1859 1863 Returns base path when all repos are stored
1860 1864
1861 1865 :param cls:
1862 1866 """
1863 1867 q = Session().query(RhodeCodeUi)\
1864 1868 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1865 1869 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1866 1870 return q.one().ui_value
1867 1871
1868 1872 @classmethod
1869 1873 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1870 1874 case_insensitive=True, archived=False):
1871 1875 q = Repository.query()
1872 1876
1873 1877 if not archived:
1874 1878 q = q.filter(Repository.archived.isnot(true()))
1875 1879
1876 1880 if not isinstance(user_id, Optional):
1877 1881 q = q.filter(Repository.user_id == user_id)
1878 1882
1879 1883 if not isinstance(group_id, Optional):
1880 1884 q = q.filter(Repository.group_id == group_id)
1881 1885
1882 1886 if case_insensitive:
1883 1887 q = q.order_by(func.lower(Repository.repo_name))
1884 1888 else:
1885 1889 q = q.order_by(Repository.repo_name)
1886 1890
1887 1891 return q.all()
1888 1892
1889 1893 @property
1890 1894 def repo_uid(self):
1891 1895 return '_{}'.format(self.repo_id)
1892 1896
1893 1897 @property
1894 1898 def forks(self):
1895 1899 """
1896 1900 Return forks of this repo
1897 1901 """
1898 1902 return Repository.get_repo_forks(self.repo_id)
1899 1903
1900 1904 @property
1901 1905 def parent(self):
1902 1906 """
1903 1907 Returns fork parent
1904 1908 """
1905 1909 return self.fork
1906 1910
1907 1911 @property
1908 1912 def just_name(self):
1909 1913 return self.repo_name.split(self.NAME_SEP)[-1]
1910 1914
1911 1915 @property
1912 1916 def groups_with_parents(self):
1913 1917 groups = []
1914 1918 if self.group is None:
1915 1919 return groups
1916 1920
1917 1921 cur_gr = self.group
1918 1922 groups.insert(0, cur_gr)
1919 1923 while 1:
1920 1924 gr = getattr(cur_gr, 'parent_group', None)
1921 1925 cur_gr = cur_gr.parent_group
1922 1926 if gr is None:
1923 1927 break
1924 1928 groups.insert(0, gr)
1925 1929
1926 1930 return groups
1927 1931
1928 1932 @property
1929 1933 def groups_and_repo(self):
1930 1934 return self.groups_with_parents, self
1931 1935
1932 1936 @LazyProperty
1933 1937 def repo_path(self):
1934 1938 """
1935 1939 Returns base full path for that repository means where it actually
1936 1940 exists on a filesystem
1937 1941 """
1938 1942 q = Session().query(RhodeCodeUi).filter(
1939 1943 RhodeCodeUi.ui_key == self.NAME_SEP)
1940 1944 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1941 1945 return q.one().ui_value
1942 1946
1943 1947 @property
1944 1948 def repo_full_path(self):
1945 1949 p = [self.repo_path]
1946 1950 # we need to split the name by / since this is how we store the
1947 1951 # names in the database, but that eventually needs to be converted
1948 1952 # into a valid system path
1949 1953 p += self.repo_name.split(self.NAME_SEP)
1950 1954 return os.path.join(*map(safe_unicode, p))
1951 1955
1952 1956 @property
1953 1957 def cache_keys(self):
1954 1958 """
1955 1959 Returns associated cache keys for that repo
1956 1960 """
1957 1961 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1958 1962 repo_id=self.repo_id)
1959 1963 return CacheKey.query()\
1960 1964 .filter(CacheKey.cache_args == invalidation_namespace)\
1961 1965 .order_by(CacheKey.cache_key)\
1962 1966 .all()
1963 1967
1964 1968 @property
1965 1969 def cached_diffs_relative_dir(self):
1966 1970 """
1967 1971 Return a relative to the repository store path of cached diffs
1968 1972 used for safe display for users, who shouldn't know the absolute store
1969 1973 path
1970 1974 """
1971 1975 return os.path.join(
1972 1976 os.path.dirname(self.repo_name),
1973 1977 self.cached_diffs_dir.split(os.path.sep)[-1])
1974 1978
1975 1979 @property
1976 1980 def cached_diffs_dir(self):
1977 1981 path = self.repo_full_path
1978 1982 return os.path.join(
1979 1983 os.path.dirname(path),
1980 1984 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1981 1985
1982 1986 def cached_diffs(self):
1983 1987 diff_cache_dir = self.cached_diffs_dir
1984 1988 if os.path.isdir(diff_cache_dir):
1985 1989 return os.listdir(diff_cache_dir)
1986 1990 return []
1987 1991
1988 1992 def shadow_repos(self):
1989 1993 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1990 1994 return [
1991 1995 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1992 1996 if x.startswith(shadow_repos_pattern)]
1993 1997
1994 1998 def get_new_name(self, repo_name):
1995 1999 """
1996 2000 returns new full repository name based on assigned group and new new
1997 2001
1998 2002 :param group_name:
1999 2003 """
2000 2004 path_prefix = self.group.full_path_splitted if self.group else []
2001 2005 return self.NAME_SEP.join(path_prefix + [repo_name])
2002 2006
2003 2007 @property
2004 2008 def _config(self):
2005 2009 """
2006 2010 Returns db based config object.
2007 2011 """
2008 2012 from rhodecode.lib.utils import make_db_config
2009 2013 return make_db_config(clear_session=False, repo=self)
2010 2014
2011 2015 def permissions(self, with_admins=True, with_owner=True,
2012 2016 expand_from_user_groups=False):
2013 2017 """
2014 2018 Permissions for repositories
2015 2019 """
2016 2020 _admin_perm = 'repository.admin'
2017 2021
2018 2022 owner_row = []
2019 2023 if with_owner:
2020 2024 usr = AttributeDict(self.user.get_dict())
2021 2025 usr.owner_row = True
2022 2026 usr.permission = _admin_perm
2023 2027 usr.permission_id = None
2024 2028 owner_row.append(usr)
2025 2029
2026 2030 super_admin_ids = []
2027 2031 super_admin_rows = []
2028 2032 if with_admins:
2029 2033 for usr in User.get_all_super_admins():
2030 2034 super_admin_ids.append(usr.user_id)
2031 2035 # if this admin is also owner, don't double the record
2032 2036 if usr.user_id == owner_row[0].user_id:
2033 2037 owner_row[0].admin_row = True
2034 2038 else:
2035 2039 usr = AttributeDict(usr.get_dict())
2036 2040 usr.admin_row = True
2037 2041 usr.permission = _admin_perm
2038 2042 usr.permission_id = None
2039 2043 super_admin_rows.append(usr)
2040 2044
2041 2045 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2042 2046 q = q.options(joinedload(UserRepoToPerm.repository),
2043 2047 joinedload(UserRepoToPerm.user),
2044 2048 joinedload(UserRepoToPerm.permission),)
2045 2049
2046 2050 # get owners and admins and permissions. We do a trick of re-writing
2047 2051 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2048 2052 # has a global reference and changing one object propagates to all
2049 2053 # others. This means if admin is also an owner admin_row that change
2050 2054 # would propagate to both objects
2051 2055 perm_rows = []
2052 2056 for _usr in q.all():
2053 2057 usr = AttributeDict(_usr.user.get_dict())
2054 2058 # if this user is also owner/admin, mark as duplicate record
2055 2059 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2056 2060 usr.duplicate_perm = True
2057 2061 # also check if this permission is maybe used by branch_permissions
2058 2062 if _usr.branch_perm_entry:
2059 2063 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2060 2064
2061 2065 usr.permission = _usr.permission.permission_name
2062 2066 usr.permission_id = _usr.repo_to_perm_id
2063 2067 perm_rows.append(usr)
2064 2068
2065 2069 # filter the perm rows by 'default' first and then sort them by
2066 2070 # admin,write,read,none permissions sorted again alphabetically in
2067 2071 # each group
2068 2072 perm_rows = sorted(perm_rows, key=display_user_sort)
2069 2073
2070 2074 user_groups_rows = []
2071 2075 if expand_from_user_groups:
2072 2076 for ug in self.permission_user_groups(with_members=True):
2073 2077 for user_data in ug.members:
2074 2078 user_groups_rows.append(user_data)
2075 2079
2076 2080 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2077 2081
2078 2082 def permission_user_groups(self, with_members=True):
2079 2083 q = UserGroupRepoToPerm.query()\
2080 2084 .filter(UserGroupRepoToPerm.repository == self)
2081 2085 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2082 2086 joinedload(UserGroupRepoToPerm.users_group),
2083 2087 joinedload(UserGroupRepoToPerm.permission),)
2084 2088
2085 2089 perm_rows = []
2086 2090 for _user_group in q.all():
2087 2091 entry = AttributeDict(_user_group.users_group.get_dict())
2088 2092 entry.permission = _user_group.permission.permission_name
2089 2093 if with_members:
2090 2094 entry.members = [x.user.get_dict()
2091 2095 for x in _user_group.users_group.members]
2092 2096 perm_rows.append(entry)
2093 2097
2094 2098 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2095 2099 return perm_rows
2096 2100
2097 2101 def get_api_data(self, include_secrets=False):
2098 2102 """
2099 2103 Common function for generating repo api data
2100 2104
2101 2105 :param include_secrets: See :meth:`User.get_api_data`.
2102 2106
2103 2107 """
2104 2108 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2105 2109 # move this methods on models level.
2106 2110 from rhodecode.model.settings import SettingsModel
2107 2111 from rhodecode.model.repo import RepoModel
2108 2112
2109 2113 repo = self
2110 2114 _user_id, _time, _reason = self.locked
2111 2115
2112 2116 data = {
2113 2117 'repo_id': repo.repo_id,
2114 2118 'repo_name': repo.repo_name,
2115 2119 'repo_type': repo.repo_type,
2116 2120 'clone_uri': repo.clone_uri or '',
2117 2121 'push_uri': repo.push_uri or '',
2118 2122 'url': RepoModel().get_url(self),
2119 2123 'private': repo.private,
2120 2124 'created_on': repo.created_on,
2121 2125 'description': repo.description_safe,
2122 2126 'landing_rev': repo.landing_rev,
2123 2127 'owner': repo.user.username,
2124 2128 'fork_of': repo.fork.repo_name if repo.fork else None,
2125 2129 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2126 2130 'enable_statistics': repo.enable_statistics,
2127 2131 'enable_locking': repo.enable_locking,
2128 2132 'enable_downloads': repo.enable_downloads,
2129 2133 'last_changeset': repo.changeset_cache,
2130 2134 'locked_by': User.get(_user_id).get_api_data(
2131 2135 include_secrets=include_secrets) if _user_id else None,
2132 2136 'locked_date': time_to_datetime(_time) if _time else None,
2133 2137 'lock_reason': _reason if _reason else None,
2134 2138 }
2135 2139
2136 2140 # TODO: mikhail: should be per-repo settings here
2137 2141 rc_config = SettingsModel().get_all_settings()
2138 2142 repository_fields = str2bool(
2139 2143 rc_config.get('rhodecode_repository_fields'))
2140 2144 if repository_fields:
2141 2145 for f in self.extra_fields:
2142 2146 data[f.field_key_prefixed] = f.field_value
2143 2147
2144 2148 return data
2145 2149
2146 2150 @classmethod
2147 2151 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2148 2152 if not lock_time:
2149 2153 lock_time = time.time()
2150 2154 if not lock_reason:
2151 2155 lock_reason = cls.LOCK_AUTOMATIC
2152 2156 repo.locked = [user_id, lock_time, lock_reason]
2153 2157 Session().add(repo)
2154 2158 Session().commit()
2155 2159
2156 2160 @classmethod
2157 2161 def unlock(cls, repo):
2158 2162 repo.locked = None
2159 2163 Session().add(repo)
2160 2164 Session().commit()
2161 2165
2162 2166 @classmethod
2163 2167 def getlock(cls, repo):
2164 2168 return repo.locked
2165 2169
2166 2170 def is_user_lock(self, user_id):
2167 2171 if self.lock[0]:
2168 2172 lock_user_id = safe_int(self.lock[0])
2169 2173 user_id = safe_int(user_id)
2170 2174 # both are ints, and they are equal
2171 2175 return all([lock_user_id, user_id]) and lock_user_id == user_id
2172 2176
2173 2177 return False
2174 2178
2175 2179 def get_locking_state(self, action, user_id, only_when_enabled=True):
2176 2180 """
2177 2181 Checks locking on this repository, if locking is enabled and lock is
2178 2182 present returns a tuple of make_lock, locked, locked_by.
2179 2183 make_lock can have 3 states None (do nothing) True, make lock
2180 2184 False release lock, This value is later propagated to hooks, which
2181 2185 do the locking. Think about this as signals passed to hooks what to do.
2182 2186
2183 2187 """
2184 2188 # TODO: johbo: This is part of the business logic and should be moved
2185 2189 # into the RepositoryModel.
2186 2190
2187 2191 if action not in ('push', 'pull'):
2188 2192 raise ValueError("Invalid action value: %s" % repr(action))
2189 2193
2190 2194 # defines if locked error should be thrown to user
2191 2195 currently_locked = False
2192 2196 # defines if new lock should be made, tri-state
2193 2197 make_lock = None
2194 2198 repo = self
2195 2199 user = User.get(user_id)
2196 2200
2197 2201 lock_info = repo.locked
2198 2202
2199 2203 if repo and (repo.enable_locking or not only_when_enabled):
2200 2204 if action == 'push':
2201 2205 # check if it's already locked !, if it is compare users
2202 2206 locked_by_user_id = lock_info[0]
2203 2207 if user.user_id == locked_by_user_id:
2204 2208 log.debug(
2205 2209 'Got `push` action from user %s, now unlocking', user)
2206 2210 # unlock if we have push from user who locked
2207 2211 make_lock = False
2208 2212 else:
2209 2213 # we're not the same user who locked, ban with
2210 2214 # code defined in settings (default is 423 HTTP Locked) !
2211 2215 log.debug('Repo %s is currently locked by %s', repo, user)
2212 2216 currently_locked = True
2213 2217 elif action == 'pull':
2214 2218 # [0] user [1] date
2215 2219 if lock_info[0] and lock_info[1]:
2216 2220 log.debug('Repo %s is currently locked by %s', repo, user)
2217 2221 currently_locked = True
2218 2222 else:
2219 2223 log.debug('Setting lock on repo %s by %s', repo, user)
2220 2224 make_lock = True
2221 2225
2222 2226 else:
2223 2227 log.debug('Repository %s do not have locking enabled', repo)
2224 2228
2225 2229 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2226 2230 make_lock, currently_locked, lock_info)
2227 2231
2228 2232 from rhodecode.lib.auth import HasRepoPermissionAny
2229 2233 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2230 2234 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2231 2235 # if we don't have at least write permission we cannot make a lock
2232 2236 log.debug('lock state reset back to FALSE due to lack '
2233 2237 'of at least read permission')
2234 2238 make_lock = False
2235 2239
2236 2240 return make_lock, currently_locked, lock_info
2237 2241
2238 2242 @property
2239 2243 def last_commit_cache_update_diff(self):
2240 2244 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2241 2245
2242 @property
2243 def last_commit_change(self):
2246 @classmethod
2247 def _load_commit_change(cls, last_commit_cache):
2244 2248 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2245 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 2251 try:
2248 2252 return parse_datetime(date_latest)
2249 2253 except Exception:
2250 2254 return empty_date
2251 2255
2252 2256 @property
2257 def last_commit_change(self):
2258 return self._load_commit_change(self.changeset_cache)
2259
2260 @property
2253 2261 def last_db_change(self):
2254 2262 return self.updated_on
2255 2263
2256 2264 @property
2257 2265 def clone_uri_hidden(self):
2258 2266 clone_uri = self.clone_uri
2259 2267 if clone_uri:
2260 2268 import urlobject
2261 2269 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2262 2270 if url_obj.password:
2263 2271 clone_uri = url_obj.with_password('*****')
2264 2272 return clone_uri
2265 2273
2266 2274 @property
2267 2275 def push_uri_hidden(self):
2268 2276 push_uri = self.push_uri
2269 2277 if push_uri:
2270 2278 import urlobject
2271 2279 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2272 2280 if url_obj.password:
2273 2281 push_uri = url_obj.with_password('*****')
2274 2282 return push_uri
2275 2283
2276 2284 def clone_url(self, **override):
2277 2285 from rhodecode.model.settings import SettingsModel
2278 2286
2279 2287 uri_tmpl = None
2280 2288 if 'with_id' in override:
2281 2289 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2282 2290 del override['with_id']
2283 2291
2284 2292 if 'uri_tmpl' in override:
2285 2293 uri_tmpl = override['uri_tmpl']
2286 2294 del override['uri_tmpl']
2287 2295
2288 2296 ssh = False
2289 2297 if 'ssh' in override:
2290 2298 ssh = True
2291 2299 del override['ssh']
2292 2300
2293 2301 # we didn't override our tmpl from **overrides
2294 2302 request = get_current_request()
2295 2303 if not uri_tmpl:
2296 2304 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2297 2305 rc_config = request.call_context.rc_config
2298 2306 else:
2299 2307 rc_config = SettingsModel().get_all_settings(cache=True)
2300 2308
2301 2309 if ssh:
2302 2310 uri_tmpl = rc_config.get(
2303 2311 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2304 2312
2305 2313 else:
2306 2314 uri_tmpl = rc_config.get(
2307 2315 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2308 2316
2309 2317 return get_clone_url(request=request,
2310 2318 uri_tmpl=uri_tmpl,
2311 2319 repo_name=self.repo_name,
2312 2320 repo_id=self.repo_id,
2313 2321 repo_type=self.repo_type,
2314 2322 **override)
2315 2323
2316 2324 def set_state(self, state):
2317 2325 self.repo_state = state
2318 2326 Session().add(self)
2319 2327 #==========================================================================
2320 2328 # SCM PROPERTIES
2321 2329 #==========================================================================
2322 2330
2323 2331 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2324 2332 return get_commit_safe(
2325 2333 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2326 2334
2327 2335 def get_changeset(self, rev=None, pre_load=None):
2328 2336 warnings.warn("Use get_commit", DeprecationWarning)
2329 2337 commit_id = None
2330 2338 commit_idx = None
2331 2339 if isinstance(rev, compat.string_types):
2332 2340 commit_id = rev
2333 2341 else:
2334 2342 commit_idx = rev
2335 2343 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2336 2344 pre_load=pre_load)
2337 2345
2338 2346 def get_landing_commit(self):
2339 2347 """
2340 2348 Returns landing commit, or if that doesn't exist returns the tip
2341 2349 """
2342 2350 _rev_type, _rev = self.landing_rev
2343 2351 commit = self.get_commit(_rev)
2344 2352 if isinstance(commit, EmptyCommit):
2345 2353 return self.get_commit()
2346 2354 return commit
2347 2355
2348 2356 def flush_commit_cache(self):
2349 2357 self.update_commit_cache(cs_cache={'raw_id':'0'})
2350 2358 self.update_commit_cache()
2351 2359
2352 2360 def update_commit_cache(self, cs_cache=None, config=None):
2353 2361 """
2354 2362 Update cache of last commit for repository, keys should be::
2355 2363
2356 2364 source_repo_id
2357 2365 short_id
2358 2366 raw_id
2359 2367 revision
2360 2368 parents
2361 2369 message
2362 2370 date
2363 2371 author
2364 2372 updated_on
2365 2373
2366 2374 """
2367 2375 from rhodecode.lib.vcs.backends.base import BaseChangeset
2368 2376 if cs_cache is None:
2369 2377 # use no-cache version here
2370 2378 scm_repo = self.scm_instance(cache=False, config=config)
2371 2379
2372 2380 empty = scm_repo is None or scm_repo.is_empty()
2373 2381 if not empty:
2374 2382 cs_cache = scm_repo.get_commit(
2375 2383 pre_load=["author", "date", "message", "parents", "branch"])
2376 2384 else:
2377 2385 cs_cache = EmptyCommit()
2378 2386
2379 2387 if isinstance(cs_cache, BaseChangeset):
2380 2388 cs_cache = cs_cache.__json__()
2381 2389
2382 2390 def is_outdated(new_cs_cache):
2383 2391 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2384 2392 new_cs_cache['revision'] != self.changeset_cache['revision']):
2385 2393 return True
2386 2394 return False
2387 2395
2388 2396 # check if we have maybe already latest cached revision
2389 2397 if is_outdated(cs_cache) or not self.changeset_cache:
2390 2398 _default = datetime.datetime.utcnow()
2391 2399 last_change = cs_cache.get('date') or _default
2392 2400 # we check if last update is newer than the new value
2393 2401 # if yes, we use the current timestamp instead. Imagine you get
2394 2402 # old commit pushed 1y ago, we'd set last update 1y to ago.
2395 2403 last_change_timestamp = datetime_to_time(last_change)
2396 2404 current_timestamp = datetime_to_time(last_change)
2397 2405 if last_change_timestamp > current_timestamp:
2398 2406 cs_cache['date'] = _default
2399 2407
2400 2408 cs_cache['updated_on'] = time.time()
2401 2409 self.changeset_cache = cs_cache
2402 2410 self.updated_on = last_change
2403 2411 Session().add(self)
2404 2412 Session().commit()
2405 2413
2406 2414 log.debug('updated repo `%s` with new commit cache %s',
2407 2415 self.repo_name, cs_cache)
2408 2416 else:
2409 2417 cs_cache = self.changeset_cache
2410 2418 cs_cache['updated_on'] = time.time()
2411 2419 self.changeset_cache = cs_cache
2412 2420 Session().add(self)
2413 2421 Session().commit()
2414 2422
2415 2423 log.debug('Skipping update_commit_cache for repo:`%s` '
2416 2424 'commit already with latest changes', self.repo_name)
2417 2425
2418 2426 @property
2419 2427 def tip(self):
2420 2428 return self.get_commit('tip')
2421 2429
2422 2430 @property
2423 2431 def author(self):
2424 2432 return self.tip.author
2425 2433
2426 2434 @property
2427 2435 def last_change(self):
2428 2436 return self.scm_instance().last_change
2429 2437
2430 2438 def get_comments(self, revisions=None):
2431 2439 """
2432 2440 Returns comments for this repository grouped by revisions
2433 2441
2434 2442 :param revisions: filter query by revisions only
2435 2443 """
2436 2444 cmts = ChangesetComment.query()\
2437 2445 .filter(ChangesetComment.repo == self)
2438 2446 if revisions:
2439 2447 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2440 2448 grouped = collections.defaultdict(list)
2441 2449 for cmt in cmts.all():
2442 2450 grouped[cmt.revision].append(cmt)
2443 2451 return grouped
2444 2452
2445 2453 def statuses(self, revisions=None):
2446 2454 """
2447 2455 Returns statuses for this repository
2448 2456
2449 2457 :param revisions: list of revisions to get statuses for
2450 2458 """
2451 2459 statuses = ChangesetStatus.query()\
2452 2460 .filter(ChangesetStatus.repo == self)\
2453 2461 .filter(ChangesetStatus.version == 0)
2454 2462
2455 2463 if revisions:
2456 2464 # Try doing the filtering in chunks to avoid hitting limits
2457 2465 size = 500
2458 2466 status_results = []
2459 2467 for chunk in xrange(0, len(revisions), size):
2460 2468 status_results += statuses.filter(
2461 2469 ChangesetStatus.revision.in_(
2462 2470 revisions[chunk: chunk+size])
2463 2471 ).all()
2464 2472 else:
2465 2473 status_results = statuses.all()
2466 2474
2467 2475 grouped = {}
2468 2476
2469 2477 # maybe we have open new pullrequest without a status?
2470 2478 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2471 2479 status_lbl = ChangesetStatus.get_status_lbl(stat)
2472 2480 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2473 2481 for rev in pr.revisions:
2474 2482 pr_id = pr.pull_request_id
2475 2483 pr_repo = pr.target_repo.repo_name
2476 2484 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2477 2485
2478 2486 for stat in status_results:
2479 2487 pr_id = pr_repo = None
2480 2488 if stat.pull_request:
2481 2489 pr_id = stat.pull_request.pull_request_id
2482 2490 pr_repo = stat.pull_request.target_repo.repo_name
2483 2491 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2484 2492 pr_id, pr_repo]
2485 2493 return grouped
2486 2494
2487 2495 # ==========================================================================
2488 2496 # SCM CACHE INSTANCE
2489 2497 # ==========================================================================
2490 2498
2491 2499 def scm_instance(self, **kwargs):
2492 2500 import rhodecode
2493 2501
2494 2502 # Passing a config will not hit the cache currently only used
2495 2503 # for repo2dbmapper
2496 2504 config = kwargs.pop('config', None)
2497 2505 cache = kwargs.pop('cache', None)
2498 2506 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2499 2507 if vcs_full_cache is not None:
2500 2508 # allows override global config
2501 2509 full_cache = vcs_full_cache
2502 2510 else:
2503 2511 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2504 2512 # if cache is NOT defined use default global, else we have a full
2505 2513 # control over cache behaviour
2506 2514 if cache is None and full_cache and not config:
2507 2515 log.debug('Initializing pure cached instance for %s', self.repo_path)
2508 2516 return self._get_instance_cached()
2509 2517
2510 2518 # cache here is sent to the "vcs server"
2511 2519 return self._get_instance(cache=bool(cache), config=config)
2512 2520
2513 2521 def _get_instance_cached(self):
2514 2522 from rhodecode.lib import rc_cache
2515 2523
2516 2524 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2517 2525 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2518 2526 repo_id=self.repo_id)
2519 2527 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2520 2528
2521 2529 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2522 2530 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2523 2531 return self._get_instance(repo_state_uid=_cache_state_uid)
2524 2532
2525 2533 # we must use thread scoped cache here,
2526 2534 # because each thread of gevent needs it's own not shared connection and cache
2527 2535 # we also alter `args` so the cache key is individual for every green thread.
2528 2536 inv_context_manager = rc_cache.InvalidationContext(
2529 2537 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2530 2538 thread_scoped=True)
2531 2539 with inv_context_manager as invalidation_context:
2532 2540 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2533 2541 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2534 2542
2535 2543 # re-compute and store cache if we get invalidate signal
2536 2544 if invalidation_context.should_invalidate():
2537 2545 instance = get_instance_cached.refresh(*args)
2538 2546 else:
2539 2547 instance = get_instance_cached(*args)
2540 2548
2541 2549 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2542 2550 return instance
2543 2551
2544 2552 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2545 2553 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2546 2554 self.repo_type, self.repo_path, cache)
2547 2555 config = config or self._config
2548 2556 custom_wire = {
2549 2557 'cache': cache, # controls the vcs.remote cache
2550 2558 'repo_state_uid': repo_state_uid
2551 2559 }
2552 2560 repo = get_vcs_instance(
2553 2561 repo_path=safe_str(self.repo_full_path),
2554 2562 config=config,
2555 2563 with_wire=custom_wire,
2556 2564 create=False,
2557 2565 _vcs_alias=self.repo_type)
2558 2566 if repo is not None:
2559 2567 repo.count() # cache rebuild
2560 2568 return repo
2561 2569
2562 2570 def get_shadow_repository_path(self, workspace_id):
2563 2571 from rhodecode.lib.vcs.backends.base import BaseRepository
2564 2572 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2565 2573 self.repo_full_path, self.repo_id, workspace_id)
2566 2574 return shadow_repo_path
2567 2575
2568 2576 def __json__(self):
2569 2577 return {'landing_rev': self.landing_rev}
2570 2578
2571 2579 def get_dict(self):
2572 2580
2573 2581 # Since we transformed `repo_name` to a hybrid property, we need to
2574 2582 # keep compatibility with the code which uses `repo_name` field.
2575 2583
2576 2584 result = super(Repository, self).get_dict()
2577 2585 result['repo_name'] = result.pop('_repo_name', None)
2578 2586 return result
2579 2587
2580 2588
2581 2589 class RepoGroup(Base, BaseModel):
2582 2590 __tablename__ = 'groups'
2583 2591 __table_args__ = (
2584 2592 UniqueConstraint('group_name', 'group_parent_id'),
2585 2593 base_table_args,
2586 2594 )
2587 2595 __mapper_args__ = {'order_by': 'group_name'}
2588 2596
2589 2597 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2590 2598
2591 2599 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2592 2600 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2593 2601 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2594 2602 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2595 2603 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2596 2604 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2597 2605 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2598 2606 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2599 2607 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2600 2608 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2601 _changeset_cache = Column(
2602 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2609 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2603 2610
2604 2611 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2605 2612 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2606 2613 parent_group = relationship('RepoGroup', remote_side=group_id)
2607 2614 user = relationship('User')
2608 2615 integrations = relationship('Integration', cascade="all, delete-orphan")
2609 2616
2610 2617 # no cascade, set NULL
2611 2618 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2612 2619
2613 2620 def __init__(self, group_name='', parent_group=None):
2614 2621 self.group_name = group_name
2615 2622 self.parent_group = parent_group
2616 2623
2617 2624 def __unicode__(self):
2618 2625 return u"<%s('id:%s:%s')>" % (
2619 2626 self.__class__.__name__, self.group_id, self.group_name)
2620 2627
2621 2628 @hybrid_property
2622 2629 def group_name(self):
2623 2630 return self._group_name
2624 2631
2625 2632 @group_name.setter
2626 2633 def group_name(self, value):
2627 2634 self._group_name = value
2628 2635 self.group_name_hash = self.hash_repo_group_name(value)
2629 2636
2630 @hybrid_property
2631 def changeset_cache(self):
2637 @classmethod
2638 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2632 2639 from rhodecode.lib.vcs.backends.base import EmptyCommit
2633 2640 dummy = EmptyCommit().__json__()
2634 if not self._changeset_cache:
2635 dummy['source_repo_id'] = ''
2641 if not changeset_cache_raw:
2642 dummy['source_repo_id'] = repo_id
2636 2643 return json.loads(json.dumps(dummy))
2637 2644
2638 2645 try:
2639 return json.loads(self._changeset_cache)
2646 return json.loads(changeset_cache_raw)
2640 2647 except TypeError:
2641 2648 return dummy
2642 2649 except Exception:
2643 2650 log.error(traceback.format_exc())
2644 2651 return dummy
2645 2652
2653 @hybrid_property
2654 def changeset_cache(self):
2655 return self._load_changeset_cache('', self._changeset_cache)
2656
2646 2657 @changeset_cache.setter
2647 2658 def changeset_cache(self, val):
2648 2659 try:
2649 2660 self._changeset_cache = json.dumps(val)
2650 2661 except Exception:
2651 2662 log.error(traceback.format_exc())
2652 2663
2653 2664 @validates('group_parent_id')
2654 2665 def validate_group_parent_id(self, key, val):
2655 2666 """
2656 2667 Check cycle references for a parent group to self
2657 2668 """
2658 2669 if self.group_id and val:
2659 2670 assert val != self.group_id
2660 2671
2661 2672 return val
2662 2673
2663 2674 @hybrid_property
2664 2675 def description_safe(self):
2665 2676 from rhodecode.lib import helpers as h
2666 2677 return h.escape(self.group_description)
2667 2678
2668 2679 @classmethod
2669 2680 def hash_repo_group_name(cls, repo_group_name):
2670 2681 val = remove_formatting(repo_group_name)
2671 2682 val = safe_str(val).lower()
2672 2683 chars = []
2673 2684 for c in val:
2674 2685 if c not in string.ascii_letters:
2675 2686 c = str(ord(c))
2676 2687 chars.append(c)
2677 2688
2678 2689 return ''.join(chars)
2679 2690
2680 2691 @classmethod
2681 2692 def _generate_choice(cls, repo_group):
2682 2693 from webhelpers2.html import literal as _literal
2683 2694 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2684 2695 return repo_group.group_id, _name(repo_group.full_path_splitted)
2685 2696
2686 2697 @classmethod
2687 2698 def groups_choices(cls, groups=None, show_empty_group=True):
2688 2699 if not groups:
2689 2700 groups = cls.query().all()
2690 2701
2691 2702 repo_groups = []
2692 2703 if show_empty_group:
2693 2704 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2694 2705
2695 2706 repo_groups.extend([cls._generate_choice(x) for x in groups])
2696 2707
2697 2708 repo_groups = sorted(
2698 2709 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2699 2710 return repo_groups
2700 2711
2701 2712 @classmethod
2702 2713 def url_sep(cls):
2703 2714 return URL_SEP
2704 2715
2705 2716 @classmethod
2706 2717 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2707 2718 if case_insensitive:
2708 2719 gr = cls.query().filter(func.lower(cls.group_name)
2709 2720 == func.lower(group_name))
2710 2721 else:
2711 2722 gr = cls.query().filter(cls.group_name == group_name)
2712 2723 if cache:
2713 2724 name_key = _hash_key(group_name)
2714 2725 gr = gr.options(
2715 2726 FromCache("sql_cache_short", "get_group_%s" % name_key))
2716 2727 return gr.scalar()
2717 2728
2718 2729 @classmethod
2719 2730 def get_user_personal_repo_group(cls, user_id):
2720 2731 user = User.get(user_id)
2721 2732 if user.username == User.DEFAULT_USER:
2722 2733 return None
2723 2734
2724 2735 return cls.query()\
2725 2736 .filter(cls.personal == true()) \
2726 2737 .filter(cls.user == user) \
2727 2738 .order_by(cls.group_id.asc()) \
2728 2739 .first()
2729 2740
2730 2741 @classmethod
2731 2742 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2732 2743 case_insensitive=True):
2733 2744 q = RepoGroup.query()
2734 2745
2735 2746 if not isinstance(user_id, Optional):
2736 2747 q = q.filter(RepoGroup.user_id == user_id)
2737 2748
2738 2749 if not isinstance(group_id, Optional):
2739 2750 q = q.filter(RepoGroup.group_parent_id == group_id)
2740 2751
2741 2752 if case_insensitive:
2742 2753 q = q.order_by(func.lower(RepoGroup.group_name))
2743 2754 else:
2744 2755 q = q.order_by(RepoGroup.group_name)
2745 2756 return q.all()
2746 2757
2747 2758 @property
2748 2759 def parents(self, parents_recursion_limit = 10):
2749 2760 groups = []
2750 2761 if self.parent_group is None:
2751 2762 return groups
2752 2763 cur_gr = self.parent_group
2753 2764 groups.insert(0, cur_gr)
2754 2765 cnt = 0
2755 2766 while 1:
2756 2767 cnt += 1
2757 2768 gr = getattr(cur_gr, 'parent_group', None)
2758 2769 cur_gr = cur_gr.parent_group
2759 2770 if gr is None:
2760 2771 break
2761 2772 if cnt == parents_recursion_limit:
2762 2773 # this will prevent accidental infinit loops
2763 2774 log.error('more than %s parents found for group %s, stopping '
2764 2775 'recursive parent fetching', parents_recursion_limit, self)
2765 2776 break
2766 2777
2767 2778 groups.insert(0, gr)
2768 2779 return groups
2769 2780
2770 2781 @property
2771 2782 def last_commit_cache_update_diff(self):
2772 2783 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2773 2784
2774 @property
2775 def last_commit_change(self):
2785 @classmethod
2786 def _load_commit_change(cls, last_commit_cache):
2776 2787 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2777 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 2790 try:
2780 2791 return parse_datetime(date_latest)
2781 2792 except Exception:
2782 2793 return empty_date
2783 2794
2784 2795 @property
2796 def last_commit_change(self):
2797 return self._load_commit_change(self.changeset_cache)
2798
2799 @property
2785 2800 def last_db_change(self):
2786 2801 return self.updated_on
2787 2802
2788 2803 @property
2789 2804 def children(self):
2790 2805 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2791 2806
2792 2807 @property
2793 2808 def name(self):
2794 2809 return self.group_name.split(RepoGroup.url_sep())[-1]
2795 2810
2796 2811 @property
2797 2812 def full_path(self):
2798 2813 return self.group_name
2799 2814
2800 2815 @property
2801 2816 def full_path_splitted(self):
2802 2817 return self.group_name.split(RepoGroup.url_sep())
2803 2818
2804 2819 @property
2805 2820 def repositories(self):
2806 2821 return Repository.query()\
2807 2822 .filter(Repository.group == self)\
2808 2823 .order_by(Repository.repo_name)
2809 2824
2810 2825 @property
2811 2826 def repositories_recursive_count(self):
2812 2827 cnt = self.repositories.count()
2813 2828
2814 2829 def children_count(group):
2815 2830 cnt = 0
2816 2831 for child in group.children:
2817 2832 cnt += child.repositories.count()
2818 2833 cnt += children_count(child)
2819 2834 return cnt
2820 2835
2821 2836 return cnt + children_count(self)
2822 2837
2823 2838 def _recursive_objects(self, include_repos=True, include_groups=True):
2824 2839 all_ = []
2825 2840
2826 2841 def _get_members(root_gr):
2827 2842 if include_repos:
2828 2843 for r in root_gr.repositories:
2829 2844 all_.append(r)
2830 2845 childs = root_gr.children.all()
2831 2846 if childs:
2832 2847 for gr in childs:
2833 2848 if include_groups:
2834 2849 all_.append(gr)
2835 2850 _get_members(gr)
2836 2851
2837 2852 root_group = []
2838 2853 if include_groups:
2839 2854 root_group = [self]
2840 2855
2841 2856 _get_members(self)
2842 2857 return root_group + all_
2843 2858
2844 2859 def recursive_groups_and_repos(self):
2845 2860 """
2846 2861 Recursive return all groups, with repositories in those groups
2847 2862 """
2848 2863 return self._recursive_objects()
2849 2864
2850 2865 def recursive_groups(self):
2851 2866 """
2852 2867 Returns all children groups for this group including children of children
2853 2868 """
2854 2869 return self._recursive_objects(include_repos=False)
2855 2870
2856 2871 def recursive_repos(self):
2857 2872 """
2858 2873 Returns all children repositories for this group
2859 2874 """
2860 2875 return self._recursive_objects(include_groups=False)
2861 2876
2862 2877 def get_new_name(self, group_name):
2863 2878 """
2864 2879 returns new full group name based on parent and new name
2865 2880
2866 2881 :param group_name:
2867 2882 """
2868 2883 path_prefix = (self.parent_group.full_path_splitted if
2869 2884 self.parent_group else [])
2870 2885 return RepoGroup.url_sep().join(path_prefix + [group_name])
2871 2886
2872 2887 def update_commit_cache(self, config=None):
2873 2888 """
2874 2889 Update cache of last changeset for newest repository inside this group, keys should be::
2875 2890
2876 2891 source_repo_id
2877 2892 short_id
2878 2893 raw_id
2879 2894 revision
2880 2895 parents
2881 2896 message
2882 2897 date
2883 2898 author
2884 2899
2885 2900 """
2886 2901 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2887 2902
2888 2903 def repo_groups_and_repos():
2889 2904 all_entries = OrderedDefaultDict(list)
2890 2905
2891 2906 def _get_members(root_gr, pos=0):
2892 2907
2893 2908 for repo in root_gr.repositories:
2894 2909 all_entries[root_gr].append(repo)
2895 2910
2896 2911 # fill in all parent positions
2897 2912 for parent_group in root_gr.parents:
2898 2913 all_entries[parent_group].extend(all_entries[root_gr])
2899 2914
2900 2915 children_groups = root_gr.children.all()
2901 2916 if children_groups:
2902 2917 for cnt, gr in enumerate(children_groups, 1):
2903 2918 _get_members(gr, pos=pos+cnt)
2904 2919
2905 2920 _get_members(root_gr=self)
2906 2921 return all_entries
2907 2922
2908 2923 empty_date = datetime.datetime.fromtimestamp(0)
2909 2924 for repo_group, repos in repo_groups_and_repos().items():
2910 2925
2911 2926 latest_repo_cs_cache = {}
2912 2927 _date_latest = empty_date
2913 2928 for repo in repos:
2914 2929 repo_cs_cache = repo.changeset_cache
2915 2930 date_latest = latest_repo_cs_cache.get('date', empty_date)
2916 2931 date_current = repo_cs_cache.get('date', empty_date)
2917 2932 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2918 2933 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2919 2934 latest_repo_cs_cache = repo_cs_cache
2920 2935 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2921 2936 _date_latest = parse_datetime(latest_repo_cs_cache['date'])
2922 2937
2923 2938 latest_repo_cs_cache['updated_on'] = time.time()
2924 2939 repo_group.changeset_cache = latest_repo_cs_cache
2925 2940 repo_group.updated_on = _date_latest
2926 2941 Session().add(repo_group)
2927 2942 Session().commit()
2928 2943
2929 2944 log.debug('updated repo group `%s` with new commit cache %s',
2930 2945 repo_group.group_name, latest_repo_cs_cache)
2931 2946
2932 2947 def permissions(self, with_admins=True, with_owner=True,
2933 2948 expand_from_user_groups=False):
2934 2949 """
2935 2950 Permissions for repository groups
2936 2951 """
2937 2952 _admin_perm = 'group.admin'
2938 2953
2939 2954 owner_row = []
2940 2955 if with_owner:
2941 2956 usr = AttributeDict(self.user.get_dict())
2942 2957 usr.owner_row = True
2943 2958 usr.permission = _admin_perm
2944 2959 owner_row.append(usr)
2945 2960
2946 2961 super_admin_ids = []
2947 2962 super_admin_rows = []
2948 2963 if with_admins:
2949 2964 for usr in User.get_all_super_admins():
2950 2965 super_admin_ids.append(usr.user_id)
2951 2966 # if this admin is also owner, don't double the record
2952 2967 if usr.user_id == owner_row[0].user_id:
2953 2968 owner_row[0].admin_row = True
2954 2969 else:
2955 2970 usr = AttributeDict(usr.get_dict())
2956 2971 usr.admin_row = True
2957 2972 usr.permission = _admin_perm
2958 2973 super_admin_rows.append(usr)
2959 2974
2960 2975 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2961 2976 q = q.options(joinedload(UserRepoGroupToPerm.group),
2962 2977 joinedload(UserRepoGroupToPerm.user),
2963 2978 joinedload(UserRepoGroupToPerm.permission),)
2964 2979
2965 2980 # get owners and admins and permissions. We do a trick of re-writing
2966 2981 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2967 2982 # has a global reference and changing one object propagates to all
2968 2983 # others. This means if admin is also an owner admin_row that change
2969 2984 # would propagate to both objects
2970 2985 perm_rows = []
2971 2986 for _usr in q.all():
2972 2987 usr = AttributeDict(_usr.user.get_dict())
2973 2988 # if this user is also owner/admin, mark as duplicate record
2974 2989 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2975 2990 usr.duplicate_perm = True
2976 2991 usr.permission = _usr.permission.permission_name
2977 2992 perm_rows.append(usr)
2978 2993
2979 2994 # filter the perm rows by 'default' first and then sort them by
2980 2995 # admin,write,read,none permissions sorted again alphabetically in
2981 2996 # each group
2982 2997 perm_rows = sorted(perm_rows, key=display_user_sort)
2983 2998
2984 2999 user_groups_rows = []
2985 3000 if expand_from_user_groups:
2986 3001 for ug in self.permission_user_groups(with_members=True):
2987 3002 for user_data in ug.members:
2988 3003 user_groups_rows.append(user_data)
2989 3004
2990 3005 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2991 3006
2992 3007 def permission_user_groups(self, with_members=False):
2993 3008 q = UserGroupRepoGroupToPerm.query()\
2994 3009 .filter(UserGroupRepoGroupToPerm.group == self)
2995 3010 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2996 3011 joinedload(UserGroupRepoGroupToPerm.users_group),
2997 3012 joinedload(UserGroupRepoGroupToPerm.permission),)
2998 3013
2999 3014 perm_rows = []
3000 3015 for _user_group in q.all():
3001 3016 entry = AttributeDict(_user_group.users_group.get_dict())
3002 3017 entry.permission = _user_group.permission.permission_name
3003 3018 if with_members:
3004 3019 entry.members = [x.user.get_dict()
3005 3020 for x in _user_group.users_group.members]
3006 3021 perm_rows.append(entry)
3007 3022
3008 3023 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3009 3024 return perm_rows
3010 3025
3011 3026 def get_api_data(self):
3012 3027 """
3013 3028 Common function for generating api data
3014 3029
3015 3030 """
3016 3031 group = self
3017 3032 data = {
3018 3033 'group_id': group.group_id,
3019 3034 'group_name': group.group_name,
3020 3035 'group_description': group.description_safe,
3021 3036 'parent_group': group.parent_group.group_name if group.parent_group else None,
3022 3037 'repositories': [x.repo_name for x in group.repositories],
3023 3038 'owner': group.user.username,
3024 3039 }
3025 3040 return data
3026 3041
3027 3042 def get_dict(self):
3028 3043 # Since we transformed `group_name` to a hybrid property, we need to
3029 3044 # keep compatibility with the code which uses `group_name` field.
3030 3045 result = super(RepoGroup, self).get_dict()
3031 3046 result['group_name'] = result.pop('_group_name', None)
3032 3047 return result
3033 3048
3034 3049
3035 3050 class Permission(Base, BaseModel):
3036 3051 __tablename__ = 'permissions'
3037 3052 __table_args__ = (
3038 3053 Index('p_perm_name_idx', 'permission_name'),
3039 3054 base_table_args,
3040 3055 )
3041 3056
3042 3057 PERMS = [
3043 3058 ('hg.admin', _('RhodeCode Super Administrator')),
3044 3059
3045 3060 ('repository.none', _('Repository no access')),
3046 3061 ('repository.read', _('Repository read access')),
3047 3062 ('repository.write', _('Repository write access')),
3048 3063 ('repository.admin', _('Repository admin access')),
3049 3064
3050 3065 ('group.none', _('Repository group no access')),
3051 3066 ('group.read', _('Repository group read access')),
3052 3067 ('group.write', _('Repository group write access')),
3053 3068 ('group.admin', _('Repository group admin access')),
3054 3069
3055 3070 ('usergroup.none', _('User group no access')),
3056 3071 ('usergroup.read', _('User group read access')),
3057 3072 ('usergroup.write', _('User group write access')),
3058 3073 ('usergroup.admin', _('User group admin access')),
3059 3074
3060 3075 ('branch.none', _('Branch no permissions')),
3061 3076 ('branch.merge', _('Branch access by web merge')),
3062 3077 ('branch.push', _('Branch access by push')),
3063 3078 ('branch.push_force', _('Branch access by push with force')),
3064 3079
3065 3080 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3066 3081 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3067 3082
3068 3083 ('hg.usergroup.create.false', _('User Group creation disabled')),
3069 3084 ('hg.usergroup.create.true', _('User Group creation enabled')),
3070 3085
3071 3086 ('hg.create.none', _('Repository creation disabled')),
3072 3087 ('hg.create.repository', _('Repository creation enabled')),
3073 3088 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3074 3089 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3075 3090
3076 3091 ('hg.fork.none', _('Repository forking disabled')),
3077 3092 ('hg.fork.repository', _('Repository forking enabled')),
3078 3093
3079 3094 ('hg.register.none', _('Registration disabled')),
3080 3095 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3081 3096 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3082 3097
3083 3098 ('hg.password_reset.enabled', _('Password reset enabled')),
3084 3099 ('hg.password_reset.hidden', _('Password reset hidden')),
3085 3100 ('hg.password_reset.disabled', _('Password reset disabled')),
3086 3101
3087 3102 ('hg.extern_activate.manual', _('Manual activation of external account')),
3088 3103 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3089 3104
3090 3105 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3091 3106 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3092 3107 ]
3093 3108
3094 3109 # definition of system default permissions for DEFAULT user, created on
3095 3110 # system setup
3096 3111 DEFAULT_USER_PERMISSIONS = [
3097 3112 # object perms
3098 3113 'repository.read',
3099 3114 'group.read',
3100 3115 'usergroup.read',
3101 3116 # branch, for backward compat we need same value as before so forced pushed
3102 3117 'branch.push_force',
3103 3118 # global
3104 3119 'hg.create.repository',
3105 3120 'hg.repogroup.create.false',
3106 3121 'hg.usergroup.create.false',
3107 3122 'hg.create.write_on_repogroup.true',
3108 3123 'hg.fork.repository',
3109 3124 'hg.register.manual_activate',
3110 3125 'hg.password_reset.enabled',
3111 3126 'hg.extern_activate.auto',
3112 3127 'hg.inherit_default_perms.true',
3113 3128 ]
3114 3129
3115 3130 # defines which permissions are more important higher the more important
3116 3131 # Weight defines which permissions are more important.
3117 3132 # The higher number the more important.
3118 3133 PERM_WEIGHTS = {
3119 3134 'repository.none': 0,
3120 3135 'repository.read': 1,
3121 3136 'repository.write': 3,
3122 3137 'repository.admin': 4,
3123 3138
3124 3139 'group.none': 0,
3125 3140 'group.read': 1,
3126 3141 'group.write': 3,
3127 3142 'group.admin': 4,
3128 3143
3129 3144 'usergroup.none': 0,
3130 3145 'usergroup.read': 1,
3131 3146 'usergroup.write': 3,
3132 3147 'usergroup.admin': 4,
3133 3148
3134 3149 'branch.none': 0,
3135 3150 'branch.merge': 1,
3136 3151 'branch.push': 3,
3137 3152 'branch.push_force': 4,
3138 3153
3139 3154 'hg.repogroup.create.false': 0,
3140 3155 'hg.repogroup.create.true': 1,
3141 3156
3142 3157 'hg.usergroup.create.false': 0,
3143 3158 'hg.usergroup.create.true': 1,
3144 3159
3145 3160 'hg.fork.none': 0,
3146 3161 'hg.fork.repository': 1,
3147 3162 'hg.create.none': 0,
3148 3163 'hg.create.repository': 1
3149 3164 }
3150 3165
3151 3166 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3152 3167 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3153 3168 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3154 3169
3155 3170 def __unicode__(self):
3156 3171 return u"<%s('%s:%s')>" % (
3157 3172 self.__class__.__name__, self.permission_id, self.permission_name
3158 3173 )
3159 3174
3160 3175 @classmethod
3161 3176 def get_by_key(cls, key):
3162 3177 return cls.query().filter(cls.permission_name == key).scalar()
3163 3178
3164 3179 @classmethod
3165 3180 def get_default_repo_perms(cls, user_id, repo_id=None):
3166 3181 q = Session().query(UserRepoToPerm, Repository, Permission)\
3167 3182 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3168 3183 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3169 3184 .filter(UserRepoToPerm.user_id == user_id)
3170 3185 if repo_id:
3171 3186 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3172 3187 return q.all()
3173 3188
3174 3189 @classmethod
3175 3190 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3176 3191 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3177 3192 .join(
3178 3193 Permission,
3179 3194 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3180 3195 .join(
3181 3196 UserRepoToPerm,
3182 3197 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3183 3198 .filter(UserRepoToPerm.user_id == user_id)
3184 3199
3185 3200 if repo_id:
3186 3201 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3187 3202 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3188 3203
3189 3204 @classmethod
3190 3205 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3191 3206 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3192 3207 .join(
3193 3208 Permission,
3194 3209 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3195 3210 .join(
3196 3211 Repository,
3197 3212 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3198 3213 .join(
3199 3214 UserGroup,
3200 3215 UserGroupRepoToPerm.users_group_id ==
3201 3216 UserGroup.users_group_id)\
3202 3217 .join(
3203 3218 UserGroupMember,
3204 3219 UserGroupRepoToPerm.users_group_id ==
3205 3220 UserGroupMember.users_group_id)\
3206 3221 .filter(
3207 3222 UserGroupMember.user_id == user_id,
3208 3223 UserGroup.users_group_active == true())
3209 3224 if repo_id:
3210 3225 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3211 3226 return q.all()
3212 3227
3213 3228 @classmethod
3214 3229 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3215 3230 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3216 3231 .join(
3217 3232 Permission,
3218 3233 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3219 3234 .join(
3220 3235 UserGroupRepoToPerm,
3221 3236 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3222 3237 .join(
3223 3238 UserGroup,
3224 3239 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3225 3240 .join(
3226 3241 UserGroupMember,
3227 3242 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3228 3243 .filter(
3229 3244 UserGroupMember.user_id == user_id,
3230 3245 UserGroup.users_group_active == true())
3231 3246
3232 3247 if repo_id:
3233 3248 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3234 3249 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3235 3250
3236 3251 @classmethod
3237 3252 def get_default_group_perms(cls, user_id, repo_group_id=None):
3238 3253 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3239 3254 .join(
3240 3255 Permission,
3241 3256 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3242 3257 .join(
3243 3258 RepoGroup,
3244 3259 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3245 3260 .filter(UserRepoGroupToPerm.user_id == user_id)
3246 3261 if repo_group_id:
3247 3262 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3248 3263 return q.all()
3249 3264
3250 3265 @classmethod
3251 3266 def get_default_group_perms_from_user_group(
3252 3267 cls, user_id, repo_group_id=None):
3253 3268 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3254 3269 .join(
3255 3270 Permission,
3256 3271 UserGroupRepoGroupToPerm.permission_id ==
3257 3272 Permission.permission_id)\
3258 3273 .join(
3259 3274 RepoGroup,
3260 3275 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3261 3276 .join(
3262 3277 UserGroup,
3263 3278 UserGroupRepoGroupToPerm.users_group_id ==
3264 3279 UserGroup.users_group_id)\
3265 3280 .join(
3266 3281 UserGroupMember,
3267 3282 UserGroupRepoGroupToPerm.users_group_id ==
3268 3283 UserGroupMember.users_group_id)\
3269 3284 .filter(
3270 3285 UserGroupMember.user_id == user_id,
3271 3286 UserGroup.users_group_active == true())
3272 3287 if repo_group_id:
3273 3288 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3274 3289 return q.all()
3275 3290
3276 3291 @classmethod
3277 3292 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3278 3293 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3279 3294 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3280 3295 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3281 3296 .filter(UserUserGroupToPerm.user_id == user_id)
3282 3297 if user_group_id:
3283 3298 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3284 3299 return q.all()
3285 3300
3286 3301 @classmethod
3287 3302 def get_default_user_group_perms_from_user_group(
3288 3303 cls, user_id, user_group_id=None):
3289 3304 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3290 3305 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3291 3306 .join(
3292 3307 Permission,
3293 3308 UserGroupUserGroupToPerm.permission_id ==
3294 3309 Permission.permission_id)\
3295 3310 .join(
3296 3311 TargetUserGroup,
3297 3312 UserGroupUserGroupToPerm.target_user_group_id ==
3298 3313 TargetUserGroup.users_group_id)\
3299 3314 .join(
3300 3315 UserGroup,
3301 3316 UserGroupUserGroupToPerm.user_group_id ==
3302 3317 UserGroup.users_group_id)\
3303 3318 .join(
3304 3319 UserGroupMember,
3305 3320 UserGroupUserGroupToPerm.user_group_id ==
3306 3321 UserGroupMember.users_group_id)\
3307 3322 .filter(
3308 3323 UserGroupMember.user_id == user_id,
3309 3324 UserGroup.users_group_active == true())
3310 3325 if user_group_id:
3311 3326 q = q.filter(
3312 3327 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3313 3328
3314 3329 return q.all()
3315 3330
3316 3331
3317 3332 class UserRepoToPerm(Base, BaseModel):
3318 3333 __tablename__ = 'repo_to_perm'
3319 3334 __table_args__ = (
3320 3335 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3321 3336 base_table_args
3322 3337 )
3323 3338
3324 3339 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 3340 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 3341 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327 3342 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3328 3343
3329 3344 user = relationship('User')
3330 3345 repository = relationship('Repository')
3331 3346 permission = relationship('Permission')
3332 3347
3333 3348 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3334 3349
3335 3350 @classmethod
3336 3351 def create(cls, user, repository, permission):
3337 3352 n = cls()
3338 3353 n.user = user
3339 3354 n.repository = repository
3340 3355 n.permission = permission
3341 3356 Session().add(n)
3342 3357 return n
3343 3358
3344 3359 def __unicode__(self):
3345 3360 return u'<%s => %s >' % (self.user, self.repository)
3346 3361
3347 3362
3348 3363 class UserUserGroupToPerm(Base, BaseModel):
3349 3364 __tablename__ = 'user_user_group_to_perm'
3350 3365 __table_args__ = (
3351 3366 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3352 3367 base_table_args
3353 3368 )
3354 3369
3355 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 3371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3357 3372 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3358 3373 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3359 3374
3360 3375 user = relationship('User')
3361 3376 user_group = relationship('UserGroup')
3362 3377 permission = relationship('Permission')
3363 3378
3364 3379 @classmethod
3365 3380 def create(cls, user, user_group, permission):
3366 3381 n = cls()
3367 3382 n.user = user
3368 3383 n.user_group = user_group
3369 3384 n.permission = permission
3370 3385 Session().add(n)
3371 3386 return n
3372 3387
3373 3388 def __unicode__(self):
3374 3389 return u'<%s => %s >' % (self.user, self.user_group)
3375 3390
3376 3391
3377 3392 class UserToPerm(Base, BaseModel):
3378 3393 __tablename__ = 'user_to_perm'
3379 3394 __table_args__ = (
3380 3395 UniqueConstraint('user_id', 'permission_id'),
3381 3396 base_table_args
3382 3397 )
3383 3398
3384 3399 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3385 3400 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3386 3401 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3387 3402
3388 3403 user = relationship('User')
3389 3404 permission = relationship('Permission', lazy='joined')
3390 3405
3391 3406 def __unicode__(self):
3392 3407 return u'<%s => %s >' % (self.user, self.permission)
3393 3408
3394 3409
3395 3410 class UserGroupRepoToPerm(Base, BaseModel):
3396 3411 __tablename__ = 'users_group_repo_to_perm'
3397 3412 __table_args__ = (
3398 3413 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3399 3414 base_table_args
3400 3415 )
3401 3416
3402 3417 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 3418 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 3419 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405 3420 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3406 3421
3407 3422 users_group = relationship('UserGroup')
3408 3423 permission = relationship('Permission')
3409 3424 repository = relationship('Repository')
3410 3425 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3411 3426
3412 3427 @classmethod
3413 3428 def create(cls, users_group, repository, permission):
3414 3429 n = cls()
3415 3430 n.users_group = users_group
3416 3431 n.repository = repository
3417 3432 n.permission = permission
3418 3433 Session().add(n)
3419 3434 return n
3420 3435
3421 3436 def __unicode__(self):
3422 3437 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3423 3438
3424 3439
3425 3440 class UserGroupUserGroupToPerm(Base, BaseModel):
3426 3441 __tablename__ = 'user_group_user_group_to_perm'
3427 3442 __table_args__ = (
3428 3443 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3429 3444 CheckConstraint('target_user_group_id != user_group_id'),
3430 3445 base_table_args
3431 3446 )
3432 3447
3433 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 3449 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3435 3450 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3436 3451 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3437 3452
3438 3453 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3439 3454 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3440 3455 permission = relationship('Permission')
3441 3456
3442 3457 @classmethod
3443 3458 def create(cls, target_user_group, user_group, permission):
3444 3459 n = cls()
3445 3460 n.target_user_group = target_user_group
3446 3461 n.user_group = user_group
3447 3462 n.permission = permission
3448 3463 Session().add(n)
3449 3464 return n
3450 3465
3451 3466 def __unicode__(self):
3452 3467 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3453 3468
3454 3469
3455 3470 class UserGroupToPerm(Base, BaseModel):
3456 3471 __tablename__ = 'users_group_to_perm'
3457 3472 __table_args__ = (
3458 3473 UniqueConstraint('users_group_id', 'permission_id',),
3459 3474 base_table_args
3460 3475 )
3461 3476
3462 3477 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3463 3478 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3464 3479 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3465 3480
3466 3481 users_group = relationship('UserGroup')
3467 3482 permission = relationship('Permission')
3468 3483
3469 3484
3470 3485 class UserRepoGroupToPerm(Base, BaseModel):
3471 3486 __tablename__ = 'user_repo_group_to_perm'
3472 3487 __table_args__ = (
3473 3488 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3474 3489 base_table_args
3475 3490 )
3476 3491
3477 3492 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3478 3493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3479 3494 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3480 3495 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3481 3496
3482 3497 user = relationship('User')
3483 3498 group = relationship('RepoGroup')
3484 3499 permission = relationship('Permission')
3485 3500
3486 3501 @classmethod
3487 3502 def create(cls, user, repository_group, permission):
3488 3503 n = cls()
3489 3504 n.user = user
3490 3505 n.group = repository_group
3491 3506 n.permission = permission
3492 3507 Session().add(n)
3493 3508 return n
3494 3509
3495 3510
3496 3511 class UserGroupRepoGroupToPerm(Base, BaseModel):
3497 3512 __tablename__ = 'users_group_repo_group_to_perm'
3498 3513 __table_args__ = (
3499 3514 UniqueConstraint('users_group_id', 'group_id'),
3500 3515 base_table_args
3501 3516 )
3502 3517
3503 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 3519 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 3520 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3506 3521 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507 3522
3508 3523 users_group = relationship('UserGroup')
3509 3524 permission = relationship('Permission')
3510 3525 group = relationship('RepoGroup')
3511 3526
3512 3527 @classmethod
3513 3528 def create(cls, user_group, repository_group, permission):
3514 3529 n = cls()
3515 3530 n.users_group = user_group
3516 3531 n.group = repository_group
3517 3532 n.permission = permission
3518 3533 Session().add(n)
3519 3534 return n
3520 3535
3521 3536 def __unicode__(self):
3522 3537 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3523 3538
3524 3539
3525 3540 class Statistics(Base, BaseModel):
3526 3541 __tablename__ = 'statistics'
3527 3542 __table_args__ = (
3528 3543 base_table_args
3529 3544 )
3530 3545
3531 3546 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3532 3547 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3533 3548 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3534 3549 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3535 3550 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3536 3551 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3537 3552
3538 3553 repository = relationship('Repository', single_parent=True)
3539 3554
3540 3555
3541 3556 class UserFollowing(Base, BaseModel):
3542 3557 __tablename__ = 'user_followings'
3543 3558 __table_args__ = (
3544 3559 UniqueConstraint('user_id', 'follows_repository_id'),
3545 3560 UniqueConstraint('user_id', 'follows_user_id'),
3546 3561 base_table_args
3547 3562 )
3548 3563
3549 3564 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3550 3565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3551 3566 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3552 3567 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3553 3568 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3554 3569
3555 3570 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3556 3571
3557 3572 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3558 3573 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3559 3574
3560 3575 @classmethod
3561 3576 def get_repo_followers(cls, repo_id):
3562 3577 return cls.query().filter(cls.follows_repo_id == repo_id)
3563 3578
3564 3579
3565 3580 class CacheKey(Base, BaseModel):
3566 3581 __tablename__ = 'cache_invalidation'
3567 3582 __table_args__ = (
3568 3583 UniqueConstraint('cache_key'),
3569 3584 Index('key_idx', 'cache_key'),
3570 3585 base_table_args,
3571 3586 )
3572 3587
3573 3588 CACHE_TYPE_FEED = 'FEED'
3574 3589
3575 3590 # namespaces used to register process/thread aware caches
3576 3591 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3577 3592 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3578 3593
3579 3594 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3580 3595 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3581 3596 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3582 3597 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3583 3598 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3584 3599
3585 3600 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3586 3601 self.cache_key = cache_key
3587 3602 self.cache_args = cache_args
3588 3603 self.cache_active = False
3589 3604 # first key should be same for all entries, since all workers should share it
3590 3605 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3591 3606
3592 3607 def __unicode__(self):
3593 3608 return u"<%s('%s:%s[%s]')>" % (
3594 3609 self.__class__.__name__,
3595 3610 self.cache_id, self.cache_key, self.cache_active)
3596 3611
3597 3612 def _cache_key_partition(self):
3598 3613 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3599 3614 return prefix, repo_name, suffix
3600 3615
3601 3616 def get_prefix(self):
3602 3617 """
3603 3618 Try to extract prefix from existing cache key. The key could consist
3604 3619 of prefix, repo_name, suffix
3605 3620 """
3606 3621 # this returns prefix, repo_name, suffix
3607 3622 return self._cache_key_partition()[0]
3608 3623
3609 3624 def get_suffix(self):
3610 3625 """
3611 3626 get suffix that might have been used in _get_cache_key to
3612 3627 generate self.cache_key. Only used for informational purposes
3613 3628 in repo_edit.mako.
3614 3629 """
3615 3630 # prefix, repo_name, suffix
3616 3631 return self._cache_key_partition()[2]
3617 3632
3618 3633 @classmethod
3619 3634 def generate_new_state_uid(cls, based_on=None):
3620 3635 if based_on:
3621 3636 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3622 3637 else:
3623 3638 return str(uuid.uuid4())
3624 3639
3625 3640 @classmethod
3626 3641 def delete_all_cache(cls):
3627 3642 """
3628 3643 Delete all cache keys from database.
3629 3644 Should only be run when all instances are down and all entries
3630 3645 thus stale.
3631 3646 """
3632 3647 cls.query().delete()
3633 3648 Session().commit()
3634 3649
3635 3650 @classmethod
3636 3651 def set_invalidate(cls, cache_uid, delete=False):
3637 3652 """
3638 3653 Mark all caches of a repo as invalid in the database.
3639 3654 """
3640 3655
3641 3656 try:
3642 3657 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3643 3658 if delete:
3644 3659 qry.delete()
3645 3660 log.debug('cache objects deleted for cache args %s',
3646 3661 safe_str(cache_uid))
3647 3662 else:
3648 3663 qry.update({"cache_active": False,
3649 3664 "cache_state_uid": cls.generate_new_state_uid()})
3650 3665 log.debug('cache objects marked as invalid for cache args %s',
3651 3666 safe_str(cache_uid))
3652 3667
3653 3668 Session().commit()
3654 3669 except Exception:
3655 3670 log.exception(
3656 3671 'Cache key invalidation failed for cache args %s',
3657 3672 safe_str(cache_uid))
3658 3673 Session().rollback()
3659 3674
3660 3675 @classmethod
3661 3676 def get_active_cache(cls, cache_key):
3662 3677 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3663 3678 if inv_obj:
3664 3679 return inv_obj
3665 3680 return None
3666 3681
3667 3682 @classmethod
3668 3683 def get_namespace_map(cls, namespace):
3669 3684 return {
3670 3685 x.cache_key: x
3671 3686 for x in cls.query().filter(cls.cache_args == namespace)}
3672 3687
3673 3688
3674 3689 class ChangesetComment(Base, BaseModel):
3675 3690 __tablename__ = 'changeset_comments'
3676 3691 __table_args__ = (
3677 3692 Index('cc_revision_idx', 'revision'),
3678 3693 base_table_args,
3679 3694 )
3680 3695
3681 3696 COMMENT_OUTDATED = u'comment_outdated'
3682 3697 COMMENT_TYPE_NOTE = u'note'
3683 3698 COMMENT_TYPE_TODO = u'todo'
3684 3699 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3685 3700
3686 3701 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3687 3702 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3688 3703 revision = Column('revision', String(40), nullable=True)
3689 3704 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3690 3705 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3691 3706 line_no = Column('line_no', Unicode(10), nullable=True)
3692 3707 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3693 3708 f_path = Column('f_path', Unicode(1000), nullable=True)
3694 3709 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3695 3710 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3696 3711 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3697 3712 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3698 3713 renderer = Column('renderer', Unicode(64), nullable=True)
3699 3714 display_state = Column('display_state', Unicode(128), nullable=True)
3700 3715
3701 3716 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3702 3717 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3703 3718
3704 3719 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3705 3720 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3706 3721
3707 3722 author = relationship('User', lazy='joined')
3708 3723 repo = relationship('Repository')
3709 3724 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3710 3725 pull_request = relationship('PullRequest', lazy='joined')
3711 3726 pull_request_version = relationship('PullRequestVersion')
3712 3727
3713 3728 @classmethod
3714 3729 def get_users(cls, revision=None, pull_request_id=None):
3715 3730 """
3716 3731 Returns user associated with this ChangesetComment. ie those
3717 3732 who actually commented
3718 3733
3719 3734 :param cls:
3720 3735 :param revision:
3721 3736 """
3722 3737 q = Session().query(User)\
3723 3738 .join(ChangesetComment.author)
3724 3739 if revision:
3725 3740 q = q.filter(cls.revision == revision)
3726 3741 elif pull_request_id:
3727 3742 q = q.filter(cls.pull_request_id == pull_request_id)
3728 3743 return q.all()
3729 3744
3730 3745 @classmethod
3731 3746 def get_index_from_version(cls, pr_version, versions):
3732 3747 num_versions = [x.pull_request_version_id for x in versions]
3733 3748 try:
3734 3749 return num_versions.index(pr_version) +1
3735 3750 except (IndexError, ValueError):
3736 3751 return
3737 3752
3738 3753 @property
3739 3754 def outdated(self):
3740 3755 return self.display_state == self.COMMENT_OUTDATED
3741 3756
3742 3757 def outdated_at_version(self, version):
3743 3758 """
3744 3759 Checks if comment is outdated for given pull request version
3745 3760 """
3746 3761 return self.outdated and self.pull_request_version_id != version
3747 3762
3748 3763 def older_than_version(self, version):
3749 3764 """
3750 3765 Checks if comment is made from previous version than given
3751 3766 """
3752 3767 if version is None:
3753 3768 return self.pull_request_version_id is not None
3754 3769
3755 3770 return self.pull_request_version_id < version
3756 3771
3757 3772 @property
3758 3773 def resolved(self):
3759 3774 return self.resolved_by[0] if self.resolved_by else None
3760 3775
3761 3776 @property
3762 3777 def is_todo(self):
3763 3778 return self.comment_type == self.COMMENT_TYPE_TODO
3764 3779
3765 3780 @property
3766 3781 def is_inline(self):
3767 3782 return self.line_no and self.f_path
3768 3783
3769 3784 def get_index_version(self, versions):
3770 3785 return self.get_index_from_version(
3771 3786 self.pull_request_version_id, versions)
3772 3787
3773 3788 def __repr__(self):
3774 3789 if self.comment_id:
3775 3790 return '<DB:Comment #%s>' % self.comment_id
3776 3791 else:
3777 3792 return '<DB:Comment at %#x>' % id(self)
3778 3793
3779 3794 def get_api_data(self):
3780 3795 comment = self
3781 3796 data = {
3782 3797 'comment_id': comment.comment_id,
3783 3798 'comment_type': comment.comment_type,
3784 3799 'comment_text': comment.text,
3785 3800 'comment_status': comment.status_change,
3786 3801 'comment_f_path': comment.f_path,
3787 3802 'comment_lineno': comment.line_no,
3788 3803 'comment_author': comment.author,
3789 3804 'comment_created_on': comment.created_on,
3790 3805 'comment_resolved_by': self.resolved
3791 3806 }
3792 3807 return data
3793 3808
3794 3809 def __json__(self):
3795 3810 data = dict()
3796 3811 data.update(self.get_api_data())
3797 3812 return data
3798 3813
3799 3814
3800 3815 class ChangesetStatus(Base, BaseModel):
3801 3816 __tablename__ = 'changeset_statuses'
3802 3817 __table_args__ = (
3803 3818 Index('cs_revision_idx', 'revision'),
3804 3819 Index('cs_version_idx', 'version'),
3805 3820 UniqueConstraint('repo_id', 'revision', 'version'),
3806 3821 base_table_args
3807 3822 )
3808 3823
3809 3824 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3810 3825 STATUS_APPROVED = 'approved'
3811 3826 STATUS_REJECTED = 'rejected'
3812 3827 STATUS_UNDER_REVIEW = 'under_review'
3813 3828
3814 3829 STATUSES = [
3815 3830 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3816 3831 (STATUS_APPROVED, _("Approved")),
3817 3832 (STATUS_REJECTED, _("Rejected")),
3818 3833 (STATUS_UNDER_REVIEW, _("Under Review")),
3819 3834 ]
3820 3835
3821 3836 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3822 3837 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3823 3838 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3824 3839 revision = Column('revision', String(40), nullable=False)
3825 3840 status = Column('status', String(128), nullable=False, default=DEFAULT)
3826 3841 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3827 3842 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3828 3843 version = Column('version', Integer(), nullable=False, default=0)
3829 3844 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3830 3845
3831 3846 author = relationship('User', lazy='joined')
3832 3847 repo = relationship('Repository')
3833 3848 comment = relationship('ChangesetComment', lazy='joined')
3834 3849 pull_request = relationship('PullRequest', lazy='joined')
3835 3850
3836 3851 def __unicode__(self):
3837 3852 return u"<%s('%s[v%s]:%s')>" % (
3838 3853 self.__class__.__name__,
3839 3854 self.status, self.version, self.author
3840 3855 )
3841 3856
3842 3857 @classmethod
3843 3858 def get_status_lbl(cls, value):
3844 3859 return dict(cls.STATUSES).get(value)
3845 3860
3846 3861 @property
3847 3862 def status_lbl(self):
3848 3863 return ChangesetStatus.get_status_lbl(self.status)
3849 3864
3850 3865 def get_api_data(self):
3851 3866 status = self
3852 3867 data = {
3853 3868 'status_id': status.changeset_status_id,
3854 3869 'status': status.status,
3855 3870 }
3856 3871 return data
3857 3872
3858 3873 def __json__(self):
3859 3874 data = dict()
3860 3875 data.update(self.get_api_data())
3861 3876 return data
3862 3877
3863 3878
3864 3879 class _SetState(object):
3865 3880 """
3866 3881 Context processor allowing changing state for sensitive operation such as
3867 3882 pull request update or merge
3868 3883 """
3869 3884
3870 3885 def __init__(self, pull_request, pr_state, back_state=None):
3871 3886 self._pr = pull_request
3872 3887 self._org_state = back_state or pull_request.pull_request_state
3873 3888 self._pr_state = pr_state
3874 3889 self._current_state = None
3875 3890
3876 3891 def __enter__(self):
3877 3892 log.debug('StateLock: entering set state context, setting state to: `%s`',
3878 3893 self._pr_state)
3879 3894 self.set_pr_state(self._pr_state)
3880 3895 return self
3881 3896
3882 3897 def __exit__(self, exc_type, exc_val, exc_tb):
3883 3898 if exc_val is not None:
3884 3899 log.error(traceback.format_exc(exc_tb))
3885 3900 return None
3886 3901
3887 3902 self.set_pr_state(self._org_state)
3888 3903 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3889 3904 self._org_state)
3890 3905 @property
3891 3906 def state(self):
3892 3907 return self._current_state
3893 3908
3894 3909 def set_pr_state(self, pr_state):
3895 3910 try:
3896 3911 self._pr.pull_request_state = pr_state
3897 3912 Session().add(self._pr)
3898 3913 Session().commit()
3899 3914 self._current_state = pr_state
3900 3915 except Exception:
3901 3916 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3902 3917 raise
3903 3918
3904 3919
3905 3920 class _PullRequestBase(BaseModel):
3906 3921 """
3907 3922 Common attributes of pull request and version entries.
3908 3923 """
3909 3924
3910 3925 # .status values
3911 3926 STATUS_NEW = u'new'
3912 3927 STATUS_OPEN = u'open'
3913 3928 STATUS_CLOSED = u'closed'
3914 3929
3915 3930 # available states
3916 3931 STATE_CREATING = u'creating'
3917 3932 STATE_UPDATING = u'updating'
3918 3933 STATE_MERGING = u'merging'
3919 3934 STATE_CREATED = u'created'
3920 3935
3921 3936 title = Column('title', Unicode(255), nullable=True)
3922 3937 description = Column(
3923 3938 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3924 3939 nullable=True)
3925 3940 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3926 3941
3927 3942 # new/open/closed status of pull request (not approve/reject/etc)
3928 3943 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3929 3944 created_on = Column(
3930 3945 'created_on', DateTime(timezone=False), nullable=False,
3931 3946 default=datetime.datetime.now)
3932 3947 updated_on = Column(
3933 3948 'updated_on', DateTime(timezone=False), nullable=False,
3934 3949 default=datetime.datetime.now)
3935 3950
3936 3951 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3937 3952
3938 3953 @declared_attr
3939 3954 def user_id(cls):
3940 3955 return Column(
3941 3956 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3942 3957 unique=None)
3943 3958
3944 3959 # 500 revisions max
3945 3960 _revisions = Column(
3946 3961 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3947 3962
3948 3963 @declared_attr
3949 3964 def source_repo_id(cls):
3950 3965 # TODO: dan: rename column to source_repo_id
3951 3966 return Column(
3952 3967 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3953 3968 nullable=False)
3954 3969
3955 3970 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3956 3971
3957 3972 @hybrid_property
3958 3973 def source_ref(self):
3959 3974 return self._source_ref
3960 3975
3961 3976 @source_ref.setter
3962 3977 def source_ref(self, val):
3963 3978 parts = (val or '').split(':')
3964 3979 if len(parts) != 3:
3965 3980 raise ValueError(
3966 3981 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3967 3982 self._source_ref = safe_unicode(val)
3968 3983
3969 3984 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3970 3985
3971 3986 @hybrid_property
3972 3987 def target_ref(self):
3973 3988 return self._target_ref
3974 3989
3975 3990 @target_ref.setter
3976 3991 def target_ref(self, val):
3977 3992 parts = (val or '').split(':')
3978 3993 if len(parts) != 3:
3979 3994 raise ValueError(
3980 3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3981 3996 self._target_ref = safe_unicode(val)
3982 3997
3983 3998 @declared_attr
3984 3999 def target_repo_id(cls):
3985 4000 # TODO: dan: rename column to target_repo_id
3986 4001 return Column(
3987 4002 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3988 4003 nullable=False)
3989 4004
3990 4005 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3991 4006
3992 4007 # TODO: dan: rename column to last_merge_source_rev
3993 4008 _last_merge_source_rev = Column(
3994 4009 'last_merge_org_rev', String(40), nullable=True)
3995 4010 # TODO: dan: rename column to last_merge_target_rev
3996 4011 _last_merge_target_rev = Column(
3997 4012 'last_merge_other_rev', String(40), nullable=True)
3998 4013 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3999 4014 merge_rev = Column('merge_rev', String(40), nullable=True)
4000 4015
4001 4016 reviewer_data = Column(
4002 4017 'reviewer_data_json', MutationObj.as_mutable(
4003 4018 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4004 4019
4005 4020 @property
4006 4021 def reviewer_data_json(self):
4007 4022 return json.dumps(self.reviewer_data)
4008 4023
4009 4024 @property
4010 4025 def work_in_progress(self):
4011 4026 """checks if pull request is work in progress by checking the title"""
4012 4027 title = self.title.upper()
4013 4028 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4014 4029 return True
4015 4030 return False
4016 4031
4017 4032 @hybrid_property
4018 4033 def description_safe(self):
4019 4034 from rhodecode.lib import helpers as h
4020 4035 return h.escape(self.description)
4021 4036
4022 4037 @hybrid_property
4023 4038 def revisions(self):
4024 4039 return self._revisions.split(':') if self._revisions else []
4025 4040
4026 4041 @revisions.setter
4027 4042 def revisions(self, val):
4028 4043 self._revisions = u':'.join(val)
4029 4044
4030 4045 @hybrid_property
4031 4046 def last_merge_status(self):
4032 4047 return safe_int(self._last_merge_status)
4033 4048
4034 4049 @last_merge_status.setter
4035 4050 def last_merge_status(self, val):
4036 4051 self._last_merge_status = val
4037 4052
4038 4053 @declared_attr
4039 4054 def author(cls):
4040 4055 return relationship('User', lazy='joined')
4041 4056
4042 4057 @declared_attr
4043 4058 def source_repo(cls):
4044 4059 return relationship(
4045 4060 'Repository',
4046 4061 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4047 4062
4048 4063 @property
4049 4064 def source_ref_parts(self):
4050 4065 return self.unicode_to_reference(self.source_ref)
4051 4066
4052 4067 @declared_attr
4053 4068 def target_repo(cls):
4054 4069 return relationship(
4055 4070 'Repository',
4056 4071 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4057 4072
4058 4073 @property
4059 4074 def target_ref_parts(self):
4060 4075 return self.unicode_to_reference(self.target_ref)
4061 4076
4062 4077 @property
4063 4078 def shadow_merge_ref(self):
4064 4079 return self.unicode_to_reference(self._shadow_merge_ref)
4065 4080
4066 4081 @shadow_merge_ref.setter
4067 4082 def shadow_merge_ref(self, ref):
4068 4083 self._shadow_merge_ref = self.reference_to_unicode(ref)
4069 4084
4070 4085 @staticmethod
4071 4086 def unicode_to_reference(raw):
4072 4087 """
4073 4088 Convert a unicode (or string) to a reference object.
4074 4089 If unicode evaluates to False it returns None.
4075 4090 """
4076 4091 if raw:
4077 4092 refs = raw.split(':')
4078 4093 return Reference(*refs)
4079 4094 else:
4080 4095 return None
4081 4096
4082 4097 @staticmethod
4083 4098 def reference_to_unicode(ref):
4084 4099 """
4085 4100 Convert a reference object to unicode.
4086 4101 If reference is None it returns None.
4087 4102 """
4088 4103 if ref:
4089 4104 return u':'.join(ref)
4090 4105 else:
4091 4106 return None
4092 4107
4093 4108 def get_api_data(self, with_merge_state=True):
4094 4109 from rhodecode.model.pull_request import PullRequestModel
4095 4110
4096 4111 pull_request = self
4097 4112 if with_merge_state:
4098 4113 merge_status = PullRequestModel().merge_status(pull_request)
4099 4114 merge_state = {
4100 4115 'status': merge_status[0],
4101 4116 'message': safe_unicode(merge_status[1]),
4102 4117 }
4103 4118 else:
4104 4119 merge_state = {'status': 'not_available',
4105 4120 'message': 'not_available'}
4106 4121
4107 4122 merge_data = {
4108 4123 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4109 4124 'reference': (
4110 4125 pull_request.shadow_merge_ref._asdict()
4111 4126 if pull_request.shadow_merge_ref else None),
4112 4127 }
4113 4128
4114 4129 data = {
4115 4130 'pull_request_id': pull_request.pull_request_id,
4116 4131 'url': PullRequestModel().get_url(pull_request),
4117 4132 'title': pull_request.title,
4118 4133 'description': pull_request.description,
4119 4134 'status': pull_request.status,
4120 4135 'state': pull_request.pull_request_state,
4121 4136 'created_on': pull_request.created_on,
4122 4137 'updated_on': pull_request.updated_on,
4123 4138 'commit_ids': pull_request.revisions,
4124 4139 'review_status': pull_request.calculated_review_status(),
4125 4140 'mergeable': merge_state,
4126 4141 'source': {
4127 4142 'clone_url': pull_request.source_repo.clone_url(),
4128 4143 'repository': pull_request.source_repo.repo_name,
4129 4144 'reference': {
4130 4145 'name': pull_request.source_ref_parts.name,
4131 4146 'type': pull_request.source_ref_parts.type,
4132 4147 'commit_id': pull_request.source_ref_parts.commit_id,
4133 4148 },
4134 4149 },
4135 4150 'target': {
4136 4151 'clone_url': pull_request.target_repo.clone_url(),
4137 4152 'repository': pull_request.target_repo.repo_name,
4138 4153 'reference': {
4139 4154 'name': pull_request.target_ref_parts.name,
4140 4155 'type': pull_request.target_ref_parts.type,
4141 4156 'commit_id': pull_request.target_ref_parts.commit_id,
4142 4157 },
4143 4158 },
4144 4159 'merge': merge_data,
4145 4160 'author': pull_request.author.get_api_data(include_secrets=False,
4146 4161 details='basic'),
4147 4162 'reviewers': [
4148 4163 {
4149 4164 'user': reviewer.get_api_data(include_secrets=False,
4150 4165 details='basic'),
4151 4166 'reasons': reasons,
4152 4167 'review_status': st[0][1].status if st else 'not_reviewed',
4153 4168 }
4154 4169 for obj, reviewer, reasons, mandatory, st in
4155 4170 pull_request.reviewers_statuses()
4156 4171 ]
4157 4172 }
4158 4173
4159 4174 return data
4160 4175
4161 4176 def set_state(self, pull_request_state, final_state=None):
4162 4177 """
4163 4178 # goes from initial state to updating to initial state.
4164 4179 # initial state can be changed by specifying back_state=
4165 4180 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4166 4181 pull_request.merge()
4167 4182
4168 4183 :param pull_request_state:
4169 4184 :param final_state:
4170 4185
4171 4186 """
4172 4187
4173 4188 return _SetState(self, pull_request_state, back_state=final_state)
4174 4189
4175 4190
4176 4191 class PullRequest(Base, _PullRequestBase):
4177 4192 __tablename__ = 'pull_requests'
4178 4193 __table_args__ = (
4179 4194 base_table_args,
4180 4195 )
4181 4196
4182 4197 pull_request_id = Column(
4183 4198 'pull_request_id', Integer(), nullable=False, primary_key=True)
4184 4199
4185 4200 def __repr__(self):
4186 4201 if self.pull_request_id:
4187 4202 return '<DB:PullRequest #%s>' % self.pull_request_id
4188 4203 else:
4189 4204 return '<DB:PullRequest at %#x>' % id(self)
4190 4205
4191 4206 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4192 4207 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4193 4208 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4194 4209 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4195 4210 lazy='dynamic')
4196 4211
4197 4212 @classmethod
4198 4213 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4199 4214 internal_methods=None):
4200 4215
4201 4216 class PullRequestDisplay(object):
4202 4217 """
4203 4218 Special object wrapper for showing PullRequest data via Versions
4204 4219 It mimics PR object as close as possible. This is read only object
4205 4220 just for display
4206 4221 """
4207 4222
4208 4223 def __init__(self, attrs, internal=None):
4209 4224 self.attrs = attrs
4210 4225 # internal have priority over the given ones via attrs
4211 4226 self.internal = internal or ['versions']
4212 4227
4213 4228 def __getattr__(self, item):
4214 4229 if item in self.internal:
4215 4230 return getattr(self, item)
4216 4231 try:
4217 4232 return self.attrs[item]
4218 4233 except KeyError:
4219 4234 raise AttributeError(
4220 4235 '%s object has no attribute %s' % (self, item))
4221 4236
4222 4237 def __repr__(self):
4223 4238 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4224 4239
4225 4240 def versions(self):
4226 4241 return pull_request_obj.versions.order_by(
4227 4242 PullRequestVersion.pull_request_version_id).all()
4228 4243
4229 4244 def is_closed(self):
4230 4245 return pull_request_obj.is_closed()
4231 4246
4232 4247 def is_state_changing(self):
4233 4248 return pull_request_obj.is_state_changing()
4234 4249
4235 4250 @property
4236 4251 def pull_request_version_id(self):
4237 4252 return getattr(pull_request_obj, 'pull_request_version_id', None)
4238 4253
4239 4254 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4240 4255
4241 4256 attrs.author = StrictAttributeDict(
4242 4257 pull_request_obj.author.get_api_data())
4243 4258 if pull_request_obj.target_repo:
4244 4259 attrs.target_repo = StrictAttributeDict(
4245 4260 pull_request_obj.target_repo.get_api_data())
4246 4261 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4247 4262
4248 4263 if pull_request_obj.source_repo:
4249 4264 attrs.source_repo = StrictAttributeDict(
4250 4265 pull_request_obj.source_repo.get_api_data())
4251 4266 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4252 4267
4253 4268 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4254 4269 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4255 4270 attrs.revisions = pull_request_obj.revisions
4256 4271
4257 4272 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4258 4273 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4259 4274 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4260 4275
4261 4276 return PullRequestDisplay(attrs, internal=internal_methods)
4262 4277
4263 4278 def is_closed(self):
4264 4279 return self.status == self.STATUS_CLOSED
4265 4280
4266 4281 def is_state_changing(self):
4267 4282 return self.pull_request_state != PullRequest.STATE_CREATED
4268 4283
4269 4284 def __json__(self):
4270 4285 return {
4271 4286 'revisions': self.revisions,
4272 4287 }
4273 4288
4274 4289 def calculated_review_status(self):
4275 4290 from rhodecode.model.changeset_status import ChangesetStatusModel
4276 4291 return ChangesetStatusModel().calculated_review_status(self)
4277 4292
4278 4293 def reviewers_statuses(self):
4279 4294 from rhodecode.model.changeset_status import ChangesetStatusModel
4280 4295 return ChangesetStatusModel().reviewers_statuses(self)
4281 4296
4282 4297 @property
4283 4298 def workspace_id(self):
4284 4299 from rhodecode.model.pull_request import PullRequestModel
4285 4300 return PullRequestModel()._workspace_id(self)
4286 4301
4287 4302 def get_shadow_repo(self):
4288 4303 workspace_id = self.workspace_id
4289 4304 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4290 4305 if os.path.isdir(shadow_repository_path):
4291 4306 vcs_obj = self.target_repo.scm_instance()
4292 4307 return vcs_obj.get_shadow_instance(shadow_repository_path)
4293 4308
4294 4309
4295 4310 class PullRequestVersion(Base, _PullRequestBase):
4296 4311 __tablename__ = 'pull_request_versions'
4297 4312 __table_args__ = (
4298 4313 base_table_args,
4299 4314 )
4300 4315
4301 4316 pull_request_version_id = Column(
4302 4317 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4303 4318 pull_request_id = Column(
4304 4319 'pull_request_id', Integer(),
4305 4320 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4306 4321 pull_request = relationship('PullRequest')
4307 4322
4308 4323 def __repr__(self):
4309 4324 if self.pull_request_version_id:
4310 4325 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4311 4326 else:
4312 4327 return '<DB:PullRequestVersion at %#x>' % id(self)
4313 4328
4314 4329 @property
4315 4330 def reviewers(self):
4316 4331 return self.pull_request.reviewers
4317 4332
4318 4333 @property
4319 4334 def versions(self):
4320 4335 return self.pull_request.versions
4321 4336
4322 4337 def is_closed(self):
4323 4338 # calculate from original
4324 4339 return self.pull_request.status == self.STATUS_CLOSED
4325 4340
4326 4341 def is_state_changing(self):
4327 4342 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4328 4343
4329 4344 def calculated_review_status(self):
4330 4345 return self.pull_request.calculated_review_status()
4331 4346
4332 4347 def reviewers_statuses(self):
4333 4348 return self.pull_request.reviewers_statuses()
4334 4349
4335 4350
4336 4351 class PullRequestReviewers(Base, BaseModel):
4337 4352 __tablename__ = 'pull_request_reviewers'
4338 4353 __table_args__ = (
4339 4354 base_table_args,
4340 4355 )
4341 4356
4342 4357 @hybrid_property
4343 4358 def reasons(self):
4344 4359 if not self._reasons:
4345 4360 return []
4346 4361 return self._reasons
4347 4362
4348 4363 @reasons.setter
4349 4364 def reasons(self, val):
4350 4365 val = val or []
4351 4366 if any(not isinstance(x, compat.string_types) for x in val):
4352 4367 raise Exception('invalid reasons type, must be list of strings')
4353 4368 self._reasons = val
4354 4369
4355 4370 pull_requests_reviewers_id = Column(
4356 4371 'pull_requests_reviewers_id', Integer(), nullable=False,
4357 4372 primary_key=True)
4358 4373 pull_request_id = Column(
4359 4374 "pull_request_id", Integer(),
4360 4375 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4361 4376 user_id = Column(
4362 4377 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4363 4378 _reasons = Column(
4364 4379 'reason', MutationList.as_mutable(
4365 4380 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4366 4381
4367 4382 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4368 4383 user = relationship('User')
4369 4384 pull_request = relationship('PullRequest')
4370 4385
4371 4386 rule_data = Column(
4372 4387 'rule_data_json',
4373 4388 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4374 4389
4375 4390 def rule_user_group_data(self):
4376 4391 """
4377 4392 Returns the voting user group rule data for this reviewer
4378 4393 """
4379 4394
4380 4395 if self.rule_data and 'vote_rule' in self.rule_data:
4381 4396 user_group_data = {}
4382 4397 if 'rule_user_group_entry_id' in self.rule_data:
4383 4398 # means a group with voting rules !
4384 4399 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4385 4400 user_group_data['name'] = self.rule_data['rule_name']
4386 4401 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4387 4402
4388 4403 return user_group_data
4389 4404
4390 4405 def __unicode__(self):
4391 4406 return u"<%s('id:%s')>" % (self.__class__.__name__,
4392 4407 self.pull_requests_reviewers_id)
4393 4408
4394 4409
4395 4410 class Notification(Base, BaseModel):
4396 4411 __tablename__ = 'notifications'
4397 4412 __table_args__ = (
4398 4413 Index('notification_type_idx', 'type'),
4399 4414 base_table_args,
4400 4415 )
4401 4416
4402 4417 TYPE_CHANGESET_COMMENT = u'cs_comment'
4403 4418 TYPE_MESSAGE = u'message'
4404 4419 TYPE_MENTION = u'mention'
4405 4420 TYPE_REGISTRATION = u'registration'
4406 4421 TYPE_PULL_REQUEST = u'pull_request'
4407 4422 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4408 4423 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4409 4424
4410 4425 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4411 4426 subject = Column('subject', Unicode(512), nullable=True)
4412 4427 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4413 4428 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4414 4429 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4415 4430 type_ = Column('type', Unicode(255))
4416 4431
4417 4432 created_by_user = relationship('User')
4418 4433 notifications_to_users = relationship('UserNotification', lazy='joined',
4419 4434 cascade="all, delete-orphan")
4420 4435
4421 4436 @property
4422 4437 def recipients(self):
4423 4438 return [x.user for x in UserNotification.query()\
4424 4439 .filter(UserNotification.notification == self)\
4425 4440 .order_by(UserNotification.user_id.asc()).all()]
4426 4441
4427 4442 @classmethod
4428 4443 def create(cls, created_by, subject, body, recipients, type_=None):
4429 4444 if type_ is None:
4430 4445 type_ = Notification.TYPE_MESSAGE
4431 4446
4432 4447 notification = cls()
4433 4448 notification.created_by_user = created_by
4434 4449 notification.subject = subject
4435 4450 notification.body = body
4436 4451 notification.type_ = type_
4437 4452 notification.created_on = datetime.datetime.now()
4438 4453
4439 4454 # For each recipient link the created notification to his account
4440 4455 for u in recipients:
4441 4456 assoc = UserNotification()
4442 4457 assoc.user_id = u.user_id
4443 4458 assoc.notification = notification
4444 4459
4445 4460 # if created_by is inside recipients mark his notification
4446 4461 # as read
4447 4462 if u.user_id == created_by.user_id:
4448 4463 assoc.read = True
4449 4464 Session().add(assoc)
4450 4465
4451 4466 Session().add(notification)
4452 4467
4453 4468 return notification
4454 4469
4455 4470
4456 4471 class UserNotification(Base, BaseModel):
4457 4472 __tablename__ = 'user_to_notification'
4458 4473 __table_args__ = (
4459 4474 UniqueConstraint('user_id', 'notification_id'),
4460 4475 base_table_args
4461 4476 )
4462 4477
4463 4478 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4464 4479 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4465 4480 read = Column('read', Boolean, default=False)
4466 4481 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4467 4482
4468 4483 user = relationship('User', lazy="joined")
4469 4484 notification = relationship('Notification', lazy="joined",
4470 4485 order_by=lambda: Notification.created_on.desc(),)
4471 4486
4472 4487 def mark_as_read(self):
4473 4488 self.read = True
4474 4489 Session().add(self)
4475 4490
4476 4491
4477 4492 class Gist(Base, BaseModel):
4478 4493 __tablename__ = 'gists'
4479 4494 __table_args__ = (
4480 4495 Index('g_gist_access_id_idx', 'gist_access_id'),
4481 4496 Index('g_created_on_idx', 'created_on'),
4482 4497 base_table_args
4483 4498 )
4484 4499
4485 4500 GIST_PUBLIC = u'public'
4486 4501 GIST_PRIVATE = u'private'
4487 4502 DEFAULT_FILENAME = u'gistfile1.txt'
4488 4503
4489 4504 ACL_LEVEL_PUBLIC = u'acl_public'
4490 4505 ACL_LEVEL_PRIVATE = u'acl_private'
4491 4506
4492 4507 gist_id = Column('gist_id', Integer(), primary_key=True)
4493 4508 gist_access_id = Column('gist_access_id', Unicode(250))
4494 4509 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4495 4510 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4496 4511 gist_expires = Column('gist_expires', Float(53), nullable=False)
4497 4512 gist_type = Column('gist_type', Unicode(128), nullable=False)
4498 4513 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4499 4514 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4500 4515 acl_level = Column('acl_level', Unicode(128), nullable=True)
4501 4516
4502 4517 owner = relationship('User')
4503 4518
4504 4519 def __repr__(self):
4505 4520 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4506 4521
4507 4522 @hybrid_property
4508 4523 def description_safe(self):
4509 4524 from rhodecode.lib import helpers as h
4510 4525 return h.escape(self.gist_description)
4511 4526
4512 4527 @classmethod
4513 4528 def get_or_404(cls, id_):
4514 4529 from pyramid.httpexceptions import HTTPNotFound
4515 4530
4516 4531 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4517 4532 if not res:
4518 4533 raise HTTPNotFound()
4519 4534 return res
4520 4535
4521 4536 @classmethod
4522 4537 def get_by_access_id(cls, gist_access_id):
4523 4538 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4524 4539
4525 4540 def gist_url(self):
4526 4541 from rhodecode.model.gist import GistModel
4527 4542 return GistModel().get_url(self)
4528 4543
4529 4544 @classmethod
4530 4545 def base_path(cls):
4531 4546 """
4532 4547 Returns base path when all gists are stored
4533 4548
4534 4549 :param cls:
4535 4550 """
4536 4551 from rhodecode.model.gist import GIST_STORE_LOC
4537 4552 q = Session().query(RhodeCodeUi)\
4538 4553 .filter(RhodeCodeUi.ui_key == URL_SEP)
4539 4554 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4540 4555 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4541 4556
4542 4557 def get_api_data(self):
4543 4558 """
4544 4559 Common function for generating gist related data for API
4545 4560 """
4546 4561 gist = self
4547 4562 data = {
4548 4563 'gist_id': gist.gist_id,
4549 4564 'type': gist.gist_type,
4550 4565 'access_id': gist.gist_access_id,
4551 4566 'description': gist.gist_description,
4552 4567 'url': gist.gist_url(),
4553 4568 'expires': gist.gist_expires,
4554 4569 'created_on': gist.created_on,
4555 4570 'modified_at': gist.modified_at,
4556 4571 'content': None,
4557 4572 'acl_level': gist.acl_level,
4558 4573 }
4559 4574 return data
4560 4575
4561 4576 def __json__(self):
4562 4577 data = dict(
4563 4578 )
4564 4579 data.update(self.get_api_data())
4565 4580 return data
4566 4581 # SCM functions
4567 4582
4568 4583 def scm_instance(self, **kwargs):
4569 4584 """
4570 4585 Get an instance of VCS Repository
4571 4586
4572 4587 :param kwargs:
4573 4588 """
4574 4589 from rhodecode.model.gist import GistModel
4575 4590 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4576 4591 return get_vcs_instance(
4577 4592 repo_path=safe_str(full_repo_path), create=False,
4578 4593 _vcs_alias=GistModel.vcs_backend)
4579 4594
4580 4595
4581 4596 class ExternalIdentity(Base, BaseModel):
4582 4597 __tablename__ = 'external_identities'
4583 4598 __table_args__ = (
4584 4599 Index('local_user_id_idx', 'local_user_id'),
4585 4600 Index('external_id_idx', 'external_id'),
4586 4601 base_table_args
4587 4602 )
4588 4603
4589 4604 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4590 4605 external_username = Column('external_username', Unicode(1024), default=u'')
4591 4606 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4592 4607 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4593 4608 access_token = Column('access_token', String(1024), default=u'')
4594 4609 alt_token = Column('alt_token', String(1024), default=u'')
4595 4610 token_secret = Column('token_secret', String(1024), default=u'')
4596 4611
4597 4612 @classmethod
4598 4613 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4599 4614 """
4600 4615 Returns ExternalIdentity instance based on search params
4601 4616
4602 4617 :param external_id:
4603 4618 :param provider_name:
4604 4619 :return: ExternalIdentity
4605 4620 """
4606 4621 query = cls.query()
4607 4622 query = query.filter(cls.external_id == external_id)
4608 4623 query = query.filter(cls.provider_name == provider_name)
4609 4624 if local_user_id:
4610 4625 query = query.filter(cls.local_user_id == local_user_id)
4611 4626 return query.first()
4612 4627
4613 4628 @classmethod
4614 4629 def user_by_external_id_and_provider(cls, external_id, provider_name):
4615 4630 """
4616 4631 Returns User instance based on search params
4617 4632
4618 4633 :param external_id:
4619 4634 :param provider_name:
4620 4635 :return: User
4621 4636 """
4622 4637 query = User.query()
4623 4638 query = query.filter(cls.external_id == external_id)
4624 4639 query = query.filter(cls.provider_name == provider_name)
4625 4640 query = query.filter(User.user_id == cls.local_user_id)
4626 4641 return query.first()
4627 4642
4628 4643 @classmethod
4629 4644 def by_local_user_id(cls, local_user_id):
4630 4645 """
4631 4646 Returns all tokens for user
4632 4647
4633 4648 :param local_user_id:
4634 4649 :return: ExternalIdentity
4635 4650 """
4636 4651 query = cls.query()
4637 4652 query = query.filter(cls.local_user_id == local_user_id)
4638 4653 return query
4639 4654
4640 4655 @classmethod
4641 4656 def load_provider_plugin(cls, plugin_id):
4642 4657 from rhodecode.authentication.base import loadplugin
4643 4658 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4644 4659 auth_plugin = loadplugin(_plugin_id)
4645 4660 return auth_plugin
4646 4661
4647 4662
4648 4663 class Integration(Base, BaseModel):
4649 4664 __tablename__ = 'integrations'
4650 4665 __table_args__ = (
4651 4666 base_table_args
4652 4667 )
4653 4668
4654 4669 integration_id = Column('integration_id', Integer(), primary_key=True)
4655 4670 integration_type = Column('integration_type', String(255))
4656 4671 enabled = Column('enabled', Boolean(), nullable=False)
4657 4672 name = Column('name', String(255), nullable=False)
4658 4673 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4659 4674 default=False)
4660 4675
4661 4676 settings = Column(
4662 4677 'settings_json', MutationObj.as_mutable(
4663 4678 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4664 4679 repo_id = Column(
4665 4680 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4666 4681 nullable=True, unique=None, default=None)
4667 4682 repo = relationship('Repository', lazy='joined')
4668 4683
4669 4684 repo_group_id = Column(
4670 4685 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4671 4686 nullable=True, unique=None, default=None)
4672 4687 repo_group = relationship('RepoGroup', lazy='joined')
4673 4688
4674 4689 @property
4675 4690 def scope(self):
4676 4691 if self.repo:
4677 4692 return repr(self.repo)
4678 4693 if self.repo_group:
4679 4694 if self.child_repos_only:
4680 4695 return repr(self.repo_group) + ' (child repos only)'
4681 4696 else:
4682 4697 return repr(self.repo_group) + ' (recursive)'
4683 4698 if self.child_repos_only:
4684 4699 return 'root_repos'
4685 4700 return 'global'
4686 4701
4687 4702 def __repr__(self):
4688 4703 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4689 4704
4690 4705
4691 4706 class RepoReviewRuleUser(Base, BaseModel):
4692 4707 __tablename__ = 'repo_review_rules_users'
4693 4708 __table_args__ = (
4694 4709 base_table_args
4695 4710 )
4696 4711
4697 4712 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4698 4713 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4699 4714 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4700 4715 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4701 4716 user = relationship('User')
4702 4717
4703 4718 def rule_data(self):
4704 4719 return {
4705 4720 'mandatory': self.mandatory
4706 4721 }
4707 4722
4708 4723
4709 4724 class RepoReviewRuleUserGroup(Base, BaseModel):
4710 4725 __tablename__ = 'repo_review_rules_users_groups'
4711 4726 __table_args__ = (
4712 4727 base_table_args
4713 4728 )
4714 4729
4715 4730 VOTE_RULE_ALL = -1
4716 4731
4717 4732 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4718 4733 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4719 4734 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4720 4735 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4721 4736 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4722 4737 users_group = relationship('UserGroup')
4723 4738
4724 4739 def rule_data(self):
4725 4740 return {
4726 4741 'mandatory': self.mandatory,
4727 4742 'vote_rule': self.vote_rule
4728 4743 }
4729 4744
4730 4745 @property
4731 4746 def vote_rule_label(self):
4732 4747 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4733 4748 return 'all must vote'
4734 4749 else:
4735 4750 return 'min. vote {}'.format(self.vote_rule)
4736 4751
4737 4752
4738 4753 class RepoReviewRule(Base, BaseModel):
4739 4754 __tablename__ = 'repo_review_rules'
4740 4755 __table_args__ = (
4741 4756 base_table_args
4742 4757 )
4743 4758
4744 4759 repo_review_rule_id = Column(
4745 4760 'repo_review_rule_id', Integer(), primary_key=True)
4746 4761 repo_id = Column(
4747 4762 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4748 4763 repo = relationship('Repository', backref='review_rules')
4749 4764
4750 4765 review_rule_name = Column('review_rule_name', String(255))
4751 4766 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4752 4767 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4753 4768 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4754 4769
4755 4770 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4756 4771 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4757 4772 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4758 4773 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4759 4774
4760 4775 rule_users = relationship('RepoReviewRuleUser')
4761 4776 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4762 4777
4763 4778 def _validate_pattern(self, value):
4764 4779 re.compile('^' + glob2re(value) + '$')
4765 4780
4766 4781 @hybrid_property
4767 4782 def source_branch_pattern(self):
4768 4783 return self._branch_pattern or '*'
4769 4784
4770 4785 @source_branch_pattern.setter
4771 4786 def source_branch_pattern(self, value):
4772 4787 self._validate_pattern(value)
4773 4788 self._branch_pattern = value or '*'
4774 4789
4775 4790 @hybrid_property
4776 4791 def target_branch_pattern(self):
4777 4792 return self._target_branch_pattern or '*'
4778 4793
4779 4794 @target_branch_pattern.setter
4780 4795 def target_branch_pattern(self, value):
4781 4796 self._validate_pattern(value)
4782 4797 self._target_branch_pattern = value or '*'
4783 4798
4784 4799 @hybrid_property
4785 4800 def file_pattern(self):
4786 4801 return self._file_pattern or '*'
4787 4802
4788 4803 @file_pattern.setter
4789 4804 def file_pattern(self, value):
4790 4805 self._validate_pattern(value)
4791 4806 self._file_pattern = value or '*'
4792 4807
4793 4808 def matches(self, source_branch, target_branch, files_changed):
4794 4809 """
4795 4810 Check if this review rule matches a branch/files in a pull request
4796 4811
4797 4812 :param source_branch: source branch name for the commit
4798 4813 :param target_branch: target branch name for the commit
4799 4814 :param files_changed: list of file paths changed in the pull request
4800 4815 """
4801 4816
4802 4817 source_branch = source_branch or ''
4803 4818 target_branch = target_branch or ''
4804 4819 files_changed = files_changed or []
4805 4820
4806 4821 branch_matches = True
4807 4822 if source_branch or target_branch:
4808 4823 if self.source_branch_pattern == '*':
4809 4824 source_branch_match = True
4810 4825 else:
4811 4826 if self.source_branch_pattern.startswith('re:'):
4812 4827 source_pattern = self.source_branch_pattern[3:]
4813 4828 else:
4814 4829 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4815 4830 source_branch_regex = re.compile(source_pattern)
4816 4831 source_branch_match = bool(source_branch_regex.search(source_branch))
4817 4832 if self.target_branch_pattern == '*':
4818 4833 target_branch_match = True
4819 4834 else:
4820 4835 if self.target_branch_pattern.startswith('re:'):
4821 4836 target_pattern = self.target_branch_pattern[3:]
4822 4837 else:
4823 4838 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4824 4839 target_branch_regex = re.compile(target_pattern)
4825 4840 target_branch_match = bool(target_branch_regex.search(target_branch))
4826 4841
4827 4842 branch_matches = source_branch_match and target_branch_match
4828 4843
4829 4844 files_matches = True
4830 4845 if self.file_pattern != '*':
4831 4846 files_matches = False
4832 4847 if self.file_pattern.startswith('re:'):
4833 4848 file_pattern = self.file_pattern[3:]
4834 4849 else:
4835 4850 file_pattern = glob2re(self.file_pattern)
4836 4851 file_regex = re.compile(file_pattern)
4837 4852 for filename in files_changed:
4838 4853 if file_regex.search(filename):
4839 4854 files_matches = True
4840 4855 break
4841 4856
4842 4857 return branch_matches and files_matches
4843 4858
4844 4859 @property
4845 4860 def review_users(self):
4846 4861 """ Returns the users which this rule applies to """
4847 4862
4848 4863 users = collections.OrderedDict()
4849 4864
4850 4865 for rule_user in self.rule_users:
4851 4866 if rule_user.user.active:
4852 4867 if rule_user.user not in users:
4853 4868 users[rule_user.user.username] = {
4854 4869 'user': rule_user.user,
4855 4870 'source': 'user',
4856 4871 'source_data': {},
4857 4872 'data': rule_user.rule_data()
4858 4873 }
4859 4874
4860 4875 for rule_user_group in self.rule_user_groups:
4861 4876 source_data = {
4862 4877 'user_group_id': rule_user_group.users_group.users_group_id,
4863 4878 'name': rule_user_group.users_group.users_group_name,
4864 4879 'members': len(rule_user_group.users_group.members)
4865 4880 }
4866 4881 for member in rule_user_group.users_group.members:
4867 4882 if member.user.active:
4868 4883 key = member.user.username
4869 4884 if key in users:
4870 4885 # skip this member as we have him already
4871 4886 # this prevents from override the "first" matched
4872 4887 # users with duplicates in multiple groups
4873 4888 continue
4874 4889
4875 4890 users[key] = {
4876 4891 'user': member.user,
4877 4892 'source': 'user_group',
4878 4893 'source_data': source_data,
4879 4894 'data': rule_user_group.rule_data()
4880 4895 }
4881 4896
4882 4897 return users
4883 4898
4884 4899 def user_group_vote_rule(self, user_id):
4885 4900
4886 4901 rules = []
4887 4902 if not self.rule_user_groups:
4888 4903 return rules
4889 4904
4890 4905 for user_group in self.rule_user_groups:
4891 4906 user_group_members = [x.user_id for x in user_group.users_group.members]
4892 4907 if user_id in user_group_members:
4893 4908 rules.append(user_group)
4894 4909 return rules
4895 4910
4896 4911 def __repr__(self):
4897 4912 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4898 4913 self.repo_review_rule_id, self.repo)
4899 4914
4900 4915
4901 4916 class ScheduleEntry(Base, BaseModel):
4902 4917 __tablename__ = 'schedule_entries'
4903 4918 __table_args__ = (
4904 4919 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4905 4920 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4906 4921 base_table_args,
4907 4922 )
4908 4923
4909 4924 schedule_types = ['crontab', 'timedelta', 'integer']
4910 4925 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4911 4926
4912 4927 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4913 4928 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4914 4929 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4915 4930
4916 4931 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4917 4932 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4918 4933
4919 4934 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4920 4935 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4921 4936
4922 4937 # task
4923 4938 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4924 4939 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4925 4940 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4926 4941 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4927 4942
4928 4943 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4929 4944 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4930 4945
4931 4946 @hybrid_property
4932 4947 def schedule_type(self):
4933 4948 return self._schedule_type
4934 4949
4935 4950 @schedule_type.setter
4936 4951 def schedule_type(self, val):
4937 4952 if val not in self.schedule_types:
4938 4953 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4939 4954 val, self.schedule_type))
4940 4955
4941 4956 self._schedule_type = val
4942 4957
4943 4958 @classmethod
4944 4959 def get_uid(cls, obj):
4945 4960 args = obj.task_args
4946 4961 kwargs = obj.task_kwargs
4947 4962 if isinstance(args, JsonRaw):
4948 4963 try:
4949 4964 args = json.loads(args)
4950 4965 except ValueError:
4951 4966 args = tuple()
4952 4967
4953 4968 if isinstance(kwargs, JsonRaw):
4954 4969 try:
4955 4970 kwargs = json.loads(kwargs)
4956 4971 except ValueError:
4957 4972 kwargs = dict()
4958 4973
4959 4974 dot_notation = obj.task_dot_notation
4960 4975 val = '.'.join(map(safe_str, [
4961 4976 sorted(dot_notation), args, sorted(kwargs.items())]))
4962 4977 return hashlib.sha1(val).hexdigest()
4963 4978
4964 4979 @classmethod
4965 4980 def get_by_schedule_name(cls, schedule_name):
4966 4981 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4967 4982
4968 4983 @classmethod
4969 4984 def get_by_schedule_id(cls, schedule_id):
4970 4985 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4971 4986
4972 4987 @property
4973 4988 def task(self):
4974 4989 return self.task_dot_notation
4975 4990
4976 4991 @property
4977 4992 def schedule(self):
4978 4993 from rhodecode.lib.celerylib.utils import raw_2_schedule
4979 4994 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4980 4995 return schedule
4981 4996
4982 4997 @property
4983 4998 def args(self):
4984 4999 try:
4985 5000 return list(self.task_args or [])
4986 5001 except ValueError:
4987 5002 return list()
4988 5003
4989 5004 @property
4990 5005 def kwargs(self):
4991 5006 try:
4992 5007 return dict(self.task_kwargs or {})
4993 5008 except ValueError:
4994 5009 return dict()
4995 5010
4996 5011 def _as_raw(self, val):
4997 5012 if hasattr(val, 'de_coerce'):
4998 5013 val = val.de_coerce()
4999 5014 if val:
5000 5015 val = json.dumps(val)
5001 5016
5002 5017 return val
5003 5018
5004 5019 @property
5005 5020 def schedule_definition_raw(self):
5006 5021 return self._as_raw(self.schedule_definition)
5007 5022
5008 5023 @property
5009 5024 def args_raw(self):
5010 5025 return self._as_raw(self.task_args)
5011 5026
5012 5027 @property
5013 5028 def kwargs_raw(self):
5014 5029 return self._as_raw(self.task_kwargs)
5015 5030
5016 5031 def __repr__(self):
5017 5032 return '<DB:ScheduleEntry({}:{})>'.format(
5018 5033 self.schedule_entry_id, self.schedule_name)
5019 5034
5020 5035
5021 5036 @event.listens_for(ScheduleEntry, 'before_update')
5022 5037 def update_task_uid(mapper, connection, target):
5023 5038 target.task_uid = ScheduleEntry.get_uid(target)
5024 5039
5025 5040
5026 5041 @event.listens_for(ScheduleEntry, 'before_insert')
5027 5042 def set_task_uid(mapper, connection, target):
5028 5043 target.task_uid = ScheduleEntry.get_uid(target)
5029 5044
5030 5045
5031 5046 class _BaseBranchPerms(BaseModel):
5032 5047 @classmethod
5033 5048 def compute_hash(cls, value):
5034 5049 return sha1_safe(value)
5035 5050
5036 5051 @hybrid_property
5037 5052 def branch_pattern(self):
5038 5053 return self._branch_pattern or '*'
5039 5054
5040 5055 @hybrid_property
5041 5056 def branch_hash(self):
5042 5057 return self._branch_hash
5043 5058
5044 5059 def _validate_glob(self, value):
5045 5060 re.compile('^' + glob2re(value) + '$')
5046 5061
5047 5062 @branch_pattern.setter
5048 5063 def branch_pattern(self, value):
5049 5064 self._validate_glob(value)
5050 5065 self._branch_pattern = value or '*'
5051 5066 # set the Hash when setting the branch pattern
5052 5067 self._branch_hash = self.compute_hash(self._branch_pattern)
5053 5068
5054 5069 def matches(self, branch):
5055 5070 """
5056 5071 Check if this the branch matches entry
5057 5072
5058 5073 :param branch: branch name for the commit
5059 5074 """
5060 5075
5061 5076 branch = branch or ''
5062 5077
5063 5078 branch_matches = True
5064 5079 if branch:
5065 5080 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5066 5081 branch_matches = bool(branch_regex.search(branch))
5067 5082
5068 5083 return branch_matches
5069 5084
5070 5085
5071 5086 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5072 5087 __tablename__ = 'user_to_repo_branch_permissions'
5073 5088 __table_args__ = (
5074 5089 base_table_args
5075 5090 )
5076 5091
5077 5092 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5078 5093
5079 5094 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5080 5095 repo = relationship('Repository', backref='user_branch_perms')
5081 5096
5082 5097 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5083 5098 permission = relationship('Permission')
5084 5099
5085 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 5101 user_repo_to_perm = relationship('UserRepoToPerm')
5087 5102
5088 5103 rule_order = Column('rule_order', Integer(), nullable=False)
5089 5104 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5090 5105 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5091 5106
5092 5107 def __unicode__(self):
5093 5108 return u'<UserBranchPermission(%s => %r)>' % (
5094 5109 self.user_repo_to_perm, self.branch_pattern)
5095 5110
5096 5111
5097 5112 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5098 5113 __tablename__ = 'user_group_to_repo_branch_permissions'
5099 5114 __table_args__ = (
5100 5115 base_table_args
5101 5116 )
5102 5117
5103 5118 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5104 5119
5105 5120 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5106 5121 repo = relationship('Repository', backref='user_group_branch_perms')
5107 5122
5108 5123 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5109 5124 permission = relationship('Permission')
5110 5125
5111 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 5127 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5113 5128
5114 5129 rule_order = Column('rule_order', Integer(), nullable=False)
5115 5130 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5116 5131 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5117 5132
5118 5133 def __unicode__(self):
5119 5134 return u'<UserBranchPermission(%s => %r)>' % (
5120 5135 self.user_group_repo_to_perm, self.branch_pattern)
5121 5136
5122 5137
5123 5138 class UserBookmark(Base, BaseModel):
5124 5139 __tablename__ = 'user_bookmarks'
5125 5140 __table_args__ = (
5126 5141 UniqueConstraint('user_id', 'bookmark_repo_id'),
5127 5142 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5128 5143 UniqueConstraint('user_id', 'bookmark_position'),
5129 5144 base_table_args
5130 5145 )
5131 5146
5132 5147 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5133 5148 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5134 5149 position = Column("bookmark_position", Integer(), nullable=False)
5135 5150 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5136 5151 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5137 5152 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5138 5153
5139 5154 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5140 5155 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5141 5156
5142 5157 user = relationship("User")
5143 5158
5144 5159 repository = relationship("Repository")
5145 5160 repository_group = relationship("RepoGroup")
5146 5161
5147 5162 @classmethod
5148 5163 def get_by_position_for_user(cls, position, user_id):
5149 5164 return cls.query() \
5150 5165 .filter(UserBookmark.user_id == user_id) \
5151 5166 .filter(UserBookmark.position == position).scalar()
5152 5167
5153 5168 @classmethod
5154 5169 def get_bookmarks_for_user(cls, user_id, cache=True):
5155 5170 bookmarks = cls.query() \
5156 5171 .filter(UserBookmark.user_id == user_id) \
5157 5172 .options(joinedload(UserBookmark.repository)) \
5158 5173 .options(joinedload(UserBookmark.repository_group)) \
5159 5174 .order_by(UserBookmark.position.asc())
5160 5175
5161 5176 if cache:
5162 5177 bookmarks = bookmarks.options(
5163 5178 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5164 5179 )
5165 5180
5166 5181 return bookmarks.all()
5167 5182
5168 5183 def __unicode__(self):
5169 5184 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5170 5185
5171 5186
5172 5187 class FileStore(Base, BaseModel):
5173 5188 __tablename__ = 'file_store'
5174 5189 __table_args__ = (
5175 5190 base_table_args
5176 5191 )
5177 5192
5178 5193 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5179 5194 file_uid = Column('file_uid', String(1024), nullable=False)
5180 5195 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5181 5196 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5182 5197 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5183 5198
5184 5199 # sha256 hash
5185 5200 file_hash = Column('file_hash', String(512), nullable=False)
5186 5201 file_size = Column('file_size', BigInteger(), nullable=False)
5187 5202
5188 5203 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5189 5204 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5190 5205 accessed_count = Column('accessed_count', Integer(), default=0)
5191 5206
5192 5207 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5193 5208
5194 5209 # if repo/repo_group reference is set, check for permissions
5195 5210 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5196 5211
5197 5212 # hidden defines an attachment that should be hidden from showing in artifact listing
5198 5213 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5199 5214
5200 5215 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5201 5216 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5202 5217
5203 5218 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5204 5219
5205 5220 # scope limited to user, which requester have access to
5206 5221 scope_user_id = Column(
5207 5222 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5208 5223 nullable=True, unique=None, default=None)
5209 5224 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5210 5225
5211 5226 # scope limited to user group, which requester have access to
5212 5227 scope_user_group_id = Column(
5213 5228 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5214 5229 nullable=True, unique=None, default=None)
5215 5230 user_group = relationship('UserGroup', lazy='joined')
5216 5231
5217 5232 # scope limited to repo, which requester have access to
5218 5233 scope_repo_id = Column(
5219 5234 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5220 5235 nullable=True, unique=None, default=None)
5221 5236 repo = relationship('Repository', lazy='joined')
5222 5237
5223 5238 # scope limited to repo group, which requester have access to
5224 5239 scope_repo_group_id = Column(
5225 5240 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5226 5241 nullable=True, unique=None, default=None)
5227 5242 repo_group = relationship('RepoGroup', lazy='joined')
5228 5243
5229 5244 @classmethod
5230 5245 def get_by_store_uid(cls, file_store_uid):
5231 5246 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5232 5247
5233 5248 @classmethod
5234 5249 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5235 5250 file_description='', enabled=True, hidden=False, check_acl=True,
5236 5251 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5237 5252
5238 5253 store_entry = FileStore()
5239 5254 store_entry.file_uid = file_uid
5240 5255 store_entry.file_display_name = file_display_name
5241 5256 store_entry.file_org_name = filename
5242 5257 store_entry.file_size = file_size
5243 5258 store_entry.file_hash = file_hash
5244 5259 store_entry.file_description = file_description
5245 5260
5246 5261 store_entry.check_acl = check_acl
5247 5262 store_entry.enabled = enabled
5248 5263 store_entry.hidden = hidden
5249 5264
5250 5265 store_entry.user_id = user_id
5251 5266 store_entry.scope_user_id = scope_user_id
5252 5267 store_entry.scope_repo_id = scope_repo_id
5253 5268 store_entry.scope_repo_group_id = scope_repo_group_id
5254 5269
5255 5270 return store_entry
5256 5271
5257 5272 @classmethod
5258 5273 def store_metadata(cls, file_store_id, args, commit=True):
5259 5274 file_store = FileStore.get(file_store_id)
5260 5275 if file_store is None:
5261 5276 return
5262 5277
5263 5278 for section, key, value, value_type in args:
5264 5279 has_key = FileStoreMetadata().query() \
5265 5280 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5266 5281 .filter(FileStoreMetadata.file_store_meta_section == section) \
5267 5282 .filter(FileStoreMetadata.file_store_meta_key == key) \
5268 5283 .scalar()
5269 5284 if has_key:
5270 5285 msg = 'key `{}` already defined under section `{}` for this file.'\
5271 5286 .format(key, section)
5272 5287 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5273 5288
5274 5289 # NOTE(marcink): raises ArtifactMetadataBadValueType
5275 5290 FileStoreMetadata.valid_value_type(value_type)
5276 5291
5277 5292 meta_entry = FileStoreMetadata()
5278 5293 meta_entry.file_store = file_store
5279 5294 meta_entry.file_store_meta_section = section
5280 5295 meta_entry.file_store_meta_key = key
5281 5296 meta_entry.file_store_meta_value_type = value_type
5282 5297 meta_entry.file_store_meta_value = value
5283 5298
5284 5299 Session().add(meta_entry)
5285 5300
5286 5301 try:
5287 5302 if commit:
5288 5303 Session().commit()
5289 5304 except IntegrityError:
5290 5305 Session().rollback()
5291 5306 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5292 5307
5293 5308 @classmethod
5294 5309 def bump_access_counter(cls, file_uid, commit=True):
5295 5310 FileStore().query()\
5296 5311 .filter(FileStore.file_uid == file_uid)\
5297 5312 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5298 5313 FileStore.accessed_on: datetime.datetime.now()})
5299 5314 if commit:
5300 5315 Session().commit()
5301 5316
5302 5317 def __json__(self):
5303 5318 data = {
5304 5319 'filename': self.file_display_name,
5305 5320 'filename_org': self.file_org_name,
5306 5321 'file_uid': self.file_uid,
5307 5322 'description': self.file_description,
5308 5323 'hidden': self.hidden,
5309 5324 'size': self.file_size,
5310 5325 'created_on': self.created_on,
5311 5326 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5312 5327 'downloaded_times': self.accessed_count,
5313 5328 'sha256': self.file_hash,
5314 5329 'metadata': self.file_metadata,
5315 5330 }
5316 5331
5317 5332 return data
5318 5333
5319 5334 def __repr__(self):
5320 5335 return '<FileStore({})>'.format(self.file_store_id)
5321 5336
5322 5337
5323 5338 class FileStoreMetadata(Base, BaseModel):
5324 5339 __tablename__ = 'file_store_metadata'
5325 5340 __table_args__ = (
5326 5341 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5327 5342 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5328 5343 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5329 5344 base_table_args
5330 5345 )
5331 5346 SETTINGS_TYPES = {
5332 5347 'str': safe_str,
5333 5348 'int': safe_int,
5334 5349 'unicode': safe_unicode,
5335 5350 'bool': str2bool,
5336 5351 'list': functools.partial(aslist, sep=',')
5337 5352 }
5338 5353
5339 5354 file_store_meta_id = Column(
5340 5355 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5341 5356 primary_key=True)
5342 5357 _file_store_meta_section = Column(
5343 5358 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5344 5359 nullable=True, unique=None, default=None)
5345 5360 _file_store_meta_section_hash = Column(
5346 5361 "file_store_meta_section_hash", String(255),
5347 5362 nullable=True, unique=None, default=None)
5348 5363 _file_store_meta_key = Column(
5349 5364 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5350 5365 nullable=True, unique=None, default=None)
5351 5366 _file_store_meta_key_hash = Column(
5352 5367 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5353 5368 _file_store_meta_value = Column(
5354 5369 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5355 5370 nullable=True, unique=None, default=None)
5356 5371 _file_store_meta_value_type = Column(
5357 5372 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5358 5373 default='unicode')
5359 5374
5360 5375 file_store_id = Column(
5361 5376 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5362 5377 nullable=True, unique=None, default=None)
5363 5378
5364 5379 file_store = relationship('FileStore', lazy='joined')
5365 5380
5366 5381 @classmethod
5367 5382 def valid_value_type(cls, value):
5368 5383 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5369 5384 raise ArtifactMetadataBadValueType(
5370 5385 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5371 5386
5372 5387 @hybrid_property
5373 5388 def file_store_meta_section(self):
5374 5389 return self._file_store_meta_section
5375 5390
5376 5391 @file_store_meta_section.setter
5377 5392 def file_store_meta_section(self, value):
5378 5393 self._file_store_meta_section = value
5379 5394 self._file_store_meta_section_hash = _hash_key(value)
5380 5395
5381 5396 @hybrid_property
5382 5397 def file_store_meta_key(self):
5383 5398 return self._file_store_meta_key
5384 5399
5385 5400 @file_store_meta_key.setter
5386 5401 def file_store_meta_key(self, value):
5387 5402 self._file_store_meta_key = value
5388 5403 self._file_store_meta_key_hash = _hash_key(value)
5389 5404
5390 5405 @hybrid_property
5391 5406 def file_store_meta_value(self):
5392 5407 val = self._file_store_meta_value
5393 5408
5394 5409 if self._file_store_meta_value_type:
5395 5410 # e.g unicode.encrypted == unicode
5396 5411 _type = self._file_store_meta_value_type.split('.')[0]
5397 5412 # decode the encrypted value if it's encrypted field type
5398 5413 if '.encrypted' in self._file_store_meta_value_type:
5399 5414 cipher = EncryptedTextValue()
5400 5415 val = safe_unicode(cipher.process_result_value(val, None))
5401 5416 # do final type conversion
5402 5417 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5403 5418 val = converter(val)
5404 5419
5405 5420 return val
5406 5421
5407 5422 @file_store_meta_value.setter
5408 5423 def file_store_meta_value(self, val):
5409 5424 val = safe_unicode(val)
5410 5425 # encode the encrypted value
5411 5426 if '.encrypted' in self.file_store_meta_value_type:
5412 5427 cipher = EncryptedTextValue()
5413 5428 val = safe_unicode(cipher.process_bind_param(val, None))
5414 5429 self._file_store_meta_value = val
5415 5430
5416 5431 @hybrid_property
5417 5432 def file_store_meta_value_type(self):
5418 5433 return self._file_store_meta_value_type
5419 5434
5420 5435 @file_store_meta_value_type.setter
5421 5436 def file_store_meta_value_type(self, val):
5422 5437 # e.g unicode.encrypted
5423 5438 self.valid_value_type(val)
5424 5439 self._file_store_meta_value_type = val
5425 5440
5426 5441 def __json__(self):
5427 5442 data = {
5428 5443 'artifact': self.file_store.file_uid,
5429 5444 'section': self.file_store_meta_section,
5430 5445 'key': self.file_store_meta_key,
5431 5446 'value': self.file_store_meta_value,
5432 5447 }
5433 5448
5434 5449 return data
5435 5450
5436 5451 def __repr__(self):
5437 5452 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5438 5453 self.file_store_meta_key, self.file_store_meta_value)
5439 5454
5440 5455
5441 5456 class DbMigrateVersion(Base, BaseModel):
5442 5457 __tablename__ = 'db_migrate_version'
5443 5458 __table_args__ = (
5444 5459 base_table_args,
5445 5460 )
5446 5461
5447 5462 repository_id = Column('repository_id', String(250), primary_key=True)
5448 5463 repository_path = Column('repository_path', Text)
5449 5464 version = Column('version', Integer)
5450 5465
5451 5466 @classmethod
5452 5467 def set_version(cls, version):
5453 5468 """
5454 5469 Helper for forcing a different version, usually for debugging purposes via ishell.
5455 5470 """
5456 5471 ver = DbMigrateVersion.query().first()
5457 5472 ver.version = version
5458 5473 Session().commit()
5459 5474
5460 5475
5461 5476 class DbSession(Base, BaseModel):
5462 5477 __tablename__ = 'db_session'
5463 5478 __table_args__ = (
5464 5479 base_table_args,
5465 5480 )
5466 5481
5467 5482 def __repr__(self):
5468 5483 return '<DB:DbSession({})>'.format(self.id)
5469 5484
5470 5485 id = Column('id', Integer())
5471 5486 namespace = Column('namespace', String(255), primary_key=True)
5472 5487 accessed = Column('accessed', DateTime, nullable=False)
5473 5488 created = Column('created', DateTime, nullable=False)
5474 5489 data = Column('data', PickleType, nullable=False)
@@ -1,1020 +1,1019 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Scm model for RhodeCode
23 23 """
24 24
25 25 import os.path
26 26 import traceback
27 27 import logging
28 28 import cStringIO
29 29
30 30 from sqlalchemy import func
31 31 from zope.cachedescriptors.property import Lazy as LazyProperty
32 32
33 33 import rhodecode
34 34 from rhodecode.lib.vcs import get_backend
35 35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 36 from rhodecode.lib.vcs.nodes import FileNode
37 37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 38 from rhodecode.lib import helpers as h, rc_cache
39 39 from rhodecode.lib.auth import (
40 40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 41 HasUserGroupPermissionAny)
42 42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 43 from rhodecode.lib import hooks_utils
44 44 from rhodecode.lib.utils import (
45 45 get_filesystem_repos, make_db_config)
46 46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 47 from rhodecode.lib.system_info import get_system_info
48 48 from rhodecode.model import BaseModel
49 49 from rhodecode.model.db import (
50 50 or_, false,
51 51 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
52 52 PullRequest, FileStore)
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class UserTemp(object):
60 60 def __init__(self, user_id):
61 61 self.user_id = user_id
62 62
63 63 def __repr__(self):
64 64 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
65 65
66 66
67 67 class RepoTemp(object):
68 68 def __init__(self, repo_id):
69 69 self.repo_id = repo_id
70 70
71 71 def __repr__(self):
72 72 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
73 73
74 74
75 75 class SimpleCachedRepoList(object):
76 76 """
77 77 Lighter version of of iteration of repos without the scm initialisation,
78 78 and with cache usage
79 79 """
80 80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 81 self.db_repo_list = db_repo_list
82 82 self.repos_path = repos_path
83 83 self.order_by = order_by
84 84 self.reversed = (order_by or '').startswith('-')
85 85 if not perm_set:
86 86 perm_set = ['repository.read', 'repository.write',
87 87 'repository.admin']
88 88 self.perm_set = perm_set
89 89
90 90 def __len__(self):
91 91 return len(self.db_repo_list)
92 92
93 93 def __repr__(self):
94 94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
95 95
96 96 def __iter__(self):
97 97 for dbr in self.db_repo_list:
98 98 # check permission at this level
99 99 has_perm = HasRepoPermissionAny(*self.perm_set)(
100 100 dbr.repo_name, 'SimpleCachedRepoList check')
101 101 if not has_perm:
102 102 continue
103 103
104 104 tmp_d = {
105 105 'name': dbr.repo_name,
106 106 'dbrepo': dbr.get_dict(),
107 107 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
108 108 }
109 109 yield tmp_d
110 110
111 111
112 112 class _PermCheckIterator(object):
113 113
114 114 def __init__(
115 115 self, obj_list, obj_attr, perm_set, perm_checker,
116 116 extra_kwargs=None):
117 117 """
118 118 Creates iterator from given list of objects, additionally
119 119 checking permission for them from perm_set var
120 120
121 121 :param obj_list: list of db objects
122 122 :param obj_attr: attribute of object to pass into perm_checker
123 123 :param perm_set: list of permissions to check
124 124 :param perm_checker: callable to check permissions against
125 125 """
126 126 self.obj_list = obj_list
127 127 self.obj_attr = obj_attr
128 128 self.perm_set = perm_set
129 129 self.perm_checker = perm_checker(*self.perm_set)
130 130 self.extra_kwargs = extra_kwargs or {}
131 131
132 132 def __len__(self):
133 133 return len(self.obj_list)
134 134
135 135 def __repr__(self):
136 136 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
137 137
138 138 def __iter__(self):
139 139 for db_obj in self.obj_list:
140 140 # check permission at this level
141 141 name = getattr(db_obj, self.obj_attr, None)
142 142 if not self.perm_checker(name, self.__class__.__name__, **self.extra_kwargs):
143 143 continue
144 144
145 145 yield db_obj
146 146
147 147
148 148 class RepoList(_PermCheckIterator):
149 149
150 150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 151 if not perm_set:
152 perm_set = [
153 'repository.read', 'repository.write', 'repository.admin']
152 perm_set = ['repository.read', 'repository.write', 'repository.admin']
154 153
155 154 super(RepoList, self).__init__(
156 155 obj_list=db_repo_list,
157 156 obj_attr='repo_name', perm_set=perm_set,
158 157 perm_checker=HasRepoPermissionAny,
159 158 extra_kwargs=extra_kwargs)
160 159
161 160
162 161 class RepoGroupList(_PermCheckIterator):
163 162
164 163 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 164 if not perm_set:
166 165 perm_set = ['group.read', 'group.write', 'group.admin']
167 166
168 167 super(RepoGroupList, self).__init__(
169 168 obj_list=db_repo_group_list,
170 169 obj_attr='group_name', perm_set=perm_set,
171 170 perm_checker=HasRepoGroupPermissionAny,
172 171 extra_kwargs=extra_kwargs)
173 172
174 173
175 174 class UserGroupList(_PermCheckIterator):
176 175
177 176 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 177 if not perm_set:
179 178 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180 179
181 180 super(UserGroupList, self).__init__(
182 181 obj_list=db_user_group_list,
183 182 obj_attr='users_group_name', perm_set=perm_set,
184 183 perm_checker=HasUserGroupPermissionAny,
185 184 extra_kwargs=extra_kwargs)
186 185
187 186
188 187 class ScmModel(BaseModel):
189 188 """
190 189 Generic Scm Model
191 190 """
192 191
193 192 @LazyProperty
194 193 def repos_path(self):
195 194 """
196 195 Gets the repositories root path from database
197 196 """
198 197
199 198 settings_model = VcsSettingsModel(sa=self.sa)
200 199 return settings_model.get_repos_location()
201 200
202 201 def repo_scan(self, repos_path=None):
203 202 """
204 203 Listing of repositories in given path. This path should not be a
205 204 repository itself. Return a dictionary of repository objects
206 205
207 206 :param repos_path: path to directory containing repositories
208 207 """
209 208
210 209 if repos_path is None:
211 210 repos_path = self.repos_path
212 211
213 212 log.info('scanning for repositories in %s', repos_path)
214 213
215 214 config = make_db_config()
216 215 config.set('extensions', 'largefiles', '')
217 216 repos = {}
218 217
219 218 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 219 # name need to be decomposed and put back together using the /
221 220 # since this is internal storage separator for rhodecode
222 221 name = Repository.normalize_repo_name(name)
223 222
224 223 try:
225 224 if name in repos:
226 225 raise RepositoryError('Duplicate repository name %s '
227 226 'found in %s' % (name, path))
228 227 elif path[0] in rhodecode.BACKENDS:
229 228 backend = get_backend(path[0])
230 229 repos[name] = backend(path[1], config=config,
231 230 with_wire={"cache": False})
232 231 except OSError:
233 232 continue
234 233 log.debug('found %s paths with repositories', len(repos))
235 234 return repos
236 235
237 236 def get_repos(self, all_repos=None, sort_key=None):
238 237 """
239 238 Get all repositories from db and for each repo create it's
240 239 backend instance and fill that backed with information from database
241 240
242 241 :param all_repos: list of repository names as strings
243 242 give specific repositories list, good for filtering
244 243
245 244 :param sort_key: initial sorting of repositories
246 245 """
247 246 if all_repos is None:
248 247 all_repos = self.sa.query(Repository)\
249 248 .filter(Repository.group_id == None)\
250 249 .order_by(func.lower(Repository.repo_name)).all()
251 250 repo_iter = SimpleCachedRepoList(
252 251 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 252 return repo_iter
254 253
255 254 def get_repo_groups(self, all_groups=None):
256 255 if all_groups is None:
257 256 all_groups = RepoGroup.query()\
258 257 .filter(RepoGroup.group_parent_id == None).all()
259 258 return [x for x in RepoGroupList(all_groups)]
260 259
261 260 def mark_for_invalidation(self, repo_name, delete=False):
262 261 """
263 262 Mark caches of this repo invalid in the database. `delete` flag
264 263 removes the cache entries
265 264
266 265 :param repo_name: the repo_name for which caches should be marked
267 266 invalid, or deleted
268 267 :param delete: delete the entry keys instead of setting bool
269 268 flag on them, and also purge caches used by the dogpile
270 269 """
271 270 repo = Repository.get_by_repo_name(repo_name)
272 271
273 272 if repo:
274 273 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 274 repo_id=repo.repo_id)
276 275 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277 276
278 277 repo_id = repo.repo_id
279 278 config = repo._config
280 279 config.set('extensions', 'largefiles', '')
281 280 repo.update_commit_cache(config=config, cs_cache=None)
282 281 if delete:
283 282 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 283 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285 284
286 285 def toggle_following_repo(self, follow_repo_id, user_id):
287 286
288 287 f = self.sa.query(UserFollowing)\
289 288 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 289 .filter(UserFollowing.user_id == user_id).scalar()
291 290
292 291 if f is not None:
293 292 try:
294 293 self.sa.delete(f)
295 294 return
296 295 except Exception:
297 296 log.error(traceback.format_exc())
298 297 raise
299 298
300 299 try:
301 300 f = UserFollowing()
302 301 f.user_id = user_id
303 302 f.follows_repo_id = follow_repo_id
304 303 self.sa.add(f)
305 304 except Exception:
306 305 log.error(traceback.format_exc())
307 306 raise
308 307
309 308 def toggle_following_user(self, follow_user_id, user_id):
310 309 f = self.sa.query(UserFollowing)\
311 310 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 311 .filter(UserFollowing.user_id == user_id).scalar()
313 312
314 313 if f is not None:
315 314 try:
316 315 self.sa.delete(f)
317 316 return
318 317 except Exception:
319 318 log.error(traceback.format_exc())
320 319 raise
321 320
322 321 try:
323 322 f = UserFollowing()
324 323 f.user_id = user_id
325 324 f.follows_user_id = follow_user_id
326 325 self.sa.add(f)
327 326 except Exception:
328 327 log.error(traceback.format_exc())
329 328 raise
330 329
331 330 def is_following_repo(self, repo_name, user_id, cache=False):
332 331 r = self.sa.query(Repository)\
333 332 .filter(Repository.repo_name == repo_name).scalar()
334 333
335 334 f = self.sa.query(UserFollowing)\
336 335 .filter(UserFollowing.follows_repository == r)\
337 336 .filter(UserFollowing.user_id == user_id).scalar()
338 337
339 338 return f is not None
340 339
341 340 def is_following_user(self, username, user_id, cache=False):
342 341 u = User.get_by_username(username)
343 342
344 343 f = self.sa.query(UserFollowing)\
345 344 .filter(UserFollowing.follows_user == u)\
346 345 .filter(UserFollowing.user_id == user_id).scalar()
347 346
348 347 return f is not None
349 348
350 349 def get_followers(self, repo):
351 350 repo = self._get_repo(repo)
352 351
353 352 return self.sa.query(UserFollowing)\
354 353 .filter(UserFollowing.follows_repository == repo).count()
355 354
356 355 def get_forks(self, repo):
357 356 repo = self._get_repo(repo)
358 357 return self.sa.query(Repository)\
359 358 .filter(Repository.fork == repo).count()
360 359
361 360 def get_pull_requests(self, repo):
362 361 repo = self._get_repo(repo)
363 362 return self.sa.query(PullRequest)\
364 363 .filter(PullRequest.target_repo == repo)\
365 364 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366 365
367 366 def get_artifacts(self, repo):
368 367 repo = self._get_repo(repo)
369 368 return self.sa.query(FileStore)\
370 369 .filter(FileStore.repo == repo)\
371 370 .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count()
372 371
373 372 def mark_as_fork(self, repo, fork, user):
374 373 repo = self._get_repo(repo)
375 374 fork = self._get_repo(fork)
376 375 if fork and repo.repo_id == fork.repo_id:
377 376 raise Exception("Cannot set repository as fork of itself")
378 377
379 378 if fork and repo.repo_type != fork.repo_type:
380 379 raise RepositoryError(
381 380 "Cannot set repository as fork of repository with other type")
382 381
383 382 repo.fork = fork
384 383 self.sa.add(repo)
385 384 return repo
386 385
387 386 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
388 387 dbrepo = self._get_repo(repo)
389 388 remote_uri = remote_uri or dbrepo.clone_uri
390 389 if not remote_uri:
391 390 raise Exception("This repository doesn't have a clone uri")
392 391
393 392 repo = dbrepo.scm_instance(cache=False)
394 393 repo.config.clear_section('hooks')
395 394
396 395 try:
397 396 # NOTE(marcink): add extra validation so we skip invalid urls
398 397 # this is due this tasks can be executed via scheduler without
399 398 # proper validation of remote_uri
400 399 if validate_uri:
401 400 config = make_db_config(clear_session=False)
402 401 url_validator(remote_uri, dbrepo.repo_type, config)
403 402 except InvalidCloneUrl:
404 403 raise
405 404
406 405 repo_name = dbrepo.repo_name
407 406 try:
408 407 # TODO: we need to make sure those operations call proper hooks !
409 408 repo.fetch(remote_uri)
410 409
411 410 self.mark_for_invalidation(repo_name)
412 411 except Exception:
413 412 log.error(traceback.format_exc())
414 413 raise
415 414
416 415 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
417 416 dbrepo = self._get_repo(repo)
418 417 remote_uri = remote_uri or dbrepo.push_uri
419 418 if not remote_uri:
420 419 raise Exception("This repository doesn't have a clone uri")
421 420
422 421 repo = dbrepo.scm_instance(cache=False)
423 422 repo.config.clear_section('hooks')
424 423
425 424 try:
426 425 # NOTE(marcink): add extra validation so we skip invalid urls
427 426 # this is due this tasks can be executed via scheduler without
428 427 # proper validation of remote_uri
429 428 if validate_uri:
430 429 config = make_db_config(clear_session=False)
431 430 url_validator(remote_uri, dbrepo.repo_type, config)
432 431 except InvalidCloneUrl:
433 432 raise
434 433
435 434 try:
436 435 repo.push(remote_uri)
437 436 except Exception:
438 437 log.error(traceback.format_exc())
439 438 raise
440 439
441 440 def commit_change(self, repo, repo_name, commit, user, author, message,
442 441 content, f_path):
443 442 """
444 443 Commits changes
445 444
446 445 :param repo: SCM instance
447 446
448 447 """
449 448 user = self._get_user(user)
450 449
451 450 # decoding here will force that we have proper encoded values
452 451 # in any other case this will throw exceptions and deny commit
453 452 content = safe_str(content)
454 453 path = safe_str(f_path)
455 454 # message and author needs to be unicode
456 455 # proper backend should then translate that into required type
457 456 message = safe_unicode(message)
458 457 author = safe_unicode(author)
459 458 imc = repo.in_memory_commit
460 459 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
461 460 try:
462 461 # TODO: handle pre-push action !
463 462 tip = imc.commit(
464 463 message=message, author=author, parents=[commit],
465 464 branch=commit.branch)
466 465 except Exception as e:
467 466 log.error(traceback.format_exc())
468 467 raise IMCCommitError(str(e))
469 468 finally:
470 469 # always clear caches, if commit fails we want fresh object also
471 470 self.mark_for_invalidation(repo_name)
472 471
473 472 # We trigger the post-push action
474 473 hooks_utils.trigger_post_push_hook(
475 474 username=user.username, action='push_local', hook_type='post_push',
476 475 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
477 476 return tip
478 477
479 478 def _sanitize_path(self, f_path):
480 479 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
481 480 raise NonRelativePathError('%s is not an relative path' % f_path)
482 481 if f_path:
483 482 f_path = os.path.normpath(f_path)
484 483 return f_path
485 484
486 485 def get_dirnode_metadata(self, request, commit, dir_node):
487 486 if not dir_node.is_dir():
488 487 return []
489 488
490 489 data = []
491 490 for node in dir_node:
492 491 if not node.is_file():
493 492 # we skip file-nodes
494 493 continue
495 494
496 495 last_commit = node.last_commit
497 496 last_commit_date = last_commit.date
498 497 data.append({
499 498 'name': node.name,
500 499 'size': h.format_byte_size_binary(node.size),
501 500 'modified_at': h.format_date(last_commit_date),
502 501 'modified_ts': last_commit_date.isoformat(),
503 502 'revision': last_commit.revision,
504 503 'short_id': last_commit.short_id,
505 504 'message': h.escape(last_commit.message),
506 505 'author': h.escape(last_commit.author),
507 506 'user_profile': h.gravatar_with_user(
508 507 request, last_commit.author),
509 508 })
510 509
511 510 return data
512 511
513 512 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
514 513 extended_info=False, content=False, max_file_bytes=None):
515 514 """
516 515 recursive walk in root dir and return a set of all path in that dir
517 516 based on repository walk function
518 517
519 518 :param repo_name: name of repository
520 519 :param commit_id: commit id for which to list nodes
521 520 :param root_path: root path to list
522 521 :param flat: return as a list, if False returns a dict with description
523 522 :param extended_info: show additional info such as md5, binary, size etc
524 523 :param content: add nodes content to the return data
525 524 :param max_file_bytes: will not return file contents over this limit
526 525
527 526 """
528 527 _files = list()
529 528 _dirs = list()
530 529 try:
531 530 _repo = self._get_repo(repo_name)
532 531 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
533 532 root_path = root_path.lstrip('/')
534 533 for __, dirs, files in commit.walk(root_path):
535 534
536 535 for f in files:
537 536 _content = None
538 537 _data = f_name = f.unicode_path
539 538
540 539 if not flat:
541 540 _data = {
542 541 "name": h.escape(f_name),
543 542 "type": "file",
544 543 }
545 544 if extended_info:
546 545 _data.update({
547 546 "md5": f.md5,
548 547 "binary": f.is_binary,
549 548 "size": f.size,
550 549 "extension": f.extension,
551 550 "mimetype": f.mimetype,
552 551 "lines": f.lines()[0]
553 552 })
554 553
555 554 if content:
556 555 over_size_limit = (max_file_bytes is not None
557 556 and f.size > max_file_bytes)
558 557 full_content = None
559 558 if not f.is_binary and not over_size_limit:
560 559 full_content = safe_str(f.content)
561 560
562 561 _data.update({
563 562 "content": full_content,
564 563 })
565 564 _files.append(_data)
566 565
567 566 for d in dirs:
568 567 _data = d_name = d.unicode_path
569 568 if not flat:
570 569 _data = {
571 570 "name": h.escape(d_name),
572 571 "type": "dir",
573 572 }
574 573 if extended_info:
575 574 _data.update({
576 575 "md5": None,
577 576 "binary": None,
578 577 "size": None,
579 578 "extension": None,
580 579 })
581 580 if content:
582 581 _data.update({
583 582 "content": None
584 583 })
585 584 _dirs.append(_data)
586 585 except RepositoryError:
587 586 log.exception("Exception in get_nodes")
588 587 raise
589 588
590 589 return _dirs, _files
591 590
592 591 def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'):
593 592 """
594 593 Generate files for quick filter in files view
595 594 """
596 595
597 596 _files = list()
598 597 _dirs = list()
599 598 try:
600 599 _repo = self._get_repo(repo_name)
601 600 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
602 601 root_path = root_path.lstrip('/')
603 602 for __, dirs, files in commit.walk(root_path):
604 603
605 604 for f in files:
606 605
607 606 _data = {
608 607 "name": h.escape(f.unicode_path),
609 608 "type": "file",
610 609 }
611 610
612 611 _files.append(_data)
613 612
614 613 for d in dirs:
615 614
616 615 _data = {
617 616 "name": h.escape(d.unicode_path),
618 617 "type": "dir",
619 618 }
620 619
621 620 _dirs.append(_data)
622 621 except RepositoryError:
623 622 log.exception("Exception in get_quick_filter_nodes")
624 623 raise
625 624
626 625 return _dirs, _files
627 626
628 627 def get_node(self, repo_name, commit_id, file_path,
629 628 extended_info=False, content=False, max_file_bytes=None, cache=True):
630 629 """
631 630 retrieve single node from commit
632 631 """
633 632 try:
634 633
635 634 _repo = self._get_repo(repo_name)
636 635 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
637 636
638 637 file_node = commit.get_node(file_path)
639 638 if file_node.is_dir():
640 639 raise RepositoryError('The given path is a directory')
641 640
642 641 _content = None
643 642 f_name = file_node.unicode_path
644 643
645 644 file_data = {
646 645 "name": h.escape(f_name),
647 646 "type": "file",
648 647 }
649 648
650 649 if extended_info:
651 650 file_data.update({
652 651 "extension": file_node.extension,
653 652 "mimetype": file_node.mimetype,
654 653 })
655 654
656 655 if cache:
657 656 md5 = file_node.md5
658 657 is_binary = file_node.is_binary
659 658 size = file_node.size
660 659 else:
661 660 is_binary, md5, size, _content = file_node.metadata_uncached()
662 661
663 662 file_data.update({
664 663 "md5": md5,
665 664 "binary": is_binary,
666 665 "size": size,
667 666 })
668 667
669 668 if content and cache:
670 669 # get content + cache
671 670 size = file_node.size
672 671 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
673 672 full_content = None
674 673 all_lines = 0
675 674 if not file_node.is_binary and not over_size_limit:
676 675 full_content = safe_unicode(file_node.content)
677 676 all_lines, empty_lines = file_node.count_lines(full_content)
678 677
679 678 file_data.update({
680 679 "content": full_content,
681 680 "lines": all_lines
682 681 })
683 682 elif content:
684 683 # get content *without* cache
685 684 if _content is None:
686 685 is_binary, md5, size, _content = file_node.metadata_uncached()
687 686
688 687 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
689 688 full_content = None
690 689 all_lines = 0
691 690 if not is_binary and not over_size_limit:
692 691 full_content = safe_unicode(_content)
693 692 all_lines, empty_lines = file_node.count_lines(full_content)
694 693
695 694 file_data.update({
696 695 "content": full_content,
697 696 "lines": all_lines
698 697 })
699 698
700 699 except RepositoryError:
701 700 log.exception("Exception in get_node")
702 701 raise
703 702
704 703 return file_data
705 704
706 705 def get_fts_data(self, repo_name, commit_id, root_path='/'):
707 706 """
708 707 Fetch node tree for usage in full text search
709 708 """
710 709
711 710 tree_info = list()
712 711
713 712 try:
714 713 _repo = self._get_repo(repo_name)
715 714 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
716 715 root_path = root_path.lstrip('/')
717 716 for __, dirs, files in commit.walk(root_path):
718 717
719 718 for f in files:
720 719 is_binary, md5, size, _content = f.metadata_uncached()
721 720 _data = {
722 721 "name": f.unicode_path,
723 722 "md5": md5,
724 723 "extension": f.extension,
725 724 "binary": is_binary,
726 725 "size": size
727 726 }
728 727
729 728 tree_info.append(_data)
730 729
731 730 except RepositoryError:
732 731 log.exception("Exception in get_nodes")
733 732 raise
734 733
735 734 return tree_info
736 735
737 736 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
738 737 author=None, trigger_push_hook=True):
739 738 """
740 739 Commits given multiple nodes into repo
741 740
742 741 :param user: RhodeCode User object or user_id, the commiter
743 742 :param repo: RhodeCode Repository object
744 743 :param message: commit message
745 744 :param nodes: mapping {filename:{'content':content},...}
746 745 :param parent_commit: parent commit, can be empty than it's
747 746 initial commit
748 747 :param author: author of commit, cna be different that commiter
749 748 only for git
750 749 :param trigger_push_hook: trigger push hooks
751 750
752 751 :returns: new commited commit
753 752 """
754 753
755 754 user = self._get_user(user)
756 755 scm_instance = repo.scm_instance(cache=False)
757 756
758 757 processed_nodes = []
759 758 for f_path in nodes:
760 759 f_path = self._sanitize_path(f_path)
761 760 content = nodes[f_path]['content']
762 761 f_path = safe_str(f_path)
763 762 # decoding here will force that we have proper encoded values
764 763 # in any other case this will throw exceptions and deny commit
765 764 if isinstance(content, (basestring,)):
766 765 content = safe_str(content)
767 766 elif isinstance(content, (file, cStringIO.OutputType,)):
768 767 content = content.read()
769 768 else:
770 769 raise Exception('Content is of unrecognized type %s' % (
771 770 type(content)
772 771 ))
773 772 processed_nodes.append((f_path, content))
774 773
775 774 message = safe_unicode(message)
776 775 commiter = user.full_contact
777 776 author = safe_unicode(author) if author else commiter
778 777
779 778 imc = scm_instance.in_memory_commit
780 779
781 780 if not parent_commit:
782 781 parent_commit = EmptyCommit(alias=scm_instance.alias)
783 782
784 783 if isinstance(parent_commit, EmptyCommit):
785 784 # EmptyCommit means we we're editing empty repository
786 785 parents = None
787 786 else:
788 787 parents = [parent_commit]
789 788 # add multiple nodes
790 789 for path, content in processed_nodes:
791 790 imc.add(FileNode(path, content=content))
792 791 # TODO: handle pre push scenario
793 792 tip = imc.commit(message=message,
794 793 author=author,
795 794 parents=parents,
796 795 branch=parent_commit.branch)
797 796
798 797 self.mark_for_invalidation(repo.repo_name)
799 798 if trigger_push_hook:
800 799 hooks_utils.trigger_post_push_hook(
801 800 username=user.username, action='push_local',
802 801 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
803 802 hook_type='post_push',
804 803 commit_ids=[tip.raw_id])
805 804 return tip
806 805
807 806 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
808 807 author=None, trigger_push_hook=True):
809 808 user = self._get_user(user)
810 809 scm_instance = repo.scm_instance(cache=False)
811 810
812 811 message = safe_unicode(message)
813 812 commiter = user.full_contact
814 813 author = safe_unicode(author) if author else commiter
815 814
816 815 imc = scm_instance.in_memory_commit
817 816
818 817 if not parent_commit:
819 818 parent_commit = EmptyCommit(alias=scm_instance.alias)
820 819
821 820 if isinstance(parent_commit, EmptyCommit):
822 821 # EmptyCommit means we we're editing empty repository
823 822 parents = None
824 823 else:
825 824 parents = [parent_commit]
826 825
827 826 # add multiple nodes
828 827 for _filename, data in nodes.items():
829 828 # new filename, can be renamed from the old one, also sanitaze
830 829 # the path for any hack around relative paths like ../../ etc.
831 830 filename = self._sanitize_path(data['filename'])
832 831 old_filename = self._sanitize_path(_filename)
833 832 content = data['content']
834 833 file_mode = data.get('mode')
835 834 filenode = FileNode(old_filename, content=content, mode=file_mode)
836 835 op = data['op']
837 836 if op == 'add':
838 837 imc.add(filenode)
839 838 elif op == 'del':
840 839 imc.remove(filenode)
841 840 elif op == 'mod':
842 841 if filename != old_filename:
843 842 # TODO: handle renames more efficient, needs vcs lib changes
844 843 imc.remove(filenode)
845 844 imc.add(FileNode(filename, content=content, mode=file_mode))
846 845 else:
847 846 imc.change(filenode)
848 847
849 848 try:
850 849 # TODO: handle pre push scenario commit changes
851 850 tip = imc.commit(message=message,
852 851 author=author,
853 852 parents=parents,
854 853 branch=parent_commit.branch)
855 854 except NodeNotChangedError:
856 855 raise
857 856 except Exception as e:
858 857 log.exception("Unexpected exception during call to imc.commit")
859 858 raise IMCCommitError(str(e))
860 859 finally:
861 860 # always clear caches, if commit fails we want fresh object also
862 861 self.mark_for_invalidation(repo.repo_name)
863 862
864 863 if trigger_push_hook:
865 864 hooks_utils.trigger_post_push_hook(
866 865 username=user.username, action='push_local', hook_type='post_push',
867 866 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
868 867 commit_ids=[tip.raw_id])
869 868
870 869 return tip
871 870
872 871 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
873 872 author=None, trigger_push_hook=True):
874 873 """
875 874 Deletes given multiple nodes into `repo`
876 875
877 876 :param user: RhodeCode User object or user_id, the committer
878 877 :param repo: RhodeCode Repository object
879 878 :param message: commit message
880 879 :param nodes: mapping {filename:{'content':content},...}
881 880 :param parent_commit: parent commit, can be empty than it's initial
882 881 commit
883 882 :param author: author of commit, cna be different that commiter only
884 883 for git
885 884 :param trigger_push_hook: trigger push hooks
886 885
887 886 :returns: new commit after deletion
888 887 """
889 888
890 889 user = self._get_user(user)
891 890 scm_instance = repo.scm_instance(cache=False)
892 891
893 892 processed_nodes = []
894 893 for f_path in nodes:
895 894 f_path = self._sanitize_path(f_path)
896 895 # content can be empty but for compatabilty it allows same dicts
897 896 # structure as add_nodes
898 897 content = nodes[f_path].get('content')
899 898 processed_nodes.append((f_path, content))
900 899
901 900 message = safe_unicode(message)
902 901 commiter = user.full_contact
903 902 author = safe_unicode(author) if author else commiter
904 903
905 904 imc = scm_instance.in_memory_commit
906 905
907 906 if not parent_commit:
908 907 parent_commit = EmptyCommit(alias=scm_instance.alias)
909 908
910 909 if isinstance(parent_commit, EmptyCommit):
911 910 # EmptyCommit means we we're editing empty repository
912 911 parents = None
913 912 else:
914 913 parents = [parent_commit]
915 914 # add multiple nodes
916 915 for path, content in processed_nodes:
917 916 imc.remove(FileNode(path, content=content))
918 917
919 918 # TODO: handle pre push scenario
920 919 tip = imc.commit(message=message,
921 920 author=author,
922 921 parents=parents,
923 922 branch=parent_commit.branch)
924 923
925 924 self.mark_for_invalidation(repo.repo_name)
926 925 if trigger_push_hook:
927 926 hooks_utils.trigger_post_push_hook(
928 927 username=user.username, action='push_local', hook_type='post_push',
929 928 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
930 929 commit_ids=[tip.raw_id])
931 930 return tip
932 931
933 932 def strip(self, repo, commit_id, branch):
934 933 scm_instance = repo.scm_instance(cache=False)
935 934 scm_instance.config.clear_section('hooks')
936 935 scm_instance.strip(commit_id, branch)
937 936 self.mark_for_invalidation(repo.repo_name)
938 937
939 938 def get_unread_journal(self):
940 939 return self.sa.query(UserLog).count()
941 940
942 941 @classmethod
943 942 def backend_landing_ref(cls, repo_type):
944 943 """
945 944 Return a default landing ref based on a repository type.
946 945 """
947 946
948 947 landing_ref = {
949 948 'hg': ('branch:default', 'default'),
950 949 'git': ('branch:master', 'master'),
951 950 'svn': ('rev:tip', 'latest tip'),
952 951 'default': ('rev:tip', 'latest tip'),
953 952 }
954 953
955 954 return landing_ref.get(repo_type) or landing_ref['default']
956 955
957 956 def get_repo_landing_revs(self, translator, repo=None):
958 957 """
959 958 Generates select option with tags branches and bookmarks (for hg only)
960 959 grouped by type
961 960
962 961 :param repo:
963 962 """
964 963 _ = translator
965 964 repo = self._get_repo(repo)
966 965
967 966 if repo:
968 967 repo_type = repo.repo_type
969 968 else:
970 969 repo_type = 'default'
971 970
972 971 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
973 972
974 973 default_ref_options = [
975 974 [default_landing_ref, landing_ref_lbl]
976 975 ]
977 976 default_choices = [
978 977 default_landing_ref
979 978 ]
980 979
981 980 if not repo:
982 981 return default_choices, default_ref_options
983 982
984 983 repo = repo.scm_instance()
985 984
986 985 ref_options = [('rev:tip', 'latest tip')]
987 986 choices = ['rev:tip']
988 987
989 988 # branches
990 989 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
991 990 if not branch_group:
992 991 # new repo, or without maybe a branch?
993 992 branch_group = default_ref_options
994 993
995 994 branches_group = (branch_group, _("Branches"))
996 995 ref_options.append(branches_group)
997 996 choices.extend([x[0] for x in branches_group[0]])
998 997
999 998 # bookmarks for HG
1000 999 if repo.alias == 'hg':
1001 1000 bookmarks_group = (
1002 1001 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
1003 1002 for b in repo.bookmarks],
1004 1003 _("Bookmarks"))
1005 1004 ref_options.append(bookmarks_group)
1006 1005 choices.extend([x[0] for x in bookmarks_group[0]])
1007 1006
1008 1007 # tags
1009 1008 tags_group = (
1010 1009 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
1011 1010 for t in repo.tags],
1012 1011 _("Tags"))
1013 1012 ref_options.append(tags_group)
1014 1013 choices.extend([x[0] for x in tags_group[0]])
1015 1014
1016 1015 return choices, ref_options
1017 1016
1018 1017 def get_server_info(self, environ=None):
1019 1018 server_info = get_system_info(environ)
1020 1019 return server_info
@@ -1,389 +1,390 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 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 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 34 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 35 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
36 36 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
37 37 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
38 38 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
39 39 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
40 40 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
41 41 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
42 42 pyroutes.register('admin_home', '/_admin', []);
43 43 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
44 44 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
45 45 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
46 46 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
47 47 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
48 48 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
49 49 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
50 50 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
51 51 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
52 52 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
53 53 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
54 54 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
55 55 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
56 56 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
57 57 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
58 58 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
59 59 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
60 60 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
61 61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
62 62 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
63 63 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
64 64 pyroutes.register('admin_settings', '/_admin/settings', []);
65 65 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
66 66 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
67 67 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
68 68 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
69 69 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
70 70 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
71 71 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
72 72 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
73 73 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
74 74 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
75 75 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
76 76 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
77 77 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
78 78 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
79 79 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
80 80 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
81 81 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
82 82 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
83 83 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
84 84 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
85 85 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
86 86 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
87 87 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
88 88 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
89 89 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
90 90 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
91 91 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
92 92 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
93 93 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
94 94 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
95 95 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
96 96 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
97 97 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
98 98 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
99 99 pyroutes.register('users', '/_admin/users', []);
100 100 pyroutes.register('users_data', '/_admin/users_data', []);
101 101 pyroutes.register('users_create', '/_admin/users/create', []);
102 102 pyroutes.register('users_new', '/_admin/users/new', []);
103 103 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
104 104 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
105 105 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
106 106 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
107 107 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
108 108 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
109 109 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
110 110 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 111 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
112 112 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
113 113 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
114 114 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
115 115 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
116 116 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
117 117 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
118 118 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
119 119 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
120 120 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
121 121 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
122 122 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
123 123 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
124 124 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
125 125 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
126 126 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
127 127 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
128 128 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
129 129 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
130 130 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
131 131 pyroutes.register('user_groups', '/_admin/user_groups', []);
132 132 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
133 133 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
134 134 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
135 135 pyroutes.register('repos', '/_admin/repos', []);
136 pyroutes.register('repos_data', '/_admin/repos_data', []);
136 137 pyroutes.register('repo_new', '/_admin/repos/new', []);
137 138 pyroutes.register('repo_create', '/_admin/repos/create', []);
138 139 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
139 140 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
140 141 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
141 142 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
142 143 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
143 144 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
144 145 pyroutes.register('channelstream_proxy', '/_channelstream', []);
145 146 pyroutes.register('upload_file', '/_file_store/upload', []);
146 147 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
147 148 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
148 149 pyroutes.register('logout', '/_admin/logout', []);
149 150 pyroutes.register('reset_password', '/_admin/password_reset', []);
150 151 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
151 152 pyroutes.register('home', '/', []);
152 153 pyroutes.register('user_autocomplete_data', '/_users', []);
153 154 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
154 155 pyroutes.register('repo_list_data', '/_repos', []);
155 156 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
156 157 pyroutes.register('goto_switcher_data', '/_goto_data', []);
157 158 pyroutes.register('markup_preview', '/_markup_preview', []);
158 159 pyroutes.register('file_preview', '/_file_preview', []);
159 160 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
160 161 pyroutes.register('journal', '/_admin/journal', []);
161 162 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
162 163 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
163 164 pyroutes.register('journal_public', '/_admin/public_journal', []);
164 165 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
165 166 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
166 167 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
167 168 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
168 169 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
169 170 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
170 171 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
171 172 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
172 173 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
173 174 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
174 175 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
175 176 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
176 177 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
177 178 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
178 179 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
179 180 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
180 181 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
181 182 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
182 183 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
183 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 185 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
185 186 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
186 187 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
187 188 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
188 189 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 190 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
190 191 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
191 192 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 193 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 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 195 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 196 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
196 197 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 198 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 199 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 200 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 201 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 202 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 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 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 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 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 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 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 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 210 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
210 211 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
211 212 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
212 213 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
213 214 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 215 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
215 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 217 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
217 218 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 219 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
219 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 221 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
221 222 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
222 223 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
223 224 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
224 225 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
225 226 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
226 227 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
227 228 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
228 229 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
229 230 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
230 231 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
231 232 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
232 233 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
233 234 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
234 235 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
235 236 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
236 237 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
237 238 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
238 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 240 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
240 241 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
241 242 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
242 243 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
243 244 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
244 245 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
245 246 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
246 247 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
247 248 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
248 249 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
249 250 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
250 251 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
251 252 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
252 253 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
253 254 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
254 255 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
255 256 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
256 257 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
257 258 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
258 259 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
259 260 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
260 261 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
261 262 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
262 263 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
263 264 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
264 265 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
265 266 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
266 267 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
267 268 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
268 269 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
269 270 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
270 271 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
271 272 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
272 273 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
273 274 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
274 275 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
275 276 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
276 277 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
277 278 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
278 279 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
279 280 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
280 281 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
281 282 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
282 283 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
283 284 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
284 285 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
285 286 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
286 287 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
287 288 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
288 289 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
289 290 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
290 291 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
291 292 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
292 293 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
293 294 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
294 295 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
295 296 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
296 297 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
297 298 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
298 299 pyroutes.register('search', '/_admin/search', []);
299 300 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
300 301 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
301 302 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
302 303 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
303 304 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
304 305 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
305 306 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
306 307 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
307 308 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
308 309 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
309 310 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
310 311 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
311 312 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
312 313 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
313 314 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
314 315 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
315 316 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
316 317 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
317 318 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
318 319 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
319 320 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
320 321 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
321 322 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
322 323 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
323 324 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
324 325 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
325 326 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
326 327 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
327 328 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
328 329 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
329 330 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
330 331 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
331 332 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
332 333 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
333 334 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
334 335 pyroutes.register('gists_show', '/_admin/gists', []);
335 336 pyroutes.register('gists_new', '/_admin/gists/new', []);
336 337 pyroutes.register('gists_create', '/_admin/gists/create', []);
337 338 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
338 339 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
339 340 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
340 341 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
341 342 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
342 343 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
343 344 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
344 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 346 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
346 347 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
347 348 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
348 349 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
349 350 pyroutes.register('apiv2', '/_admin/api', []);
350 351 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
351 352 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
352 353 pyroutes.register('login', '/_admin/login', []);
353 354 pyroutes.register('register', '/_admin/register', []);
354 355 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
355 356 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
356 357 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
357 358 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
358 359 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
359 360 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
360 361 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
361 362 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
362 363 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
363 364 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
364 365 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
365 366 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
366 367 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
367 368 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
368 369 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
369 370 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
370 371 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
371 372 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
372 373 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
373 374 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
374 375 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
375 376 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
376 377 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
377 378 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
378 379 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
379 380 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
380 381 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
381 382 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
382 383 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
383 384 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
384 385 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
385 386 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
386 387 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
387 388 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
388 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 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()"></%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='admin')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.admin_menu(active='repository_groups')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23
24 24 <div class="title">
25 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 28 <ul class="links">
29 29 %if c.can_create_repo_group:
30 30 <li>
31 31 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36 <div id="repos_list_wrap">
37 37 <table id="group_list_table" class="display"></table>
38 38 </div>
39 39 </div>
40 40
41 41 <script>
42 42 $(document).ready(function() {
43 43 var $repoGroupsListTable = $('#group_list_table');
44 44
45 45 // repo group list
46 46 $repoGroupsListTable.DataTable({
47 47 processing: true,
48 48 serverSide: true,
49 49 ajax: {
50 50 "url": "${h.route_path('repo_groups_data')}",
51 51 "dataSrc": function (json) {
52 52 var filteredCount = json.recordsFiltered;
53 53 var filteredInactiveCount = json.recordsFilteredInactive;
54 54 var totalInactive = json.recordsTotalInactive;
55 55 var total = json.recordsTotal;
56 56
57 57 var _text = _gettext(
58 58 "{0} of {1} repository groups").format(
59 59 filteredCount, total);
60 60
61 61 if (total === filteredCount) {
62 62 _text = _gettext("{0} repository groups").format(total);
63 63 }
64 64 $('#repo_group_count').text(_text);
65 65 return json.data;
66 66 },
67 67 },
68 68
69 69 dom: 'rtp',
70 70 pageLength: ${c.visual.admin_grid_items},
71 71 order: [[ 0, "asc" ]],
72 72 columns: [
73 73 { data: {"_": "name",
74 74 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
75 75 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
76 76 { data: {"_": "desc",
77 77 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
78 78 { data: {"_": "last_change",
79 79 "sort": "last_change_raw",
80 80 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
81 81 { data: {"_": "top_level_repos",
82 82 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
83 83 { data: {"_": "owner",
84 84 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
85 85 { data: {"_": "action",
86 86 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
87 87 ],
88 88 language: {
89 89 paginate: DEFAULT_GRID_PAGINATION,
90 90 sProcessing: _gettext('loading...'),
91 91 emptyTable: _gettext("No repository groups available yet.")
92 92 },
93 93 });
94 94
95 95 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
96 96 $repoGroupsListTable.css('opacity', 1);
97 97 });
98 98
99 99 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
100 100 $repoGroupsListTable.css('opacity', 0.3);
101 101 });
102 102
103 103 // filter
104 104 $('#q_filter').on('keyup',
105 105 $.debounce(250, function() {
106 106 $repoGroupsListTable.DataTable().search(
107 107 $('#q_filter').val()
108 108 ).draw();
109 109 })
110 110 );
111
111 112 });
112 113
113 114 </script>
114 115
115 116 </%def>
116 117
@@ -1,105 +1,149 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()"></%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='admin')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.admin_menu(active='repositories')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23
24 24 <div class="title">
25 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 28 <ul class="links">
29 29 %if c.can_create_repo:
30 30 <li>
31 31 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
32 32 </li>
33 33 %endif
34 34 </ul>
35 35 </div>
36 36 <div id="repos_list_wrap">
37 37 <table id="repo_list_table" class="display"></table>
38 38 </div>
39
39 40 </div>
40 41
41 42 <script>
42 43 $(document).ready(function() {
43
44 var get_datatable_count = function(){
45 var api = $('#repo_list_table').dataTable().api();
46 $('#repo_count').text(api.page.info().recordsDisplay);
47 };
48
44 var $repoListTable = $('#repo_list_table');
49 45
50 46 // repo list
51 $('#repo_list_table').DataTable({
52 data: ${c.data|n},
47 $repoListTable.DataTable({
48 processing: true,
49 serverSide: true,
50 ajax: {
51 "url": "${h.route_path('repos_data')}",
52 "dataSrc": function (json) {
53 var filteredCount = json.recordsFiltered;
54 var total = json.recordsTotal;
55
56 var _text = _gettext(
57 "{0} of {1} repositories").format(
58 filteredCount, total);
59
60 if (total === filteredCount) {
61 _text = _gettext("{0} repositories").format(total);
62 }
63 $('#repo_count').text(_text);
64
65 return json.data;
66 },
67 },
53 68 dom: 'rtp',
54 69 pageLength: ${c.visual.admin_grid_items},
55 70 order: [[ 0, "asc" ]],
56 71 columns: [
57 { data: {"_": "name",
58 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
59 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
60 { data: {"_": "desc",
61 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
62 { data: {"_": "last_change",
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",
63 89 "sort": "last_change_raw",
64 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
65 { data: {"_": "last_changeset",
90 "type": Number
91 }, title: "${_('Last Change')}", className: "td-time"
92 },
93 {
94 data: {
95 "_": "last_changeset",
66 96 "sort": "last_changeset_raw",
67 "type": Number}, title: "${_('Commit')}", className: "td-commit" },
68 { data: {"_": "owner",
69 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
70 { data: {"_": "state",
71 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
72 { data: {"_": "action",
73 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
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 }
74 118 ],
75 119 language: {
76 120 paginate: DEFAULT_GRID_PAGINATION,
77 emptyTable:_gettext("No repositories available yet.")
121 sProcessing: _gettext('loading...'),
122 emptyTable:_gettext("No repositories present.")
78 123 },
79 124 "initComplete": function( settings, json ) {
80 get_datatable_count();
81 125 quick_repo_menu();
82 126 }
83 127 });
84 128
85 // update the counter when doing search
86 $('#repo_list_table').on( 'search.dt', function (e,settings) {
87 get_datatable_count();
129 $repoListTable.on('xhr.dt', function(e, settings, json, xhr){
130 $repoListTable.css('opacity', 1);
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
91 $('#q_filter').on( 'keyup', function () {
92 var repo_api = $('#repo_list_table').dataTable().api();
93 repo_api
94 .columns(0)
95 .search(this.value)
96 .draw();
97 });
137 $('#q_filter').on('keyup',
138 $.debounce(250, function() {
139 $repoListTable.DataTable().search(
140 $('#q_filter').val()
141 ).draw();
142 })
143 );
98 144
99 // refilter table if page load via back button
100 $("#q_filter").trigger('keyup');
101 145 });
102 146
103 147 </script>
104 148
105 149 </%def>
@@ -1,372 +1,372 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 7 <% section_to_label = {
8 8 'global': 'Global Permissions',
9 9 'repository_branches': 'Repository Branch Rules',
10 10 'repositories': 'Repository Access Permissions',
11 11 'user_groups': 'User Group Permissions',
12 12 'repositories_groups': 'Repository Group Permissions',
13 13 } %>
14 14
15 15 <div id="perms" class="table fields">
16 16 %for section in sorted(permissions.keys(), key=lambda item: {'global': 0, 'repository_branches': 1}.get(item, 1000)):
17 17 <% total_counter = 0 %>
18 18
19 19 <div class="panel panel-default">
20 20 <div class="panel-heading" id="${section.replace("_","-")}-permissions">
21 21 <h3 class="panel-title">${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
22 22 <a class="permalink" href="#${section.replace("_","-")}-permissions"> ΒΆ</a>
23 23 </h3>
24 24 % if side_link:
25 25 <div class="pull-right">
26 26 <a href="${side_link}">${_('in JSON format')}</a>
27 27 </div>
28 28 % endif
29 29 </div>
30 30 <div class="panel-body">
31 31 <div class="perms_section_head field">
32 32 <div class="radios">
33 33 % if section == 'repository_branches':
34 34 <span class="permissions_boxes">
35 35 <span class="desc">${_('show')}: </span>
36 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 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 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 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 40 </span>
41 41 % elif section != 'global':
42 42 <span class="permissions_boxes">
43 43 <span class="desc">${_('show')}: </span>
44 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 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 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 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 48 </span>
49 49 % endif
50 50
51 51 </div>
52 52 </div>
53 53 <div class="field">
54 54 %if not permissions[section]:
55 55 <p class="empty_data help-block">${_('No permissions defined')}</p>
56 56 %else:
57 57 <div id='tbl_list_wrap_${section}'>
58 58 <table id="tbl_list_${section}" class="rctable">
59 59 ## global permission box
60 60 %if section == 'global':
61 61 <thead>
62 62 <tr>
63 63 <th colspan="2" class="left">${_('Permission')}</th>
64 64 %if actions:
65 65 <th colspan="2">${_('Edit Permission')}</th>
66 66 %endif
67 67 </thead>
68 68 <tbody>
69 69
70 70 <%
71 71 def get_section_perms(prefix, opts):
72 72 _selected = []
73 73 for op in opts:
74 74 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
75 75 _selected.append(op)
76 76 admin = 'hg.admin' in opts
77 77 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
78 78 return admin, _selected_vals, _selected
79 79 %>
80 80
81 81 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
82 82 <tr>
83 83 <td class="td-tags">
84 84 ${lbl}
85 85 </td>
86 86 <td class="td-tags">
87 87 %if val[0]:
88 88 %if not val_lbl:
89 89 ## super-admin case
90 90 True
91 91 %else:
92 92 <span class="perm_tag admin">${val_lbl}.admin</span>
93 93 %endif
94 94 %else:
95 95 %if not val_lbl:
96 96 ${{'false': False,
97 97 'true': True,
98 98 'none': False,
99 99 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')}
100 100 %else:
101 101 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
102 102 %endif
103 103 %endif
104 104 </td>
105 105 %if actions:
106 106
107 107 % if edit_url or edit_global_url:
108 108
109 109 <td class="td-action">
110 110 % if edit_url:
111 111 <a href="${edit_url}">${_('edit')}</a>
112 112 % else:
113 113 -
114 114 % endif
115 115 </td>
116 116
117 117 <td class="td-action">
118 118 % if edit_global_url:
119 119 <a href="${edit_global_url}">${_('edit global')}</a>
120 120 % else:
121 121 -
122 122 % endif
123 123 </td>
124 124
125 125 % else:
126 126 <td class="td-action"></td>
127 127 <td class="td-action">
128 128 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
129 129 <td class="td-action">
130 130 % endif
131 131
132 132 %endif
133 133 </tr>
134 134 </%def>
135 135
136 136 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
137 137 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
138 138
139 139 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
140 140 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
141 141
142 142 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
143 143 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
144 144
145 145 ${glob(_('Super-admin'), get_section_perms('hg.admin', permissions[section]),
146 146 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
147 147
148 148 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
149 149 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
150 150
151 151 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
152 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 154 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
155 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 157 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
158 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 160 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
161 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 163 </tbody>
164 164 ## Branch perms
165 165 %elif section == 'repository_branches':
166 166 <thead>
167 167 <tr>
168 168 <th>${_('Name')}</th>
169 169 <th>${_('Pattern')}</th>
170 170 <th>${_('Permission')}</th>
171 171 %if actions:
172 172 <th>${_('Edit Branch Permission')}</th>
173 173 %endif
174 174 </thead>
175 175 <tbody class="section_${section}">
176 176 <%
177 177 def name_sorter(permissions):
178 178 def custom_sorter(item):
179 179 return item[0]
180 180 return sorted(permissions, key=custom_sorter)
181 181
182 182 def branch_sorter(permissions):
183 183 def custom_sorter(item):
184 184 ## none, merge, push, push_force
185 185 section = item[1].split('.')[-1]
186 186 section_importance = {'none': u'0',
187 187 'merge': u'1',
188 188 'push': u'2',
189 189 'push_force': u'3'}.get(section)
190 190 ## sort by importance + name
191 191 return section_importance + item[0]
192 192 return sorted(permissions, key=custom_sorter)
193 193 %>
194 194 %for k, section_perms in name_sorter(permissions[section].items()):
195 195 ## for display purposes, for non super-admins we need to check if shown
196 196 ## repository is actually accessible for user
197 197 <% repo_perm = permissions['repositories'][k] %>
198 198 % if repo_perm == 'repository.none' and not c.rhodecode_user.is_admin:
199 199 ## skip this entry
200 200 <% continue %>
201 201 % endif
202 202
203 203 <% total_counter +=1 %>
204 204 % for pattern, perm in branch_sorter(section_perms.items()):
205 205 <tr class="perm_row ${'{}_{}'.format(section, perm.split('.')[-1])}">
206 206 <td class="td-name">
207 207 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
208 208 </td>
209 209 <td>${pattern}</td>
210 210 <td class="td-tags">
211 211 ## TODO: calculate origin somehow
212 212 ## % for i, ((_pat, perm), origin) in enumerate((permissions[section].perm_origin_stack[k])):
213 213
214 214 <div>
215 215 <% i = 0 %>
216 216 <% origin = 'unknown' %>
217 217 <% _css_class = i > 0 and 'perm_overriden' or '' %>
218 218
219 219 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
220 220 ${perm}
221 221 ##(${origin})
222 222 </span>
223 223 </div>
224 224 ## % endfor
225 225 </td>
226 226 %if actions:
227 227 <td class="td-action">
228 228 <a href="${h.route_path('edit_repo_perms_branch',repo_name=k)}">${_('edit')}</a>
229 229 </td>
230 230 %endif
231 231 </tr>
232 232 % endfor
233 233 %endfor
234 234 </tbody>
235 235
236 236 ## Repos/Repo Groups/users groups perms
237 237 %else:
238 238
239 239 ## none/read/write/admin permissions on groups/repos etc
240 240 <thead>
241 241 <tr>
242 242 <th>${_('Name')}</th>
243 243 <th>${_('Permission')}</th>
244 244 %if actions:
245 245 <th>${_('Edit Permission')}</th>
246 246 %endif
247 247 </thead>
248 248 <tbody class="section_${section}">
249 249 <%
250 250 def sorter(permissions):
251 251 def custom_sorter(item):
252 252 ## read/write/admin
253 253 section = item[1].split('.')[-1]
254 254 section_importance = {'none': u'0',
255 255 'read': u'1',
256 256 'write':u'2',
257 257 'admin':u'3'}.get(section)
258 258 ## sort by group importance+name
259 259 return section_importance+item[0]
260 260 return sorted(permissions, key=custom_sorter)
261 261 %>
262 262 %for k, section_perm in sorter(permissions[section].items()):
263 263 <% perm_value = section_perm.split('.')[-1] %>
264 264 <% _css_class = 'display:none' if perm_value in ['none'] else '' %>
265 265
266 266 %if perm_value != 'none' or show_all:
267 267 <tr class="perm_row ${'{}_{}'.format(section, section_perm.split('.')[-1])}" style="${_css_class}">
268 268 <td class="td-name">
269 269 %if section == 'repositories':
270 270 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
271 271 %elif section == 'repositories_groups':
272 272 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
273 273 %elif section == 'user_groups':
274 274 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
275 275 ${k}
276 276 %endif
277 277 </td>
278 278 <td class="td-tags">
279 279 %if hasattr(permissions[section], 'perm_origin_stack'):
280 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 282 <% _css_class = i > 0 and 'perm_overriden' or '' %>
283 283 % if i > 0:
284 284 <div style="color: #979797">
285 285 <i class="icon-arrow_up"></i>
286 286 ${_('overridden by')}
287 287 <i class="icon-arrow_up"></i>
288 288 </div>
289 289 % endif
290 290
291 291 <div>
292 292 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
293 293 ${perm} (${origin})
294 294 </span>
295 295 </div>
296 296
297 297 %endfor
298 298 </div>
299 299 %else:
300 300 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
301 301 %endif
302 302 </td>
303 303 %if actions:
304 304 <td class="td-action">
305 305 %if section == 'repositories':
306 306 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
307 307 %elif section == 'repositories_groups':
308 308 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
309 309 %elif section == 'user_groups':
310 310 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
311 311 %endif
312 312 </td>
313 313 %endif
314 314 </tr>
315 315 <% total_counter +=1 %>
316 316 %endif
317 317
318 318 %endfor
319 319
320 320 <tr id="empty_${section}" class="noborder" style="display:none;">
321 321 <td colspan="6">${_('No matching permission defined')}</td>
322 322 </tr>
323 323
324 324 </tbody>
325 325 %endif
326 326 </table>
327 327 </div>
328 328 %endif
329 329 </div>
330 330 </div>
331 331 </div>
332 332
333 333 <script>
334 334 $('#total_count_${section}').html(${total_counter})
335 335 </script>
336 336
337 337 %endfor
338 338 </div>
339 339
340 340 <script>
341 341 $(document).ready(function(){
342 342 var showEmpty = function(section){
343 343 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
344 344 if(visible === 0){
345 345 $('#empty_{0}'.format(section)).show();
346 346 }
347 347 else{
348 348 $('#empty_{0}'.format(section)).hide();
349 349 }
350 350 };
351 351
352 352 $('.perm_filter').on('change', function(e){
353 353 var self = this;
354 354 var section = $(this).attr('section');
355 355
356 356 var opts = {};
357 357 var elems = $('.filter_' + section).each(function(el){
358 358 var perm_type = $(this).attr('perm_type');
359 359 var checked = this.checked;
360 360 opts[perm_type] = checked;
361 361 if(checked){
362 362 $('.'+section+'_'+perm_type).show();
363 363 }
364 364 else{
365 365 $('.'+section+'_'+perm_type).hide();
366 366 }
367 367 });
368 368 showEmpty(section);
369 369 })
370 370 })
371 371 </script>
372 372 </%def>
@@ -1,632 +1,632 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 from hashlib import sha1
23 23
24 24 import pytest
25 25 from mock import patch
26 26
27 27 from rhodecode.lib import auth
28 28 from rhodecode.lib.utils2 import md5
29 29 from rhodecode.model.auth_token import AuthTokenModel
30 30 from rhodecode.model.db import Session, User
31 31 from rhodecode.model.repo import RepoModel
32 32 from rhodecode.model.user import UserModel
33 33 from rhodecode.model.user_group import UserGroupModel
34 34
35 35
36 36 def test_perm_origin_dict():
37 37 pod = auth.PermOriginDict()
38 pod['thing'] = 'read', 'default'
38 pod['thing'] = 'read', 'default', 1
39 39 assert pod['thing'] == 'read'
40 40
41 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 45 assert pod['thing'] == 'write'
46 46
47 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 52 assert pod.perm_origin_stack == {
53 'other': [('write', 'default')],
54 'thing': [('read', 'default'), ('write', 'admin')]}
53 'other': [('write', 'default', 8)],
54 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
55 55
56 pod['other'] = 'none', 'override'
56 pod['other'] = 'none', 'override', 8
57 57
58 58 assert pod.perm_origin_stack == {
59 'other': [('write', 'default'), ('none', 'override')],
60 'thing': [('read', 'default'), ('write', 'admin')]}
59 'other': [('write', 'default', 8), ('none', 'override', 8)],
60 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
61 61
62 62 with pytest.raises(ValueError):
63 63 pod['thing'] = 'read'
64 64
65 65
66 66 def test_cached_perms_data(user_regular, backend_random):
67 67 permissions = get_permissions(user_regular)
68 68 repo_name = backend_random.repo.repo_name
69 69 expected_global_permissions = {
70 70 'repository.read', 'group.read', 'usergroup.read'}
71 71 assert expected_global_permissions.issubset(permissions['global'])
72 72 assert permissions['repositories'][repo_name] == 'repository.read'
73 73
74 74
75 75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
76 76 permissions = get_permissions(user_regular, user_is_admin=True)
77 77 repo_name = backend_random.repo.repo_name
78 78 assert 'hg.admin' in permissions['global']
79 79 assert permissions['repositories'][repo_name] == 'repository.admin'
80 80
81 81
82 82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
83 83 permissions = get_permissions(user_regular, user_is_admin=True,
84 84 calculate_super_admin=True)
85 85 repo_name = backend_random.repo.repo_name
86 86 assert 'hg.admin' in permissions['global']
87 87 assert permissions['repositories'][repo_name] == 'repository.admin'
88 88
89 89
90 90 def test_cached_perms_data_user_group_global_permissions(user_util):
91 91 user, user_group = user_util.create_user_with_group()
92 92 user_group.inherit_default_permissions = False
93 93
94 94 granted_permission = 'repository.write'
95 95 UserGroupModel().grant_perm(user_group, granted_permission)
96 96 Session().commit()
97 97
98 98 permissions = get_permissions(user)
99 99 assert granted_permission in permissions['global']
100 100
101 101
102 102 @pytest.mark.xfail(reason="Not implemented, see TODO note")
103 103 def test_cached_perms_data_user_group_global_permissions_(user_util):
104 104 user, user_group = user_util.create_user_with_group()
105 105
106 106 granted_permission = 'repository.write'
107 107 UserGroupModel().grant_perm(user_group, granted_permission)
108 108 Session().commit()
109 109
110 110 permissions = get_permissions(user)
111 111 assert granted_permission in permissions['global']
112 112
113 113
114 114 def test_cached_perms_data_user_global_permissions(user_util):
115 115 user = user_util.create_user()
116 116 UserModel().grant_perm(user, 'repository.none')
117 117 Session().commit()
118 118
119 119 permissions = get_permissions(user, user_inherit_default_permissions=True)
120 120 assert 'repository.read' in permissions['global']
121 121
122 122
123 123 def test_cached_perms_data_repository_permissions_on_private_repository(
124 124 backend_random, user_util):
125 125 user, user_group = user_util.create_user_with_group()
126 126
127 127 repo = backend_random.create_repo()
128 128 repo.private = True
129 129
130 130 granted_permission = 'repository.write'
131 131 RepoModel().grant_user_group_permission(
132 132 repo, user_group.users_group_name, granted_permission)
133 133 Session().commit()
134 134
135 135 permissions = get_permissions(user)
136 136 assert permissions['repositories'][repo.repo_name] == granted_permission
137 137
138 138
139 139 def test_cached_perms_data_repository_permissions_for_owner(
140 140 backend_random, user_util):
141 141 user = user_util.create_user()
142 142
143 143 repo = backend_random.create_repo()
144 144 repo.user_id = user.user_id
145 145
146 146 permissions = get_permissions(user)
147 147 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
148 148
149 149 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
150 150 repo.user_id = User.get_default_user().user_id
151 151
152 152
153 153 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
154 154 backend_random, user_util):
155 155 user = user_util.create_user()
156 156 repo = backend_random.create_repo()
157 157
158 158 # Don't inherit default object permissions
159 159 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
160 160 Session().commit()
161 161
162 162 permissions = get_permissions(user)
163 163 assert permissions['repositories'][repo.repo_name] == 'repository.none'
164 164
165 165
166 166 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
167 167 # Have a repository group with default permissions set
168 168 repo_group = user_util.create_repo_group()
169 169 default_user = User.get_default_user()
170 170 user_util.grant_user_permission_to_repo_group(
171 171 repo_group, default_user, 'repository.write')
172 172 user = user_util.create_user()
173 173
174 174 permissions = get_permissions(user)
175 175 assert permissions['repositories_groups'][repo_group.group_name] == \
176 176 'repository.write'
177 177
178 178
179 179 def test_cached_perms_data_default_permissions_on_repository_group_owner(
180 180 user_util):
181 181 # Have a repository group
182 182 repo_group = user_util.create_repo_group()
183 183 default_user = User.get_default_user()
184 184
185 185 # Add a permission for the default user to hit the code path
186 186 user_util.grant_user_permission_to_repo_group(
187 187 repo_group, default_user, 'repository.write')
188 188
189 189 # Have an owner of the group
190 190 user = user_util.create_user()
191 191 repo_group.user_id = user.user_id
192 192
193 193 permissions = get_permissions(user)
194 194 assert permissions['repositories_groups'][repo_group.group_name] == \
195 195 'group.admin'
196 196
197 197
198 198 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
199 199 user_util):
200 200 # Have a repository group
201 201 repo_group = user_util.create_repo_group()
202 202 default_user = User.get_default_user()
203 203
204 204 # Add a permission for the default user to hit the code path
205 205 user_util.grant_user_permission_to_repo_group(
206 206 repo_group, default_user, 'repository.write')
207 207
208 208 # Don't inherit default object permissions
209 209 user = user_util.create_user()
210 210 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
211 211 Session().commit()
212 212
213 213 permissions = get_permissions(user)
214 214 assert permissions['repositories_groups'][repo_group.group_name] == \
215 215 'group.none'
216 216
217 217
218 218 def test_cached_perms_data_repository_permissions_from_user_group(
219 219 user_util, backend_random):
220 220 user, user_group = user_util.create_user_with_group()
221 221
222 222 # Needs a second user group to make sure that we select the right
223 223 # permissions.
224 224 user_group2 = user_util.create_user_group()
225 225 UserGroupModel().add_user_to_group(user_group2, user)
226 226
227 227 repo = backend_random.create_repo()
228 228
229 229 RepoModel().grant_user_group_permission(
230 230 repo, user_group.users_group_name, 'repository.read')
231 231 RepoModel().grant_user_group_permission(
232 232 repo, user_group2.users_group_name, 'repository.write')
233 233 Session().commit()
234 234
235 235 permissions = get_permissions(user)
236 236 assert permissions['repositories'][repo.repo_name] == 'repository.write'
237 237
238 238
239 239 def test_cached_perms_data_repository_permissions_from_user_group_owner(
240 240 user_util, backend_random):
241 241 user, user_group = user_util.create_user_with_group()
242 242
243 243 repo = backend_random.create_repo()
244 244 repo.user_id = user.user_id
245 245
246 246 RepoModel().grant_user_group_permission(
247 247 repo, user_group.users_group_name, 'repository.write')
248 248 Session().commit()
249 249
250 250 permissions = get_permissions(user)
251 251 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
252 252
253 253
254 254 def test_cached_perms_data_user_repository_permissions(
255 255 user_util, backend_random):
256 256 user = user_util.create_user()
257 257 repo = backend_random.create_repo()
258 258 granted_permission = 'repository.write'
259 259 RepoModel().grant_user_permission(repo, user, granted_permission)
260 260 Session().commit()
261 261
262 262 permissions = get_permissions(user)
263 263 assert permissions['repositories'][repo.repo_name] == granted_permission
264 264
265 265
266 266 def test_cached_perms_data_user_repository_permissions_explicit(
267 267 user_util, backend_random):
268 268 user = user_util.create_user()
269 269 repo = backend_random.create_repo()
270 270 granted_permission = 'repository.none'
271 271 RepoModel().grant_user_permission(repo, user, granted_permission)
272 272 Session().commit()
273 273
274 274 permissions = get_permissions(user, explicit=True)
275 275 assert permissions['repositories'][repo.repo_name] == granted_permission
276 276
277 277
278 278 def test_cached_perms_data_user_repository_permissions_owner(
279 279 user_util, backend_random):
280 280 user = user_util.create_user()
281 281 repo = backend_random.create_repo()
282 282 repo.user_id = user.user_id
283 283 RepoModel().grant_user_permission(repo, user, 'repository.write')
284 284 Session().commit()
285 285
286 286 permissions = get_permissions(user)
287 287 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
288 288
289 289
290 290 def test_cached_perms_data_repository_groups_permissions_inherited(
291 291 user_util, backend_random):
292 292 user, user_group = user_util.create_user_with_group()
293 293
294 294 # Needs a second group to hit the last condition
295 295 user_group2 = user_util.create_user_group()
296 296 UserGroupModel().add_user_to_group(user_group2, user)
297 297
298 298 repo_group = user_util.create_repo_group()
299 299
300 300 user_util.grant_user_group_permission_to_repo_group(
301 301 repo_group, user_group, 'group.read')
302 302 user_util.grant_user_group_permission_to_repo_group(
303 303 repo_group, user_group2, 'group.write')
304 304
305 305 permissions = get_permissions(user)
306 306 assert permissions['repositories_groups'][repo_group.group_name] == \
307 307 'group.write'
308 308
309 309
310 310 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
311 311 user_util, backend_random):
312 312 user, user_group = user_util.create_user_with_group()
313 313 repo_group = user_util.create_repo_group()
314 314 repo_group.user_id = user.user_id
315 315
316 316 granted_permission = 'group.write'
317 317 user_util.grant_user_group_permission_to_repo_group(
318 318 repo_group, user_group, granted_permission)
319 319
320 320 permissions = get_permissions(user)
321 321 assert permissions['repositories_groups'][repo_group.group_name] == \
322 322 'group.admin'
323 323
324 324
325 325 def test_cached_perms_data_repository_groups_permissions(
326 326 user_util, backend_random):
327 327 user = user_util.create_user()
328 328
329 329 repo_group = user_util.create_repo_group()
330 330
331 331 granted_permission = 'group.write'
332 332 user_util.grant_user_permission_to_repo_group(
333 333 repo_group, user, granted_permission)
334 334
335 335 permissions = get_permissions(user)
336 336 assert permissions['repositories_groups'][repo_group.group_name] == \
337 337 'group.write'
338 338
339 339
340 340 def test_cached_perms_data_repository_groups_permissions_explicit(
341 341 user_util, backend_random):
342 342 user = user_util.create_user()
343 343
344 344 repo_group = user_util.create_repo_group()
345 345
346 346 granted_permission = 'group.none'
347 347 user_util.grant_user_permission_to_repo_group(
348 348 repo_group, user, granted_permission)
349 349
350 350 permissions = get_permissions(user, explicit=True)
351 351 assert permissions['repositories_groups'][repo_group.group_name] == \
352 352 'group.none'
353 353
354 354
355 355 def test_cached_perms_data_repository_groups_permissions_owner(
356 356 user_util, backend_random):
357 357 user = user_util.create_user()
358 358
359 359 repo_group = user_util.create_repo_group()
360 360 repo_group.user_id = user.user_id
361 361
362 362 granted_permission = 'group.write'
363 363 user_util.grant_user_permission_to_repo_group(
364 364 repo_group, user, granted_permission)
365 365
366 366 permissions = get_permissions(user)
367 367 assert permissions['repositories_groups'][repo_group.group_name] == \
368 368 'group.admin'
369 369
370 370
371 371 def test_cached_perms_data_user_group_permissions_inherited(
372 372 user_util, backend_random):
373 373 user, user_group = user_util.create_user_with_group()
374 374 user_group2 = user_util.create_user_group()
375 375 UserGroupModel().add_user_to_group(user_group2, user)
376 376
377 377 target_user_group = user_util.create_user_group()
378 378
379 379 user_util.grant_user_group_permission_to_user_group(
380 380 target_user_group, user_group, 'usergroup.read')
381 381 user_util.grant_user_group_permission_to_user_group(
382 382 target_user_group, user_group2, 'usergroup.write')
383 383
384 384 permissions = get_permissions(user)
385 385 assert permissions['user_groups'][target_user_group.users_group_name] == \
386 386 'usergroup.write'
387 387
388 388
389 389 def test_cached_perms_data_user_group_permissions(
390 390 user_util, backend_random):
391 391 user = user_util.create_user()
392 392 user_group = user_util.create_user_group()
393 393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
394 394 Session().commit()
395 395
396 396 permissions = get_permissions(user)
397 397 assert permissions['user_groups'][user_group.users_group_name] == \
398 398 'usergroup.write'
399 399
400 400
401 401 def test_cached_perms_data_user_group_permissions_explicit(
402 402 user_util, backend_random):
403 403 user = user_util.create_user()
404 404 user_group = user_util.create_user_group()
405 405 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
406 406 Session().commit()
407 407
408 408 permissions = get_permissions(user, explicit=True)
409 409 assert permissions['user_groups'][user_group.users_group_name] == \
410 410 'usergroup.none'
411 411
412 412
413 413 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
414 414 user_util, backend_random):
415 415 user = user_util.create_user()
416 416 user_group = user_util.create_user_group()
417 417
418 418 # Don't inherit default object permissions
419 419 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
420 420 Session().commit()
421 421
422 422 permissions = get_permissions(user)
423 423 assert permissions['user_groups'][user_group.users_group_name] == \
424 424 'usergroup.none'
425 425
426 426
427 427 def test_permission_calculator_admin_permissions(
428 428 user_util, backend_random):
429 429 user = user_util.create_user()
430 430 user_group = user_util.create_user_group()
431 431 repo = backend_random.repo
432 432 repo_group = user_util.create_repo_group()
433 433
434 434 calculator = auth.PermissionCalculator(
435 435 user.user_id, {}, False, False, True, 'higherwin')
436 436 permissions = calculator._calculate_admin_permissions()
437 437
438 438 assert permissions['repositories_groups'][repo_group.group_name] == \
439 439 'group.admin'
440 440 assert permissions['user_groups'][user_group.users_group_name] == \
441 441 'usergroup.admin'
442 442 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
443 443 assert 'hg.admin' in permissions['global']
444 444
445 445
446 446 def test_permission_calculator_repository_permissions_robustness_from_group(
447 447 user_util, backend_random):
448 448 user, user_group = user_util.create_user_with_group()
449 449
450 450 RepoModel().grant_user_group_permission(
451 451 backend_random.repo, user_group.users_group_name, 'repository.write')
452 452
453 453 calculator = auth.PermissionCalculator(
454 454 user.user_id, {}, False, False, False, 'higherwin')
455 455 calculator._calculate_repository_permissions()
456 456
457 457
458 458 def test_permission_calculator_repository_permissions_robustness_from_user(
459 459 user_util, backend_random):
460 460 user = user_util.create_user()
461 461
462 462 RepoModel().grant_user_permission(
463 463 backend_random.repo, user, 'repository.write')
464 464 Session().commit()
465 465
466 466 calculator = auth.PermissionCalculator(
467 467 user.user_id, {}, False, False, False, 'higherwin')
468 468 calculator._calculate_repository_permissions()
469 469
470 470
471 471 def test_permission_calculator_repo_group_permissions_robustness_from_group(
472 472 user_util, backend_random):
473 473 user, user_group = user_util.create_user_with_group()
474 474 repo_group = user_util.create_repo_group()
475 475
476 476 user_util.grant_user_group_permission_to_repo_group(
477 477 repo_group, user_group, 'group.write')
478 478
479 479 calculator = auth.PermissionCalculator(
480 480 user.user_id, {}, False, False, False, 'higherwin')
481 481 calculator._calculate_repository_group_permissions()
482 482
483 483
484 484 def test_permission_calculator_repo_group_permissions_robustness_from_user(
485 485 user_util, backend_random):
486 486 user = user_util.create_user()
487 487 repo_group = user_util.create_repo_group()
488 488
489 489 user_util.grant_user_permission_to_repo_group(
490 490 repo_group, user, 'group.write')
491 491
492 492 calculator = auth.PermissionCalculator(
493 493 user.user_id, {}, False, False, False, 'higherwin')
494 494 calculator._calculate_repository_group_permissions()
495 495
496 496
497 497 def test_permission_calculator_user_group_permissions_robustness_from_group(
498 498 user_util, backend_random):
499 499 user, user_group = user_util.create_user_with_group()
500 500 target_user_group = user_util.create_user_group()
501 501
502 502 user_util.grant_user_group_permission_to_user_group(
503 503 target_user_group, user_group, 'usergroup.write')
504 504
505 505 calculator = auth.PermissionCalculator(
506 506 user.user_id, {}, False, False, False, 'higherwin')
507 507 calculator._calculate_user_group_permissions()
508 508
509 509
510 510 def test_permission_calculator_user_group_permissions_robustness_from_user(
511 511 user_util, backend_random):
512 512 user = user_util.create_user()
513 513 target_user_group = user_util.create_user_group()
514 514
515 515 user_util.grant_user_permission_to_user_group(
516 516 target_user_group, user, 'usergroup.write')
517 517
518 518 calculator = auth.PermissionCalculator(
519 519 user.user_id, {}, False, False, False, 'higherwin')
520 520 calculator._calculate_user_group_permissions()
521 521
522 522
523 523 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
524 524 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
525 525 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
526 526 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
527 527 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
528 528 ])
529 529 def test_permission_calculator_choose_permission(
530 530 user_regular, algo, new_permission, old_permission, expected):
531 531 calculator = auth.PermissionCalculator(
532 532 user_regular.user_id, {}, False, False, False, algo)
533 533 result = calculator._choose_permission(new_permission, old_permission)
534 534 assert result == expected
535 535
536 536
537 537 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
538 538 user_regular):
539 539 calculator = auth.PermissionCalculator(
540 540 user_regular.user_id, {}, False, False, False, 'invalid')
541 541 result = calculator._choose_permission(
542 542 'repository.read', 'repository.read')
543 543 # TODO: johbo: This documents the existing behavior. Think of an
544 544 # improvement.
545 545 assert result is None
546 546
547 547
548 548 def test_auth_user_get_cookie_store_for_normal_user(user_util):
549 549 user = user_util.create_user()
550 550 auth_user = auth.AuthUser(user_id=user.user_id)
551 551 expected_data = {
552 552 'username': user.username,
553 553 'user_id': user.user_id,
554 554 'password': md5(user.password),
555 555 'is_authenticated': False
556 556 }
557 557 assert auth_user.get_cookie_store() == expected_data
558 558
559 559
560 560 def test_auth_user_get_cookie_store_for_default_user():
561 561 default_user = User.get_default_user()
562 562 auth_user = auth.AuthUser()
563 563 expected_data = {
564 564 'username': User.DEFAULT_USER,
565 565 'user_id': default_user.user_id,
566 566 'password': md5(default_user.password),
567 567 'is_authenticated': True
568 568 }
569 569 assert auth_user.get_cookie_store() == expected_data
570 570
571 571
572 572 def get_permissions(user, **kwargs):
573 573 """
574 574 Utility filling in useful defaults into the call to `_cached_perms_data`.
575 575
576 576 Fill in `**kwargs` if specific values are needed for a test.
577 577 """
578 578 call_args = {
579 579 'user_id': user.user_id,
580 580 'scope': {},
581 581 'user_is_admin': False,
582 582 'user_inherit_default_permissions': False,
583 583 'explicit': False,
584 584 'algo': 'higherwin',
585 585 'calculate_super_admin': False,
586 586 }
587 587 call_args.update(kwargs)
588 588 permissions = auth._cached_perms_data(**call_args)
589 589 return permissions
590 590
591 591
592 592 class TestGenerateAuthToken(object):
593 593 def test_salt_is_used_when_specified(self):
594 594 salt = 'abcde'
595 595 user_name = 'test_user'
596 596 result = auth.generate_auth_token(user_name, salt)
597 597 expected_result = sha1(user_name + salt).hexdigest()
598 598 assert result == expected_result
599 599
600 600 def test_salt_is_geneated_when_not_specified(self):
601 601 user_name = 'test_user'
602 602 random_salt = os.urandom(16)
603 603 with patch.object(auth, 'os') as os_mock:
604 604 os_mock.urandom.return_value = random_salt
605 605 result = auth.generate_auth_token(user_name)
606 606 expected_result = sha1(user_name + random_salt).hexdigest()
607 607 assert result == expected_result
608 608
609 609
610 610 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
611 611 ('', None, False,
612 612 []),
613 613 ('wrongtoken', None, False,
614 614 []),
615 615 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
616 616 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
617 617 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
618 618 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
619 619 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
620 620 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
621 621 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
622 622 ])
623 623 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
624 624 user_util):
625 625 user = user_util.create_user()
626 626 user_id = user.user_id
627 627 for token, role, expires in expected_tokens:
628 628 new_token = AuthTokenModel().create(user_id, u'test-token', expires, role)
629 629 new_token.api_key = token # inject known name for testing...
630 630
631 631 assert auth_result == user.authenticate_by_token(
632 632 test_token, roles=test_roles)
General Comments 0
You need to be logged in to leave comments. Login now