##// END OF EJS Templates
caches: disable caches in predicates....
marcink -
r2893:59faac05 default
parent child Browse files
Show More
@@ -1,641 +1,638 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
465 464 if hasattr(request, 'vcs_call'):
466 465 # skip vcs calls
467 466 return
468 467
469 468 repo_name = info['match']['repo_name']
470 469 repo_model = repo.RepoModel()
471 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
470
471 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
472 472
473 473 def redirect_if_creating(db_repo):
474 474 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
475 raise HTTPFound(
476 request.route_path('repo_creating',
477 repo_name=db_repo.repo_name))
475 repo_creating_url = request.route_path(
476 'repo_creating', repo_name=db_repo.repo_name)
477 raise HTTPFound(repo_creating_url)
478 478
479 479 if by_name_match:
480 480 # register this as request object we can re-use later
481 481 request.db_repo = by_name_match
482 482 redirect_if_creating(by_name_match)
483 483 return True
484 484
485 485 by_id_match = repo_model.get_repo_by_id(repo_name)
486 486 if by_id_match:
487 487 request.db_repo = by_id_match
488 488 redirect_if_creating(by_id_match)
489 489 return True
490 490
491 491 return False
492 492
493 493
494 494 class RepoTypeRoutePredicate(object):
495 495 def __init__(self, val, config):
496 496 self.val = val or ['hg', 'git', 'svn']
497 497
498 498 def text(self):
499 499 return 'repo_accepted_type = %s' % self.val
500 500
501 501 phash = text
502 502
503 503 def __call__(self, info, request):
504 504 if hasattr(request, 'vcs_call'):
505 505 # skip vcs calls
506 506 return
507 507
508 508 rhodecode_db_repo = request.db_repo
509 509
510 510 log.debug(
511 511 '%s checking repo type for %s in %s',
512 512 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
513 513
514 514 if rhodecode_db_repo.repo_type in self.val:
515 515 return True
516 516 else:
517 517 log.warning('Current view is not supported for repo type:%s',
518 518 rhodecode_db_repo.repo_type)
519 #
519
520 520 # h.flash(h.literal(
521 521 # _('Action not supported for %s.' % rhodecode_repo.alias)),
522 522 # category='warning')
523 523 # return redirect(
524 524 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
525 525
526 526 return False
527 527
528 528
529 529 class RepoGroupRoutePredicate(object):
530 530 def __init__(self, val, config):
531 531 self.val = val
532 532
533 533 def text(self):
534 534 return 'repo_group_route = %s' % self.val
535 535
536 536 phash = text
537 537
538 538 def __call__(self, info, request):
539 539 if hasattr(request, 'vcs_call'):
540 540 # skip vcs calls
541 541 return
542 542
543 543 repo_group_name = info['match']['repo_group_name']
544 544 repo_group_model = repo_group.RepoGroupModel()
545 by_name_match = repo_group_model.get_by_group_name(
546 repo_group_name, cache=True)
545 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
547 546
548 547 if by_name_match:
549 548 # register this as request object we can re-use later
550 549 request.db_repo_group = by_name_match
551 550 return True
552 551
553 552 return False
554 553
555 554
556 555 class UserGroupRoutePredicate(object):
557 556 def __init__(self, val, config):
558 557 self.val = val
559 558
560 559 def text(self):
561 560 return 'user_group_route = %s' % self.val
562 561
563 562 phash = text
564 563
565 564 def __call__(self, info, request):
566 565 if hasattr(request, 'vcs_call'):
567 566 # skip vcs calls
568 567 return
569 568
570 569 user_group_id = info['match']['user_group_id']
571 570 user_group_model = user_group.UserGroup()
572 by_id_match = user_group_model.get(
573 user_group_id, cache=True)
571 by_id_match = user_group_model.get(user_group_id, cache=False)
574 572
575 573 if by_id_match:
576 574 # register this as request object we can re-use later
577 575 request.db_user_group = by_id_match
578 576 return True
579 577
580 578 return False
581 579
582 580
583 581 class UserRoutePredicateBase(object):
584 582 supports_default = None
585 583
586 584 def __init__(self, val, config):
587 585 self.val = val
588 586
589 587 def text(self):
590 588 raise NotImplementedError()
591 589
592 590 def __call__(self, info, request):
593 591 if hasattr(request, 'vcs_call'):
594 592 # skip vcs calls
595 593 return
596 594
597 595 user_id = info['match']['user_id']
598 596 user_model = user.User()
599 by_id_match = user_model.get(
600 user_id, cache=True)
597 by_id_match = user_model.get(user_id, cache=False)
601 598
602 599 if by_id_match:
603 600 # register this as request object we can re-use later
604 601 request.db_user = by_id_match
605 602 request.db_user_supports_default = self.supports_default
606 603 return True
607 604
608 605 return False
609 606
610 607
611 608 class UserRoutePredicate(UserRoutePredicateBase):
612 609 supports_default = False
613 610
614 611 def text(self):
615 612 return 'user_route = %s' % self.val
616 613
617 614 phash = text
618 615
619 616
620 617 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
621 618 supports_default = True
622 619
623 620 def text(self):
624 621 return 'user_with_default_route = %s' % self.val
625 622
626 623 phash = text
627 624
628 625
629 626 def includeme(config):
630 627 config.add_route_predicate(
631 628 'repo_route', RepoRoutePredicate)
632 629 config.add_route_predicate(
633 630 'repo_accepted_types', RepoTypeRoutePredicate)
634 631 config.add_route_predicate(
635 632 'repo_group_route', RepoGroupRoutePredicate)
636 633 config.add_route_predicate(
637 634 'user_group_route', UserGroupRoutePredicate)
638 635 config.add_route_predicate(
639 636 'user_route_with_default', UserRouteWithDefaultPredicate)
640 637 config.add_route_predicate(
641 638 'user_route', UserRoutePredicate) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now