Merge lp:~gmb/launchpad/dont-piss-the-losas-off-bug-596877 into lp:launchpad/db-devel
| Status: | Merged |
|---|---|
| Merged at revision: | 9700 |
| Proposed branch: | lp:~gmb/launchpad/dont-piss-the-losas-off-bug-596877 |
| Merge into: | lp:launchpad/db-devel |
| Diff against target: |
656 lines (+299/-89) 12 files modified
lib/canonical/launchpad/security.py (+11/-0) lib/lp/bugs/browser/bugtracker.py (+24/-2) lib/lp/bugs/browser/tests/test_bugtracker_views.py (+29/-0) lib/lp/bugs/configure.zcml (+2/-1) lib/lp/bugs/doc/bugtracker.txt (+10/-60) lib/lp/bugs/interfaces/bugtracker.py (+5/-4) lib/lp/bugs/model/bugtracker.py (+13/-8) lib/lp/bugs/scripts/checkwatches/core.py (+2/-1) lib/lp/bugs/stories/bugtracker/xx-reschedule-all-watches.txt (+94/-0) lib/lp/bugs/tests/test_bugtracker.py (+97/-11) lib/lp/testing/factory.py (+3/-2) lib/lp/testing/sampledata.py (+9/-0) |
| To merge this branch: | bzr merge lp:~gmb/launchpad/dont-piss-the-losas-off-bug-596877 |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Gavin Panella (community) | 2010-07-17 | Approve on 2010-08-25 | |
| Robert Collins (community) | Needs Information on 2010-07-20 | ||
|
Review via email:
|
|||
Commit Message
A button, visible to admins and LP developers, has been added to the BugTracker +edit page to allow all of a tracker's watches to be rescheduled en masse.
Description of the Change
A button to reschedule all watches
=======
This branch adds a button to the BugTracker +edit page which will
allow a user with sufficient privileges to reschedule all the watches on
a given BugTracker[1]. This is useful in situations where a bug tracker
has not been updated for a long period of time, so all the watches are
out of date. In the past we've done this by asking a LOSA to run some
SQL, but since that's a drain on their time and ours, a button seemed
more useful.
One of the problems associated with a large number of out-of-date
watches, however, is that checkwatches attempts to burn through them all
at once. This can lead to us adding a great deal of load to the remote
server, which is generally a Bad Thing and can get Launchpad blocked
from communicating with the upstream bug tracker (at the very least).
In order to obviate this problem, we have in the past used SQL that
would randomise the next_check time for each bug watch within a range of
up to twenty-four hours. This would then take some of the strain off the
remote server since checkwatches wouldn't be trying to update everything
as quickly as possible. It seemed sensible to use this same SQL when
creating the reschedule button. Note that 24 hours is the optimal
maximum time frame in which to schedule watches; anything longer means
that watches remain unchecked whilst other watches go out of date and
need to be checked again, potentially creating further load issues.
Finally, I cleaned up some lint in this branch, but I'm not wholly
certain that the new linter isn't getting some things wrong about our
coding standards.
Changes made
------------
lib/lp/
~~~~~~~
- I've added an exception, UserCannotReset
when a user attempts to all watches on a bug tracker without
permission.
- I've added a user_can_
This returns True if the user is either a Launchpad Admin or is a
member of ~launchpad.
- I've added a method, resetBugTracker
This calls BugTracker.
method so that it could be tested properly in view tests rather than
putting all the implementation in the body of the action.
- I've added a reschedule_
BugTrackerEd
reschedule and user_can_
determine whether the reschedule button shows up on the +edit page.
- I've added a rescheduleAction() method / action to
BugTrackerEd
info notification to the page stating that all watches have been
updated. Note that it makes no attempt to catch
UserCannotRe
the OOPS machinery as they are indicative of someone trying to craft
an HTTP POST to reschedule watches.
lib/lp/
~~~~~~~
- I've added tests to cover the resetBugTracker
BugTrackerEd
resetBugTrac
and that it also checks that the user is allowed to reset a bug
tracker's watches.
lib/lp/
~~~~~~~
- I've updated the ZCML and moved BugTracker.
launchpad.Admin section and into launchpad.Edit (hence the need for
stricter checking of permissions in
BugTrackerEd
to allow Launchpad Developers to reset watches.
lib/lp/
~~~~~~~
- I've updated the existing tests for BugTracker.
cover the new randomisation-
refactor the whole of bugtracker.txt into unittest format, but
decided against it for reasons of space and sanity.
- I've fixed a bunch of lint issues with the doctest.
lib/lp/
~~~~~~~
- I've updated BugTracker.
to a random time between now and one day in the future, rather than
just setting it to now as it used to.
- I've also fixed a minor typo.
lib/lp/
~~~~~~~
- This pagetest covers the Reschedule all watches button which will now
appear on the BugTracker +edit page when it is viewed by admins or LP
developers.
lib/lp/
~~~~~~~
- I updated makeBugTracker() so that it now accepts name and title
arguments.
lib/lp/
~~~~~~~
- I added constants for ADMIN_EMAIL (foo.bar@) and USER_EMAIL(test@).
Test commands
-------------
`bin/test -cvv -t /bugtracker.txt -t xx-reschedule-
-t test_bugtracker
| Robert Collins (lifeless) wrote : | # |
Also as discussed there are no particularly special permission issues here so raising the normal permission denied stuff should be all thats needed.
| Graham Binns (gmb) wrote : | # |
I've updated the branch per our discussions. Here comes the diff:
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -27,6 +27,7 @@
from zope.formlib import form
from zope.schema import Choice
from zope.schema.
+from zope.security.
from canonical.
from canonical.
@@ -63,10 +64,6 @@
)
-class UserCannotReset
- """Raised when a user cannot reset a bug tracker's watches."""
-
-
class BugTrackerSetNa
usedfor = IBugTrackerSet
@@ -387,11 +384,13 @@
def resetBugTracker
"""Call the resetWatches() method of the current context.
- :raises `UserCannotRese
+ :raises `Unauthorized` if the user isn't an admin
or a member of the Launchpad Developers team.
"""
if not self.user_
- raise UserCannotReset
+ raise Unauthorized(
+ "You don't have permission to reset all the watches for "
+ "this bug tracker.")
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -11,12 +11,13 @@
from pytz import utc
from zope.component import getUtility
+from zope.security.
from canonical.
from canonical.
from lp.bugs.
- BugTrackerEditView, UserCannotReset
+ BugTrackerEditView)
from lp.registry.
from lp.testing import login, TestCaseWithFactory
from lp.testing.
@@ -70,7 +71,7 @@
view = create_
- UserCannotReset
+ Unauthorized, view.resetBugTr
def test_admin_
# Launchpad admins can reset the watches on a bugtracker.
| Robert Collins (lifeless) wrote : | # |
Sorry, I missed that its still in the view. Shouldn't it be in the model?
| Graham Binns (gmb) wrote : | # |
It should, and I've moved it there (for some reason I had it in my head that we should always check stuff in the view, but that turns out not to be true).
Here's a diff:
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -374,38 +374,22 @@
def cancel_url(self):
return canonical_
- @property
- def user_can_
+ def reschedule_
+ """Return True if the user can see the reschedule action."""
- return (
+ user_can_
-
- def resetBugTracker
- """Call the resetWatches() method of the current context.
-
- :raises `Unauthorized` if the user isn't an admin
- or a member of the Launchpad Developers team.
- """
- if not self.user_
- raise Unauthorized(
- "You don't have permission to reset all the watches for "
- "this bug tracker.")
-
- self.context.
-
- def reschedule_
- """Return True if the user can see the reschedule action."""
return (
- self.user_
+ user_can_
@action(
def rescheduleActio
- self.resetBugTr
+ self.context.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -24,77 +24,6 @@
from lp.testing.views import create_
-class BugTrackerEditV
- """A TestCase for the `BugTrackerEdit
-
- layer = DatabaseFunctio
-
- def setUp(self):
- super(BugTracke
- self.bug_tracker = self.factory.
- for i in range(5):
- self.factory.
-
- self.now = datetime.now(utc)
-
- def _assertBugWatch
- """Check the dates of all self.bug_
-
- Raise an error if:
- * The next_check dates aren't in the future.
- * The next_check dates aren't <= 1 day in the future.
- * The lastcheck dates are not None
- * The last_error_types are not None.
- """
- for watch i...
| Gavin Panella (allenap) wrote : | # |
This branch is a really cool addition. I think it can be simplified by
leaning more heavily on Zope security, and there are a few other
things to consider.
I wrote the review notes in the Landscape style before I remembered
that this isn't Landscape. However, I think it's quite readable,
especially since responses need only reference the number given to
each comment, so I'll leave it. I hope you don't mind. Let me know
what you think.
[1]
+ user_can_
+ check_permissio
+ self.user.
This would perhaps be better placed on the model, something like
canResetWatches
[2]
+ check_permissio
I may well be wrong, but this looks like it checks if the current user
has the launchpad.Admin permission on self.user, which will always be
true.
I think it might be worth defining a new AdminBugTracker authorization
adapter in canonical.
class AdminBugTracker
permission = 'launchpad.Admin'
usedfor = IBugTracker
def checkAuthentica
return (user.in_admin or
Then the changes to lib/lp/
[3]
=== added file 'lib/lp/
There are no tests in this file! :)
[4]
+ :param new_next_check: If specified, contains the datetime to
+ which to set the BugWatches' next_check times.
+ If not specified, the watches' next_check
+ times will be set to a point between now
+ and 24 hours hence.
I /think/ it's usual to indent the second and subsequent lines by 4
spaces. Doesn't matter much though.
[5]
+ def resetWatches(self, user, new_next_
"""See `IBugTracker`."""
- if now is None:
- now = datetime.
+ celebrities = getUtility(
+ launchpad_
+ admin_team = celebrities.admin
+ janitor = celebrities.janitor
+
+ if (not user.inTeam(
+ not user.inTeam(
+ user != janitor):
+ raise Unauthorized(
+ "You do not have permission to reset the watches for "
+ "this bug tracker.")
If you add the security adapter in [2] then the user checking stuff
here can be dropped.
[6]
+ self.request.
+ structured(
+ "All bug watches on %s have been rescheduled." %
+ self.context.
I don't think this should be a structured() string; that would allow
markup/script injection via the bug tracker title.
| Graham Binns (gmb) wrote : | # |
Diff of changes:
=== modified file 'lib/canonical/
--- lib/canonical/
+++ lib/canonical/
@@ -2561,3 +2561,14 @@
if parent is None:
return False
return check_permissio
+
+
+class AdminBugTracker
+ permission = 'launchpad.Admin'
+ usedfor = IBugTracker
+
+ def checkAuthentica
+ return (
+ user.in_janitor or
+ user.in_admin or
+ user.in_
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -397,10 +397,8 @@
def reschedule_
"""Return True if the user can see the reschedule action."""
- lp_developers = getUtility(
- user_can_
- check_permissio
- self.user.
+ user_can_
+ "launchpad.Admin", self.context)
return (
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -394,7 +394,7 @@
- resetWatches"
+ "
@@ -408,6 +408,7 @@
+ attributes=
</class>
<adapter
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -344,14 +344,13 @@
def destroySelf():
"""Delete this bug tracker."""
- def resetWatches(user, new_next_
+ def resetWatches(
"""Reset the next_check times of this BugTracker's `BugWatch`es.
:param new_next_check: If specified, contains the datetime to
- which to set the BugWatches' next_check times.
- If not specified, the watches' next_check
- times will be set to a point between now
- and 24 hours hence.
+ which to set the BugWatches' next_check times. If not
+ specified, the watches' next_check times will be set to a
+ point between now and 24 hours hence.
"""
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
| Gavin Panella (allenap) wrote : | # |
I think [6] still needs to be addressed, but the rest looks good. I do
have a couple of comments:
[7]
return (
The order here could be reversed; user_can_
calculated so it may as well short circuit on it and avoid a query if
possible:
return (
Also it would be more efficient to use is_empty():
return (
not self.context.
[8]
+ user_can_
+ "launchpad.Admin", self.context)
This works fine, and is nothing controversial, but if, say,
resetWatches is given a new permission then it will no longer check
the right thing. Would something like the following work better?
try:
except Unauthorized:
return False
else:
return not self.context.

Shouldn't the permission check be in the model? the LBYP idiom seems likely to miss things...