/usr/lib/python2.7/dist-packages/sensitivetickets/sensitivetickets.py is in trac-sensitivetickets 0.21-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | """
SensitiveTicketsPlugin : a plugin for Trac, http://trac.edgewall.org
Based on the example vulnerability_tickets plugin, but SensitivityTickets uses a checkbox to control status versus text.
See: http://trac.edgewall.org/browser/trunk/sample-plugins/permissions/vulnerability_tickets.py
"""
from trac.core import *
from trac.config import BoolOption
from trac.perm import IPermissionPolicy, IPermissionRequestor
from trac.env import IEnvironmentSetupParticipant
from trac.ticket.model import Ticket
from trac.ticket.api import ITicketManipulator
from trac.timeline.api import ITimelineEventProvider
from trac.resource import ResourceNotFound
from datetime import datetime
from trac.util.datefmt import format_datetime, from_utimestamp, \
to_utimestamp, utc
from trac.util import as_bool
class SensitiveTicketsPolicy(Component):
"""Prevent public access to security sensitive tickets.
Add the SENSITIVE_VIEW permission as a pre-requisite for any
other permission check done on tickets that have been marked (through
the UI) as "Sensitive".
Once this plugin is enabled, you'll have to insert it at the appropriate
place in your list of permission policies, e.g.
{{{
[trac]
permission_policies = SensitiveTicketsPolicy, AuthzPolicy,
DefaultPermissionPolicy, LegacyAttachmentPolicy
}}}
This plugin also adds the SENSITIVE_ACTIVITY_VIEW permission,
which is narrower in scope than SENSITIVE_VIEW. Accounts with
SENSITIVE_ACTIVITY_VIEW will be able to see activity on sensitive
material in the timeline, but will only be able to identify it by
ticket number, comment number, and timestamp. All other content
will be redacted.
SENSITIVE_ACTIVITY_VIEW can be useful (for example) for providing
a notification daemon the ability to tell that some activity
happened without leaking the content of that activity.
"""
implements(IPermissionPolicy, IPermissionRequestor, IEnvironmentSetupParticipant, ITicketManipulator, ITimelineEventProvider)
allow_reporter = BoolOption('sensitivetickets', 'allow_reporter', 'false',
'''Whether the reporter of a sensitive
ticket should have access to that ticket even if they do not have
SENSITIVE_VIEW privileges''')
allow_cc = BoolOption('sensitivetickets', 'allow_cc', 'false',
'''Whether users listed in the cc field of a
sensitive ticket should have access to that ticket even if they do not
have SENSITIVE_VIEW privileges''')
allow_owner = BoolOption('sensitivetickets', 'allow_owner', 'true',
'''Whether the owner of a sensitive
ticket should have access to that ticket even if they do not have
SENSITIVE_VIEW privileges''')
limit_sensitivity = BoolOption('sensitivetickets', 'limit_sensitivity', 'false',
'''With limit_sensitivity set to
true, users cannot set the sensitivity checkbox on a ticket unless
they are authenticated and would otherwise be permitted to deal with
the ticket if it were marked sensitive.
This prevents users from marking the tickets of other users as "sensitive".''')
# IPermissionPolicy methods
def check_permission(self, action, username, resource, perm):
# We add the 'SENSITIVE_VIEW' pre-requisite for any action
# other than 'SENSITIVE_VIEW' itself, as this would lead
# to recursion.
if action == 'SENSITIVE_VIEW':
return
# Check whether we're dealing with a ticket resource
while resource:
if resource.realm == 'ticket':
break
resource = resource.parent
if resource and resource.realm == 'ticket' and resource.id is not None:
bypass = False
try:
ticket = Ticket(self.env, int(resource.id))
sensitive = ticket['sensitive']
if as_bool(sensitive):
bypass = self.bypass_sensitive_view(ticket, username)
except ResourceNotFound:
sensitive = 1 # Fail safe to prevent a race condition.
if as_bool(sensitive):
if 'SENSITIVE_VIEW' not in perm and not bypass:
return False
# IPermissionRequestor methods
def get_permission_actions(self):
yield 'SENSITIVE_VIEW'
yield 'SENSITIVE_ACTIVITY_VIEW'
# ITicketManipulator methods:
def validate_ticket(self, req, ticket):
if not self.limit_sensitivity:
return []
sensitive = 1
try:
sensitive = ticket['sensitive']
except:
pass
if as_bool(sensitive):
if req.authname is 'anonymous':
return [(None, 'Sorry, you cannot create or update a sensitive ticket without at least logging in first')]
if self.bypass_sensitive_view(ticket, req.authname):
return []
req.perm(ticket.resource).require('SENSITIVE_VIEW')
return []
### methods for IEnvironmentSetupParticipant
"""Extension point interface for components that need to participate in the
creation and upgrading of Trac environments, for example to create
additional database tables."""
def environment_created(self):
"""Called when a new Trac environment is created."""
if self.environment_needs_upgrade(None):
self.upgrade_environment(None)
def environment_needs_upgrade(self, db):
"""Called when Trac checks whether the environment needs to be upgraded.
Should return `True` if this participant needs an upgrade to be
performed, `False` otherwise.
"""
return 'sensitive' not in self.config['ticket-custom']
def upgrade_environment(self, db):
"""Actually perform an environment upgrade.
Implementations of this method should not commit any database
transactions. This is done implicitly after all participants have
performed the upgrades they need without an error being raised.
"""
if not self.environment_needs_upgrade(db):
return
custom = self.config['ticket-custom']
custom.set('sensitive','checkbox')
custom.set('sensitive.label', "Sensitive")
custom.set('sensitive.value', '0')
self.config.save()
### ITimelineEventProvider methods:
def get_timeline_filters(self, req):
if ('SENSITIVE_ACTIVITY_VIEW' in req.perm and
'SENSITIVE_VIEW' not in req.perm):
yield ('sensitive_activity', 'Activity on sensitive tickets', False)
def get_timeline_events(self, req, start, stop, filters):
if ('sensitive_activity' in filters and
'SENSITIVE_ACTIVITY_VIEW' in req.perm and
'SENSITIVE_VIEW' not in req.perm):
ts_start = to_utimestamp(start)
ts_stop = to_utimestamp(stop)
db = self.env.get_db_cnx()
cursor = db.cursor()
if 'ticket_details' in filters:
# only show sensitive ticket changes (edits, closure) if the 'ticket_details' filter is on:
cursor.execute("""
SELECT DISTINCT t.id,tc.time,tc.oldvalue
FROM ticket_change tc
INNER JOIN ticket t ON t.id = tc.ticket
AND tc.time >= %s AND tc.time <= %s AND tc.field = %s
INNER JOIN ticket_custom td ON t.id = td.ticket
AND td.name = %s AND td.value = %s
ORDER BY tc.time
""", (ts_start, ts_stop, 'comment', 'sensitive', '1'))
for tid,t,cid in cursor:
yield ('sensitive_activity', from_utimestamp(t), 'redacted', (tid, cid))
# always show new sensitive tickets:
cursor.execute('''
SELECT DISTINCT id, time FROM
ticket t INNER JOIN ticket_custom tc ON t.id = tc.ticket
AND t.time >= %s AND t.time <= %s
AND tc.name = %s AND tc.value = %s
ORDER BY time
''', (ts_start, ts_stop, 'sensitive', '1'))
for tid,t in cursor:
yield ('sensitive_activity', from_utimestamp(t), 'redacted', (tid, None))
def render_timeline_event(self, context, field, event):
tid, cid = event[3]
if field == 'title':
return 'Sensitive Activity'
elif field == 'description':
return '[REDACTED]'
elif field == 'url':
href = context.href.ticket(tid)
if cid:
href += '#comment:' + str(cid)
return href
### private methods:
def bypass_sensitive_view(self, ticket, username):
'''Returns whether the sensitivetickets permission allows a
bypass of the SENSITIVE_VIEW setting for a given ticket
'''
if username == 'anonymous':
return False
return (self.allow_owner and (ticket['owner'] == username)) or \
(self.allow_reporter and (ticket['reporter'] == username)) or \
(self.allow_cc and (username in [x.strip() for x in ticket['cc'].split(',')]))
|