launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26757
[Merge] ~cjwatson/launchpad:legitimate-polls into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:legitimate-polls into launchpad:master.
Commit message:
Restrict poll creation to AnyLegitimatePerson
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400340
This makes them a bit less trivial to abuse.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:legitimate-polls into launchpad:master.
diff --git a/lib/lp/registry/browser/poll.py b/lib/lp/registry/browser/poll.py
index 234139a..066407c 100644
--- a/lib/lp/registry/browser/poll.py
+++ b/lib/lp/registry/browser/poll.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -36,6 +36,7 @@ from lp.app.browser.launchpadform import (
)
from lp.registry.browser.person import PersonView
from lp.registry.interfaces.poll import (
+ CannotCreatePoll,
IPoll,
IPollOption,
IPollOptionSet,
@@ -55,6 +56,7 @@ from lp.services.webapp import (
NavigationMenu,
stepthrough,
)
+from lp.services.webapp.authorization import check_permission
from lp.services.webapp.breadcrumb import TitleBreadcrumb
@@ -409,6 +411,12 @@ class PollAddView(LaunchpadFormView):
page_title = 'New poll'
+ def __init__(self, context, request):
+ if not check_permission("launchpad.AnyLegitimatePerson", context):
+ raise CannotCreatePoll(
+ "You do not have permission to create polls.")
+ super(PollAddView, self).__init__(context, request)
+
@property
def cancel_url(self):
"""See `LaunchpadFormView`."""
diff --git a/lib/lp/registry/browser/tests/test_poll.py b/lib/lp/registry/browser/tests/test_poll.py
index 825bb81..fa1674d 100644
--- a/lib/lp/registry/browser/tests/test_poll.py
+++ b/lib/lp/registry/browser/tests/test_poll.py
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for IPoll views."""
@@ -7,10 +7,22 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
+from datetime import (
+ datetime,
+ timedelta,
+ )
import os
-from lp.registry.interfaces.poll import PollAlgorithm
-from lp.testing import TestCaseWithFactory
+import pytz
+
+from lp.registry.interfaces.poll import (
+ CannotCreatePoll,
+ PollAlgorithm,
+ )
+from lp.testing import (
+ BrowserTestCase,
+ TestCaseWithFactory,
+ )
from lp.testing.layers import DatabaseFunctionalLayer
from lp.testing.views import create_view
@@ -39,3 +51,38 @@ class TestPollVoteView(TestCaseWithFactory):
self.assertEqual(
'poll-vote-condorcet.pt',
os.path.basename(view.template.filename))
+
+
+class TestPollAddView(BrowserTestCase):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestPollAddView, self).setUp()
+ self.pushConfig(
+ "launchpad", min_legitimate_karma=5, min_legitimate_account_age=5)
+
+ def test_new_user(self):
+ # A brand new user cannot create polls.
+ new_person = self.factory.makePerson()
+ team = self.factory.makeTeam(owner=new_person)
+ self.assertRaises(
+ CannotCreatePoll,
+ self.getViewBrowser, team, view_name="+newpoll", user=new_person)
+
+ def test_legitimate_user(self):
+ # A user with some kind of track record can create polls.
+ person = self.factory.makePerson(karma=10)
+ team = self.factory.makeTeam(owner=person)
+ now = datetime.now(pytz.UTC)
+ browser = self.getViewBrowser(team, view_name="+newpoll", user=person)
+ browser.getControl("The unique name of this poll").value = "colour"
+ browser.getControl("The title of this poll").value = "Favourite Colour"
+ browser.getControl("The date and time when this poll opens").value = (
+ str(now + timedelta(days=1)))
+ browser.getControl("The date and time when this poll closes").value = (
+ str(now + timedelta(days=2)))
+ browser.getControl(
+ "The proposition that is going to be voted").value = (
+ "What is your favourite colour?")
+ browser.getControl("Continue").click()
diff --git a/lib/lp/registry/interfaces/poll.py b/lib/lp/registry/interfaces/poll.py
index 8fab288..6b894a4 100644
--- a/lib/lp/registry/interfaces/poll.py
+++ b/lib/lp/registry/interfaces/poll.py
@@ -1,7 +1,8 @@
-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
+ 'CannotCreatePoll',
'IPoll',
'IPollSet',
'IPollSubset',
@@ -26,7 +27,9 @@ from lazr.enum import (
DBEnumeratedType,
DBItem,
)
+from lazr.restful.declarations import error_status
import pytz
+from six.moves import http_client
from zope.component import getUtility
from zope.interface import (
Attribute,
@@ -112,6 +115,11 @@ class PollStatus:
ALL = frozenset([OPEN, CLOSED, NOT_YET_OPENED])
+@error_status(http_client.FORBIDDEN)
+class CannotCreatePoll(Exception):
+ pass
+
+
class IPoll(Interface):
"""A poll for a given proposition in a team."""
diff --git a/lib/lp/registry/model/poll.py b/lib/lp/registry/model/poll.py
index b34786b..419a620 100644
--- a/lib/lp/registry/model/poll.py
+++ b/lib/lp/registry/model/poll.py
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
from __future__ import absolute_import, print_function, unicode_literals
@@ -33,6 +33,7 @@ from zope.interface import implementer
from lp.registry.interfaces.person import validate_public_person
from lp.registry.interfaces.poll import (
+ CannotCreatePoll,
IPoll,
IPollOption,
IPollOptionSet,
@@ -51,6 +52,7 @@ from lp.services.database.interfaces import IStore
from lp.services.database.sqlbase import sqlvalues
from lp.services.database.stormbase import StormBase
from lp.services.tokens import create_token
+from lp.services.webapp.authorization import check_permission
@implementer(IPoll)
@@ -292,8 +294,13 @@ class PollSet:
"""See IPollSet."""
def new(self, team, name, title, proposition, dateopens, datecloses,
- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
+ secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE,
+ check_permissions=True):
"""See IPollSet."""
+ if (check_permissions and
+ not check_permission("launchpad.AnyLegitimatePerson", team)):
+ raise CannotCreatePoll(
+ "You do not have permission to create polls.")
poll = Poll(
team=team, name=name, title=title,
proposition=proposition, dateopens=dateopens,
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index d4ed1fd..ec73f72 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -837,7 +837,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
return getUtility(IPollSet).new(
team, name, title, proposition, dateopens, datecloses,
PollSecrecy.SECRET, allowspoilt=True,
- poll_type=poll_type)
+ poll_type=poll_type, check_permissions=False)
def makeTranslationGroup(self, owner=None, name=None, title=None,
summary=None, url=None):