Edgewall Software

Ticket #5640: 5640-hide-forbidden-changesets-r8198.patch

File 5640-hide-forbidden-changesets-r8198.patch, 11.8 KB (added by rblank, 3 years ago)

Even another approach, with its own issues

  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    a b  
    185185                                  ((msg and '%s: ' % msg) or '', path, rev), 
    186186                                  _('No such node')) 
    187187 
     188class ChangesetDenied(ResourceNotFound): 
     189    def __init__(self, rev): 
     190        ResourceNotFound.__init__(self, 
     191                                  _('Access to changeset %(rev)s is denied', 
     192                                    rev=rev), 
     193                                  _('Changeset not authorized')) 
     194 
     195 
    188196class Repository(object): 
    189197    """Base class for a repository provided by a version control system.""" 
    190198 
     
    230238        return [] 
    231239     
    232240    def get_changeset(self, rev): 
    233         """Retrieve a Changeset corresponding to the  given revision `rev`.""" 
     241        """Retrieve a Changeset corresponding to the given revision `rev`.""" 
    234242        raise NotImplementedError 
    235243 
    236244    def get_changesets(self, start, stop): 
  • trac/versioncontrol/cache.py

    diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
    a b  
    2222from trac.util.datefmt import utc, to_timestamp 
    2323from trac.util.translation import _ 
    2424from trac.versioncontrol import Changeset, Node, Repository, Authorizer, \ 
    25                                 NoSuchChangeset 
     25                                NoSuchChangeset, ChangesetDenied 
    2626 
    2727 
    2828_kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE} 
     
    136136        if self.youngest != repos_youngest: 
    137137            self.log.info("repos rev [%s] != cached rev [%s]" % 
    138138                          (repos_youngest, self.youngest)) 
    139             if self.youngest: 
    140                 next_youngest = self.repos.next_rev(self.youngest) 
    141             else: 
    142                 next_youngest = None 
    143                 try: 
    144                     next_youngest = self.repos.oldest_rev 
    145                     # Ugly hack needed because doing that everytime in  
    146                     # oldest_rev suffers from horrendeous performance (#5213) 
    147                     if hasattr(self.repos, 'scope'): 
    148                         if self.repos.scope != '/': 
    149                             next_youngest = self.repos.next_rev(next_youngest,  
    150                                     find_initial_rev=True) 
    151                     next_youngest = self.repos.normalize_rev(next_youngest) 
    152                 except TracError: 
    153                     return # can't normalize oldest_rev: repository was empty 
     139            authz = self.repos.authz 
     140            self.repos.authz = Authorizer()     # remove permission checking 
     141            try: 
     142                if self.youngest: 
     143                    next_youngest = self.repos.next_rev(self.youngest) 
     144                else: 
     145                    next_youngest = None 
     146                    try: 
     147                        next_youngest = self.repos.oldest_rev 
     148                        # Ugly hack needed because doing that everytime in  
     149                        # oldest_rev suffers from horrendeous performance 
     150                        # (#5213) 
     151                        if hasattr(self.repos, 'scope'): 
     152                            if self.repos.scope != '/': 
     153                                next_youngest = self.repos.next_rev( 
     154                                        next_youngest, find_initial_rev=True) 
     155                        next_youngest = self.repos.normalize_rev(next_youngest) 
     156                    except TracError: 
     157                        # can't normalize oldest_rev: repository was empty 
     158                        return 
     159     
     160                if next_youngest is None: # nothing to cache yet 
     161                    return 
     162     
     163                # 0. first check if there's no (obvious) resync in progress 
     164                cursor.execute("SELECT rev FROM revision WHERE rev=%s", 
     165                               (str(next_youngest),)) 
     166                for rev, in cursor: 
     167                    # already there, but in progress, so keep ''previous'' 
     168                    # notion of 'youngest' 
     169                    self.repos.clear(youngest_rev=self.youngest) 
     170                    return 
     171     
     172                # 1. prepare for resyncing 
     173                #    (there still might be a race condition at this point) 
     174     
     175                kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
     176                actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
    154177 
    155             if next_youngest is None: # nothing to cache yet 
    156                 return 
    157  
    158             # 0. first check if there's no (obvious) resync in progress 
    159             cursor.execute("SELECT rev FROM revision WHERE rev=%s", 
    160                            (str(next_youngest),)) 
    161             for rev, in cursor: 
    162                 # already there, but in progress, so keep ''previous'' 
    163                 # notion of 'youngest' 
    164                 self.repos.clear(youngest_rev=self.youngest) 
    165                 return 
    166  
    167             # 1. prepare for resyncing 
    168             #    (there still might be a race condition at this point) 
    169  
    170             authz = self.repos.authz 
    171             self.repos.authz = Authorizer() # remove permission checking 
    172  
    173             kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
    174             actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
    175  
    176             try: 
    177178                while next_youngest is not None: 
    178179                     
    179180                    # 1.1 Attempt to resync the 'revision' table 
     
    270271            args.append(path) 
    271272            sql += " OR " 
    272273            # changes on path children 
    273             sql += "path "+db.like() 
     274            sql += "path " + db.like() 
    274275            args.append(db.like_escape(path+'/') + '%') 
    275276            sql += " OR " 
    276277            # deletion of path ancestors 
     
    282283            sql += ")" 
    283284 
    284285        sql += " ORDER BY " + db.cast('rev', 'int') + \ 
    285                 (direction == '<' and " DESC" or "") + " LIMIT 1" 
     286                (direction == '<' and " DESC" or "") 
    286287         
    287288        cursor = db.cursor() 
    288289        cursor.execute(sql, args) 
    289290        for rev, in cursor: 
    290             return rev 
     291            if self.authz.has_permission_for_changeset(rev): 
     292                return rev 
    291293 
    292294    def rev_older_than(self, rev1, rev2): 
    293295        return self.repos.rev_older_than(rev1, rev2) 
     
    332334        cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " 
    333335                       "FROM node_change WHERE rev=%s " 
    334336                       "ORDER BY path", (str(self.rev),)) 
     337        empty = True 
     338        any_returned = False 
    335339        for path, kind, change, base_path, base_rev in cursor: 
     340            empty = False 
    336341            if not self.authz.has_permission(posixpath.join(self.scope, 
    337342                                                            path.strip('/'))): 
    338343                # FIXME: what about the base_path? 
    339344                continue 
     345            any_returned = True 
    340346            kind = _kindmap[kind] 
    341347            change = _actionmap[change] 
    342348            yield path, kind, change, base_path, base_rev 
     349        if not empty and not any_returned: 
     350            raise ChangesetDenied(self.rev) 
    343351 
    344352    def get_properties(self): 
    345353        return self.repos.get_changeset(self.rev).get_properties() 
  • trac/versioncontrol/svn_authz.py

    diff --git a/trac/versioncontrol/svn_authz.py b/trac/versioncontrol/svn_authz.py
    a b  
    2020 
    2121from trac.config import Option 
    2222from trac.core import * 
    23 from trac.versioncontrol import Authorizer 
     23from trac.versioncontrol import Authorizer, ChangesetDenied 
    2424 
    2525 
    2626class SvnAuthzOptions(Component): 
     
    106106 
    107107    def has_permission_for_changeset(self, rev): 
    108108        changeset = self.repos.get_changeset(rev) 
    109         for change in changeset.get_changes(): 
    110             # the repository checks permissions for each change, so just check 
    111             # if any changes can be accessed 
     109        try: 
     110            for change in changeset.get_changes(): 
     111                break 
    112112            return 1 
    113         return 0 
     113        except ChangesetDenied: 
     114            return 0 
    114115 
    115116    # Internal API 
    116117 
  • trac/versioncontrol/svn_fs.py

    diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
    a b  
    5656from trac.core import * 
    5757from trac.versioncontrol import Changeset, Node, Repository, \ 
    5858                                IRepositoryConnector, \ 
    59                                 NoSuchChangeset, NoSuchNode 
     59                                NoSuchChangeset, NoSuchNode, \ 
     60                                ChangesetDenied 
    6061from trac.versioncontrol.cache import CachedRepository 
    6162from trac.versioncontrol.svn_authz import SubversionAuthorizer 
    6263from trac.versioncontrol.web_ui.browser import IPropertyRenderer 
     
    544545            tmp1, tmp2 = tmp2, tmp1 
    545546            if history_ptr: 
    546547                path_utf8, rev = fs.history_location(history_ptr, tmp2()) 
     548                if not self.authz.has_permission_for_changeset(rev): 
     549                    continue 
    547550                tmp2.clear() 
    548551                if rev < end: 
    549552                    break 
     
    560563                return prev 
    561564        return None 
    562565     
    563  
    564566    def get_oldest_rev(self): 
    565567        if self.oldest is None: 
    566568            self.oldest = 1 
     
    909911        copies, deletions = {}, {} 
    910912        changes = [] 
    911913        revroots = {} 
     914        empty = True 
    912915        for path_utf8, change in editor.changes.items(): 
    913             path = _from_svn(path_utf8) 
     916            change_path = _from_svn(path_utf8) 
    914917 
    915918            # Filtering on `path` 
    916             if not (_is_path_within_scope(self.scope, path) and 
    917                     self.authz.has_permission(path)): 
     919            if not _is_path_within_scope(self.scope, change_path): 
    918920                continue 
    919921 
    920922            path_utf8 = change.path 
     
    922924            path = _from_svn(path_utf8) 
    923925            base_path = _from_svn(base_path_utf8) 
    924926            base_rev = change.base_rev 
    925  
     927             
    926928            # Ensure `base_path` is within the scope 
    927929            if not (_is_path_within_scope(self.scope, base_path) and 
    928930                    self.authz.has_permission(base_path)): 
    929931                base_path, base_rev = None, -1 
    930932 
     933            if not path and not base_path and self.scope != '/': 
     934                continue                # deletion outside of scope, ignore 
     935             
     936            empty = False 
     937            if not self.authz.has_permission(change_path): 
     938                continue 
     939 
    931940            # Determine the action 
    932941            if not path:                # deletion 
    933942                if base_path: 
     
    937946                    deletions[base_path] = idx 
    938947                elif self.scope == '/': # root property change 
    939948                    action = Changeset.EDIT 
    940                 else:                   # deletion outside of scope, ignore 
    941                     continue 
    942949            elif change.added or not base_path: # add or copy 
    943950                action = Changeset.ADD 
    944951                if base_path and base_rev: 
     
    979986            del changes[i - offset] 
    980987            offset += 1 
    981988 
     989        if not empty and not changes: 
     990            raise ChangesetDenied(self.rev) 
    982991        changes.sort() 
    983992        for change in changes: 
    984993            yield tuple(change) 
  • trac/versioncontrol/web_ui/util.py

    diff --git a/trac/versioncontrol/web_ui/util.py b/trac/versioncontrol/web_ui/util.py
    a b  
    3434    for rev in revs: 
    3535        if rev in changes: 
    3636            continue 
    37         try: 
    38             changeset = repos.get_changeset(rev) 
    39         except NoSuchChangeset: 
    40             changeset = {} 
     37        changeset = {} 
     38        if repos.authz.has_permission_for_changeset(rev): 
     39            try: 
     40                changeset = repos.get_changeset(rev) 
     41            except NoSuchChangeset: 
     42                pass 
    4143        changes[rev] = changeset 
    4244    return changes 
    4345