Merge lp:~jcsackett/launchpad/bug-nick-validator into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12501
Proposed branch: lp:~jcsackett/launchpad/bug-nick-validator
Merge into: lp:launchpad
Prerequisite: lp:~jcsackett/launchpad/move-validators
Diff against target: 146 lines (+61/-6)
3 files modified
lib/lp/app/validators/name.py (+24/-0)
lib/lp/bugs/interfaces/bug.py (+4/-4)
lib/lp/bugs/tests/test_bugs_webservice.py (+33/-2)
To merge this branch: bzr merge lp:~jcsackett/launchpad/bug-nick-validator
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+51033@code.launchpad.net

Commit message

[r=lifeless][bug=192135] Puts a more restrictive validator on bug name to match database constraints, to prevent oopses over the webservice.

Description of the change

Summary
=======

Bug name, while not available in the web ui, can still be accessed and changed via the webservice. The validator being used for it is more permissive than the validation code used in the database, so nicks like '1.1' trigger IntegrityErrors.

This creates a new validator that matches the database constraint, and updates the bug interface to use that.

Implementation
==============

lib/lp/app/validators/name.py
-----------------------------
A new validator, bug_name_validator was created, incorporating the rules used in the database constraint.

lib/lp/bugs/interfaces/bug.py
-----------------------------
The name attribute is changed to use bug_name_validator as its validator.

Tests
=====

bin/test -vvct TestBugConstraints

Demo & QA
=========

Change a bug's name to '1.1' or similar over the webservice; it will throw back an HTTPError rather than failing. Changing to something starting with a letter will be fine.

Lint
====

make lint output:

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/app/validators/name.py
  lib/lp/bugs/interfaces/bug.py
  lib/lp/bugs/tests/test_bugs_webservice.py

./lib/lp/bugs/interfaces/bug.py
     445: E301 expected 1 blank line, found 0
    1177: E301 expected 1 blank line, found 2

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/validators/name.py'
2--- lib/lp/app/validators/name.py 2011-02-28 21:23:46 +0000
3+++ lib/lp/app/validators/name.py 2011-02-28 21:23:48 +0000
4@@ -15,6 +15,7 @@
5
6
7 valid_name_pattern = re.compile(r"^[a-z0-9][a-z0-9\+\.\-]+$")
8+valid_bug_name_pattern = re.compile(r"^[a-z][a-z0-9\+\.\-]+$")
9 invalid_name_pattern = re.compile(r"^[^a-z0-9]+|[^a-z0-9\\+\\.\\-]+")
10
11
12@@ -57,6 +58,13 @@
13 return False
14
15
16+def valid_bug_name(name):
17+ """Return True if the bug name is valid, otherwise False."""
18+ if valid_bug_name_pattern.match(name):
19+ return True
20+ return False
21+
22+
23 def name_validator(name):
24 """Return True if the name is valid, or raise a
25 LaunchpadValidationError.
26@@ -71,3 +79,19 @@
27
28 raise LaunchpadValidationError(structured(message))
29 return True
30+
31+
32+def bug_name_validator(name):
33+ """Return True if the name is valid, or raise a
34+ LaunchpadValidationError.
35+ """
36+ if not valid_bug_name(name):
37+ message = _(dedent("""
38+ Invalid name '${name}'. Names must be at least two characters long
39+ and start with a letter. All letters must be lower-case.
40+ The characters <samp>+</samp>, <samp>-</samp> and <samp>.</samp>
41+ are also allowed after the first character."""),
42+ mapping={'name': escape(name)})
43+
44+ raise LaunchpadValidationError(structured(message))
45+ return True
46
47=== modified file 'lib/lp/bugs/interfaces/bug.py'
48--- lib/lp/bugs/interfaces/bug.py 2011-02-28 21:23:46 +0000
49+++ lib/lp/bugs/interfaces/bug.py 2011-02-28 21:23:48 +0000
50@@ -65,9 +65,9 @@
51 from canonical.launchpad import _
52 from canonical.launchpad.interfaces.launchpad import IPrivacy
53 from canonical.launchpad.interfaces.message import IMessage
54+from lp.app.validators.attachment import attachment_size_constraint
55+from lp.app.validators.name import bug_name_validator
56 from lp.app.errors import NotFoundError
57-from lp.app.validators.attachment import attachment_size_constraint
58-from lp.app.validators.name import name_validator
59 from lp.bugs.interfaces.bugactivity import IBugActivity
60 from lp.bugs.interfaces.bugattachment import IBugAttachment
61 from lp.bugs.interfaces.bugbranch import IBugBranch
62@@ -206,7 +206,7 @@
63 description=_("""A short and unique name.
64 Add one only if you often need to retype the URL
65 but have trouble remembering the bug number."""),
66- constraint=name_validator))
67+ constraint=bug_name_validator))
68 title = exported(
69 Title(title=_('Summary'), required=True,
70 description=_("""A one-line summary of the problem.""")))
71@@ -705,7 +705,7 @@
72
73 def getMessagesForView(slice_info):
74 """Return BugMessage,Message,MessageChunks for renderinger.
75-
76+
77 This eager loads message.owner validity associated with the
78 bugmessages.
79
80
81=== modified file 'lib/lp/bugs/tests/test_bugs_webservice.py'
82--- lib/lp/bugs/tests/test_bugs_webservice.py 2011-02-21 12:03:43 +0000
83+++ lib/lp/bugs/tests/test_bugs_webservice.py 2011-02-28 21:23:48 +0000
84@@ -9,12 +9,12 @@
85
86 from BeautifulSoup import BeautifulSoup
87 from lazr.lifecycle.interfaces import IDoNotSnapshot
88+from lazr.restfulclient.errors import HTTPError
89 from simplejson import dumps
90 from storm.store import Store
91 from testtools.matchers import (
92 Equals,
93 LessThan,
94- MatchesAny,
95 )
96 from zope.component import getMultiAdapter
97
98@@ -31,7 +31,10 @@
99 )
100 from lp.bugs.browser.bugtask import get_comments_for_bugtask
101 from lp.bugs.interfaces.bug import IBug
102-from lp.testing import TestCaseWithFactory
103+from lp.testing import (
104+ launchpadlib_for,
105+ TestCaseWithFactory,
106+ )
107 from lp.testing.matchers import HasQueryCount
108 from lp.testing.sampledata import (
109 ADMIN_EMAIL,
110@@ -40,8 +43,36 @@
111 from lp.testing._webservice import QueryCollector
112
113
114+class TestBugConstraints(TestCaseWithFactory):
115+ """Test constrainsts on bug inputs over the API."""
116+
117+ layer = DatabaseFunctionalLayer
118+
119+ def setUp(self):
120+ super(TestBugConstraints, self).setUp()
121+ product = self.factory.makeProduct(name='foo')
122+ bug = self.factory.makeBug(product=product)
123+ lp = launchpadlib_for('testing', product.owner)
124+ self.bug = lp.bugs[bug.id]
125+
126+ def _update_bug(self, nick):
127+ self.bug.name = nick
128+ self.bug.lp_save()
129+
130+ def test_numeric_nicknames_fail(self):
131+ self.assertRaises(
132+ HTTPError,
133+ self._update_bug,
134+ '1.1')
135+
136+ def test_non_numeric_nicknames_pass(self):
137+ self._update_bug('bunny')
138+ self.assertEqual('bunny', self.bug.name)
139+
140+
141 class TestBugDescriptionRepresentation(TestCaseWithFactory):
142 """Test ways of interacting with Bug webservice representations."""
143+
144 layer = DatabaseFunctionalLayer
145
146 def setUp(self):