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
=== modified file 'lib/lp/registry/doc/vocabularies.txt'
--- lib/lp/registry/doc/vocabularies.txt 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/doc/vocabularies.txt 2009-11-21 19:05:35 +0000
@@ -906,8 +906,29 @@
906 >>> [person.displayname for person in vocab.search('team')]906 >>> [person.displayname for person in vocab.search('team')]
907 [u'HWDB Team', u'No Team Memberships', u'testing Spanish team']907 [u'HWDB Team', u'No Team Memberships', u'testing Spanish team']
908908
909909Search for names with '%' and '?' is supported.
910=== ValidOwner ===910
911 >>> from lp.registry.interfaces.irc import IIrcIDSet
912
913 >>> symbolic_person = factory.makePerson(name='symbolic')
914 >>> ircid_set = getUtility(IIrcIDSet)
915 >>> irc_id = ircid_set.new(
916 ... symbolic_person, 'irc.freenode.net', '%percent')
917 >>> irc_id =ircid_set.new(
918 ... symbolic_person, 'irc.fnord.net', 'question?')
919 >>> transaction.commit()
920
921 >>> for person in vocab.search('%percent'):
922 ... print person.name
923 symbolic
924
925 >>> for person in vocab.search('question?'):
926 ... print person.name
927 symbolic
928
929
930ValidOwner
931..........
911932
912All valid persons and teams are also valid owners.933All valid persons and teams are also valid owners.
913934
914935
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/vocabularies.py 2009-11-21 19:05:35 +0000
@@ -504,33 +504,31 @@
504 # the best results. The fti rank will be between 0 and 1.504 # the best results. The fti rank will be between 0 and 1.
505 # Note we use lower() instead of the non-standard ILIKE because505 # Note we use lower() instead of the non-standard ILIKE because
506 # ILIKE doesn't hit the indexes.506 # ILIKE doesn't hit the indexes.
507 # The '%%%%' is necessary, since the first variable substitution507 # The '%%' is necessary because storm variable substitution
508 # converts it to '%%' and the storm variable substitution converts508 # converts it to '%'.
509 # it to '%'.
510 public_inner_textual_select = SQL("""509 public_inner_textual_select = SQL("""
511 SELECT id FROM (510 SELECT id FROM (
512 SELECT Person.id, 100 AS rank511 SELECT Person.id, 100 AS rank
513 FROM Person512 FROM Person
514 WHERE name = %(text)s513 WHERE name = ?
515 UNION ALL514 UNION ALL
516 SELECT Person.id, rank(fti, ftq(%(text)s))515 SELECT Person.id, rank(fti, ftq(?))
517 FROM Person516 FROM Person
518 WHERE Person.fti @@ ftq(%(text)s)517 WHERE Person.fti @@ ftq(?)
519 UNION ALL518 UNION ALL
520 SELECT Person.id, 10 AS rank519 SELECT Person.id, 10 AS rank
521 FROM Person, IrcId520 FROM Person, IrcId
522 WHERE IrcId.person = Person.id521 WHERE IrcId.person = Person.id
523 AND lower(IrcId.nickname) = %(text)s522 AND lower(IrcId.nickname) = ?
524 UNION ALL523 UNION ALL
525 SELECT Person.id, 1 AS rank524 SELECT Person.id, 1 AS rank
526 FROM Person, EmailAddress525 FROM Person, EmailAddress
527 WHERE EmailAddress.person = Person.id526 WHERE EmailAddress.person = Person.id
528 AND lower(email) LIKE %(like_text)s || '%%%%'527 AND lower(email) LIKE ? || '%%'
529 ) AS public_subquery528 ) AS public_subquery
530 ORDER BY rank DESC529 ORDER BY rank DESC
531 LIMIT %(limit)d530 LIMIT ?
532 """ % dict(text=quote(text), like_text=quote_like(text),531 """, (text, text, text, text, text, self.LIMIT))
533 limit=self.LIMIT))
534532
535 public_result = self.store.using(*public_tables).find(533 public_result = self.store.using(*public_tables).find(
536 Person,534 Person,
@@ -564,9 +562,9 @@
564 private_inner_select = SQL("""562 private_inner_select = SQL("""
565 SELECT Person.id563 SELECT Person.id
566 FROM Person564 FROM Person
567 WHERE Person.fti @@ ftq(%s)565 WHERE Person.fti @@ ftq(?)
568 LIMIT %d566 LIMIT ?
569 """ % (quote(text), self.LIMIT))567 """, (text, self.LIMIT))
570 private_result = self.store.using(*private_tables).find(568 private_result = self.store.using(*private_tables).find(
571 Person,569 Person,
572 And(570 And(