Merge lp:~stevenk/launchpad/expose-iarchive-newauth into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 11057
Proposed branch: lp:~stevenk/launchpad/expose-iarchive-newauth
Merge into: lp:launchpad
Prerequisite: lp:~stevenk/launchpad/subscribers-can-view-p3as
Diff against target: 212 lines (+83/-43)
5 files modified
lib/lp/soyuz/doc/archiveauthtoken.txt (+1/-8)
lib/lp/soyuz/interfaces/archive.py (+28/-19)
lib/lp/soyuz/model/archive.py (+20/-16)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+10/-0)
lib/lp/soyuz/tests/test_archive.py (+24/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/expose-iarchive-newauth
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) code Approve
Review via email: mp+28090@code.launchpad.net

Commit message

Allow subscribers to view P3As, but not +packages. Add IArchive.getPrivateSourcesList(), and expose it via the API.

Description of the change

This branch implements two new functions in IArchive -- getAuthToken(), and getPrivateSourcesList(), the latter of which is exported via the API.

getAuthToken() simply checks if the user is subscribed to an archive, and returns an authtoken if they have one. If they don't, it returns None.

getPrivateSourcesList() returns a sources.list entry for the *private* PPA, including username and authtoken information, and will generate a new authtoken if required.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

As William mentions on IRC, it doesn't seem desirable to let people retrieve private sources list lines for other people using the API. You probably want to force person to always be REQUEST_USER.

Not as important, but it would be nice to test that demonstrates newAuthToken does indeed raise ArchiveNotPrivate if the archive is not private.

And to nitpick: there's a missing empty line above the definition of TestArchiveTokens.

review: Needs Fixing (code)
Revision history for this message
Jelmer Vernooij (jelmer) :
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/soyuz/doc/archiveauthtoken.txt'
2--- lib/lp/soyuz/doc/archiveauthtoken.txt 2010-04-28 16:28:02 +0000
3+++ lib/lp/soyuz/doc/archiveauthtoken.txt 2010-06-24 14:20:58 +0000
4@@ -21,8 +21,7 @@
5 possible if there is already a valid subscription for the user for
6 that archive.
7
8-First, login as joe and try to create a token for ourselves, even
9-though we do not yet have a subscription:
10+Create Brad, and his team:
11
12 >>> login("admin@canonical.com")
13 >>> bradsmith = factory.makePerson(
14@@ -30,12 +29,6 @@
15 ... email="brad@example.com")
16 >>> teambrad = factory.makeTeam(
17 ... owner=bradsmith, displayname="Team Brad", name='teambrad')
18- >>> login("brad@example.com")
19- >>> new_token = joe_private_ppa.newAuthToken(bradsmith)
20- Traceback (most recent call last):
21- ...
22- Unauthorized: You do not have a subscription for
23- PPA for Joe Smith.
24
25 Create a subscription for Team Brad to joe's archive:
26
27
28=== modified file 'lib/lp/soyuz/interfaces/archive.py'
29--- lib/lp/soyuz/interfaces/archive.py 2010-06-16 12:07:31 +0000
30+++ lib/lp/soyuz/interfaces/archive.py 2010-06-24 14:20:58 +0000
31@@ -1,4 +1,4 @@
32-# Copyright 2009 Canonical Ltd. This software is licensed under the
33+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
34 # GNU Affero General Public License version 3 (see the file LICENSE).
35
36 # pylint: disable-msg=E0211,E0213
37@@ -620,24 +620,6 @@
38 :return The new `IPackageCopyRequest`
39 """
40
41- # XXX: noodles 2009-03-02 bug=336779: This should be moved into
42- # IArchiveView once the archive permissions are updated to grant
43- # IArchiveView to archive subscribers.
44- def newAuthToken(person, token=None, date_created=None):
45- """Create a new authorisation token.
46-
47- XXX: noodles 2009-03-12 bug=341600 This method should not be exposed
48- through the API as we do not yet check that the callsite has
49- launchpad.Edit on the person.
50-
51- :param person: An IPerson whom this token is for
52- :param token: Optional unicode text to use as the token. One will be
53- generated if not given
54- :param date_created: Optional, defaults to now
55-
56- :return: A new IArchiveAuthToken
57- """
58-
59 @operation_parameters(
60 person=Reference(schema=IPerson),
61 # Really IPackageset, corrected in _schema_circular_imports to avoid
62@@ -1103,6 +1085,33 @@
63 :return: A dictionary of filenames and SHA1s.
64 """
65
66+ def getAuthToken(person):
67+ """Returns an IArchiveAuthToken for the archive in question for
68+ IPerson provided.
69+
70+ :return: A IArchiveAuthToken, or None if the user has none.
71+ """
72+
73+ def newAuthToken(person, token=None, date_created=None):
74+ """Create a new authorisation token.
75+
76+ :param person: An IPerson whom this token is for
77+ :param token: Optional unicode text to use as the token. One will be
78+ generated if not given
79+ :param date_created: Optional, defaults to now
80+
81+ :return: A new IArchiveAuthToken
82+ """
83+
84+ @call_with(person=REQUEST_USER)
85+ @export_write_operation()
86+ def getPrivateSourcesList(person):
87+ """Get a text line that is suitable to be used for a sources.list
88+ entry.
89+
90+ It will create a new IArchiveAuthToken if one doesn't already exist.
91+ """
92+
93 class IArchiveAppend(Interface):
94 """Archive interface for operations restricted by append privilege."""
95
96
97=== modified file 'lib/lp/soyuz/model/archive.py'
98--- lib/lp/soyuz/model/archive.py 2010-06-16 12:07:31 +0000
99+++ lib/lp/soyuz/model/archive.py 2010-06-24 14:20:58 +0000
100@@ -1389,30 +1389,26 @@
101 # Perform the copy, may raise CannotCopy.
102 do_copy(sources, self, series, pocket, include_binaries)
103
104+ def getAuthToken(self, person):
105+ """See `IArchive`."""
106+
107+ token_set = getUtility(IArchiveAuthTokenSet)
108+ return token_set.getActiveTokenForArchiveAndPerson(self, person)
109+
110 def newAuthToken(self, person, token=None, date_created=None):
111 """See `IArchive`."""
112
113+ # Bail if the archive isn't private
114+ if not self.private:
115+ raise ArchiveNotPrivate("Archive must be private.")
116+
117 # Tokens can only be created for individuals.
118 if person.is_team:
119 raise NoTokensForTeams(
120 "Subscription tokens can be created for individuals only.")
121
122- # First, ensure that a current subscription exists for the
123- # person and archive:
124- # XXX: noodles 2009-03-02 bug=336779: This can be removed once
125- # newAuthToken() is moved into IArchiveView.
126- subscription_set = getUtility(IArchiveSubscriberSet)
127- subscriptions = subscription_set.getBySubscriber(person, archive=self)
128- if subscriptions.count() == 0:
129- raise Unauthorized(
130- "You do not have a subscription for %s." % self.displayname)
131-
132- # Second, ensure that the current subscription does not already
133- # have a token:
134- token_set = getUtility(IArchiveAuthTokenSet)
135- previous_token = token_set.getActiveTokenForArchiveAndPerson(
136- self, person)
137- if previous_token:
138+ # Ensure that the current subscription does not already have a token
139+ if self.getAuthToken(person) is not None:
140 raise ArchiveSubscriptionError(
141 "%s already has a token for %s." % (
142 person.displayname, self.displayname))
143@@ -1430,6 +1426,14 @@
144 store.add(archive_auth_token)
145 return archive_auth_token
146
147+ def getPrivateSourcesList(self, person):
148+ """See `IArchive`."""
149+
150+ token = self.getAuthToken(person)
151+ if token is None:
152+ token = self.newAuthToken(person)
153+ return token.archive_url
154+
155 def newSubscription(self, subscriber, registrant, date_expires=None,
156 description=None):
157 """See `IArchive`."""
158
159=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
160--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-05-27 15:20:24 +0000
161+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-06-24 14:20:58 +0000
162@@ -886,6 +886,16 @@
163 >>> print response.getHeader('Location')
164 http://.../~cprov/+archive/p3a/+subscriptions/mark
165
166+We can print the sources.list entry for the archive, which will include an
167+AuthToken:
168+
169+ >>> sources_response = webservice.named_post(
170+ ... cprov_private_ppa['self_link'], 'getPrivateSourcesList')
171+ >>> print sources_response
172+ HTTP/1.1 200 Ok
173+ ...
174+ "http://salgado:...@private-ppa.launchpad.dev/cprov/p3a/ubuntu"
175+
176 We publish a subset of the IArchiveSubscriber attributes.
177
178 >>> new_subscription = cprov_webservice.get(
179
180=== modified file 'lib/lp/soyuz/tests/test_archive.py'
181--- lib/lp/soyuz/tests/test_archive.py 2010-06-15 13:31:51 +0000
182+++ lib/lp/soyuz/tests/test_archive.py 2010-06-24 14:20:58 +0000
183@@ -789,6 +789,30 @@
184 self.archive, self.arm).count())
185 self.assertFalse(self.archive.arm_builds_allowed)
186
187+class TestArchiveTokens(TestCaseWithFactory):
188+ layer = LaunchpadZopelessLayer
189+
190+ def setUp(self):
191+ super(TestArchiveTokens, self).setUp()
192+ owner = self.factory.makePerson()
193+ self.private_ppa = self.factory.makeArchive(owner=owner)
194+ self.private_ppa.buildd_secret = 'blah'
195+ self.private_ppa.private = True
196+ self.joe = self.factory.makePerson(name='joe')
197+ self.private_ppa.newSubscription(self.joe, owner)
198+
199+ def test_getAuthToken_with_no_token(self):
200+ token = self.private_ppa.getAuthToken(self.joe)
201+ self.assertEqual(token, None)
202+
203+ def test_getAuthToken_with_token(self):
204+ token = self.private_ppa.newAuthToken(self.joe)
205+ self.assertEqual(self.private_ppa.getAuthToken(self.joe), token)
206+
207+ def test_getPrivateSourcesList(self):
208+ url = self.private_ppa.getPrivateSourcesList(self.joe)
209+ token = self.private_ppa.getAuthToken(self.joe)
210+ self.assertEqual(token.archive_url, url)
211
212 class TestArchivePrivacySwitching(TestCaseWithFactory):
213