PK0ƒ68“×2EGG-INFO/zip-safe PK+ƒ68bIÒózzEGG-INFO/SOURCES.txtsetup.cfg setup.py TracPrivateTickets.egg-info/PKG-INFO TracPrivateTickets.egg-info/SOURCES.txt TracPrivateTickets.egg-info/dependency_links.txt TracPrivateTickets.egg-info/entry_points.txt TracPrivateTickets.egg-info/top_level.txt privatetickets/__init__.py privatetickets/api.py privatetickets/query.py privatetickets/report.py privatetickets/search.py privatetickets/view.py PK*ƒ68wVBßêêEGG-INFO/entry_points.txt[trac.plugins] privatetickets.api = privatetickets.api privatetickets.view = privatetickets.view privatetickets.search = privatetickets.search privatetickets.report = privatetickets.report privatetickets.query = privatetickets.query PK*ƒ68“×2EGG-INFO/dependency_links.txt PK*ƒ68?¡|‰‰EGG-INFO/PKG-INFOMetadata-Version: 1.0 Name: TracPrivateTickets Version: 1.1.1 Summary: Modified ticket security for Trac. Home-page: http://trac-hacks.org/wiki/PrivateTickets Author: Noah Kantrowitz Author-email: coderanger@yahoo.com License: BSD Description: Allow users to only see tickets are involved with. Keywords: trac plugin ticket permissions security Platform: UNKNOWN Classifier: Framework :: Trac PK*ƒ68’[â EGG-INFO/top_level.txtprivatetickets PK¸v6ÒŸýÄ//privatetickets/report.pyfrom trac.core import * from trac.web.api import IRequestFilter from trac.ticket.report import ReportModule from api import PrivateTicketsSystem __all__ = ['PrivateTicketsReportFilter'] class PrivateTicketsReportFilter(Component): """Show only ticket the user is involved in in the reports.""" implements(IRequestFilter) # IRequestFilter methods def pre_process_request(self, req, handler): if isinstance(handler, ReportModule) and \ not req.perm.has_permission('TICKET_VIEW') and \ req.args.get('format') in ('tab', 'csv'): raise TracError('Access denied') return handler def post_process_request(self, req, template, content_type): if req.args.get('DO_PRIVATETICKETS_FILTER') == 'report': # Walk the HDF fn = PrivateTicketsSystem(self.env).check_ticket_access deleted = [] left = [] node = req.hdf.getObj('report.items') if node is None: return template, content_type node = node.child() while node: i = node.name() id = req.hdf['report.items.%s.ticket'%i] if not fn(req, id): deleted.append(i) else: left.append(i) node = node.next() # Delete the needed subtrees for n in deleted: req.hdf.removeTree('report.items.%s'%n) # Recalculate this req.hdf['report.numrows'] = len(left) # Move the remaining items into their normal places for src, dest in zip(left, xrange(len(left)+len(deleted))): if src == dest: continue req.hdf.getObj('report.items').copy(str(dest), req.hdf.getObj('report.items.%s'%src)) for n in xrange(len(left), len(left)+len(deleted)): req.hdf.removeTree('report.items.%s'%n) return template, content_type PK(»_5privatetickets/__init__.pyPK.ƒ68ÎÉû« « privatetickets/view.pyc;ò Ž(1Fc@s’dkTdklZdklZdklZdklZdk l Z dk l Z dk lZdklZd gZd efd „ƒYZd S( (s*(sINavigationContributor(s TicketModule(s QueryModule(s SearchModule(s ReportModule(sAttachmentModule(shtml(sPrivateTicketsSystemsPrivateTicketsViewModulecBs3tZdZeeƒd„Zd„Zd„ZRS(s0Allow users to see tickets they are involved in.cCsdSdS(Ns((sselfsreq((s7build/bdist.darwin-8.0.1-x86/egg/privatetickets/view.pysget_active_navigation_itemscCs§d|iiƒjo|id=n|iidƒ oo|iidƒp\|iidƒpI|iidƒp6|iidƒp#|iidƒp|iidƒoõt|iƒi|ƒo8t |iƒi ||id ƒo|i |ƒqGnMt |iƒi|ƒoY|id d jo-t |iƒi ||id i d ƒdƒo|i |ƒqGnÛt|iƒi|ƒod|id<|i |ƒn¤t|iƒi|ƒo<d |iiƒjo"|id |id<|id =qGnOt|iƒi|ƒo5|i |ƒ|iid ƒod|ids(s__name__s __module__s__doc__s implementssINavigationContributorsget_active_navigation_itemsget_navigation_itemss _grant_view(((s7build/bdist.darwin-8.0.1-x86/egg/privatetickets/view.pysPrivateTicketsViewModules    'N(s trac.corestrac.web.chromesINavigationContributorstrac.ticket.web_uis TicketModulestrac.ticket.querys QueryModules trac.Searchs SearchModulestrac.ticket.reports ReportModulestrac.attachmentsAttachmentModulestrac.util.htmlshtmlsapisPrivateTicketsSystems__all__s ComponentsPrivateTicketsViewModule( sINavigationContributors__all__s TicketModulesPrivateTicketsViewModules QueryModules SearchModuleshtmlsAttachmentModules ReportModulesPrivateTicketsSystem((s7build/bdist.darwin-8.0.1-x86/egg/privatetickets/view.pys?s         PKºv6úÐîy00privatetickets/api.pyfrom trac.core import * from trac.perm import IPermissionRequestor, IPermissionGroupProvider, PermissionSystem from trac.ticket.model import Ticket from trac.config import IntOption, ListOption try: set = set except NameError: from sets import Set as set __all__ = ['PrivateTicketsSystem'] class PrivateTicketsSystem(Component): """Central tasks for the PrivateTickets plugin.""" implements(IPermissionRequestor) group_providers = ExtensionPoint(IPermissionGroupProvider) blacklist = ListOption('privatetickets', 'group_blacklist', default='anonymous, authenticated', doc='Groups that do not affect the common membership check.') # IPermissionRequestor methods def get_permission_actions(self): actions = ['TICKET_VIEW_REPORTER', 'TICKET_VIEW_OWNER', 'TICKET_VIEW_CC'] group_actions = ['TICKET_VIEW_REPORTER_GROUP', 'TICKET_VIEW_OWNER_GROUP', 'TICKET_VIEW_CC_GROUP'] all_actions = actions + [(a+'_GROUP', [a]) for a in actions] return all_actions + [('TICKET_VIEW_SELF', actions), ('TICKET_VIEW_GROUP', group_actions)] # Public methods def check_ticket_access(self, req, id): """Return if this req is permitted access to the given ticket ID.""" try: tkt = Ticket(self.env, id) except TracError: return False # Ticket doesn't exist if req.perm.has_permission('TICKET_VIEW_REPORTER') and \ tkt['reporter'] == req.authname: return True if req.perm.has_permission('TICKET_VIEW_CC') and \ req.authname in [x.strip() for x in tkt['cc'].split(',')]: return True if req.perm.has_permission('TICKET_VIEW_OWNER') and \ req.authname == tkt['owner']: return True if req.perm.has_permission('TICKET_VIEW_REPORTER_GROUP') and \ self._check_group(req.authname, tkt['reporter']): return True if req.perm.has_permission('TICKET_VIEW_OWNER_GROUP') and \ self._check_group(req.authname, tkt['owner']): return True if req.perm.has_permission('TICKET_VIEW_CC_GROUP'): for user in tkt['cc'].split(','): #self.log.debug('Private: CC check: %s, %s', req.authname, user.strip()) if self._check_group(req.authname, user.strip()): return True return False # Internal methods def _check_group(self, user1, user2): """Check if user1 and user2 share a common group.""" user1_groups = self._get_groups(user1) user2_groups = self._get_groups(user2) both = user1_groups.intersection(user2_groups) both -= set(self.blacklist) #self.log.debug('PrivateTicket: %s&%s = (%s)&(%s) = (%s)', user1, user2, ','.join(user1_groups), ','.join(user2_groups), ','.join(both)) return bool(both) def _get_groups(self, user): # Get initial subjects groups = set([user]) for provider in self.group_providers: for group in provider.get_permission_groups(user): groups.add(group) perms = PermissionSystem(self.env).get_all_permissions() repeat = True while repeat: repeat = False for subject, action in perms: if subject in groups and action.islower() and action not in groups: groups.add(action) repeat = True return groups PK.ƒ68ëx!” privatetickets/search.pyc;ò ­!HEc@s^dkTdklZdklZdklZdklZdgZ de fd„ƒYZ dS((s*(s ISearchSource(s TicketSystem(sIRequestFilter(sPrivateTicketsSystemsPrivateTicketsSearchModulecBs?tZdZeeeƒd„Zd„Zd„Zd„Z RS(s3Search restricted to tickets you are involved with.ccs_|iidƒ o6|iidƒp#|iidƒp|iidƒoddfVndS(Ns TICKET_VIEWsTICKET_VIEW_REPORTERsTICKET_VIEW_CCsTICKET_VIEW_ASSIGNEDspticketsTickets(sreqspermshas_permission(sselfsreq((s9build/bdist.darwin-8.0.1-x86/egg/privatetickets/search.pysget_search_filterssMccsÃ|iidƒodSnd|jodSnt|_t|iƒi }xqt |iƒi ||dgƒD]N}t|didƒdƒ}|iid|ƒ|||ƒo|VqmqmWdS(Ns TICKET_VIEWspticketsticketis/iÿÿÿÿs'PrivateTicketsSearchModule: Check id %r(sreqspermshas_permissionsfilterssTrues _MUNGE_FILTERsPrivateTicketsSystemsselfsenvscheck_ticket_accesssfns TicketSystemsget_search_resultsstermssresultsintssplitsidslogsdebug(sselfsreqstermssfilterssresultsidsfn((s9build/bdist.darwin-8.0.1-x86/egg/privatetickets/search.pysget_search_resultss  "cCs|SdS(N(shandler(sselfsreqshandler((s9build/bdist.darwin-8.0.1-x86/egg/privatetickets/search.pyspre_process_request&scCsŽt|dƒop|iidƒiƒ}xU|oI|id|iƒdjod|id|iƒ} |t|| ƒi|dƒiddƒiddƒƒqÐ~ƒtƒq”W| iƒd|fSdS(Ns+PrivateTicket: Running hacked CSV convertersids_s s s s%s;charset=utf-8(sselfslogsdebugsStringIOscontentsquerys get_columnsscolsswritessepsjoinsappends_[1]scolsCRLFsPrivateTicketsSystemsenvscheck_ticket_accesssfnsexecutesreqs get_db_cnxsresultssresultsunicodesreplacesgetvaluesmimetype( sselfsreqsqueryssepsmimetypes_[1]sresultscolssresultsscontentscolsfn((s8build/bdist.darwin-8.0.1-x86/egg/privatetickets/query.pys export_csvps  ;mc Cs¦t|_|iiƒ}t|iƒi} gi }|i ||ƒD](}| ||dƒo||ƒqDqD~}x´|D]¬}|ii|dƒ|d<|didƒdjod|dReturn if this req is permitted access to the given ticket ID.sTICKET_VIEW_REPORTERsreportersTICKET_VIEW_CCsccs,sTICKET_VIEW_OWNERsownersTICKET_VIEW_REPORTER_GROUPsTICKET_VIEW_OWNER_GROUPsTICKET_VIEW_CC_GROUPN(sTicketsselfsenvsidstkts TracErrorsFalsesreqspermshas_permissionsauthnamesTruesappends_[1]ssplitsxsstrips _check_groupsuser(sselfsreqsids_[1]stktsusersx((s6build/bdist.darwin-8.0.1-x86/egg/privatetickets/api.pyscheck_ticket_accesss* 'W'--cCsN|i|ƒ}|i|ƒ}|i|ƒ}|t|i ƒ8}t |ƒSdS(s.Check if user1 and user2 share a common group.N( sselfs _get_groupssuser1s user1_groupssuser2s user2_groupss intersectionsbothssets blacklistsbool(sselfsuser1suser2sboths user1_groupss user2_groups((s6build/bdist.darwin-8.0.1-x86/egg/privatetickets/api.pys _check_groupCs c Cs×t|gƒ}x8|iD]-}x$|i|ƒD]}|i|ƒq/WqWt |i ƒi ƒ}t }xg|o_t}xR|D]J\}}||jo|iƒo ||jo|i|ƒt }q|q|WqhW|SdS(N(ssetsusersgroupssselfsgroup_providerssprovidersget_permission_groupssgroupsaddsPermissionSystemsenvsget_all_permissionsspermssTruesrepeatsFalsessubjectsactionsislower( sselfsusersgroupspermssrepeatsgroupssprovidersactionssubject((s6build/bdist.darwin-8.0.1-x86/egg/privatetickets/api.pys _get_groupsMs"  ' (s__name__s __module__s__doc__s implementssIPermissionRequestorsExtensionPointsIPermissionGroupProvidersgroup_providerss ListOptions blacklistsget_permission_actionsscheck_ticket_accesss _check_groups _get_groups(((s6build/bdist.darwin-8.0.1-x86/egg/privatetickets/api.pysPrivateTicketsSystem s      $ N(s trac.cores trac.permsIPermissionRequestorsIPermissionGroupProvidersPermissionSystemstrac.ticket.modelsTickets trac.configs IntOptions ListOptionssets NameErrorssetssSets__all__s ComponentsPrivateTicketsSystem( s ListOptionsIPermissionGroupProviderssets__all__sIPermissionRequestors IntOptionsPermissionSystemsTicketsPrivateTicketsSystem((s6build/bdist.darwin-8.0.1-x86/egg/privatetickets/api.pys?s   PK.ƒ68"¬ˆ ˆ privatetickets/report.pyc;ò ÎBFc@sQdkTdklZdklZdklZdgZdefd„ƒYZ dS((s*(sIRequestFilter(s ReportModule(sPrivateTicketsSystemsPrivateTicketsReportFiltercBs*tZdZeeƒd„Zd„ZRS(s8Show only ticket the user is involved in in the reports.cCs[t|tƒo0|iidƒ o|iidƒddfjotdƒ‚n|SdS(Ns TICKET_VIEWsformatstabscsvs Access denied( s isinstanceshandlers ReportModulesreqspermshas_permissionsargssgets TracError(sselfsreqshandler((s9build/bdist.darwin-8.0.1-x86/egg/privatetickets/report.pyspre_process_requestsCc Csá|iidƒdjoºt|iƒi} g}g} |i i dƒ}|t jo||fSn|iƒ}xd|o\|iƒ} |i d| } | || ƒ o|i| ƒn| i| ƒ|iƒ}qsWx"|D]}|i id|ƒqÞWt| ƒ|i d return handler def post_process_request(self, req, template, content_type): if req.args.get('DO_PRIVATETICKETS_FILTER') == 'query': # Extract the data results = [] node = req.hdf.getObj('query.results') if not node: return template, content_type node = node.child() while node: data = {} sub_node = node.child() while sub_node: data[sub_node.name()] = sub_node.value() sub_node = sub_node.next() results.append(data) node = node.next() self.log.debug('PrivateTickets: results = %r', results) # Nuke the old data req.hdf.removeTree('query.results') # Filter down the data fn = PrivateTicketsSystem(self.env).check_ticket_access new_results = [d for d in results if fn(req, d['id'])] self.log.debug('PrivateTickets: new_results = %r', new_results) # Reinsert the data req.hdf['query.results'] = new_results return template, content_type # Content conversion insanity def process_request(self, req): constraints = QueryModule(self.env)._get_constraints(req) if not constraints and not req.args.has_key('order'): # avoid displaying all tickets when the query module is invoked # with no parameters. Instead show only open tickets, possibly # associated with the user constraints = {'status': ('new', 'assigned', 'reopened')} if req.authname and req.authname != 'anonymous': constraints['owner'] = (req.authname,) else: email = req.session.get('email') name = req.session.get('name') if email or name: constraints['cc'] = ('~%s' % email or name,) query = Query(self.env, constraints, req.args.get('order'), req.args.has_key('desc'), req.args.get('group'), req.args.has_key('groupdesc'), req.args.has_key('verbose')) format = req.args.get('format') self.send_converted(req, 'trac.ticket.Query', query, format, 'query') def get_supported_conversions(self): yield ('csv', 'Comma-delimited Text', 'csv', 'trac.ticket.Query', 'text/csv', 9) def convert_content(self, req, mimetype, query, key): if key == 'rss': return self.export_rss(req, query) + ('rss',) elif key == 'csv': return self.export_csv(req, query, mimetype='text/csv') + ('csv',) elif key == 'tab': return self.export_csv(req, query, '\t', 'text/tab-separated-values') + ('tsv',) def send_converted(self, req, in_type, content, selector, filename='file'): # Stolen from Mimetype """Helper method for converting `content` and sending it directly. `selector` can be either a key or a MIME Type.""" from trac.web import RequestDone content, output_type, ext = self.convert_content(req, in_type, content, selector) req.send_response(200) req.send_header('Content-Type', output_type) req.send_header('Content-Disposition', 'filename=%s.%s' % (filename, ext)) req.end_headers() req.write(content) raise RequestDone # Hacked content converters def export_csv(self, req, query, sep=',', mimetype='text/plain'): self.log.debug('PrivateTicket: Running hacked CSV converter') content = StringIO() cols = query.get_columns() content.write(sep.join([col for col in cols]) + CRLF) fn = PrivateTicketsSystem(self.env).check_ticket_access results = query.execute(req, self.env.get_db_cnx()) for result in results: # Filter data if not fn(req, result['id']): continue content.write(sep.join([unicode(result[col]).replace(sep, '_') .replace('\n', ' ') .replace('\r', ' ') for col in cols]) + CRLF) return (content.getvalue(), '%s;charset=utf-8' % mimetype) def export_rss(self, req, query): query.verbose = True db = self.env.get_db_cnx() fn = PrivateTicketsSystem(self.env).check_ticket_access results = [r for r in query.execute(req, db) if fn(req, r['id'])] for result in results: result['href'] = req.abs_href.ticket(result['id']) if result['reporter'].find('@') == -1: result['reporter'] = '' if result['description']: # unicode() cancels out the Markup() returned by wiki_to_html descr = wiki_to_html(result['description'], self.env, req, db, absurls=True) result['description'] = unicode(descr) if result['time']: result['time'] = http_date(result['time']) req.hdf['query.results'] = results req.hdf['query.href'] = req.abs_href.query(group=query.group, groupdesc=query.groupdesc and 1 or None, verbose=query.verbose and 1 or None, **query.constraints) return (req.hdf.render('query_rss.cs'), 'application/rss+xml') PK”š6šý¯¤~ ~ privatetickets/view.pyfrom trac.core import * from trac.web.chrome import INavigationContributor from trac.ticket.web_ui import TicketModule from trac.ticket.query import QueryModule from trac.Search import SearchModule from trac.ticket.report import ReportModule from trac.attachment import AttachmentModule from trac.util.html import html from api import PrivateTicketsSystem __all__ = ['PrivateTicketsViewModule'] class PrivateTicketsViewModule(Component): """Allow users to see tickets they are involved in.""" implements(INavigationContributor) # INavigationContributor methods def get_active_navigation_item(self, req): return '' def get_navigation_items(self, req): # Don't allow this to be exposed if 'DO_PRIVATETICKETS_FILTER' in req.args.keys(): del req.args['DO_PRIVATETICKETS_FILTER'] # Various ways to allow access if not req.perm.has_permission('TICKET_VIEW') and \ (req.perm.has_permission('TICKET_VIEW_REPORTER') or \ req.perm.has_permission('TICKET_VIEW_OWNER') or \ req.perm.has_permission('TICKET_VIEW_CC') or \ req.perm.has_permission('TICKET_VIEW_REPORTER_GROUP') or \ req.perm.has_permission('TICKET_VIEW_OWNER_GROUP') or \ req.perm.has_permission('TICKET_VIEW_CC_GROUP')): if TicketModule(self.env).match_request(req): if PrivateTicketsSystem(self.env).check_ticket_access(req, req.args['id']): self._grant_view(req) elif AttachmentModule(self.env).match_request(req): if req.args['type'] == 'ticket' and PrivateTicketsSystem(self.env).check_ticket_access(req, req.args['path'].split('/')[0]): self._grant_view(req) elif QueryModule(self.env).match_request(req): req.args['DO_PRIVATETICKETS_FILTER'] = 'query' self._grant_view(req) # Further filtering in query.py elif SearchModule(self.env).match_request(req): if 'ticket' in req.args.keys(): req.args['pticket'] = req.args['ticket'] del req.args['ticket'] elif ReportModule(self.env).match_request(req): self._grant_view(req) # So they can see the query page link if req.args.get('id'): req.args['DO_PRIVATETICKETS_FILTER'] = 'report' # NOTE: Send this back here because the button would be hidden otherwise. if not self.env.is_component_enabled(ReportModule) or not req.perm.has_permission('REPORT_VIEW'): return [('mainnav', 'tickets', html.A('View Tickets', href=req.href.query()))] return [] # Internal methods def _grant_view(self, req): req.perm.perms['TICKET_VIEW'] = True req.hdf['trac.acl.TICKET_VIEW'] = 1 PK(»_5ij8Ü;;privatetickets/search.pyfrom trac.core import * from trac.Search import ISearchSource from trac.ticket.api import TicketSystem from trac.web.api import IRequestFilter from api import PrivateTicketsSystem __all__ = ['PrivateTicketsSearchModule'] class PrivateTicketsSearchModule(Component): """Search restricted to tickets you are involved with.""" implements(ISearchSource, IRequestFilter) # ISearchSource methods def get_search_filters(self, req): if not req.perm.has_permission('TICKET_VIEW') and \ ( req.perm.has_permission('TICKET_VIEW_REPORTER') or \ req.perm.has_permission('TICKET_VIEW_CC') or \ req.perm.has_permission('TICKET_VIEW_ASSIGNED') ): yield ('pticket', 'Tickets') def get_search_results(self, req, terms, filters): if req.perm.has_permission('TICKET_VIEW'): return if 'pticket' not in filters: return req._MUNGE_FILTER = True fn = PrivateTicketsSystem(self.env).check_ticket_access for result in TicketSystem(self.env).get_search_results(req, terms, ['ticket']): id = int(result[0].split('/')[-1]) self.log.debug('PrivateTicketsSearchModule: Check id %r', id) if fn(req, id): yield result # IRequestFilter methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, content_type): if hasattr(req, '_MUNGE_FILTER'): node = req.hdf.getObj('search.filters').child() while node: if req.hdf['search.filters.%s.name'%node.name()] == 'pticket': req.hdf['search.filters.%s.name'%node.name()] = 'ticket' node = node.next() return template, content_type PK0ƒ68“×2¤EGG-INFO/zip-safePK+ƒ68bIÒózz¤0EGG-INFO/SOURCES.txtPK*ƒ68wVBßêê¤ÜEGG-INFO/entry_points.txtPK*ƒ68“×2¤ýEGG-INFO/dependency_links.txtPK*ƒ68?¡|‰‰¤9EGG-INFO/PKG-INFOPK*ƒ68’[â ¤ñEGG-INFO/top_level.txtPK¸v6ÒŸýÄ//¤4privatetickets/report.pyPK(»_5¤™ privatetickets/__init__.pyPK.ƒ68ÎÉû« « ¤Ñ privatetickets/view.pycPKºv6úÐîy00¤±privatetickets/api.pyPK.ƒ68ëx!” ¤*privatetickets/search.pycPK.ƒ68…™‹…HH¤k6privatetickets/query.pycPK.ƒ68ñ©Æ‹‹¤éUprivatetickets/__init__.pycPK.ƒ68¬,ØZÎΤ­Vprivatetickets/api.pycPK.ƒ68"¬ˆ ˆ ¤¯hprivatetickets/report.pycPK™Ž}5ˆ£âÙÙ¤nsprivatetickets/query.pyPK”š6šý¯¤~ ~ ¤|privatetickets/view.pyPK(»_5ij8Ü;;¤.™privatetickets/search.pyPKÜŸ