path-permissions: Initial support for path-based permissions

This commit is contained in:
Sandu Turcan 2018-02-18 19:30:42 -05:00
parent f689a025ce
commit e4981d4e72
9 changed files with 122 additions and 30 deletions

View file

@ -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 default
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
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):

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -231,8 +231,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
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

View file

@ -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()

View file

@ -132,7 +132,9 @@ collapse_all = len(diffset.files) > collapse_when_files_over
</h2>
</div>
%if not diffset.files:
%if diffset.has_hidden_changes:
<p class="empty_data">${_('Some changes may be hidden')}</p>
%elif not diffset.files:
<p class="empty_data">${_('No files')}</p>
%endif

View file

@ -17,6 +17,10 @@ ${_('%(user)s commited on %(date)s UTC') % {
tag: ${tag} <br/>
% endfor
% if has_hidden_changes:
Has hidden changes<br/>
% endif
commit: <a href="${h.route_url('repo_commit', repo_name=c.rhodecode_db_repo.repo_name, commit_id=commit.raw_id)}">${h.show_id(commit)}</a>
<pre>
${h.urlify_commit_message(commit.message)}
@ -29,6 +33,6 @@ ${h.urlify_commit_message(commit.message)}
% endfor
% if feed_include_diff:
${diff_processor.as_raw()}
${c.path_filter.get_raw_patch(diff_processor)}
% endif
</pre>