Merge lp:~sinzui/launchpad/vocab-storm-bug-413287 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Eleanor Berger
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~sinzui/launchpad/vocab-storm-bug-413287
Merge into: lp:launchpad
Diff against target: 95 lines (+35/-16)
2 files modified
lib/lp/registry/doc/vocabularies.txt (+23/-2)
lib/lp/registry/vocabularies.py (+12/-14)
To merge this branch: bzr merge lp:~sinzui/launchpad/vocab-storm-bug-413287
Reviewer Review Type Date Requested Status
Eleanor Berger (community) Approve
Review via email: mp+15125@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

This is my branch to prevent oopses search or validating persons with '%'
in their names.

    lp:~sinzui/launchpad/vocab-storm-bug-413287
    Diff size: 96
    Launchpad bug: https://bugs.launchpad.net/bugs/413287
    Test command: ./bin/test -vvt "registry.*doc/vocab"
    Pre-implementation: no one
    Target release: 3.1.11

= Prevent oopses search or validating persons with '%' in their names =

The '%' is just not escaped, so psycopg's variable substitution blows up.
The ValidPersonOrTeamVocabulary uses SQL() to add hand-coded sql to a storm
query. When it does this, it uses python string substitutions. Instead, we
should use the optional second argument to SQL() to pass in the variables,
which will escape them properly.
For example:
   SQL('SELECT name FROM person WHERE id = ? AND displayname = ?', (33, 'foo'))

This should also eliminate the need to use quote() and quote_like() on the
parameters.

== Rules ==

    * Revise the query to be a Storm expression.

== QA ==

    * Try to reassign an project or team to another user.
    * Place a percent in the user's name.
    * Verify that the screen explains that there are no matching users.

    * Using the person picker
    * Search for a name with a percent in it
    * Verify you do not get an error.

== Lint ==

Linting changed files:
  lib/lp/registry/vocabularies.py
  lib/lp/registry/doc/vocabularies.txt

== Test ==

    * lib/lp/registry/doc/vocabularies.txt
      * Added a test to verify that searching for % and ? work.

== Implementation ==

    * lib/lp/registry/vocabularies.py
      * Revised the two parts of the SQL to use a storm expression

Revision history for this message
Eleanor Berger (intellectronica) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/doc/vocabularies.txt'
2--- lib/lp/registry/doc/vocabularies.txt 2009-10-26 18:40:04 +0000
3+++ lib/lp/registry/doc/vocabularies.txt 2009-11-21 19:05:35 +0000
4@@ -906,8 +906,29 @@
5 >>> [person.displayname for person in vocab.search('team')]
6 [u'HWDB Team', u'No Team Memberships', u'testing Spanish team']
7
8-
9-=== ValidOwner ===
10+Search for names with '%' and '?' is supported.
11+
12+ >>> from lp.registry.interfaces.irc import IIrcIDSet
13+
14+ >>> symbolic_person = factory.makePerson(name='symbolic')
15+ >>> ircid_set = getUtility(IIrcIDSet)
16+ >>> irc_id = ircid_set.new(
17+ ... symbolic_person, 'irc.freenode.net', '%percent')
18+ >>> irc_id =ircid_set.new(
19+ ... symbolic_person, 'irc.fnord.net', 'question?')
20+ >>> transaction.commit()
21+
22+ >>> for person in vocab.search('%percent'):
23+ ... print person.name
24+ symbolic
25+
26+ >>> for person in vocab.search('question?'):
27+ ... print person.name
28+ symbolic
29+
30+
31+ValidOwner
32+..........
33
34 All valid persons and teams are also valid owners.
35
36
37=== modified file 'lib/lp/registry/vocabularies.py'
38--- lib/lp/registry/vocabularies.py 2009-10-26 18:40:04 +0000
39+++ lib/lp/registry/vocabularies.py 2009-11-21 19:05:35 +0000
40@@ -504,33 +504,31 @@
41 # the best results. The fti rank will be between 0 and 1.
42 # Note we use lower() instead of the non-standard ILIKE because
43 # ILIKE doesn't hit the indexes.
44- # The '%%%%' is necessary, since the first variable substitution
45- # converts it to '%%' and the storm variable substitution converts
46- # it to '%'.
47+ # The '%%' is necessary because storm variable substitution
48+ # converts it to '%'.
49 public_inner_textual_select = SQL("""
50 SELECT id FROM (
51 SELECT Person.id, 100 AS rank
52 FROM Person
53- WHERE name = %(text)s
54+ WHERE name = ?
55 UNION ALL
56- SELECT Person.id, rank(fti, ftq(%(text)s))
57+ SELECT Person.id, rank(fti, ftq(?))
58 FROM Person
59- WHERE Person.fti @@ ftq(%(text)s)
60+ WHERE Person.fti @@ ftq(?)
61 UNION ALL
62 SELECT Person.id, 10 AS rank
63 FROM Person, IrcId
64 WHERE IrcId.person = Person.id
65- AND lower(IrcId.nickname) = %(text)s
66+ AND lower(IrcId.nickname) = ?
67 UNION ALL
68 SELECT Person.id, 1 AS rank
69 FROM Person, EmailAddress
70 WHERE EmailAddress.person = Person.id
71- AND lower(email) LIKE %(like_text)s || '%%%%'
72+ AND lower(email) LIKE ? || '%%'
73 ) AS public_subquery
74 ORDER BY rank DESC
75- LIMIT %(limit)d
76- """ % dict(text=quote(text), like_text=quote_like(text),
77- limit=self.LIMIT))
78+ LIMIT ?
79+ """, (text, text, text, text, text, self.LIMIT))
80
81 public_result = self.store.using(*public_tables).find(
82 Person,
83@@ -564,9 +562,9 @@
84 private_inner_select = SQL("""
85 SELECT Person.id
86 FROM Person
87- WHERE Person.fti @@ ftq(%s)
88- LIMIT %d
89- """ % (quote(text), self.LIMIT))
90+ WHERE Person.fti @@ ftq(?)
91+ LIMIT ?
92+ """, (text, self.LIMIT))
93 private_result = self.store.using(*private_tables).find(
94 Person,
95 And(