~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/model/bug.py

[rs=buildbot-poller] automatic merge from stable. Revisions: 14023
        included.

Show diffs side-by-side

added added

removed removed

Lines of Context:
812
812
        self.updateHeat()
813
813
        return sub
814
814
 
815
 
    def unsubscribe(self, person, unsubscribed_by):
 
815
    def unsubscribe(self, person, unsubscribed_by, **kwargs):
816
816
        """See `IBug`."""
817
817
        # Drop cached subscription info.
818
818
        clear_property_cache(self)
822
822
        if person is None:
823
823
            person = unsubscribed_by
824
824
 
 
825
        ignore_permissions = kwargs.get('ignore_permissions', False)
 
826
        recipients = kwargs.get('recipients')
825
827
        for sub in self.subscriptions:
826
828
            if sub.person.id == person.id:
827
 
                if not sub.canBeUnsubscribedByUser(unsubscribed_by):
 
829
                if (not ignore_permissions
 
830
                        and not sub.canBeUnsubscribedByUser(unsubscribed_by)):
828
831
                    raise UserCannotUnsubscribePerson(
829
832
                        '%s does not have permission to unsubscribe %s.' % (
830
833
                            unsubscribed_by.displayname,
831
834
                            person.displayname))
832
835
 
833
836
                self.addChange(UnsubscribedFromBug(
834
 
                    when=UTC_NOW, person=unsubscribed_by,
835
 
                    unsubscribed_user=person))
 
837
                        when=UTC_NOW, person=unsubscribed_by,
 
838
                        unsubscribed_user=person, **kwargs),
 
839
                    recipients=recipients)
836
840
                store = Store.of(sub)
837
841
                store.remove(sub)
838
842
                # Make sure that the subscription removal has been
1645
1649
 
1646
1650
        return bugtask
1647
1651
 
1648
 
    def setPrivate(self, private, who):
1649
 
        """See `IBug`.
1650
 
 
1651
 
        We also record who made the change and when the change took
1652
 
        place.
1653
 
        """
 
1652
    def setPrivacyAndSecurityRelated(self, private, security_related, who):
 
1653
        """ See `IBug`."""
 
1654
        private_changed = False
 
1655
        security_related_changed = False
 
1656
        bug_before_modification = Snapshot(self, providing=providedBy(self))
 
1657
 
 
1658
        # Before we update the privacy or security_related status, we need to
 
1659
        # reconcile the subscribers to avoid leaking private information.
 
1660
        if (self.private != private
 
1661
                or self.security_related != security_related):
 
1662
            self.reconcileSubscribers(private, security_related, who)
 
1663
 
1654
1664
        if self.private != private:
1655
 
            if private:
1656
 
                # Change indirect subscribers into direct subscribers
1657
 
                # *before* setting private because
1658
 
                # getIndirectSubscribers() behaves differently when
1659
 
                # the bug is private.
1660
 
                for person in self.getIndirectSubscribers():
1661
 
                    self.subscribe(person, who)
1662
 
                subscribers_for_who = self.getSubscribersForPerson(who)
1663
 
                if subscribers_for_who.is_empty():
1664
 
                    # We also add `who` as a subscriber, if they're not
1665
 
                    # already directly subscribed or part of a team
1666
 
                    # that's directly subscribed, so that they can
1667
 
                    # see the bug they've just marked private.
1668
 
                    self.subscribe(who, who)
1669
 
 
 
1665
            private_changed = True
1670
1666
            self.private = private
1671
1667
 
1672
1668
            if private:
1681
1677
            for attachment in self.attachments_unpopulated:
1682
1678
                attachment.libraryfile.restricted = private
1683
1679
 
1684
 
            # Correct the heat for the bug immediately, so that we don't have
1685
 
            # to wait for the next calculation job for the adjusted heat.
1686
 
            self.updateHeat()
1687
 
            return True  # Changed.
1688
 
        else:
1689
 
            return False  # Not changed.
1690
 
 
1691
 
    def setSecurityRelated(self, security_related):
1692
 
        """Setter for the `security_related` property."""
1693
1680
        if self.security_related != security_related:
 
