##// END OF EJS Templates
routing: skip advanced delete for repo creating so we can remove stuck repositories.
marcink -
r2913:3474cae9 default
parent child Browse files
Show More
@@ -1,638 +1,645 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 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 time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 26
27 27 from rhodecode.lib import helpers as h, diffs
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 32 from rhodecode.model import user_group
33 33 from rhodecode.model import user
34 34 from rhodecode.model.db import User
35 35 from rhodecode.model.scm import ScmModel
36 36 from rhodecode.model.settings import VcsSettingsModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 ADMIN_PREFIX = '/_admin'
42 42 STATIC_FILE_PREFIX = '/_static'
43 43
44 44 URL_NAME_REQUIREMENTS = {
45 45 # group name can have a slash in them, but they must not end with a slash
46 46 'group_name': r'.*?[^/]',
47 47 'repo_group_name': r'.*?[^/]',
48 48 # repo names can have a slash in them, but they must not end with a slash
49 49 'repo_name': r'.*?[^/]',
50 50 # file path eats up everything at the end
51 51 'f_path': r'.*',
52 52 # reference types
53 53 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
54 54 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
55 55 }
56 56
57 57
58 58 def add_route_with_slash(config,name, pattern, **kw):
59 59 config.add_route(name, pattern, **kw)
60 60 if not pattern.endswith('/'):
61 61 config.add_route(name + '_slash', pattern + '/', **kw)
62 62
63 63
64 64 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
65 65 """
66 66 Adds regex requirements to pyramid routes using a mapping dict
67 67 e.g::
68 68 add_route_requirements('{repo_name}/settings')
69 69 """
70 70 for key, regex in requirements.items():
71 71 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
72 72 return route_path
73 73
74 74
75 75 def get_format_ref_id(repo):
76 76 """Returns a `repo` specific reference formatter function"""
77 77 if h.is_svn(repo):
78 78 return _format_ref_id_svn
79 79 else:
80 80 return _format_ref_id
81 81
82 82
83 83 def _format_ref_id(name, raw_id):
84 84 """Default formatting of a given reference `name`"""
85 85 return name
86 86
87 87
88 88 def _format_ref_id_svn(name, raw_id):
89 89 """Special way of formatting a reference for Subversion including path"""
90 90 return '%s@%s' % (name, raw_id)
91 91
92 92
93 93 class TemplateArgs(StrictAttributeDict):
94 94 pass
95 95
96 96
97 97 class BaseAppView(object):
98 98
99 99 def __init__(self, context, request):
100 100 self.request = request
101 101 self.context = context
102 102 self.session = request.session
103 103 self._rhodecode_user = request.user # auth user
104 104 self._rhodecode_db_user = self._rhodecode_user.get_instance()
105 105 self._maybe_needs_password_change(
106 106 request.matched_route.name, self._rhodecode_db_user)
107 107
108 108 def _maybe_needs_password_change(self, view_name, user_obj):
109 109 log.debug('Checking if user %s needs password change on view %s',
110 110 user_obj, view_name)
111 111 skip_user_views = [
112 112 'logout', 'login',
113 113 'my_account_password', 'my_account_password_update'
114 114 ]
115 115
116 116 if not user_obj:
117 117 return
118 118
119 119 if user_obj.username == User.DEFAULT_USER:
120 120 return
121 121
122 122 now = time.time()
123 123 should_change = user_obj.user_data.get('force_password_change')
124 124 change_after = safe_int(should_change) or 0
125 125 if should_change and now > change_after:
126 126 log.debug('User %s requires password change', user_obj)
127 127 h.flash('You are required to change your password', 'warning',
128 128 ignore_duplicate=True)
129 129
130 130 if view_name not in skip_user_views:
131 131 raise HTTPFound(
132 132 self.request.route_path('my_account_password'))
133 133
134 134 def _log_creation_exception(self, e, repo_name):
135 135 _ = self.request.translate
136 136 reason = None
137 137 if len(e.args) == 2:
138 138 reason = e.args[1]
139 139
140 140 if reason == 'INVALID_CERTIFICATE':
141 141 log.exception(
142 142 'Exception creating a repository: invalid certificate')
143 143 msg = (_('Error creating repository %s: invalid certificate')
144 144 % repo_name)
145 145 else:
146 146 log.exception("Exception creating a repository")
147 147 msg = (_('Error creating repository %s')
148 148 % repo_name)
149 149 return msg
150 150
151 151 def _get_local_tmpl_context(self, include_app_defaults=True):
152 152 c = TemplateArgs()
153 153 c.auth_user = self.request.user
154 154 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
155 155 c.rhodecode_user = self.request.user
156 156
157 157 if include_app_defaults:
158 158 from rhodecode.lib.base import attach_context_attributes
159 159 attach_context_attributes(c, self.request, self.request.user.user_id)
160 160
161 161 return c
162 162
163 163 def _get_template_context(self, tmpl_args, **kwargs):
164 164
165 165 local_tmpl_args = {
166 166 'defaults': {},
167 167 'errors': {},
168 168 'c': tmpl_args
169 169 }
170 170 local_tmpl_args.update(kwargs)
171 171 return local_tmpl_args
172 172
173 173 def load_default_context(self):
174 174 """
175 175 example:
176 176
177 177 def load_default_context(self):
178 178 c = self._get_local_tmpl_context()
179 179 c.custom_var = 'foobar'
180 180
181 181 return c
182 182 """
183 183 raise NotImplementedError('Needs implementation in view class')
184 184
185 185
186 186 class RepoAppView(BaseAppView):
187 187
188 188 def __init__(self, context, request):
189 189 super(RepoAppView, self).__init__(context, request)
190 190 self.db_repo = request.db_repo
191 191 self.db_repo_name = self.db_repo.repo_name
192 192 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
193 193
194 194 def _handle_missing_requirements(self, error):
195 195 log.error(
196 196 'Requirements are missing for repository %s: %s',
197 197 self.db_repo_name, error.message)
198 198
199 199 def _get_local_tmpl_context(self, include_app_defaults=True):
200 200 _ = self.request.translate
201 201 c = super(RepoAppView, self)._get_local_tmpl_context(
202 202 include_app_defaults=include_app_defaults)
203 203
204 204 # register common vars for this type of view
205 205 c.rhodecode_db_repo = self.db_repo
206 206 c.repo_name = self.db_repo_name
207 207 c.repository_pull_requests = self.db_repo_pull_requests
208 208 self.path_filter = PathFilter(None)
209 209
210 210 c.repository_requirements_missing = {}
211 211 try:
212 212 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
213 213 if self.rhodecode_vcs_repo:
214 214 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
215 215 c.auth_user.username)
216 216 self.path_filter = PathFilter(path_perms)
217 217 except RepositoryRequirementError as e:
218 218 c.repository_requirements_missing = {'error': str(e)}
219 219 self._handle_missing_requirements(e)
220 220 self.rhodecode_vcs_repo = None
221 221
222 222 c.path_filter = self.path_filter # used by atom_feed_entry.mako
223 223
224 224 if self.rhodecode_vcs_repo is None:
225 225 # unable to fetch this repo as vcs instance, report back to user
226 226 h.flash(_(
227 227 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
228 228 "Please check if it exist, or is not damaged.") %
229 229 {'repo_name': c.repo_name},
230 230 category='error', ignore_duplicate=True)
231 231 if c.repository_requirements_missing:
232 232 route = self.request.matched_route.name
233 233 if route.startswith(('edit_repo', 'repo_summary')):
234 234 # allow summary and edit repo on missing requirements
235 235 return c
236 236
237 237 raise HTTPFound(
238 238 h.route_path('repo_summary', repo_name=self.db_repo_name))
239 239
240 240 else: # redirect if we don't show missing requirements
241 241 raise HTTPFound(h.route_path('home'))
242 242
243 243 return c
244 244
245 245 def _get_f_path_unchecked(self, matchdict, default=None):
246 246 """
247 247 Should only be used by redirects, everything else should call _get_f_path
248 248 """
249 249 f_path = matchdict.get('f_path')
250 250 if f_path:
251 251 # fix for multiple initial slashes that causes errors for GIT
252 252 return f_path.lstrip('/')
253 253
254 254 return default
255 255
256 256 def _get_f_path(self, matchdict, default=None):
257 257 f_path_match = self._get_f_path_unchecked(matchdict, default)
258 258 return self.path_filter.assert_path_permissions(f_path_match)
259 259
260 260 def _get_general_setting(self, target_repo, settings_key, default=False):
261 261 settings_model = VcsSettingsModel(repo=target_repo)
262 262 settings = settings_model.get_general_settings()
263 263 return settings.get(settings_key, default)
264 264
265 265
266 266 class PathFilter(object):
267 267
268 268 # Expects and instance of BasePathPermissionChecker or None
269 269 def __init__(self, permission_checker):
270 270 self.permission_checker = permission_checker
271 271
272 272 def assert_path_permissions(self, path):
273 273 if path and self.permission_checker and not self.permission_checker.has_access(path):
274 274 raise HTTPForbidden()
275 275 return path
276 276
277 277 def filter_patchset(self, patchset):
278 278 if not self.permission_checker or not patchset:
279 279 return patchset, False
280 280 had_filtered = False
281 281 filtered_patchset = []
282 282 for patch in patchset:
283 283 filename = patch.get('filename', None)
284 284 if not filename or self.permission_checker.has_access(filename):
285 285 filtered_patchset.append(patch)
286 286 else:
287 287 had_filtered = True
288 288 if had_filtered:
289 289 if isinstance(patchset, diffs.LimitedDiffContainer):
290 290 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
291 291 return filtered_patchset, True
292 292 else:
293 293 return patchset, False
294 294
295 295 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
296 296 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
297 297 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
298 298 result.has_hidden_changes = has_hidden_changes
299 299 return result
300 300
301 301 def get_raw_patch(self, diff_processor):
302 302 if self.permission_checker is None:
303 303 return diff_processor.as_raw()
304 304 elif self.permission_checker.has_full_access:
305 305 return diff_processor.as_raw()
306 306 else:
307 307 return '# Repository has user-specific filters, raw patch generation is disabled.'
308 308
309 309 @property
310 310 def is_enabled(self):
311 311 return self.permission_checker is not None
312 312
313 313
314 314 class RepoGroupAppView(BaseAppView):
315 315 def __init__(self, context, request):
316 316 super(RepoGroupAppView, self).__init__(context, request)
317 317 self.db_repo_group = request.db_repo_group
318 318 self.db_repo_group_name = self.db_repo_group.group_name
319 319
320 320 def _revoke_perms_on_yourself(self, form_result):
321 321 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
322 322 form_result['perm_updates'])
323 323 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
324 324 form_result['perm_additions'])
325 325 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
326 326 form_result['perm_deletions'])
327 327 admin_perm = 'group.admin'
328 328 if _updates and _updates[0][1] != admin_perm or \
329 329 _additions and _additions[0][1] != admin_perm or \
330 330 _deletions and _deletions[0][1] != admin_perm:
331 331 return True
332 332 return False
333 333
334 334
335 335 class UserGroupAppView(BaseAppView):
336 336 def __init__(self, context, request):
337 337 super(UserGroupAppView, self).__init__(context, request)
338 338 self.db_user_group = request.db_user_group
339 339 self.db_user_group_name = self.db_user_group.users_group_name
340 340
341 341
342 342 class UserAppView(BaseAppView):
343 343 def __init__(self, context, request):
344 344 super(UserAppView, self).__init__(context, request)
345 345 self.db_user = request.db_user
346 346 self.db_user_id = self.db_user.user_id
347 347
348 348 _ = self.request.translate
349 349 if not request.db_user_supports_default:
350 350 if self.db_user.username == User.DEFAULT_USER:
351 351 h.flash(_("Editing user `{}` is disabled.".format(
352 352 User.DEFAULT_USER)), category='warning')
353 353 raise HTTPFound(h.route_path('users'))
354 354
355 355
356 356 class DataGridAppView(object):
357 357 """
358 358 Common class to have re-usable grid rendering components
359 359 """
360 360
361 361 def _extract_ordering(self, request, column_map=None):
362 362 column_map = column_map or {}
363 363 column_index = safe_int(request.GET.get('order[0][column]'))
364 364 order_dir = request.GET.get(
365 365 'order[0][dir]', 'desc')
366 366 order_by = request.GET.get(
367 367 'columns[%s][data][sort]' % column_index, 'name_raw')
368 368
369 369 # translate datatable to DB columns
370 370 order_by = column_map.get(order_by) or order_by
371 371
372 372 search_q = request.GET.get('search[value]')
373 373 return search_q, order_by, order_dir
374 374
375 375 def _extract_chunk(self, request):
376 376 start = safe_int(request.GET.get('start'), 0)
377 377 length = safe_int(request.GET.get('length'), 25)
378 378 draw = safe_int(request.GET.get('draw'))
379 379 return draw, start, length
380 380
381 381 def _get_order_col(self, order_by, model):
382 382 if isinstance(order_by, basestring):
383 383 try:
384 384 return operator.attrgetter(order_by)(model)
385 385 except AttributeError:
386 386 return None
387 387 else:
388 388 return order_by
389 389
390 390
391 391 class BaseReferencesView(RepoAppView):
392 392 """
393 393 Base for reference view for branches, tags and bookmarks.
394 394 """
395 395 def load_default_context(self):
396 396 c = self._get_local_tmpl_context()
397 397
398 398
399 399 return c
400 400
401 401 def load_refs_context(self, ref_items, partials_template):
402 402 _render = self.request.get_partial_renderer(partials_template)
403 403 pre_load = ["author", "date", "message"]
404 404
405 405 is_svn = h.is_svn(self.rhodecode_vcs_repo)
406 406 is_hg = h.is_hg(self.rhodecode_vcs_repo)
407 407
408 408 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
409 409
410 410 closed_refs = {}
411 411 if is_hg:
412 412 closed_refs = self.rhodecode_vcs_repo.branches_closed
413 413
414 414 data = []
415 415 for ref_name, commit_id in ref_items:
416 416 commit = self.rhodecode_vcs_repo.get_commit(
417 417 commit_id=commit_id, pre_load=pre_load)
418 418 closed = ref_name in closed_refs
419 419
420 420 # TODO: johbo: Unify generation of reference links
421 421 use_commit_id = '/' in ref_name or is_svn
422 422
423 423 if use_commit_id:
424 424 files_url = h.route_path(
425 425 'repo_files',
426 426 repo_name=self.db_repo_name,
427 427 f_path=ref_name if is_svn else '',
428 428 commit_id=commit_id)
429 429
430 430 else:
431 431 files_url = h.route_path(
432 432 'repo_files',
433 433 repo_name=self.db_repo_name,
434 434 f_path=ref_name if is_svn else '',
435 435 commit_id=ref_name,
436 436 _query=dict(at=ref_name))
437 437
438 438 data.append({
439 439 "name": _render('name', ref_name, files_url, closed),
440 440 "name_raw": ref_name,
441 441 "date": _render('date', commit.date),
442 442 "date_raw": datetime_to_time(commit.date),
443 443 "author": _render('author', commit.author),
444 444 "commit": _render(
445 445 'commit', commit.message, commit.raw_id, commit.idx),
446 446 "commit_raw": commit.idx,
447 447 "compare": _render(
448 448 'compare', format_ref_id(ref_name, commit.raw_id)),
449 449 })
450 450
451 451 return data
452 452
453 453
454 454 class RepoRoutePredicate(object):
455 455 def __init__(self, val, config):
456 456 self.val = val
457 457
458 458 def text(self):
459 459 return 'repo_route = %s' % self.val
460 460
461 461 phash = text
462 462
463 463 def __call__(self, info, request):
464 464 if hasattr(request, 'vcs_call'):
465 465 # skip vcs calls
466 466 return
467 467
468 468 repo_name = info['match']['repo_name']
469 469 repo_model = repo.RepoModel()
470 470
471 471 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
472 472
473 def redirect_if_creating(db_repo):
473 def redirect_if_creating(route_info, db_repo):
474 skip_views = ['edit_repo_advanced_delete']
475 route = route_info['route']
476 # we should skip delete view so we can actually "remove" repositories
477 # if they get stuck in creating state.
478 if route.name in skip_views:
479 return
480
474 481 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
475 482 repo_creating_url = request.route_path(
476 483 'repo_creating', repo_name=db_repo.repo_name)
477 484 raise HTTPFound(repo_creating_url)
478 485
479 486 if by_name_match:
480 487 # register this as request object we can re-use later
481 488 request.db_repo = by_name_match
482 redirect_if_creating(by_name_match)
489 redirect_if_creating(info, by_name_match)
483 490 return True
484 491
485 492 by_id_match = repo_model.get_repo_by_id(repo_name)
486 493 if by_id_match:
487 494 request.db_repo = by_id_match
488 redirect_if_creating(by_id_match)
495 redirect_if_creating(info, by_id_match)
489 496 return True
490 497
491 498 return False
492 499
493 500
494 501 class RepoTypeRoutePredicate(object):
495 502 def __init__(self, val, config):
496 503 self.val = val or ['hg', 'git', 'svn']
497 504
498 505 def text(self):
499 506 return 'repo_accepted_type = %s' % self.val
500 507
501 508 phash = text
502 509
503 510 def __call__(self, info, request):
504 511 if hasattr(request, 'vcs_call'):
505 512 # skip vcs calls
506 513 return
507 514
508 515 rhodecode_db_repo = request.db_repo
509 516
510 517 log.debug(
511 518 '%s checking repo type for %s in %s',
512 519 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
513 520
514 521 if rhodecode_db_repo.repo_type in self.val:
515 522 return True
516 523 else:
517 524 log.warning('Current view is not supported for repo type:%s',
518 525 rhodecode_db_repo.repo_type)
519 526
520 527 # h.flash(h.literal(
521 528 # _('Action not supported for %s.' % rhodecode_repo.alias)),
522 529 # category='warning')
523 530 # return redirect(
524 531 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
525 532
526 533 return False
527 534
528 535
529 536 class RepoGroupRoutePredicate(object):
530 537 def __init__(self, val, config):
531 538 self.val = val
532 539
533 540 def text(self):
534 541 return 'repo_group_route = %s' % self.val
535 542
536 543 phash = text
537 544
538 545 def __call__(self, info, request):
539 546 if hasattr(request, 'vcs_call'):
540 547 # skip vcs calls
541 548 return
542 549
543 550 repo_group_name = info['match']['repo_group_name']
544 551 repo_group_model = repo_group.RepoGroupModel()
545 552 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
546 553
547 554 if by_name_match:
548 555 # register this as request object we can re-use later
549 556 request.db_repo_group = by_name_match
550 557 return True
551 558
552 559 return False
553 560
554 561
555 562 class UserGroupRoutePredicate(object):
556 563 def __init__(self, val, config):
557 564 self.val = val
558 565
559 566 def text(self):
560 567 return 'user_group_route = %s' % self.val
561 568
562 569 phash = text
563 570
564 571 def __call__(self, info, request):
565 572 if hasattr(request, 'vcs_call'):
566 573 # skip vcs calls
567 574 return
568 575
569 576 user_group_id = info['match']['user_group_id']
570 577 user_group_model = user_group.UserGroup()
571 578 by_id_match = user_group_model.get(user_group_id, cache=False)
572 579
573 580 if by_id_match:
574 581 # register this as request object we can re-use later
575 582 request.db_user_group = by_id_match
576 583 return True
577 584
578 585 return False
579 586
580 587
581 588 class UserRoutePredicateBase(object):
582 589 supports_default = None
583 590
584 591 def __init__(self, val, config):
585 592 self.val = val
586 593
587 594 def text(self):
588 595 raise NotImplementedError()
589 596
590 597 def __call__(self, info, request):
591 598 if hasattr(request, 'vcs_call'):
592 599 # skip vcs calls
593 600 return
594 601
595 602 user_id = info['match']['user_id']
596 603 user_model = user.User()
597 604 by_id_match = user_model.get(user_id, cache=False)
598 605
599 606 if by_id_match:
600 607 # register this as request object we can re-use later
601 608 request.db_user = by_id_match
602 609 request.db_user_supports_default = self.supports_default
603 610 return True
604 611
605 612 return False
606 613
607 614
608 615 class UserRoutePredicate(UserRoutePredicateBase):
609 616 supports_default = False
610 617
611 618 def text(self):
612 619 return 'user_route = %s' % self.val
613 620
614 621 phash = text
615 622
616 623
617 624 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
618 625 supports_default = True
619 626
620 627 def text(self):
621 628 return 'user_with_default_route = %s' % self.val
622 629
623 630 phash = text
624 631
625 632
626 633 def includeme(config):
627 634 config.add_route_predicate(
628 635 'repo_route', RepoRoutePredicate)
629 636 config.add_route_predicate(
630 637 'repo_accepted_types', RepoTypeRoutePredicate)
631 638 config.add_route_predicate(
632 639 'repo_group_route', RepoGroupRoutePredicate)
633 640 config.add_route_predicate(
634 641 'user_group_route', UserGroupRoutePredicate)
635 642 config.add_route_predicate(
636 643 'user_route_with_default', UserRouteWithDefaultPredicate)
637 644 config.add_route_predicate(
638 645 'user_route', UserRoutePredicate) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now