##// END OF EJS Templates
path-permissions: Initial support for path-based permissions
idlsoft -
r2618:940ad8b4 default
parent child Browse files
Show More
@@ -22,9 +22,9 b' import time'
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26
26
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.model import repo
30 from rhodecode.model import repo
@@ -208,10 +208,14 b' class RepoAppView(BaseAppView):'
208 c.repository_requirements_missing = False
208 c.repository_requirements_missing = False
209 try:
209 try:
210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
210 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
211 self.path_filter = PathFilter(self.rhodecode_vcs_repo.get_path_permissions(c.auth_user.username))
211 except RepositoryRequirementError as e:
212 except RepositoryRequirementError as e:
212 c.repository_requirements_missing = True
213 c.repository_requirements_missing = True
213 self._handle_missing_requirements(e)
214 self._handle_missing_requirements(e)
214 self.rhodecode_vcs_repo = None
215 self.rhodecode_vcs_repo = None
216 self.path_filter = None
217
218 c.path_filter = self.path_filter # used by atom_feed_entry.mako
215
219
216 if (not c.repository_requirements_missing
220 if (not c.repository_requirements_missing
217 and self.rhodecode_vcs_repo is None):
221 and self.rhodecode_vcs_repo is None):
@@ -229,9 +233,57 b' class RepoAppView(BaseAppView):'
229 f_path = matchdict.get('f_path')
233 f_path = matchdict.get('f_path')
230 if f_path:
234 if f_path:
231 # fix for multiple initial slashes that causes errors for GIT
235 # fix for multiple initial slashes that causes errors for GIT
232 return f_path.lstrip('/')
236 return self.path_filter.assert_path_permissions(f_path.lstrip('/'))
237
238 return self.path_filter.assert_path_permissions(default)
239
240
241 class PathFilter(object):
242
243 # Expects and instance of BasePathPermissionChecker or None
244 def __init__(self, permission_checker):
245 self.permission_checker = permission_checker
246
247 def assert_path_permissions(self, path):
248 if path and self.permission_checker and not self.permission_checker.has_access(path):
249 raise HTTPForbidden()
250 return path
233
251
234 return default
252 def filter_patchset(self, patchset):
253 if not self.permission_checker or not patchset:
254 return patchset, False
255 had_filtered = False
256 filtered_patchset = []
257 for patch in patchset:
258 filename = patch.get('filename', None)
259 if not filename or self.permission_checker.has_access(filename):
260 filtered_patchset.append(patch)
261 else:
262 had_filtered = True
263 if had_filtered:
264 if isinstance(patchset, diffs.LimitedDiffContainer):
265 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
266 return filtered_patchset, True
267 else:
268 return patchset, False
269
270 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
271 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
272 result = diffset.render_patchset(filtered_patchset, source_ref=source_ref, target_ref=target_ref)
273 result.has_hidden_changes = has_hidden_changes
274 return result
275
276 def get_raw_patch(self, diff_processor):
277 if self.permission_checker is None:
278 return diff_processor.as_raw()
279 elif self.permission_checker.has_full_access:
280 return diff_processor.as_raw()
281 else:
282 return '# Repository has user-specific filters, raw patch generation is disabled.'
283
284 @property
285 def is_enabled(self):
286 return self.permission_checker is not None
235
287
236
288
237 class RepoGroupAppView(BaseAppView):
289 class RepoGroupAppView(BaseAppView):
@@ -272,13 +272,13 b' class RepoCommitsView(RepoAppView):'
272 source_node_getter=_node_getter(commit1),
272 source_node_getter=_node_getter(commit1),
273 target_node_getter=_node_getter(commit2),
273 target_node_getter=_node_getter(commit2),
274 comments=inline_comments)
274 comments=inline_comments)
275 diffset = diffset.render_patchset(
275 diffset = self.path_filter.render_patchset_filtered(
276 _parsed, commit1.raw_id, commit2.raw_id)
276 diffset, _parsed, commit1.raw_id, commit2.raw_id)
277
277
278 c.changes[commit.raw_id] = diffset
278 c.changes[commit.raw_id] = diffset
279 else:
279 else:
280 # downloads/raw we only need RAW diff nothing else
280 # downloads/raw we only need RAW diff nothing else
281 diff = diff_processor.as_raw()
281 diff = self.path_filter.get_raw_patch(diff_processor)
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
283
283
284 # sort comments by how they were generated
284 # sort comments by how they were generated
@@ -309,8 +309,8 b' class RepoCompareView(RepoAppView):'
309 source_node_getter=_node_getter(source_commit),
309 source_node_getter=_node_getter(source_commit),
310 target_node_getter=_node_getter(target_commit),
310 target_node_getter=_node_getter(target_commit),
311 )
311 )
312 c.diffset = diffset.render_patchset(
312 c.diffset = self.path_filter.render_patchset_filtered(
313 _parsed, source_ref, target_ref)
313 diffset, _parsed, source_ref, target_ref)
314
314
315 c.preview_mode = merge
315 c.preview_mode = merge
316 c.source_commit = source_commit
316 c.source_commit = source_commit
@@ -90,13 +90,15 b' class RepoFeedView(RepoAppView):'
90 _renderer = self.request.get_partial_renderer(
90 _renderer = self.request.get_partial_renderer(
91 'rhodecode:templates/feed/atom_feed_entry.mako')
91 'rhodecode:templates/feed/atom_feed_entry.mako')
92 diff_processor, parsed_diff, limited_diff = self._changes(commit)
92 diff_processor, parsed_diff, limited_diff = self._changes(commit)
93 filtered_parsed_diff, has_hidden_changes = self.path_filter.filter_patchset(parsed_diff)
93 return _renderer(
94 return _renderer(
94 'body',
95 'body',
95 commit=commit,
96 commit=commit,
96 parsed_diff=parsed_diff,
97 parsed_diff=filtered_parsed_diff,
97 limited_diff=limited_diff,
98 limited_diff=limited_diff,
98 feed_include_diff=self.feed_include_diff,
99 feed_include_diff=self.feed_include_diff,
99 diff_processor=diff_processor,
100 diff_processor=diff_processor,
101 has_hidden_changes=has_hidden_changes
100 )
102 )
101
103
102 def _set_timezone(self, date, tzinfo=pytz.utc):
104 def _set_timezone(self, date, tzinfo=pytz.utc):
@@ -122,8 +124,7 b' class RepoFeedView(RepoAppView):'
122 """
124 """
123 self.load_default_context()
125 self.load_default_context()
124
126
125 @cache_region('long_term')
127 def _generate_feed():
126 def _generate_feed(cache_key):
127 feed = Atom1Feed(
128 feed = Atom1Feed(
128 title=self.title % self.db_repo_name,
129 title=self.title % self.db_repo_name,
129 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
130 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
@@ -146,12 +147,18 b' class RepoFeedView(RepoAppView):'
146
147
147 return feed.mime_type, feed.writeString('utf-8')
148 return feed.mime_type, feed.writeString('utf-8')
148
149
149 invalidator_context = CacheKey.repo_context_cache(
150 @cache_region('long_term')
150 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
151 def _generate_feed_and_cache(cache_key):
152 return _generate_feed()
151
153
152 with invalidator_context as context:
154 if self.path_filter.is_enabled:
153 context.invalidate()
155 invalidator_context = CacheKey.repo_context_cache(
154 mime_type, feed = context.compute()
156 _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_ATOM)
157 with invalidator_context as context:
158 context.invalidate()
159 mime_type, feed = context.compute()
160 else:
161 mime_type, feed = _generate_feed()
155
162
156 response = Response(feed)
163 response = Response(feed)
157 response.content_type = mime_type
164 response.content_type = mime_type
@@ -169,8 +176,7 b' class RepoFeedView(RepoAppView):'
169 """
176 """
170 self.load_default_context()
177 self.load_default_context()
171
178
172 @cache_region('long_term')
179 def _generate_feed():
173 def _generate_feed(cache_key):
174 feed = Rss201rev2Feed(
180 feed = Rss201rev2Feed(
175 title=self.title % self.db_repo_name,
181 title=self.title % self.db_repo_name,
176 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
182 link=h.route_url('repo_summary', repo_name=self.db_repo_name),
@@ -193,12 +199,19 b' class RepoFeedView(RepoAppView):'
193
199
194 return feed.mime_type, feed.writeString('utf-8')
200 return feed.mime_type, feed.writeString('utf-8')
195
201
196 invalidator_context = CacheKey.repo_context_cache(
202 @cache_region('long_term')
197 _generate_feed, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
203 def _generate_feed_and_cache(cache_key):
204 return _generate_feed()
198
205
199 with invalidator_context as context:
206 if self.path_filter.is_enabled:
200 context.invalidate()
207 invalidator_context = CacheKey.repo_context_cache(
201 mime_type, feed = context.compute()
208 _generate_feed_and_cache, self.db_repo_name, CacheKey.CACHE_TYPE_RSS)
209
210 with invalidator_context as context:
211 context.invalidate()
212 mime_type, feed = context.compute()
213 else:
214 mime_type, feed = _generate_feed()
202
215
203 response = Response(feed)
216 response = Response(feed)
204 response.content_type = mime_type
217 response.content_type = mime_type
@@ -426,7 +426,7 b' class RepoFilesView(RepoAppView):'
426 context=line_context)
426 context=line_context)
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
427 diff = diffs.DiffProcessor(_diff, format='gitdiff')
428
428
429 response = Response(diff.as_raw())
429 response = Response(self.path_filter.get_raw_patch(diff))
430 response.content_type = 'text/plain'
430 response.content_type = 'text/plain'
431 response.content_disposition = (
431 response.content_disposition = (
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
432 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
@@ -442,7 +442,7 b' class RepoFilesView(RepoAppView):'
442 context=line_context)
442 context=line_context)
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
443 diff = diffs.DiffProcessor(_diff, format='gitdiff')
444
444
445 response = Response(diff.as_raw())
445 response = Response(self.path_filter.get_raw_patch(diff))
446 response.content_type = 'text/plain'
446 response.content_type = 'text/plain'
447 charset = self._get_default_encoding(c)
447 charset = self._get_default_encoding(c)
448 if charset:
448 if charset:
@@ -231,8 +231,8 b' class RepoPullRequestsView(RepoAppView, '
231 target_node_getter=_node_getter(source_commit),
231 target_node_getter=_node_getter(source_commit),
232 comments=display_inline_comments
232 comments=display_inline_comments
233 )
233 )
234 diffset = diffset.render_patchset(
234 diffset = self.path_filter.render_patchset_filtered(
235 _parsed, target_commit.raw_id, source_commit.raw_id)
235 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
236
236
237 return diffset
237 return diffset
238
238
@@ -637,6 +637,17 b' class BaseRepository(object):'
637 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
637 warnings.warn("Use in_memory_commit instead", DeprecationWarning)
638 return self.in_memory_commit
638 return self.in_memory_commit
639
639
640 #
641 def get_path_permissions(self, username):
642 """
643
644 Returns a path permission checker or None if not supported
645
646 :param username: session user name
647 :return: an instance of BasePathPermissionChecker or None
648 """
649 return None
650
640
651
641 class BaseCommit(object):
652 class BaseCommit(object):
642 """
653 """
@@ -1618,3 +1629,13 b' class DiffChunk(object):'
1618 self.header = match.groupdict()
1629 self.header = match.groupdict()
1619 self.diff = chunk[match.end():]
1630 self.diff = chunk[match.end():]
1620 self.raw = chunk
1631 self.raw = chunk
1632
1633
1634 class BasePathPermissionChecker(object):
1635
1636 def __init__(self, username, has_full_access = False):
1637 self.username = username
1638 self.has_full_access = has_full_access
1639
1640 def has_access(self, path):
1641 raise NotImplemented()
@@ -132,7 +132,9 b' collapse_all = len(diffset.files) > coll'
132 </h2>
132 </h2>
133 </div>
133 </div>
134
134
135 %if not diffset.files:
135 %if diffset.has_hidden_changes:
136 <p class="empty_data">${_('Some changes may be hidden')}</p>
137 %elif not diffset.files:
136 <p class="empty_data">${_('No files')}</p>
138 <p class="empty_data">${_('No files')}</p>
137 %endif
139 %endif
138
140
@@ -17,6 +17,10 b''
17 tag: ${tag} <br/>
17 tag: ${tag} <br/>
18 % endfor
18 % endfor
19
19
20 % if has_hidden_changes:
21 Has hidden changes<br/>
22 % endif
23
20 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>
24 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>
21 <pre>
25 <pre>
22 ${h.urlify_commit_message(commit.message)}
26 ${h.urlify_commit_message(commit.message)}
@@ -29,6 +33,6 b' commit: <a href="${h.route_url(\'repo_com'
29 % endfor
33 % endfor
30
34
31 % if feed_include_diff:
35 % if feed_include_diff:
32 ${diff_processor.as_raw()}
36 ${c.path_filter.get_raw_patch(diff_processor)}
33 % endif
37 % endif
34 </pre>
38 </pre>
General Comments 0
You need to be logged in to leave comments. Login now