1681
            security_related_changed = True
1694
1682
            self.security_related = security_related
1695
1683
 
 
1684
        if private_changed or security_related_changed:
1696
1685
            # Correct the heat for the bug immediately, so that we don't have
1697
1686
            # to wait for the next calculation job for the adjusted heat.
1698
1687
            self.updateHeat()
1699
1688
 
1700
 
            return True  # Changed
1701
 
        else:
1702
 
            return False  # Unchanged
 
1689
        if private_changed or security_related_changed:
 
1690
            changed_fields = []
 
1691
            if private_changed:
 
1692
                changed_fields.append('private')
 
1693
            if security_related_changed:
 
1694
                changed_fields.append('security_related')
 
1695
            notify(ObjectModifiedEvent(
 
1696
                    self, bug_before_modification, changed_fields, user=who))
 
1697
 
 
1698
        return private_changed, security_related_changed
 
1699
 
 
1700
    def setPrivate(self, private, who):
 
1701
        """See `IBug`.
 
1702
 
 
1703
        We also record who made the change and when the change took
 
1704
        place.
 
1705
        """
 
1706
        return self.setPrivacyAndSecurityRelated(
 
1707
            private, self.security_related, who)[0]
 
1708
 
 
1709
    def setSecurityRelated(self, security_related, who):
 
1710
        """Setter for the `security_related` property."""
 
1711
        return self.setPrivacyAndSecurityRelated(
 
1712
            self.private, security_related, who)[1]
 
1713
 
 
1714
    def getRequiredSubscribers(self, for_private, for_security_related, who):
 
1715
        """Return the mandatory subscribers for a bug with given attributes.
 
1716
 
 
1717
        When a bug is marked as private or security related, it is required
 
1718
        that certain people be subscribed so they can access details about the
 
1719
        bug. The rules are:
 
1720
            security=true, private=true/false ->
 
1721
                subscribers = the reporter + security contact for each task
 
1722
            security=false, private=true ->
 
1723
                subscribers = the reporter + bug supervisor for each task
 
1724
            security=false, private=false ->
 
1725
                subscribers = ()
 
1726
 
 
1727
        If bug supervisor or security contact is unset, fallback to bugtask
 
1728
        reporter/owner.
 
1729
        """
 
1730
        if not for_private and not for_security_related:
 
1731
            return set()
 
1732
        result = set()
 
1733
        result.add(self.owner)
 
1734
        for bugtask in self.bugtasks:
 
1735
            maintainer = bugtask.pillar.owner
 
1736
            if for_security_related:
 
1737
                result.add(bugtask.pillar.security_contact or maintainer)
 
1738
            if for_private:
 
1739
                result.add(bugtask.pillar.bug_supervisor or maintainer)
 
1740
        if for_private:
 
1741
            subscribers_for_who = self.getSubscribersForPerson(who)
 
1742
            if subscribers_for_who.is_empty():
 
1743
                result.add(who)
 
1744
        return result
 
1745
 
 
1746
    def getAutoRemovedSubscribers(self, for_private, for_security_related):
 
1747
        """Return the to be removed subscribers for bug with given attributes.
 
1748
 
 
1749
        When a bug's privacy or security related attributes change, some
 
1750
        existing subscribers may need to be automatically removed.
 
1751
        The rules are:
 
1752
            security=false ->
 
1753
                auto removed subscribers = (bugtask security contacts)
 
1754
            privacy=false ->
 
1755
                auto removed subscribers = (bugtask bug supervisors)
 
1756
 
 
1757
        """
 
1758
        bug_supervisors = []
 
1759
        security_contacts = []
 
1760
        for pillar in self.affected_pillars:
 
1761
            if (self.security_related and not for_security_related
 
1762
                and pillar.security_contact):
 
1763
                    security_contacts.append(pillar.security_contact)
 
1764
            if (self.private and not for_private
 
1765
                and pillar.bug_supervisor):
 
1766
                    bug_supervisors.append(pillar.bug_supervisor)
 
1767
        return bug_supervisors, security_contacts
 
1768
 
 
1769
    def reconcileSubscribers(self, for_private, for_security_related, who):
 
