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