path-permissions: Initial support for path-based permissions
This commit is contained in:
parent
f689a025ce
commit
e4981d4e72
9 changed files with 122 additions and 30 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue