Merge lp:~sinzui/launchpad/newline-removal into lp:launchpad

Proposed by Curtis Hovey on 2010-04-06
Status: Merged
Approved by: Gavin Panella on 2010-04-07
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~sinzui/launchpad/newline-removal
Merge into: lp:launchpad
Diff against target: 190 lines (+59/-22)
3 files modified
lib/lp/registry/interfaces/distribution.py (+2/-1)
lib/lp/registry/scripts/listteammembers.py (+21/-12)
lib/lp/registry/tests/test_listteammembers.py (+36/-9)
To merge this branch: bzr merge lp:~sinzui/launchpad/newline-removal
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) Approve on 2010-04-07
Gavin Panella (community) 2010-04-06 Approve on 2010-04-07
Nick Moffitt Approve on 2010-04-07
Review via email: mp+22915@code.launchpad.net

Description of the Change

This is my branch to strip line breaks from listteammembers sshkeys.
This branch elaborates on the solution suggested by Nick Moffitt. Nick's
approach broke the existing tests, so I wrote a helper function that is
easy to test.

    lp:~sinzui/launchpad/newline-removal
    Diff size: 175 (50% lint)
    Launchpad bug: https://bugs.launchpad.net/bugs/556525
    Test command: ./bin/test -vv -t test_listteammembers
    Pre-implementation: no one
    Target release: 10.04

Strip line breaks from listteammembers sshkeys
-----------------------------------------------

The current report generation script preserves newlines in ssh key data as
input by a user. Often users will mis-paste their key from a terminal display
that inserted hard newlines (such as some configurations of less under certain
terminal types) and this will result in a key formatted such that it cannot be
used in an authorized_keys file.

In addition, any user can create a key such as the one at
https://launchpad.net/~lp-test-outflux/+sshkeys and create a false entry for
another user. This is the more troublesome aspect of this behavior, and
constitutes a security risk for systems that make use of this ssh key data.

Rules
-----

    * Using Nick's branch as a base, provide a test to verify that '\r\n\f'
      characters are removed from sshkeys.

QA
--

    * On staging, create a teeam named goat-boys
      and add ~lp-test-outflux as a member.
    * Run scripts/list-team-members -s goat-boys
    * Verify that ~lp-test-outflux's key is on one line.

Lint
----

Lint hated these files.

Linting changed files:
  lib/lp/registry/scripts/listteammembers.py
  lib/lp/registry/tests/test_listteammembers.py

Test
----

    * lib/lp/registry/tests/test_listteammembers.py
      * Added a test for the new function.

Implementation
--------------

    * lib/lp/registry/scripts/listteammembers.py
      * Extracted the sshkey formatting to a function and added a rule to
        remove '[\r\n\f]'

To post a comment you must log in.
Nick Moffitt (nick-moffitt) wrote :

Is your multi-line break-up of the key on line 108 meant to be part of the test for this functionality? It would appear to me that no newlines would be inserted using that syntax.

review: Needs Fixing
Nick Moffitt (nick-moffitt) wrote :

Also, pawing through the whitespace changes and all, perhaps "bad_ssh_patters" could be written as "bad_ssh_patterns"?

Oh, I just spotted your u'123badKeysMight\r\nContain\fBadCharacters' line. It's worth also testing for newlines in the comment of a key as well.

Curtis Hovey (sinzui) wrote :

Hi Nick.

Our lint checker hated the two files I edited. It reported that I had to wrap the long lines and fix the commas before submitt my changes for review. So all the line wrapping was done just to quiet lint.

I renamed bad_ssh_pattern and I added bad characters to the sshkey comment.

{{{
=== modified file 'lib/lp/registry/scripts/listteammembers.py'
--- lib/lp/registry/scripts/listteammembers.py 2010-04-06 21:43:55 +0000
+++ lib/lp/registry/scripts/listteammembers.py 2010-04-07 13:07:27 +0000
@@ -27,14 +27,14 @@
     """Used if non-existent team name is specified."""

-bad_ssh_patters = re.compile('[\r\n\f]')
+bad_ssh_pattern = re.compile('[\r\n\f]')

 def make_sshkey_params(member, type_name, key):
     sshkey = "%s %s %s" % (
         type_name,
- bad_ssh_patters.sub('', key.keytext),
- bad_ssh_patters.sub('', key.comment).strip())
+ bad_ssh_pattern.sub('', key.keytext),
+ bad_ssh_pattern.sub('', key.comment).strip())
     return dict(name=member.name, sshkey=sshkey)

=== modified file 'lib/lp/registry/tests/test_listteammembers.py'
--- lib/lp/registry/tests/test_listteammembers.py 2010-04-06 21:43:55 +0000
+++ lib/lp/registry/tests/test_listteammembers.py 2010-04-07 13:07:27 +0000
@@ -98,7 +98,7 @@
         team.addMember(member, reviewer=team.teamowner)
         sshkey = self.factory.makeSSHKey(member)
         sshkey.keytext = u'123badKeysMight\r\nContain\fBadCharacters'
- sshkey.comment = 'comment'
+ sshkey.comment = 'co\rmm\ne\f\fnt'
         expected = dict(
             name=u'biggles',
             sshkey=u'ssh-rsa 123badKeysMightContainBadCharacters comment')

}}}

Nick Moffitt (nick-moffitt) wrote :

Many thanks!

review: Approve
Gavin Panella (allenap) wrote :

Some of the tests that you've folded the lines for are (result, expected) rather than (expected, result), so drive-by those if that knowledge now bothers you. Other than that, all's cool.

review: Approve
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (3.6 KiB)

Hi Curtis,

This branch looks good. I just have a couple of minor comments below.

merge-conditional

-Edwin

>=== modified file 'lib/lp/registry/tests/test_listteammembers.py'
>--- lib/lp/registry/tests/test_listteammembers.py 2009-08-14 12:59:56 +0000
>+++ lib/lp/registry/tests/test_listteammembers.py 2010-04-07 13:59:25 +0000
>@@ -48,14 +52,22 @@
> ])
>
> ubuntuteam_sshkeys = [
>- u'mark: ssh-dss AAAAB3NzaC1kc3MAAABBAL5VoWG5sy3CnLYeOw47L8m9A15hA/PzdX2u0B7c2Z1ktFPcEaEuKbLqKVSkXpYm7YwKj9y88A9Qm61CdvI0c50AAAAVAKGY0YON9dEFH3DzeVYHVEBGFGfVAAAAQCoe0RhBcefm4YiyQVwMAxwTlgySTk7FSk6GZ95EZ5Q8/OTdViTaalvGXaRIsBdaQamHEBB+Vek/VpnF1UGGm8YAAABAaCXDl0r1k93JhnMdF0ap4UJQ2/NnqCyoE8Xd5KdUWWwqwGdMzqB1NOeKN6ladIAXRggLc2E00UsnUXh3GE3Rgw== Private key in lib/lp/codehosting/tests/id_dsa',
>+ u'mark: ssh-dss AAAAB3NzaC1kc3MAAABBAL5VoWG5sy3CnLYeOw47L8m9A15hA/PzdX2u0'
>+ u'B7c2Z1ktFPcEaEuKbLqKVSkXpYm7YwKj9y88A9Qm61CdvI0c50AAAAVAKGY0YON9dEFH3Dz'
>+ u'eVYHVEBGFGfVAAAAQCoe0RhBcefm4YiyQVwMAxwTlgySTk7FSk6GZ95EZ5Q8/OTdViTaalv'
>+ u'GXaRIsBdaQamHEBB+Vek/VpnF1UGGm8YAAABAaCXDl0r1k93JhnMdF0ap4UJQ2/NnqCyoE8'
>+ u'Xd5KdUWWwqwGdMzqB1NOeKN6ladIAXRggLc2E00UsnUXh3GE3Rgw== Private key in '
>+ u'lib/lp/codehosting/tests/id_dsa',
> ]
>
>
>-class ListTeamMembersTestCase(unittest.TestCase):
>+class ListTeamMembersTestCase(TestCaseWithFactory):
> """Test listing team members."""
> layer = LaunchpadZopelessLayer
>
>+ def setUp(self):
>+ super(ListTeamMembersTestCase, self).setUp()

Why is this necessary?

> def test_listteammembers_default_list(self):
> """Test the default option."""
> self.assertEqual(
>@@ -64,22 +76,40 @@
> def test_listteammembers_email_only(self):
> """Test the email only option."""
> self.assertEqual(
>- listteammembers.process_team('ubuntu-team', 'email'), ubuntuteam_email)
>+ listteammembers.process_team('ubuntu-team', 'email'),
>+ ubuntuteam_email)
>
> def test_listteammembers_full_details(self):
> """Test the full details option."""
> self.assertEqual(
>- listteammembers.process_team('ubuntu-team', 'full'), ubuntuteam_full)
>+ listteammembers.process_team('ubuntu-team', 'full'),
>+ ubuntuteam_full)
>
> def test_listteammembers_sshkeys(self):
> """Test the ssh keys option."""
> self.assertEqual(
>- listteammembers.process_team('ubuntu-team', 'sshkeys'), ubuntuteam_sshkeys)
>+ listteammembers.process_team('ubuntu-team', 'sshkeys'),
>+ ubuntuteam_sshkeys)
>+
>+ def test_make_sshkey_params(self):
>+ """Test that ssh keys ."""

Incomplete docstring.

>+ member = self.factory.makePerson(name='biggles')
>+ team = self.factory.makeTeam(name='squadron')
>+ team.addMember(member, reviewer=team.teamowner)
>+ sshkey = self.factory.makeSSHKey(member)
>+ sshkey.keytext = u'123badKeysMight\r\nContain\fBadCharacters'
>+ sshkey.comment = 'co\rmm\ne\f\fnt'
>+ expected = dict(
>+ name=u'biggles',
>+ sshkey=u'ssh-rsa 123badKeysMightContainBadCharacters comment')
>+ ...

Read more...

review: Approve
Curtis Hovey (sinzui) wrote :

On Wed, 2010-04-07 at 15:50 +0000, Gavin Panella wrote:
> Review: Approve
> Some of the tests that you've folded the lines for are (result,
> expected) rather than (expected, result), so drive-by those if that
> knowledge now bothers you. Other than that, all's cool.

Oh that is so cruel. How can I sleep knowing this? I cannot. Fixed.

On Wed, 2010-04-07 at 16:04 +0000, Edwin Grubbs wrote:
> Review: Approve
> Hi Curtis,
>
> This branch looks good. I just have a couple of minor comments below.
>
> merge-conditional
...
> >+class ListTeamMembersTestCase(TestCaseWithFactory):
> > """Test listing team members."""
> > layer = LaunchpadZopelessLayer
> >
> >+ def setUp(self):
> >+ super(ListTeamMembersTestCase, self).setUp()
>
>
> Why is this necessary?

It is not. I assumed for a moment that I needs to do some setup for
everything. I Removed this.

...

> >+ def test_make_sshkey_params(self):
> >+ """Test that ssh keys ."""
>
>
> Incomplete docstring.

Indeed:
    """Test that ssh keys are rendered as a single line."""

{{{

=== modified file 'lib/lp/registry/tests/test_listteammembers.py'
--- lib/lp/registry/tests/test_listteammembers.py 2010-04-07 13:07:27 +0000
+++ lib/lp/registry/tests/test_listteammembers.py 2010-04-07 20:15:44 +0000
@@ -65,34 +65,31 @@
     """Test listing team members."""
     layer = LaunchpadZopelessLayer

- def setUp(self):
- super(ListTeamMembersTestCase, self).setUp()
-
     def test_listteammembers_default_list(self):
         """Test the default option."""
         self.assertEqual(
- listteammembers.process_team('ubuntu-team'), ubuntuteam_default)
+ ubuntuteam_default, listteammembers.process_team('ubuntu-team'))

     def test_listteammembers_email_only(self):
         """Test the email only option."""
         self.assertEqual(
- listteammembers.process_team('ubuntu-team', 'email'),
- ubuntuteam_email)
+ ubuntuteam_email,
+ listteammembers.process_team('ubuntu-team', 'email'))

     def test_listteammembers_full_details(self):
         """Test the full details option."""
         self.assertEqual(
- listteammembers.process_team('ubuntu-team', 'full'),
- ubuntuteam_full)
+ ubuntuteam_full,
+ listteammembers.process_team('ubuntu-team', 'full'))

     def test_listteammembers_sshkeys(self):
         """Test the ssh keys option."""
         self.assertEqual(
- listteammembers.process_team('ubuntu-team', 'sshkeys'),
- ubuntuteam_sshkeys)
+ ubuntuteam_sshkeys,
+ listteammembers.process_team('ubuntu-team', 'sshkeys'))

     def test_make_sshkey_params(self):
- """Test that ssh keys ."""
+ """Test that ssh keys are rendered as a single line."""
         member = self.factory.makePerson(name='biggles')
         team = self.factory.makeTeam(name='squadron')
         team.addMember(member, reviewer=team.teamowner)

}}}
--
__Curtis C. Hovey_________
http://launchpad.net/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/interfaces/distribution.py'
2--- lib/lp/registry/interfaces/distribution.py 2010-03-29 16:44:37 +0000
3+++ lib/lp/registry/interfaces/distribution.py 2010-04-07 20:59:30 +0000
4@@ -25,11 +25,12 @@
5
6 from lazr.restful.fields import CollectionField, Reference
7 from lazr.restful.declarations import (
8- collection_default_content, copy_field, export_as_webservice_collection,
9+ collection_default_content, export_as_webservice_collection,
10 export_as_webservice_entry, export_operation_as,
11 export_read_operation, exported, operation_parameters,
12 operation_returns_collection_of, operation_returns_entry,
13 rename_parameters_as)
14+from lazr.restful.interface import copy_field
15
16 from canonical.launchpad import _
17 from canonical.launchpad.fields import (
18
19=== modified file 'lib/lp/registry/scripts/listteammembers.py'
20--- lib/lp/registry/scripts/listteammembers.py 2009-06-25 04:06:00 +0000
21+++ lib/lp/registry/scripts/listteammembers.py 2010-04-07 20:59:30 +0000
22@@ -6,6 +6,8 @@
23 __metaclass__ = type
24 __all__ = ['process_team']
25
26+import re
27+
28 from zope.component import getUtility
29
30 from lp.registry.interfaces.person import IPersonSet
31@@ -13,17 +15,29 @@
32
33
34 OUTPUT_TEMPLATES = {
35- 'simple': '%(name)s, %(email)s',
36- 'email': '%(email)s',
37- 'full': '%(teamname)s|%(id)s|%(name)s|%(email)s|%(displayname)s|%(ubuntite)s',
38- 'sshkeys': '%(name)s: %(sshkey)s',
39- }
40+ 'simple': '%(name)s, %(email)s',
41+ 'email': '%(email)s',
42+ 'full': '%(teamname)s|%(id)s|%(name)s|%(email)s|'
43+ '%(displayname)s|%(ubuntite)s',
44+ 'sshkeys': '%(name)s: %(sshkey)s',
45+ }
46
47
48 class NoSuchTeamError(Exception):
49 """Used if non-existent team name is specified."""
50
51
52+bad_ssh_pattern = re.compile('[\r\n\f]')
53+
54+
55+def make_sshkey_params(member, type_name, key):
56+ sshkey = "%s %s %s" % (
57+ type_name,
58+ bad_ssh_pattern.sub('', key.keytext),
59+ bad_ssh_pattern.sub('', key.comment).strip())
60+ return dict(name=member.name, sshkey=sshkey)
61+
62+
63 def process_team(teamname, display_option='simple'):
64 output = []
65 people = getUtility(IPersonSet)
66@@ -54,11 +68,7 @@
67 type_name = 'ssh-rsa'
68 else:
69 type_name = 'Unknown key type'
70- params = dict(
71- name=member.name,
72- sshkey="%s %s %s" % (type_name, key.keytext,
73- key.comment.strip())
74- )
75+ params = make_sshkey_params(member, type_name, key)
76 output.append(template % params)
77 # Ubuntite
78 ubuntite = "no"
79@@ -74,7 +84,7 @@
80 id=member.id,
81 displayname=member.displayname.encode("ascii", "replace"),
82 ubuntite=ubuntite,
83- sshkey=sshkey
84+ sshkey=sshkey,
85 )
86 output.append(template % params)
87 # If we're only looking at email, remove --none-- entries
88@@ -86,4 +96,3 @@
89 if display_option == 'sshkeys':
90 output = [line for line in output if line[-8:] != '--none--']
91 return sorted(output)
92-
93
94=== modified file 'lib/lp/registry/tests/test_listteammembers.py'
95--- lib/lp/registry/tests/test_listteammembers.py 2009-08-14 12:59:56 +0000
96+++ lib/lp/registry/tests/test_listteammembers.py 2010-04-07 20:59:30 +0000
97@@ -6,6 +6,8 @@
98 from canonical.testing import LaunchpadZopelessLayer
99
100 from lp.registry.scripts import listteammembers
101+from lp.testing import TestCaseWithFactory
102+
103
104 ubuntuteam_default = sorted([
105 u'cprov, celso.providelo@canonical.com',
106@@ -36,11 +38,13 @@
107
108 ubuntuteam_full = sorted([
109 u'ubuntu-team|10|limi|limi@plone.org|Alexander Limi|no',
110- u'ubuntu-team|11|stevea|steve.alexander@ubuntulinux.com|Steve Alexander|no',
111+ u'ubuntu-team|11|stevea|steve.alexander@ubuntulinux.com'
112+ u'|Steve Alexander|no',
113 u'ubuntu-team|16|name16|foo.bar@canonical.com|Foo Bar|yes',
114 u'ubuntu-team|19|warty-gnome|--none--|Warty Gnome Team|no',
115 u'ubuntu-team|1|mark|mark@example.com|Mark Shuttleworth|no',
116- u'ubuntu-team|26|kinnison|daniel.silverstone@canonical.com|Daniel Silverstone|no',
117+ u'ubuntu-team|26|kinnison|daniel.silverstone@canonical.com'
118+ u'|Daniel Silverstone|no',
119 u'ubuntu-team|28|cprov|celso.providelo@canonical.com|Celso Providelo|no',
120 u'ubuntu-team|33|edgar|edgar@monteparadiso.hr|Edgar Bursic|no',
121 u'ubuntu-team|4|kamion|colin.watson@ubuntulinux.com|Colin Watson|no',
122@@ -48,38 +52,61 @@
123 ])
124
125 ubuntuteam_sshkeys = [
126- u'mark: ssh-dss AAAAB3NzaC1kc3MAAABBAL5VoWG5sy3CnLYeOw47L8m9A15hA/PzdX2u0B7c2Z1ktFPcEaEuKbLqKVSkXpYm7YwKj9y88A9Qm61CdvI0c50AAAAVAKGY0YON9dEFH3DzeVYHVEBGFGfVAAAAQCoe0RhBcefm4YiyQVwMAxwTlgySTk7FSk6GZ95EZ5Q8/OTdViTaalvGXaRIsBdaQamHEBB+Vek/VpnF1UGGm8YAAABAaCXDl0r1k93JhnMdF0ap4UJQ2/NnqCyoE8Xd5KdUWWwqwGdMzqB1NOeKN6ladIAXRggLc2E00UsnUXh3GE3Rgw== Private key in lib/lp/codehosting/tests/id_dsa',
127+ u'mark: ssh-dss AAAAB3NzaC1kc3MAAABBAL5VoWG5sy3CnLYeOw47L8m9A15hA/PzdX2u0'
128+ u'B7c2Z1ktFPcEaEuKbLqKVSkXpYm7YwKj9y88A9Qm61CdvI0c50AAAAVAKGY0YON9dEFH3Dz'
129+ u'eVYHVEBGFGfVAAAAQCoe0RhBcefm4YiyQVwMAxwTlgySTk7FSk6GZ95EZ5Q8/OTdViTaalv'
130+ u'GXaRIsBdaQamHEBB+Vek/VpnF1UGGm8YAAABAaCXDl0r1k93JhnMdF0ap4UJQ2/NnqCyoE8'
131+ u'Xd5KdUWWwqwGdMzqB1NOeKN6ladIAXRggLc2E00UsnUXh3GE3Rgw== Private key in '
132+ u'lib/lp/codehosting/tests/id_dsa',
133 ]
134
135
136-class ListTeamMembersTestCase(unittest.TestCase):
137+class ListTeamMembersTestCase(TestCaseWithFactory):
138 """Test listing team members."""
139 layer = LaunchpadZopelessLayer
140
141 def test_listteammembers_default_list(self):
142 """Test the default option."""
143 self.assertEqual(
144- listteammembers.process_team('ubuntu-team'), ubuntuteam_default)
145+ ubuntuteam_default, listteammembers.process_team('ubuntu-team'))
146
147 def test_listteammembers_email_only(self):
148 """Test the email only option."""
149 self.assertEqual(
150- listteammembers.process_team('ubuntu-team', 'email'), ubuntuteam_email)
151+ ubuntuteam_email,
152+ listteammembers.process_team('ubuntu-team', 'email'))
153
154 def test_listteammembers_full_details(self):
155 """Test the full details option."""
156 self.assertEqual(
157- listteammembers.process_team('ubuntu-team', 'full'), ubuntuteam_full)
158+ ubuntuteam_full,
159+ listteammembers.process_team('ubuntu-team', 'full'))
160
161 def test_listteammembers_sshkeys(self):
162 """Test the ssh keys option."""
163 self.assertEqual(
164- listteammembers.process_team('ubuntu-team', 'sshkeys'), ubuntuteam_sshkeys)
165+ ubuntuteam_sshkeys,
166+ listteammembers.process_team('ubuntu-team', 'sshkeys'))
167+
168+ def test_make_sshkey_params(self):
169+ """Test that ssh keys are rendered as a single line."""
170+ member = self.factory.makePerson(name='biggles')
171+ team = self.factory.makeTeam(name='squadron')
172+ team.addMember(member, reviewer=team.teamowner)
173+ sshkey = self.factory.makeSSHKey(member)
174+ sshkey.keytext = u'123badKeysMight\r\nContain\fBadCharacters'
175+ sshkey.comment = 'co\rmm\ne\f\fnt'
176+ expected = dict(
177+ name=u'biggles',
178+ sshkey=u'ssh-rsa 123badKeysMightContainBadCharacters comment')
179+ result = listteammembers.make_sshkey_params(member, 'ssh-rsa', sshkey)
180+ self.assertEqual(expected, result)
181
182 def test_listteammembers_unknown_team(self):
183 """Test unknown team."""
184 self.assertRaises(
185- listteammembers.NoSuchTeamError, listteammembers.process_team, 'nosuchteam-matey')
186+ listteammembers.NoSuchTeamError, listteammembers.process_team,
187+ 'nosuchteam-matey')
188
189
190 def test_suite():