Merge lp:~jpds/launchpad/fix_517020 into lp:launchpad

Proposed by Jonathan Davies
Status: Merged
Approved by: Michael Nelson
Approved revision: not available
Merged at revision: 10283
Proposed branch: lp:~jpds/launchpad/fix_517020
Merge into: lp:launchpad
Diff against target: 247 lines (+113/-20)
3 files modified
lib/lp/registry/interfaces/distributionmirror.py (+22/-16)
lib/lp/registry/stories/webservice/xx-distribution-mirror.txt (+88/-3)
lib/lp/registry/stories/webservice/xx-distribution.txt (+3/-1)
To merge this branch: bzr merge lp:~jpds/launchpad/fix_517020
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Michael Nelson (community) code Approve
Review via email: mp+18608@code.launchpad.net

Commit message

Various fixes to exported() values in the newly exposed distribution_mirror's API.

To post a comment you must log in.
Revision history for this message
Jonathan Davies (jpds) wrote :

= Summary =

This branch fixes various problems with the newly exposed distribution_mirror's as described in bug #517020.

It also exposes various other things whose permissions to access are restricted by Zope's configuration.

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (7.3 KiB)

Hi Jonathan,

It's great that you're so quick to get in there and fix his. Other than some formatting issues, the only point that I think needs addressing here is testing the writable of the fields you're changing/exporting.

> === modified file 'lib/lp/registry/interfaces/distributionmirror.py'
> --- a/lib/lp/registry/interfaces/distributionmirror.py 2010-02-01 23:17:27 +0000
> +++ b/lib/lp/registry/interfaces/distributionmirror.py 2010-02-04 13:07:13 +0000
> @@ -287,17 +287,19 @@
>
> id = Int(title=_('The unique id'), required=True, readonly=True)
> owner = exported(PublicPersonChoice(
> - title=_('Owner'), required=False, readonly=True,
> - vocabulary='ValidOwner'))
> - reviewer = PublicPersonChoice(
> - title=_('Reviewer'), required=False, readonly=False,
> - vocabulary='ValidPersonOrTeam')
> + title=_('Owner'), description=_("The person who is set as the "
> + "current administrator of this mirror."), required=True,
> + readonly=False, vocabulary='ValidOwner'))

So the owner attribute is no-longer readonly? If that's intentional, we really
should have that tested/documented (ie. who can/can't write to it).

Also, just a few small indentation things looking at
the style guide. It might be easier to format this like:

    title=_('Owner'), description=_(
 "The person who is set as the current administrator of this mirror."),
    required=True, readonly=False, vocabulary='ValidOwner')

or

    title=_('Owner'), required=True, readonly=False, vocabulary='ValidOwner'
    description=_(
 "The person who is set as the current administrator of this mirror."))

> + reviewer = exported(PublicPersonChoice(
> + title=_('Reviewer'), description=_("The person who last reviewed this "
> + "mirror."), required=False, readonly=True,
> + vocabulary='ValidPersonOrTeam'))

So the main change is that readonly is now true. Please ensure this is tested
also.

Similar formatting issues here. Although, I can't see anything in the
style-guide, but I thought that concatenated strings needed to be aligned (if
you have to break them).

> distribution = exported(
> Reference(
> Interface,
> # Really IDistribution, circular import fixed in
> # _schema_circular_imports.
> - title=_("Distribution"), required=True,
> + title=_("Distribution"), required=True, readonly=True,

Again, this needs to be tested (so it doesn't happen again).

> description=_("The distribution that is mirrored")))
> name = exported(DistributionMirrorNameField(
> title=_('Name'), required=True, readonly=False,
> @@ -325,7 +327,7 @@
> description=_('e.g.: rsync://archive.ubuntu.com/ubuntu/')))
> enabled = exported(Bool(
> title=_('This mirror was probed successfully.'),
> - required=False, readonly=False, default=False))
> + required=False, readonly=True, default=False))

And here too.

> speed = exported(Choice(
> title=_('Link Speed'), required=True, readonly=False,
> vocabulary=MirrorSpeed))
> @@ -343,7 +345,8 @@
> title=_('Apply to...

Read more...

review: Needs Fixing (code)
Revision history for this message
Jonathan Davies (jpds) wrote :
Download full text (19.1 KiB)

> === modified file 'lib/lp/registry/interfaces/distributionmirror.py'
> > --- a/lib/lp/registry/interfaces/distributionmirror.py 2010-02-01 23:17:27 +0000
> > +++ b/lib/lp/registry/interfaces/distributionmirror.py 2010-02-04 13:07:13 +0000
> > @@ -287,17 +287,19 @@
> >
> > id = Int(title=_('The unique id'), required=True, readonly=True)
> > owner = exported(PublicPersonChoice(
> > - title=_('Owner'), required=False, readonly=True,
> > - vocabulary='ValidOwner'))
> > - reviewer = PublicPersonChoice(
> > - title=_('Reviewer'), required=False, readonly=False,
> > - vocabulary='ValidPersonOrTeam')
> > + title=_('Owner'), description=_("The person who is set as the "
> > + "current administrator of this mirror."), required=True,
> > + readonly=False, vocabulary='ValidOwner'))
>
> So the owner attribute is no-longer readonly? If that's intentional, we really
> should have that tested/documented (ie. who can/can't write to it).

This is now tested in registry/stories/webservice/xx-distribution-mirror.txt.

> Also, just a few small indentation things looking at
> the style guide. It might be easier to format this like:
>
> title=_('Owner'), description=_(
> "The person who is set as the current administrator of this mirror."),
> required=True, readonly=False, vocabulary='ValidOwner')
>
> or
>
> title=_('Owner'), required=True, readonly=False, vocabulary='ValidOwner'
> description=_(
> "The person who is set as the current administrator of this mirror."))

I've changed all the fields to the latter.

> > + reviewer = exported(PublicPersonChoice(
> > + title=_('Reviewer'), description=_("The person who last reviewed this "
> > + "mirror."), required=False, readonly=True,
> > + vocabulary='ValidPersonOrTeam'))
>
> So the main change is that readonly is now true. Please ensure this is tested
> also.

Also done.

> Similar formatting issues here. Although, I can't see anything in the
> style-guide, but I thought that concatenated strings needed to be aligned (if
> you have to break them).
>
> > distribution = exported(
> > Reference(
> > Interface,
> > # Really IDistribution, circular import fixed in
> > # _schema_circular_imports.
> > - title=_("Distribution"), required=True,
> > + title=_("Distribution"), required=True, readonly=True,
>
> Again, this needs to be tested (so it doesn't happen again).

This is now tested.

> > description=_("The distribution that is mirrored")))
> > name = exported(DistributionMirrorNameField(
> > title=_('Name'), required=True, readonly=False,
> > @@ -325,7 +327,7 @@
> > description=_('e.g.: rsync://archive.ubuntu.com/ubuntu/')))
> > enabled = exported(Bool(
> > title=_('This mirror was probed successfully.'),
> > - required=False, readonly=False, default=False))
> > + required=False, readonly=True, default=False))
>
> And here too.

Likewise.

> > speed = exported(Choice(
> > title=_('Link Speed'), required=True, readonly=False,
> > vocabul...

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (4.6 KiB)

Thanks for the changes Jonathan. I've approved the review, but do have a couple of things below that I'm interested to hear back on (they could be just oversights on my part).

> Done and full diff since r10278:
> === modified file 'lib/lp/registry/stories/webservice/xx-distribution-
> mirror.txt'
> --- lib/lp/registry/stories/webservice/xx-distribution-mirror.txt
> 2010-02-04 12:22:19 +0000
> +++ lib/lp/registry/stories/webservice/xx-distribution-mirror.txt
> 2010-02-05 11:32:41 +0000
> @@ -57,6 +57,88 @@
> status: u'Official'
> whiteboard: None
>
> += Security checks =
> +
> +People who are not mirror listing admins or the mirrors registrar may not
> +change the owner's of mirrors:
> +
> + >>> from canonical.launchpad.testing.pages import webservice_for_person
> + >>> from canonical.launchpad.webapp.interfaces import OAuthPermission
> + >>> from zope.component import getUtility
> + >>> from lp.registry.interfaces.person import IPersonSet
> + >>> from simplejson import dumps
> + >>> login(ANONYMOUS)
> + >>> karl_db = getUtility(IPersonSet).getByName('karl')
> + >>> test_db = getUtility(IPersonSet).getByName('name12')
> + >>> karl_webservice = webservice_for_person(karl_db,
> + ... permission=OAuthPermission.WRITE_PUBLIC)
> + >>> test_webservice = webservice_for_person(test_db,
> + ... permission=OAuthPermission.WRITE_PUBLIC)
> + >>> logout()
> + >>> karl = webservice.get("/~karl").jsonBody()
> + >>> patch = {
> + ... u'owner_link': karl['self_link']
> + ... }
> +
> +Now trying to set the owner using Sample Person's webservice is not
> authorized.
> +
> + >>> response = test_webservice.patch(
> + ... canonical_archive['self_link'], 'application/json', dumps(patch))
> + >>> print response.getheader('status')
> + 401 Unauthorized
> +
> +But if we use Karl, the mirror listing admin's, webservice, we can update the

s/admin's,/admin's

> owner.
> +
> + >>> response = karl_webservice.patch(
> + ... canonical_archive['self_link'], 'application/json', dumps(patch))
> + >>> print response.getheader('status')
> + 209 Content Returned
> +
> + >>> patched_canonical_archive = response.jsonBody()
> + >>> print patched_canonical_archive['owner_link']
> + http://.../~karl
> +
> +Some attributes are read-only via the API:
> +
> + >>> distros = webservice.get("/distros").jsonBody()
> + >>> distro = distros['entries'][3]
> + >>> debian = webservice.get(distro['self_link']).jsonBody()

I haven't checked, but is the above line necessary? you're getting distro['self_link'] which should be equivalent to distro? (ie. distro == debian?). Actually, you only seem to use debian['self_link'] below anyway (which should be equivalent to distro['self_link'] unless I'm missing something).

> + >>> patch = {
> + ... u'date_reviewed' : u'2010-02-04T17:19:16.424198+00:00',
> + ... u'distribution_link' : debian['self_link'],
> + ... u'enabled' : False,
> + ... u'reviewer_link' : karl['self_link']
> + ... }
> + >>> response = karl_webservice.patch(
> + ... canonical_releases['self_link'], 'application/json',
> dum...

Read more...

review: Approve (code)
Revision history for this message
Jonathan Davies (jpds) wrote :

The branch failed a test on distribution.txt, as I was just testing distribution-mirror.txt. The last commit fixes this and this is the diff:

=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-01 23:17:27 +0000
+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-05 18:18:28 +0000
@@ -124,18 +124,20 @@
     >>> pprint_entry(canonical_releases)
     content: u'CD Image'
     date_created: u'2006-10-16T18:31:43.434567+00:00'
+ date_reviewed: None
     description: None
     displayname: None
     distribution_link: u'http://.../ubuntu'
     enabled: True
     ftp_base_url: None
- has_ftp_or_rsync_base_url: False
     http_base_url: u'http://releases.ubuntu.com/'
     name: u'canonical-releases'
     official_candidate: True
     owner_link: u'http://.../~mark'
+ reviewer_link: None
     resource_type_link: u'http://.../#distribution_mirror'
     rsync_base_url: None
     self_link: u'http://.../ubuntu/+mirror/canonical-releases'
     speed: u'100 Mbps'
     status: u'Official'
+ whiteboard: None

Revision history for this message
Jonathan Davies (jpds) wrote :

>> >>> is_official_mirror =
>> webservice.named_get(canonical_releases['self_link'],
>> ... 'isOfficial').jsonBody()
>> >>> print is_official_mirror
>> - True
>> + False
>
> I can't see anything that should have changed the expected value of this test? Why did it
> change here?

For the record:

<noodles> Also, I can't see why the expected value of is_official_mirror changed at the end of your diff?
<jpds> 13:47:01 <jpds> is_o_m> IT changes because I patch it with: u'status' : 'Unofficial'

Revision history for this message
Brad Crittenden (bac) wrote :

Your additional test changes look good. Thanks.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/interfaces/distributionmirror.py'
2--- lib/lp/registry/interfaces/distributionmirror.py 2010-02-01 23:17:27 +0000
3+++ lib/lp/registry/interfaces/distributionmirror.py 2010-02-05 18:56:19 +0000
4@@ -287,17 +287,19 @@
5
6 id = Int(title=_('The unique id'), required=True, readonly=True)
7 owner = exported(PublicPersonChoice(
8- title=_('Owner'), required=False, readonly=True,
9- vocabulary='ValidOwner'))
10- reviewer = PublicPersonChoice(
11- title=_('Reviewer'), required=False, readonly=False,
12- vocabulary='ValidPersonOrTeam')
13+ title=_('Owner'), readonly=False, vocabulary='ValidOwner',
14+ required=True, description=_(
15+ "The person who is set as the current administrator of this mirror.")))
16+ reviewer = exported(PublicPersonChoice(
17+ title=_('Reviewer'), required=False, readonly=True,
18+ vocabulary='ValidPersonOrTeam', description=_(
19+ "The person who last reviewed this mirror.")))
20 distribution = exported(
21 Reference(
22 Interface,
23 # Really IDistribution, circular import fixed in
24 # _schema_circular_imports.
25- title=_("Distribution"), required=True,
26+ title=_("Distribution"), required=True, readonly=True,
27 description=_("The distribution that is mirrored")))
28 name = exported(DistributionMirrorNameField(
29 title=_('Name'), required=True, readonly=False,
30@@ -325,7 +327,7 @@
31 description=_('e.g.: rsync://archive.ubuntu.com/ubuntu/')))
32 enabled = exported(Bool(
33 title=_('This mirror was probed successfully.'),
34- required=False, readonly=False, default=False))
35+ required=False, readonly=True, default=False))
36 speed = exported(Choice(
37 title=_('Link Speed'), required=True, readonly=False,
38 vocabulary=MirrorSpeed))
39@@ -344,7 +346,8 @@
40 required=False, readonly=False, default=True))
41 status = exported(Choice(
42 title=_('Status'), required=True, readonly=False,
43- vocabulary=MirrorStatus))
44+ vocabulary=MirrorStatus,
45+ description=_("The current status of a mirror's registration.")))
46
47 title = Attribute('The title of this mirror')
48 cdimage_series = Attribute(
49@@ -355,17 +358,20 @@
50 last_probe_record = Attribute(
51 'The last MirrorProbeRecord for this mirror.')
52 all_probe_records = Attribute('All MirrorProbeRecords for this mirror.')
53- has_ftp_or_rsync_base_url = exported(Bool(
54- title=_('Does this mirror have a ftp or rsync base URL?')))
55+ has_ftp_or_rsync_base_url = Bool(
56+ title=_('Does this mirror have a FTP or Rsync base URL?'))
57 base_url = Attribute('The HTTP or FTP base URL of this mirror')
58 date_created = exported(Datetime(
59- title=_('Date Created'), required=True, readonly=True))
60- date_reviewed = Datetime(
61- title=_('Date reviewed'), required=False, readonly=False)
62- whiteboard = Whiteboard(
63- title=_('Whiteboard'), required=False,
64+ title=_('Date Created'), required=True, readonly=True,
65+ description=_("The date on which this mirror was registered.")))
66+ date_reviewed = exported(Datetime(
67+ title=_('Date reviewed'), required=False, readonly=True,
68+ description=_(
69+ "The date on which this mirror was last reviewed by a mirror admin.")))
70+ whiteboard = exported(Whiteboard(
71+ title=_('Whiteboard'), required=False, readonly=False,
72 description=_("Notes on the current status of the mirror (only "
73- "visible to admins and the mirror's registrant)."))
74+ "visible to admins and the mirror's registrant).")))
75
76 @invariant
77 def mirrorMustHaveHTTPOrFTPURL(mirror):
78
79=== modified file 'lib/lp/registry/stories/webservice/xx-distribution-mirror.txt'
80--- lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-01 23:17:27 +0000
81+++ lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-05 18:56:19 +0000
82@@ -13,21 +13,23 @@
83 >>> pprint_entry(canonical_archive_json)
84 content: u'Archive'
85 date_created: u'2006-10-16T18:31:43.434567+00:00'
86+ date_reviewed: None
87 description: None
88 displayname: None
89 distribution_link: u'http://.../ubuntu'
90 enabled: True
91 ftp_base_url: None
92- has_ftp_or_rsync_base_url: False
93 http_base_url: u'http://archive.ubuntu.com/ubuntu/'
94 name: u'canonical-archive'
95 official_candidate: True
96 owner_link: u'http://.../~mark'
97 resource_type_link: u'http://.../#distribution_mirror'
98+ reviewer_link: None
99 rsync_base_url: None
100 self_link: u'http://.../ubuntu/+mirror/canonical-archive'
101 speed: u'100 Mbps'
102 status: u'Official'
103+ whiteboard: None
104
105 And CD image mirrors:
106
107@@ -37,21 +39,104 @@
108 >>> pprint_entry(canonical_releases_json)
109 content: u'CD Image'
110 date_created: u'2006-10-16T18:31:43.434567+00:00'
111+ date_reviewed: None
112 description: None
113 displayname: None
114 distribution_link: u'http://.../ubuntu'
115 enabled: True
116 ftp_base_url: None
117- has_ftp_or_rsync_base_url: False
118 http_base_url: u'http://releases.ubuntu.com/'
119 name: u'canonical-releases'
120 official_candidate: True
121 owner_link: u'http://.../~mark'
122 resource_type_link: u'http://.../#distribution_mirror'
123+ reviewer_link: None
124 rsync_base_url: None
125 self_link: u'http://.../ubuntu/+mirror/canonical-releases'
126 speed: u'100 Mbps'
127 status: u'Official'
128+ whiteboard: None
129+
130+= Security checks =
131+
132+People who are not mirror listing admins or the mirrors registrar may not
133+change the owner's of mirrors:
134+
135+ >>> from canonical.launchpad.testing.pages import webservice_for_person
136+ >>> from canonical.launchpad.webapp.interfaces import OAuthPermission
137+ >>> from zope.component import getUtility
138+ >>> from lp.registry.interfaces.person import IPersonSet
139+ >>> from simplejson import dumps
140+ >>> login(ANONYMOUS)
141+ >>> karl_db = getUtility(IPersonSet).getByName('karl')
142+ >>> test_db = getUtility(IPersonSet).getByName('name12')
143+ >>> karl_webservice = webservice_for_person(karl_db,
144+ ... permission=OAuthPermission.WRITE_PUBLIC)
145+ >>> test_webservice = webservice_for_person(test_db,
146+ ... permission=OAuthPermission.WRITE_PUBLIC)
147+ >>> logout()
148+ >>> karl = webservice.get("/~karl").jsonBody()
149+ >>> patch = {
150+ ... u'owner_link': karl['self_link']
151+ ... }
152+
153+Now trying to set the owner using Sample Person's webservice is not authorized.
154+
155+ >>> response = test_webservice.patch(
156+ ... canonical_archive['self_link'], 'application/json', dumps(patch))
157+ >>> print response.getheader('status')
158+ 401 Unauthorized
159+
160+But if we use Karl, the mirror listing admin's, webservice, we can update the owner.
161+
162+ >>> response = karl_webservice.patch(
163+ ... canonical_archive['self_link'], 'application/json', dumps(patch))
164+ >>> print response.getheader('status')
165+ 209 Content Returned
166+
167+ >>> patched_canonical_archive = response.jsonBody()
168+ >>> print patched_canonical_archive['owner_link']
169+ http://.../~karl
170+
171+Some attributes are read-only via the API:
172+
173+ >>> distros = webservice.get("/distros").jsonBody()
174+ >>> debian_distro = distros['entries'][3]
175+ >>> patch = {
176+ ... u'date_reviewed' : u'2010-02-04T17:19:16.424198+00:00',
177+ ... u'distribution_link' : debian_distro['self_link'],
178+ ... u'enabled' : False,
179+ ... u'reviewer_link' : karl['self_link']
180+ ... }
181+ >>> response = karl_webservice.patch(
182+ ... canonical_releases['self_link'], 'application/json', dumps(patch))
183+ >>> print response
184+ HTTP/1.1 400 Bad Request
185+ Status: 400 Bad Request
186+ ...
187+ enabled: You tried to modify a read-only attribute.
188+ date_reviewed: You tried to modify a read-only attribute.
189+ reviewer_link: You tried to modify a read-only attribute.
190+ distribution_link: You tried to modify a read-only attribute.
191+
192+While others can be set with the appropriate authorization:
193+
194+ >>> patch = {
195+ ... u'status' : 'Unofficial',
196+ ... u'whiteboard' : u'This mirror is too shiny to be true'
197+ ... }
198+ >>> response = test_webservice.patch(
199+ ... canonical_releases['self_link'], 'application/json', dumps(patch))
200+ >>> print response.getheader('status')
201+ 401 Unauthorized
202+
203+ >>> response = karl_webservice.patch(
204+ ... canonical_releases['self_link'], 'application/json', dumps(patch)).jsonBody()
205+ >>> pprint_entry(response)
206+ content: u'CD Image'
207+ ...
208+ status: u'Unofficial'
209+ whiteboard: u'This mirror is too shiny to be true'
210
211 = Distribution Mirror Custom Operations =
212
213@@ -63,7 +148,7 @@
214 >>> is_official_mirror = webservice.named_get(canonical_releases['self_link'],
215 ... 'isOfficial').jsonBody()
216 >>> print is_official_mirror
217- True
218+ False
219
220 "getOverallFreshness" returns the freshness of the mirror determined by the
221 mirror prober from the mirror's last probe.
222
223=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
224--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-01 23:17:27 +0000
225+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-05 18:56:19 +0000
226@@ -124,18 +124,20 @@
227 >>> pprint_entry(canonical_releases)
228 content: u'CD Image'
229 date_created: u'2006-10-16T18:31:43.434567+00:00'
230+ date_reviewed: None
231 description: None
232 displayname: None
233 distribution_link: u'http://.../ubuntu'
234 enabled: True
235 ftp_base_url: None
236- has_ftp_or_rsync_base_url: False
237 http_base_url: u'http://releases.ubuntu.com/'
238 name: u'canonical-releases'
239 official_candidate: True
240 owner_link: u'http://.../~mark'
241 resource_type_link: u'http://.../#distribution_mirror'
242+ reviewer_link: None
243 rsync_base_url: None
244 self_link: u'http://.../ubuntu/+mirror/canonical-releases'
245 speed: u'100 Mbps'
246 status: u'Official'
247+ whiteboard: None