diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py --- a/rhodecode/apps/_base/__init__.py +++ b/rhodecode/apps/_base/__init__.py @@ -22,9 +22,9 @@ import time import logging import operator -from pyramid.httpexceptions import HTTPFound +from pyramid.httpexceptions import HTTPFound, HTTPForbidden -from rhodecode.lib import helpers as h +from rhodecode.lib import helpers as h, diffs from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time from rhodecode.lib.vcs.exceptions import RepositoryRequirementError from rhodecode.model import repo @@ -208,10 +208,14 @@ class RepoAppView(BaseAppView): c.repository_requirements_missing = False try: self.rhodecode_vcs_repo = self.db_repo.scm_instance() + self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username)) except RepositoryRequirementError as e: c.repository_requirements_missing = True self._handle_missing_requirements(e) self.rhodecode_vcs_repo = None + self.path_filter = None + + c.path_filter = self.path_filter # used by atom_feed_entry.mako if (not c.repository_requirements_missing and self.rhodecode_vcs_repo is None): @@ -229,9 +233,57 @@ class RepoAppView(BaseAppView): f_path = matchdict.get('f_path') if f_path: # fix for multiple initial slashes that causes errors for GIT - return f_path.lstrip('/') + return self.path_filter.assert_path_permissions(f_path.lstrip('/')) + + return self.path_filter.assert_path_permissions(default) + + +class PathFilter(object): + + # Expects and instance of BasePathPermissionChecker or None + def __init__(self, permission_checker): + self.permission_checker = permission_checker + + def assert_path_permissions(self, path): + if path and self.permission_checker and not self.permission_checker.has_access(path): + raise HTTPForbidden() + return path - return default + def filter_patchset(self, patchset): + if not self.permission_checker or not patchset: + return patchset, False + had_filtered = False + filtered_patchset = [] + for patch in patchset: + filename = patch.get('filename', None) + if not filename or self.permission_checker.has_access(filename): + filtered_patchset.append(patch) + else: + had_filtered = True + if had_filtered: + if isinstance(patchset, diffs.LimitedDiffContainer): + filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset) + return filtered_patchset, True + else: + return patchset, False + + def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None): + filtered_patchset, has_hidden_changes = self.filter_patchset(patchset) + result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref) + result.has_hidden_changes = has_hidden_changes + return result + + def get_raw_patch(self, diff_processor): + if self.permission_checker is None: + return diff_processor.as_raw() + elif self.permission_checker.has_full_access: + return diff_processor.as_raw() + else: + return '# Repository has user-specific filters, raw patch generation is disabled.' + + @property + def is_enabled(self): + return self.permission_checker is not None class RepoGroupAppView(BaseAppView): diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -272,13 +272,13 @@ class RepoCommitsView(RepoAppView): source_node_getter=_node_getter(commit1), target_node_getter=_node_getter(commit2), comments=inline_comments) - diffset = diffset.render_patchset( - _parsed, commit1.raw_id, commit2.raw_id) + diffset = self.path_filter.render_patchset_filtered( + diffset, _parsed, commit1.raw_id, commit2.raw_id) c.changes[commit.raw_id] = diffset else: # downloads/raw we only need RAW diff nothing else - diff = diff_processor.as_raw() + diff = self.path_filter.get_raw_patch(diff_processor) c.changes[commit.raw_id] = [None, None, None, None, diff, None, None] # sort comments by how they were generated diff --git a/rhodecode/apps/repository/views/repo_compare.py b/rhodecode/apps/repository/views/repo_compare.py --- a/rhodecode/apps/repository/views/repo_compare.py +++ b/rhodecode/apps/repository/views/repo_compare.py @@ -309,8 +309,8 @@ class RepoCompareView(RepoAppView): source_node_getter=_node_getter(source_commit), target_node_getter=_node_getter(target_commit), ) - c.diffset = diffset.render_patchset( - _parsed, source_ref, target_ref) + c.diffset = self.path_filter.render_patchset_filtered( + diffset, _parsed, source_ref, target_ref) c.preview_mode = merge c.source_commit = source_commit diff --git a/rhodecode/apps/repository/views/repo_feed.py b/rhodecode/apps/repository/views/repo_feed.py --- a/rhodecode/apps/repository/views/repo_feed.py +++ b/rhodecode/apps/repository/views/repo_feed.py @@ -90,13 +90,15 @@ class RepoFeedView(RepoAppView): _renderer = self.request.get_partial_renderer( 'rhodecode:templates/feed/atom_feed_entry.mako') diff_processor, parsed_diff, limited_diff = self._changes(commit) + filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff) return _renderer( 'body', commit=commit, - parsed_diff=parsed_diff, + parsed_diff=filtered_parsed_diff, limited_diff=limited_diff, feed_include_diff=self.feed_include_diff, diff_processor=diff_processor, + has_hidden_changes=has_hidden_changes ) def _set_timezone(self, date, tzinfo=pytz.utc): @@ -122,8 +124,7 @@ class RepoFeedView(RepoAppView): """ self.load_default_context() - @cache_region('long_term') - def _generate_feed(cache_key): + def _generate_feed(): feed = Atom1Feed( title=self.title % self.db_repo_name, link=h.route_url('repo_summary', repo_name=self.db_repo_name), @@ -146,12 +147,18 @@ class RepoFeedView(RepoAppView): return feed.mime_type, feed.writeString('utf-8') - invalidator_context = CacheKey.repo_context_cache( - _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM) + @cache_region('long_term') + def _generate_feed_and_cache(cache_key): + return _generate_feed() - with invalidator_context as context: - context.invalidate() - mime_type, feed = context.compute() + if self.path_filter.is_enabled: + invalidator_context = CacheKey.repo_context_cache( + _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM) + with invalidator_context as context: + context.invalidate() + mime_type, feed = context.compute() + else: + mime_type, feed = _generate_feed() response = Response(feed) response.content_type = mime_type @@ -169,8 +176,7 @@ class RepoFeedView(RepoAppView): """ self.load_default_context() - @cache_region('long_term') - def _generate_feed(cache_key): + def _generate_feed(): feed = Rss201rev2Feed( title=self.title % self.db_repo_name, link=h.route_url('repo_summary', repo_name=self.db_repo_name), @@ -193,12 +199,19 @@ class RepoFeedView(RepoAppView): return feed.mime_type, feed.writeString('utf-8') - invalidator_context = CacheKey.repo_context_cache( - _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS) + @cache_region('long_term') + def _generate_feed_and_cache(cache_key): + return _generate_feed() - with invalidator_context as context: - context.invalidate() - mime_type, feed = context.compute() + if self.path_filter.is_enabled: + invalidator_context = CacheKey.repo_context_cache( + _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_RSS) + + with invalidator_context as context: + context.invalidate() + mime_type, feed = context.compute() + else: + mime_type, feed = _generate_feed() response = Response(feed) response.content_type = mime_type diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py --- a/rhodecode/apps/repository/views/repo_files.py +++ b/rhodecode/apps/repository/views/repo_files.py @@ -426,7 +426,7 @@ class RepoFilesView(RepoAppView): context=line_context) diff = diffs.DiffProcessor(_diff, format='gitdiff') - response = Response(diff.as_raw()) + response = Response(self.path_filter.get_raw_patch(diff)) response.content_type = 'text/plain' response.content_disposition = ( 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2) @@ -442,7 +442,7 @@ class RepoFilesView(RepoAppView): context=line_context) diff = diffs.DiffProcessor(_diff, format='gitdiff') - response = Response(diff.as_raw()) + response = Response(self.path_filter.get_raw_patch(diff)) response.content_type = 'text/plain' charset = self._get_default_encoding(c) if charset: diff --git a/rhodecode/apps/repository/views/repo_pull_requests.py b/rhodecode/apps/repository/views/repo_pull_requests.py --- a/rhodecode/apps/repository/views/repo_pull_requests.py +++ b/rhodecode/apps/repository/views/repo_pull_requests.py @@ -231,8 +231,8 @@ class RepoPullRequestsView(RepoAppView, target_node_getter=_node_getter(source_commit), comments=display_inline_comments ) - diffset = diffset.render_patchset( - _parsed, target_commit.raw_id, source_commit.raw_id) + diffset = self.path_filter.render_patchset_filtered( + diffset, _parsed, target_commit.raw_id, source_commit.raw_id) return diffset diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py --- a/rhodecode/lib/vcs/backends/base.py +++ b/rhodecode/lib/vcs/backends/base.py @@ -637,6 +637,17 @@ class BaseRepository(object): warnings.warn("Use in_memory_commit instead", DeprecationWarning) return self.in_memory_commit + # + def get_path_permissions(self, username): + """ + + Returns a path permission checker or None if not supported + + :param username: session user name + :return: an instance of BasePathPermissionChecker or None + """ + return None + class BaseCommit(object): """ @@ -1618,3 +1629,13 @@ class DiffChunk(object): self.header = match.groupdict() self.diff = chunk[match.end():] self.raw = chunk + + +class BasePathPermissionChecker(object): + + def __init__(self, username, has_full_access = False): + self.username = username + self.has_full_access = has_full_access + + def has_access(self, path): + raise NotImplemented() diff --git a/rhodecode/templates/codeblocks/diffs.mako b/rhodecode/templates/codeblocks/diffs.mako --- a/rhodecode/templates/codeblocks/diffs.mako +++ b/rhodecode/templates/codeblocks/diffs.mako @@ -132,7 +132,9 @@ collapse_all = len(diffset.files) > coll - %if not diffset.files: + %if diffset.has_hidden_changes: +

${_('Some changes may be hidden')}

+ %elif not diffset.files:

${_('No files')}

%endif diff --git a/rhodecode/templates/feed/atom_feed_entry.mako b/rhodecode/templates/feed/atom_feed_entry.mako --- a/rhodecode/templates/feed/atom_feed_entry.mako +++ b/rhodecode/templates/feed/atom_feed_entry.mako @@ -17,6 +17,10 @@ tag: ${tag}
% endfor +% if has_hidden_changes: + Has hidden changes
+% endif + commit: ${h.show_id(commit)}
 ${h.urlify_commit_message(commit.message)}
@@ -29,6 +33,6 @@ commit: