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
=== modified file 'lib/lp/soyuz/doc/archiveauthtoken.txt'
--- lib/lp/soyuz/doc/archiveauthtoken.txt 2010-04-28 16:28:02 +0000
+++ lib/lp/soyuz/doc/archiveauthtoken.txt 2010-06-24 14:20:58 +0000
@@ -21,8 +21,7 @@
21possible if there is already a valid subscription for the user for21possible if there is already a valid subscription for the user for
22that archive.22that archive.
2323
24First, login as joe and try to create a token for ourselves, even24Create Brad, and his team:
25though we do not yet have a subscription:
2625
27 >>> login("admin@canonical.com")26 >>> login("admin@canonical.com")
28 >>> bradsmith = factory.makePerson(27 >>> bradsmith = factory.makePerson(
@@ -30,12 +29,6 @@
30 ... email="brad@example.com")29 ... email="brad@example.com")
31 >>> teambrad = factory.makeTeam(30 >>> teambrad = factory.makeTeam(
32 ... owner=bradsmith, displayname="Team Brad", name='teambrad')31 ... owner=bradsmith, displayname="Team Brad", name='teambrad')
33 >>> login("brad@example.com")
34 >>> new_token = joe_private_ppa.newAuthToken(bradsmith)
35 Traceback (most recent call last):
36 ...
37 Unauthorized: You do not have a subscription for
38 PPA for Joe Smith.
3932
40Create a subscription for Team Brad to joe's archive:33Create a subscription for Team Brad to joe's archive:
4134
4235
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2010-06-16 12:07:31 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2010-06-24 14:20:58 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E0211,E02134# pylint: disable-msg=E0211,E0213
@@ -620,24 +620,6 @@
620 :return The new `IPackageCopyRequest`620 :return The new `IPackageCopyRequest`
621 """621 """
622622
623 # XXX: noodles 2009-03-02 bug=336779: This should be moved into
624 # IArchiveView once the archive permissions are updated to grant
625 # IArchiveView to archive subscribers.
626 def newAuthToken(person, token=None, date_created=None):
627 """Create a new authorisation token.
628
629 XXX: noodles 2009-03-12 bug=341600 This method should not be exposed
630 through the API as we do not yet check that the callsite has
631 launchpad.Edit on the person.
632
633 :param person: An IPerson whom this token is for
634 :param token: Optional unicode text to use as the token. One will be
635 generated if not given
636 :param date_created: Optional, defaults to now
637
638 :return: A new IArchiveAuthToken
639 """
640
641 @operation_parameters(623 @operation_parameters(
642 person=Reference(schema=IPerson),624 person=Reference(schema=IPerson),
643 # Really IPackageset, corrected in _schema_circular_imports to avoid625 # Really IPackageset, corrected in _schema_circular_imports to avoid
@@ -1103,6 +1085,33 @@
1103 :return: A dictionary of filenames and SHA1s.1085 :return: A dictionary of filenames and SHA1s.
1104 """1086 """
11051087
1088 def getAuthToken(person):
1089 """Returns an IArchiveAuthToken for the archive in question for
1090 IPerson provided.
1091
1092 :return: A IArchiveAuthToken, or None if the user has none.
1093 """
1094
1095 def newAuthToken(person, token=None, date_created=None):
1096 """Create a new authorisation token.
1097
1098 :param person: An IPerson whom this token is for
1099 :param token: Optional unicode text to use as the token. One will be
1100 generated if not given
1101 :param date_created: Optional, defaults to now
1102
1103 :return: A new IArchiveAuthToken
1104 """
1105
1106 @call_with(person=REQUEST_USER)
1107 @export_write_operation()
1108 def getPrivateSourcesList(person):
1109 """Get a text line that is suitable to be used for a sources.list
1110 entry.
1111
1112 It will create a new IArchiveAuthToken if one doesn't already exist.
1113 """
1114
1106class IArchiveAppend(Interface):1115class IArchiveAppend(Interface):
1107 """Archive interface for operations restricted by append privilege."""1116 """Archive interface for operations restricted by append privilege."""
11081117
11091118
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-06-16 12:07:31 +0000
+++ lib/lp/soyuz/model/archive.py 2010-06-24 14:20:58 +0000
@@ -1389,30 +1389,26 @@
1389 # Perform the copy, may raise CannotCopy.1389 # Perform the copy, may raise CannotCopy.
1390 do_copy(sources, self, series, pocket, include_binaries)1390 do_copy(sources, self, series, pocket, include_binaries)
13911391
1392 def getAuthToken(self, person):
1393 """See `IArchive`."""
1394
1395 token_set = getUtility(IArchiveAuthTokenSet)
1396 return token_set.getActiveTokenForArchiveAndPerson(self, person)
1397
1392 def newAuthToken(self, person, token=None, date_created=None):1398 def newAuthToken(self, person, token=None, date_created=None):
1393 """See `IArchive`."""1399 """See `IArchive`."""
13941400
1401 # Bail if the archive isn't private
1402 if not self.private:
1403 raise ArchiveNotPrivate("Archive must be private.")
1404
1395 # Tokens can only be created for individuals.1405 # Tokens can only be created for individuals.
1396 if person.is_team:1406 if person.is_team:
1397 raise NoTokensForTeams(1407 raise NoTokensForTeams(
1398 "Subscription tokens can be created for individuals only.")1408 "Subscription tokens can be created for individuals only.")
13991409
1400 # First, ensure that a current subscription exists for the1410 # Ensure that the current subscription does not already have a token
1401 # person and archive:1411 if self.getAuthToken(person) is not None:
1402 # XXX: noodles 2009-03-02 bug=336779: This can be removed once
1403 # newAuthToken() is moved into IArchiveView.
1404 subscription_set = getUtility(IArchiveSubscriberSet)
1405 subscriptions = subscription_set.getBySubscriber(person, archive=self)
1406 if subscriptions.count() == 0:
1407 raise Unauthorized(
1408 "You do not have a subscription for %s." % self.displayname)
1409
1410 # Second, ensure that the current subscription does not already
1411 # have a token:
1412 token_set = getUtility(IArchiveAuthTokenSet)
1413 previous_token = token_set.getActiveTokenForArchiveAndPerson(
1414 self, person)
1415 if previous_token:
1416 raise ArchiveSubscriptionError(1412 raise ArchiveSubscriptionError(
1417 "%s already has a token for %s." % (1413 "%s already has a token for %s." % (
1418 person.displayname, self.displayname))1414 person.displayname, self.displayname))
@@ -1430,6 +1426,14 @@
1430 store.add(archive_auth_token)1426 store.add(archive_auth_token)
1431 return archive_auth_token1427 return archive_auth_token
14321428
1429 def getPrivateSourcesList(self, person):
1430 """See `IArchive`."""
1431
1432 token = self.getAuthToken(person)
1433 if token is None:
1434 token = self.newAuthToken(person)
1435 return token.archive_url
1436
1433 def newSubscription(self, subscriber, registrant, date_expires=None,1437 def newSubscription(self, subscriber, registrant, date_expires=None,
1434 description=None):1438 description=None):
1435 """See `IArchive`."""1439 """See `IArchive`."""
14361440
=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-05-27 15:20:24 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-06-24 14:20:58 +0000
@@ -886,6 +886,16 @@
886 >>> print response.getHeader('Location')886 >>> print response.getHeader('Location')
887 http://.../~cprov/+archive/p3a/+subscriptions/mark887 http://.../~cprov/+archive/p3a/+subscriptions/mark
888888
889We can print the sources.list entry for the archive, which will include an
890AuthToken:
891
892 >>> sources_response = webservice.named_post(
893 ... cprov_private_ppa['self_link'], 'getPrivateSourcesList')
894 >>> print sources_response
895 HTTP/1.1 200 Ok
896 ...
897 "http://salgado:...@private-ppa.launchpad.dev/cprov/p3a/ubuntu"
898
889We publish a subset of the IArchiveSubscriber attributes.899We publish a subset of the IArchiveSubscriber attributes.
890900
891 >>> new_subscription = cprov_webservice.get(901 >>> new_subscription = cprov_webservice.get(
892902
=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py 2010-06-15 13:31:51 +0000
+++ lib/lp/soyuz/tests/test_archive.py 2010-06-24 14:20:58 +0000
@@ -789,6 +789,30 @@
789 self.archive, self.arm).count())789 self.archive, self.arm).count())
790 self.assertFalse(self.archive.arm_builds_allowed)790 self.assertFalse(self.archive.arm_builds_allowed)
791791
792class TestArchiveTokens(TestCaseWithFactory):
793 layer = LaunchpadZopelessLayer
794
795 def setUp(self):
796 super(TestArchiveTokens, self).setUp()
797 owner = self.factory.makePerson()
798 self.private_ppa = self.factory.makeArchive(owner=owner)
799 self.private_ppa.buildd_secret = 'blah'
800 self.private_ppa.private = True
801 self.joe = self.factory.makePerson(name='joe')
802 self.private_ppa.newSubscription(self.joe, owner)
803
804 def test_getAuthToken_with_no_token(self):
805 token = self.private_ppa.getAuthToken(self.joe)
806 self.assertEqual(token, None)
807
808 def test_getAuthToken_with_token(self):
809 token = self.private_ppa.newAuthToken(self.joe)
810 self.assertEqual(self.private_ppa.getAuthToken(self.joe), token)
811
812 def test_getPrivateSourcesList(self):
813 url = self.private_ppa.getPrivateSourcesList(self.joe)
814 token = self.private_ppa.getAuthToken(self.joe)
815 self.assertEqual(token.archive_url, url)
792816
793class TestArchivePrivacySwitching(TestCaseWithFactory):817class TestArchivePrivacySwitching(TestCaseWithFactory):
794818