Edgewall Software

Ticket #1396: TicketClosePermissions.diff

File TicketClosePermissions.diff, 13.0 KB (added by ludde, 4 years ago)

Patch for fine grained ticket permissions

  • trac/attachment.py

     
    6363                self.render_view(req, parent_type, parent_id, filename) 
    6464 
    6565    def render_form(self, req, parent_type, parent_id): 
    66         perm_map = {'ticket': perm.TICKET_MODIFY, 'wiki': perm.WIKI_MODIFY} 
     66        perm_map = {'ticket': perm.TICKET_ATTACH, 'wiki': perm.WIKI_MODIFY} 
    6767        self.perm.assert_permission(perm_map[parent_type]) 
    6868 
    6969        text, link = self.get_parent_link(parent_type, parent_id) 
     
    7979        req.display('attachment.cs') 
    8080 
    8181    def save_attachment(self, req, parent_type, parent_id): 
    82         perm_map = {'ticket': perm.TICKET_MODIFY, 'wiki': perm.WIKI_MODIFY} 
     82        perm_map = {'ticket': perm.TICKET_ATTACH, 'wiki': perm.WIKI_MODIFY} 
    8383        self.perm.assert_permission(perm_map[parent_type]) 
    8484 
    8585        if req.args.has_key('cancel'): 
  • trac/perm.py

     
    2424    'TIMELINE_VIEW', 'SEARCH_VIEW', 'CONFIG_VIEW', 'LOG_VIEW', 'FILE_VIEW', 
    2525    'CHANGESET_VIEW', 'BROWSER_VIEW', 'ROADMAP_VIEW', 
    2626     
    27     'TICKET_VIEW', 'TICKET_CREATE', 'TICKET_MODIFY', 
     27    'TICKET_VIEW', 'TICKET_CREATE', 'TICKET_EDIT', 'TICKET_RESOLVE',  
     28    'TICKET_APPEND','TICKET_ATTACH', 
    2829     
    2930    'REPORT_VIEW', 'REPORT_SQL_VIEW', 'REPORT_CREATE', 'REPORT_MODIFY', 
    3031    'REPORT_DELETE', 
     
    3940    'TRAC_ADMIN': ['TICKET_ADMIN', 'REPORT_ADMIN', 'WIKI_ADMIN', 'ROADMAP_ADMIN', 
    4041                   'TIMELINE_VIEW', 'SEARCH_VIEW', 'CONFIG_VIEW', 'LOG_VIEW', 
    4142                   'FILE_VIEW', 'CHANGESET_VIEW', 'BROWSER_VIEW'], 
    42     'TICKET_ADMIN': ['TICKET_VIEW', 'TICKET_CREATE', 'TICKET_MODIFY'], 
     43    'TICKET_ADMIN': ['TICKET_VIEW', 'TICKET_CREATE', 'TICKET_EDIT',  
     44                     'TICKET_RESOLVE', 'TICKET_ATTACH'], 
     45    'TICKET_MODIFY': ['TICKET_EDIT', 'TICKET_RESOLVE', 'TICKET_ATTACH'], 
    4346    'REPORT_ADMIN': ['REPORT_VIEW', 'REPORT_SQL_VIEW', 'REPORT_CREATE', 
    4447                     'REPORT_MODIFY', 'REPORT_DELETE'], 
    4548    'WIKI_ADMIN': ['WIKI_VIEW', 'WIKI_CREATE', 'WIKI_MODIFY', 'WIKI_DELETE'], 
  • trac/Ticket.py

     
    7878            for row in rows: 
    7979                self['custom_' + row[0]] = row[1] 
    8080        self._forget_changes() 
    81  
     81 
    8282    def populate(self, dict): 
    8383        """Populate the ticket with 'suitable' values from a dictionary""" 
    8484        def is_field(name): 
     
    210210            log.append((int(row[0]), row[1], row[2], row[3] or '', row[4] or '')) 
    211211        return log 
    212212 
    213  
    214213def get_custom_fields(env): 
    215214    cfg = env.get_config_items('ticket-custom') 
    216215    if not cfg: 
     
    368367 
    369368        req.display('newticket.cs') 
    370369 
     370def get_ticket_actions(ticket, prm): 
     371    """ Returns the actions that can be performed on the ticket""" 
     372    actions = { 
     373        'new':      ['leave', 'resolve', 'reassign', 'accept'], 
     374        'assigned': ['leave', 'resolve', 'reassign'          ], 
     375        'reopened': ['leave', 'resolve', 'reassign'          ], 
     376        'closed':   ['leave',                        'reopen'] 
     377    } 
     378    permissions = { 
     379        'resolve': perm.TICKET_RESOLVE, 
     380        'reassign': perm.TICKET_EDIT, 
     381        'accept': perm.TICKET_EDIT, 
     382        'reopen': perm.TICKET_CREATE 
     383    } 
     384    def has_perm(p): 
     385        n = permissions.get(p, None) 
     386        return not n or prm.has_permission(n) 
     387    return filter(has_perm, actions.get(ticket['status'], ['leave'])) 
    371388 
    372 class TicketModule (Module): 
    373  
     389class TicketModule (Module): 
     390 
    374391    def save_changes(self, req, id): 
    375         self.perm.assert_permission (perm.TICKET_MODIFY) 
    376         ticket = Ticket(self.db, id) 
    377  
    378         if not req.args.get('summary'): 
    379             raise util.TracError('Tickets must contain Summary.') 
    380  
    381         if req.args.has_key('description'): 
    382             self.perm.assert_permission (perm.TICKET_ADMIN) 
    383  
    384         if req.args.has_key('reporter'): 
    385             self.perm.assert_permission (perm.TICKET_ADMIN) 
    386  
    387         # TODO: this should not be hard-coded like this 
    388         action = req.args.get('action', None) 
    389         if action == 'accept': 
    390             ticket['status'] =  'assigned' 
    391             ticket['owner'] = req.authname 
    392         if action == 'resolve': 
    393             ticket['status'] = 'closed' 
    394             ticket['resolution'] = req.args.get('resolve_resolution') 
    395         elif action == 'reassign': 
    396             ticket['owner'] = req.args.get('reassign_owner') 
    397             ticket['status'] = 'new' 
    398         elif action == 'reopen': 
    399             ticket['status'] = 'reopened' 
    400             ticket['resolution'] = '' 
    401  
    402         ticket.populate(req.args) 
    403  
     392        if self.perm.has_permission(perm.TICKET_EDIT): 
     393            # TICKET_EDIT gives permission to edit the ticket 
     394            if not req.args.get('summary'): 
     395                raise util.TracError('Tickets must contain Summary.') 
     396 
     397            ticket = Ticket(self.db, id) 
     398            if req.args.has_key('description'): 
     399                self.perm.assert_permission (perm.TICKET_ADMIN) 
     400 
     401            if req.args.has_key('reporter'): 
     402                self.perm.assert_permission (perm.TICKET_ADMIN) 
     403 
     404            ticket.populate(req.args) 
     405 
     406        elif self.perm.has_permission(perm.TICKET_APPEND): 
     407            # Allow appending a comment to the ticket only 
     408            ticket = Ticket(self.db, id) 
     409        else: 
     410            raise perm.PermissionError(perm.TICKET_EDIT) 
     411 
     412        # Do any action on the ticket? 
     413        action = req.args.get('action', None) 
     414 
     415        # Make sure the action is valid 
     416        if action not in get_ticket_actions(ticket, self.perm): 
     417            raise util.TracError('Invalid action') 
     418 
     419        # TODO: this should not be hard-coded like this 
     420        if action == 'accept': 
     421            ticket['status'] =  'assigned' 
     422            ticket['owner'] = req.authname 
     423        if action == 'resolve': 
     424            ticket['status'] = 'closed' 
     425            ticket['resolution'] = req.args.get('resolve_resolution') 
     426        elif action == 'reassign': 
     427            ticket['owner'] = req.args.get('reassign_owner') 
     428            ticket['status'] = 'new' 
     429        elif action == 'reopen': 
     430            ticket['status'] = 'reopened' 
     431            ticket['resolution'] = '' 
     432 
    404433        now = int(time.time()) 
    405434        ticket.save_changes(self.db, req.args.get('author', req.authname), 
    406435                            req.args.get('comment'), when=now) 
     
    493522        # List attached files 
    494523        self.env.get_attachments_hdf(self.db, 'ticket', str(id), req.hdf, 
    495524                                     'ticket.attachments') 
    496         req.hdf['ticket.attach_href'] = self.env.href.attachment('ticket', id) 
     525        if self.perm.has_permission(perm.TICKET_ATTACH): 
     526            req.hdf['ticket.attach_href'] = self.env.href.attachment('ticket', id) 
     527 
     528        # Add the possible workflow paths to hdf 
     529        for action in get_ticket_actions(ticket,self.perm): 
     530            req.hdf['ticket.workflow.' + action] = '1' 
    497531 
    498532    def render(self, req): 
    499533        self.perm.assert_permission (perm.TICKET_VIEW) 
     
    530564        else: 
    531565            req.hdf['ticket.reassign_owner'] = req.authname 
    532566 
    533         self.insert_ticket_data(req, id, ticket, reporter_id) 
     567        self.insert_ticket_data(req, id, ticket, reporter_id) 
    534568 
    535569        # If the ticket is being shown in the context of a query, add 
    536570        # links to help navigate in the query result set 
  • templates/ticket.cs

     
    128128 /each ?></div><?cs 
    129129/if ?> 
    130130 
    131 <?cs if $trac.acl.TICKET_MODIFY ?> 
     131<?cs if $trac.acl.TICKET_EDIT || $trac.acl.TICKET_APPEND ?> 
    132132<form action="<?cs var:cgi_location ?>#preview" method="post"> 
    133133 <hr /> 
    134134 <h3><a name="edit" onfocus="document.getElementById('comment').focus()">Add/Change #<?cs 
     
    155155  /if ?> 
    156156 </div> 
    157157 
     158 <?cs if $trac.acl.TICKET_EDIT ?> 
    158159 <fieldset id="properties"> 
    159160  <legend>Change Properties</legend> 
    160161  <div class="main"> 
     
    203204   <?cs call:ticket_custom_props(ticket) ?> 
    204205  </div><?cs /if ?> 
    205206 </fieldset> 
     207 <?cs /if ?> 
    206208 
     209 <?cs if ticket.workflow.accept || ticket.workflow.reopen || ticket.workflow.resolve || ticket.workflow.reassign ?> 
    207210 <fieldset id="action"> 
    208211  <legend>Action</legend><?cs 
    209212  if:!ticket.action ?><?cs set:ticket.action = 'leave' ?><?cs 
     
    214217     /if ?> /><?cs 
    215218  /def ?> 
    216219  <?cs call:action_radio('leave') ?> 
    217   <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 
    218   if $ticket.status == "new" ?> 
    219    <?cs call:action_radio('accept') ?> 
    220    <label for="accept">accept ticket</label><br /><?cs 
     220    <label for="leave">leave as <?cs var:ticket.status ?></label><br /><?cs 
     221  if ticket.workflow.accept ?><?cs 
     222    call:action_radio('accept') ?> 
     223    <label for="accept">accept ticket</label><br /><?cs 
    221224  /if ?><?cs 
    222   if $ticket.status == "closed" ?> 
    223    <?cs call:action_radio('reopen') ?> 
    224    <label for="reopen">reopen ticket</label><br /><?cs 
     225  if ticket.workflow.reopen ?><?cs 
     226    call:action_radio('reopen') ?> 
     227    <label for="reopen">reopen ticket</label><br /><?cs 
    225228  /if ?><?cs 
    226   if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?> 
    227    <?cs call:action_radio('resolve') ?> 
    228    <label for="resolve">resolve</label> 
    229    <label for="resolve_resolution">as:</label> 
    230    <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution, 0) ?><br /> 
    231    <?cs call:action_radio('reassign') ?> 
    232    <label for="reassign">reassign</label> 
    233    <label>to:<?cs 
    234    if:len(ticket.users) ?><?cs 
    235     call:hdf_select(ticket.users, "reassign_owner", ticket.reassign_owner, 0) ?><?cs 
    236    else ?> 
    237     <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs 
    238       var:ticket.reassign_owner ?>" /><?cs 
    239    /if ?></label><?cs 
     229  if ticket.workflow.resolve ?><?cs 
     230    call:action_radio('resolve') ?> 
     231    <label for="resolve">resolve</label> 
     232    <label for="resolve_resolution">as:</label> 
     233    <?cs call:hdf_select(enums.resolution, "resolve_resolution", args.resolve_resolution, 0) ?><br /><?cs 
    240234  /if ?><?cs 
    241   if $ticket.status == "new" || $ticket.status == "assigned" || $ticket.status == "reopened" ?> 
    242    <script type="text/javascript"> 
    243      var resolve = document.getElementById("resolve"); 
    244      var reassign = document.getElementById("reassign"); 
    245      var updateActionFields = function() { 
    246        enableControl('resolve_resolution', resolve.checked); 
    247        enableControl('reassign_owner', reassign.checked); 
     235  if ticket.workflow.reassign ?><?cs 
     236    call:action_radio('reassign') ?> 
     237    <label for="reassign">reassign</label> 
     238    <label>to:<?cs 
     239    if:len(ticket.users) ?><?cs 
     240     call:hdf_select(ticket.users, "reassign_owner", ticket.reassign_owner, 0) ?><?cs 
     241    else ?> 
     242     <input type="text" id="reassign_owner" name="reassign_owner" size="40" value="<?cs 
     243       var:ticket.reassign_owner ?>" /><?cs 
     244    /if ?></label><?cs 
     245  /if ?><?cs 
     246  if ticket.workflow.resolve || ticket.workflow.reassign ?> 
     247   <script type="text/javascript"><?cs 
     248   if ticket.workflow.resolve ?> 
     249     var resolve = document.getElementById("resolve");<?cs 
     250   /if ?><?cs 
     251   if ticket.workflow.reassign ?> 
     252     var reassign = document.getElementById("reassign");<?cs 
     253   /if ?> 
     254     var updateActionFields = function() {<?cs 
     255   if ticket.workflow.resolve ?> 
     256       enableControl('resolve_resolution', resolve.checked);<?cs 
     257   /if ?><?cs 
     258   if ticket.workflow.reassign ?> 
     259       enableControl('reassign_owner', reassign.checked);<?cs 
     260   /if ?> 
    248261     }; 
    249262     addEvent(window, 'load', updateActionFields); 
    250263     addEvent(document.getElementById("leave"), 'click', updateActionFields);<?cs 
    251     if $ticket.status == "new" ?> 
     264   if ticket.workflow.accept ?> 
    252265     addEvent(document.getElementById("accept"), 'click', updateActionFields);<?cs 
    253     /if ?> 
    254     addEvent(resolve, 'click', updateActionFields); 
    255     addEvent(reassign, 'click', updateActionFields); 
     266   /if ?><?cs 
     267   if ticket.workflow.resolve ?> 
     268           addEvent(resolve, 'click', updateActionFields);<?cs 
     269         /if ?><?cs 
     270         if ticket.workflow.reopen ?> 
     271           addEvent(document.getElementById("reopen"), 'click', updateActionFields);<?cs 
     272         /if ?><?cs 
     273         if ticket.workflow.reassign ?> 
     274           addEvent(reassign, 'click', updateActionFields);<?cs 
     275   /if ?> 
    256276   </script><?cs 
    257277  /if ?> 
    258278 </fieldset> 
     279 <?cs else ?> 
     280 <input type="hidden" name="action" value="leave" /> 
     281 <?cs /if ?> 
    259282 
    260283 <script type="text/javascript" src="<?cs 
    261284   var:htdocs_location ?>js/wikitoolbar.js"></script> 
     
    263286 <div class="buttons"> 
    264287  <input type="reset" value="Reset" />&nbsp; 
    265288  <input type="submit" name="preview" value="Preview" />&nbsp; 
    266   <input type="submit" value="Submit changes" />  
     289  <input type="submit" value="Submit changes" /> 
    267290 </div> 
    268291</form> 
    269292<?cs /if ?>