1770
        """ Ensure only appropriate people are subscribed to private bugs.
 
1771
 
 
1772
        When a bug is marked as either private = True or security_related =
 
1773
        True, we need to ensure that only people who are authorised to know
 
1774
        about the privileged contents of the bug remain directly subscribed
 
1775
        to it. So we:
 
1776
          1. Get the required subscribers depending on the bug status.
 
1777
          2. Get the auto removed subscribers depending on the bug status.
 
1778
             eg security contacts when a bug is updated to security related =
 
1779
             false.
 
1780
          3. Get the allowed subscribers = required subscribers
 
1781
                                            + bugtask owners
 
1782
          4. Remove any current direct subscribers who are not allowed or are
 
1783
             to be auto removed.
 
1784
          5. Add any subscribers who are required.
 
1785
        """
 
1786
        current_direct_subscribers = (
 
1787
            self.getSubscriptionInfo().direct_subscribers)
 
1788
        required_subscribers = self.getRequiredSubscribers(
 
1789
            for_private, for_security_related, who)
 
1790
        removed_bug_supervisors, removed_security_contacts = (
 
1791
            self.getAutoRemovedSubscribers(for_private, for_security_related))
 
1792
        for subscriber in removed_bug_supervisors:
 
1793
            recipients = BugNotificationRecipients()
 
1794
            recipients.addBugSupervisor(subscriber)
 
1795
            notification_text = ("This bug is no longer private so the bug "
 
1796
                "supervisor was unsubscribed. You will no longer be notified "
 
1797
                "of changes to this bug for privacy related reasons, but you "
 
1798
                "may receive notifications about this bug from other "
 
1799
                "subscriptions.")
 
1800
            self.unsubscribe(
 
1801
                subscriber, who, ignore_permissions=True,
 
1802
                send_notification=True,
 
1803
                notification_text=notification_text,
 
1804
                recipients=recipients)
 
1805
        for subscriber in removed_security_contacts:
 
1806
            recipients = BugNotificationRecipients()
 
1807
            recipients.addSecurityContact(subscriber)
 
1808
            notification_text = ("This bug is no longer security related so "
 
1809
                "the security contact was unsubscribed. You will no longer "
 
1810
                "be notified of changes to this bug for privacy related "
 
1811
                "reasons, but you may receive notifications about this bug "
 
1812
                "from other subscriptions.")
 
1813
            self.unsubscribe(
 
1814
                subscriber, who, ignore_permissions=True,
 
1815
                send_notification=True,
 
1816
                notification_text=notification_text,
 
1817
                recipients=recipients)
 
1818
 
 
1819
        # If this bug is for a project that is marked as having private bugs
 
1820
        # by default, and the bug is private or security related, we will
 
1821
        # unsubscribe any unauthorised direct subscribers.
 
1822
        pillar = self.default_bugtask.pillar
 
1823
        private_project = IProduct.providedBy(pillar) and pillar.private_bugs
 
1824
        if private_project and (for_private or for_security_related):
 
1825
            allowed_subscribers = set()
 
1826
            allowed_subscribers.add(self.owner)
 
1827
            for bugtask in self.bugtasks:
 
1828
                allowed_subscribers.add(bugtask.owner)
 
1829
                allowed_subscribers.add(bugtask.pillar.owner)
 
1830
                allowed_subscribers.update(set(bugtask.pillar.drivers))
 
1831
            allowed_subscribers = required_subscribers.union(
 
1832
                allowed_subscribers)
 
1833
            subscribers_to_remove = (
 
1834
                current_direct_subscribers.difference(allowed_subscribers))
 
1835
            for subscriber in subscribers_to_remove:
 
1836
                self.unsubscribe(subscriber, who, ignore_permissions=True)
 
1837
 
 
1838
        subscribers_to_add = (
 
1839
            required_subscribers.difference(current_direct_subscribers))
 
1840
        for subscriber in subscribers_to_add:
 
1841
            self.subscribe(subscriber, who)
1703
1842
 
1704
1843
    def getBugTask(self, target):
1705
1844
        """See `IBug`."""