← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/oauth-secret-split into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/oauth-secret-split into lp:launchpad with lp:~wgrant/launchpad/oauth-secret-checking as a prerequisite.

Commit message:
Change OAuth token generation APIs to return the secret as well as the token, as the token soon won't know the plaintext secret.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/oauth-secret-split/+merge/224762

Change OAuth token generation APIs to return the secret as well as the token, as the token soon won't know the plaintext secret.
-- 
https://code.launchpad.net/~wgrant/launchpad/oauth-secret-split/+merge/224762
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/oauth-secret-split into lp:launchpad.
=== modified file 'lib/lp/services/oauth/browser/__init__.py'
--- lib/lp/services/oauth/browser/__init__.py	2013-04-11 02:12:09 +0000
+++ lib/lp/services/oauth/browser/__init__.py	2014-06-27 08:22:29 +0000
@@ -46,7 +46,7 @@
 class JSONTokenMixin:
 
     def getJSONRepresentation(self, permissions, token=None,
-                              include_secret=False):
+                              secret=None):
         """Return a JSON representation of the authorization policy.
 
         This includes a description of some subset of OAuthPermission,
@@ -56,8 +56,8 @@
         if token is not None:
             structure['oauth_token'] = token.key
             structure['oauth_token_consumer'] = token.consumer.key
-            if include_secret:
-                structure['oauth_token_secret'] = token.secret
+            if secret is not None:
+                structure['oauth_token_secret'] = secret
         access_levels = [{
                 'value': permission.name,
                 'title': permission.title,
@@ -93,7 +93,7 @@
         if not check_oauth_signature(self.request, consumer, None):
             return u''
 
-        token = consumer.newRequestToken()
+        token, secret = consumer.newRequestToken()
         if self.request.headers.get('Accept') == HTTPResource.JSON_TYPE:
             # Don't show the client the DESKTOP_INTEGRATION access
             # level. If they have a legitimate need to use it, they'll
@@ -103,7 +103,7 @@
                 if (permission != OAuthPermission.DESKTOP_INTEGRATION)
                 ]
             return self.getJSONRepresentation(
-                permissions, token, include_secret=True)
+                permissions, token, secret=secret)
         return u'oauth_token=%s&oauth_token_secret=%s' % (
             token.key, token.secret)
 
@@ -442,7 +442,7 @@
                 'End-user refused to authorize request token.')
 
         try:
-            access_token = token.createAccessToken()
+            access_token, access_secret = token.createAccessToken()
         except OAuthValidationError as e:
             return self._set_status_and_error(e)
 
@@ -450,5 +450,5 @@
         if access_token.context is not None:
             context_name = access_token.context.name
         body = u'oauth_token=%s&oauth_token_secret=%s&lp.context=%s' % (
-            access_token.key, access_token.secret, context_name)
+            access_token.key, access_secret, context_name)
         return body

=== modified file 'lib/lp/services/oauth/doc/oauth-pages.txt'
--- lib/lp/services/oauth/doc/oauth-pages.txt	2011-12-24 17:49:30 +0000
+++ lib/lp/services/oauth/doc/oauth-pages.txt	2014-06-27 08:22:29 +0000
@@ -17,7 +17,7 @@
     >>> consumer = getUtility(IOAuthConsumerSet).getByKey('launchpad-library')
     >>> root = getUtility(ILaunchpadRoot)
     >>> def get_view_with_fresh_token(form):
-    ...     token = consumer.newRequestToken()
+    ...     token, _ = consumer.newRequestToken()
     ...     form.update({'oauth_token': token.key})
     ...     request = LaunchpadTestRequest(form=form)
     ...     login('salgado@xxxxxxxxxx', request)

=== modified file 'lib/lp/services/oauth/doc/oauth.txt'
--- lib/lp/services/oauth/doc/oauth.txt	2014-01-30 15:04:06 +0000
+++ lib/lp/services/oauth/doc/oauth.txt	2014-06-27 08:22:29 +0000
@@ -18,7 +18,7 @@
 Here's a quick demonstration of the behaviour.
 
    >>> login('foo.bar@xxxxxxxxxxxxx')
-   >>> access_token = factory.makeOAuthAccessToken()
+   >>> access_token, _ = factory.makeOAuthAccessToken()
    >>> logout()
 
 - We can use a nonce.

=== modified file 'lib/lp/services/oauth/interfaces.py'
--- lib/lp/services/oauth/interfaces.py	2014-06-27 08:22:29 +0000
+++ lib/lp/services/oauth/interfaces.py	2014-06-27 08:22:29 +0000
@@ -87,10 +87,11 @@
         """Check if a secret is valid for this consumer."""
 
     def newRequestToken():
-        """Return a new `IOAuthRequestToken` with a random key and secret.
+        """Return a new `IOAuthRequestToken` and its random secret.
 
-        The other attributes of the token are supposed to be set whenever the
-        user logs into Launchpad and grants (or not) access to this consumer.
+        The key and secret are random, while the other attributes of the
+        token are supposed to be set whenever the user logs into
+        Launchpad and grants (or not) access to this consumer.
         """
 
     def getAccessToken(key):
@@ -272,6 +273,8 @@
     def createAccessToken():
         """Create an `IOAuthAccessToken` identical to this request token.
 
+        The new token and its secret are returned.
+
         After the access token is created, this one is deleted as it can't be
         used anymore.
 

=== modified file 'lib/lp/services/oauth/model.py'
--- lib/lp/services/oauth/model.py	2014-06-27 08:22:29 +0000
+++ lib/lp/services/oauth/model.py	2014-06-27 08:22:29 +0000
@@ -178,8 +178,8 @@
     def newRequestToken(self):
         """See `IOAuthConsumer`."""
         key, secret = create_token_key_and_secret(table=OAuthRequestToken)
-        return OAuthRequestToken(
-            consumer=self, key=key, secret=secret)
+        return (
+            OAuthRequestToken(consumer=self, key=key, secret=secret), secret)
 
     def getAccessToken(self, key):
         """See `IOAuthConsumer`."""
@@ -401,7 +401,7 @@
             "A new OAuth token consumer was enabled in Launchpad.")
 
         self.destroySelf()
-        return access_token
+        return access_token, secret
 
     @property
     def is_reviewed(self):

=== modified file 'lib/lp/services/oauth/stories/access-token.txt'
--- lib/lp/services/oauth/stories/access-token.txt	2011-12-24 17:49:30 +0000
+++ lib/lp/services/oauth/stories/access-token.txt	2014-06-27 08:22:29 +0000
@@ -13,7 +13,7 @@
     >>> from lp.testing import login, logout
     >>> login('salgado@xxxxxxxxxx')
     >>> consumer = getUtility(IOAuthConsumerSet).getByKey('foobar123451432')
-    >>> token = consumer.newRequestToken()
+    >>> token, secret = consumer.newRequestToken()
     >>> salgado = getUtility(IPersonSet).getByName('salgado')
     >>> token.review(salgado, OAuthPermission.WRITE_PUBLIC)
     >>> logout()
@@ -25,7 +25,7 @@
     ...     oauth_version='1.0',
     ...     oauth_token=token.key,
     ...     oauth_signature_method='PLAINTEXT',
-    ...     oauth_signature='&'.join([consumer.secret, token.secret]),
+    ...     oauth_signature='&'.join(['', secret]),
     ...     oauth_timestamp=time.time(),
     ...     oauth_nonce='4572616e48616d6d65724c61686176')
     >>> anon_browser.open(
@@ -48,7 +48,7 @@
 
     # Create a new request token, with firefox as its context, and review it.
     >>> login('salgado@xxxxxxxxxx')
-    >>> token = consumer.newRequestToken()
+    >>> token, secret = consumer.newRequestToken()
     >>> firefox = getUtility(IProductSet)['firefox']
     >>> token.review(salgado, OAuthPermission.WRITE_PUBLIC, context=firefox)
     >>> logout()
@@ -56,7 +56,7 @@
     # Exchange the request token for an access token.
     >>> data2 = data.copy()
     >>> data2['oauth_token'] = token.key
-    >>> data2['oauth_signature'] = '&'.join([consumer.secret, token.secret])
+    >>> data2['oauth_signature'] = '&'.join(['', secret])
     >>> anon_browser.open(
     ...     'http://launchpad.dev/+access-token', data=urlencode(data2))
     >>> print anon_browser.contents
@@ -65,10 +65,10 @@
 The consumer shall not attempt to exchange any given request token
 before it's been reviewed, though, or it'll get a 401 response.
 
-    >>> token = consumer.newRequestToken()
+    >>> token, secret = consumer.newRequestToken()
     >>> data2 = data.copy()
     >>> data2['oauth_token'] = token.key
-    >>> data2['oauth_signature'] = '&'.join([consumer.secret, token.secret])
+    >>> data2['oauth_signature'] = '&'.join(['', secret])
     >>> print http(r"""
     ... GET /+access-token?%s HTTP/1.1
     ... Host: launchpad.dev
@@ -80,7 +80,7 @@
 If the token is missing or the signature is wrong the response will
 also be 401.
 
-    >>> data2['oauth_signature'] = '&'.join(['foobar', token.secret])
+    >>> data2['oauth_signature'] = '&'.join(['foobar', secret])
     >>> print http(r"""
     ... GET /+access-token?%s HTTP/1.1
     ... Host: launchpad.dev
@@ -105,7 +105,7 @@
 exchanged for an access token.
 
     >>> token.review(salgado, OAuthPermission.UNAUTHORIZED)
-    >>> data2['oauth_signature'] = '&'.join([consumer.secret, token.secret])
+    >>> data2['oauth_signature'] = '&'.join(['', secret])
     >>> print http(r"""
     ... GET /+access-token?%s HTTP/1.1
     ... Host: launchpad.dev

=== modified file 'lib/lp/services/oauth/stories/authorize-token.txt'
--- lib/lp/services/oauth/stories/authorize-token.txt	2012-10-03 07:04:24 +0000
+++ lib/lp/services/oauth/stories/authorize-token.txt	2014-06-27 08:22:29 +0000
@@ -8,7 +8,7 @@
     >>> def request_token_for(consumer):
     ...     """Helper method to create a request token."""
     ...     login('salgado@xxxxxxxxxx')
-    ...     token = consumer.newRequestToken()
+    ...     token, _ = consumer.newRequestToken()
     ...     logout()
     ...     return token
 

=== modified file 'lib/lp/services/oauth/stories/managing-tokens.txt'
--- lib/lp/services/oauth/stories/managing-tokens.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/services/oauth/stories/managing-tokens.txt	2014-06-27 08:22:29 +0000
@@ -12,7 +12,7 @@
     >>> consumer = factory.makeOAuthConsumer(
     ...     "System-wide: Ubuntu (mycomputer)")
     >>> salgado = getUtility(IPersonSet).getByName('salgado')
-    >>> desktop_token = factory.makeOAuthAccessToken(
+    >>> desktop_token, _ = factory.makeOAuthAccessToken(
     ...     consumer, salgado, OAuthPermission.DESKTOP_INTEGRATION)
 
     # Create a request token, authorize it for READ_PRIVATE access,
@@ -111,11 +111,11 @@
     >>> from lp.registry.interfaces.product import IProductSet
     >>> from lp.services.oauth.interfaces import IOAuthConsumerSet
     >>> login('salgado@xxxxxxxxxx')
-    >>> token = getUtility(IOAuthConsumerSet).getByKey(
+    >>> token, _ = getUtility(IOAuthConsumerSet).getByKey(
     ...     'launchpad-library').newRequestToken()
     >>> token.review(salgado, OAuthPermission.WRITE_PUBLIC,
     ...              context=getUtility(IProductSet)['firefox'])
-    >>> access_token = token.createAccessToken()
+    >>> access_token, _ = token.createAccessToken()
     >>> logout()
     >>> my_browser.open('http://launchpad.dev/~salgado/+oauth-tokens')
     >>> print extract_text(find_tag_by_id(my_browser.contents, 'maincontent'))

=== modified file 'lib/lp/services/oauth/tests/test_tokens.py'
--- lib/lp/services/oauth/tests/test_tokens.py	2014-06-27 08:22:29 +0000
+++ lib/lp/services/oauth/tests/test_tokens.py	2014-06-27 08:22:29 +0000
@@ -15,7 +15,6 @@
 import pytz
 import transaction
 from zope.component import getUtility
-from zope.proxy import sameProxiedObjects
 from zope.security.interfaces import Unauthorized
 
 from lp.services.mail import stub
@@ -95,7 +94,7 @@
         self.tokens = getUtility(IOAuthRequestTokenSet)
 
     def test_getByKey(self):
-        token = self.consumer.newRequestToken()
+        token, _ = self.consumer.newRequestToken()
         self.assertEquals(token, self.tokens.getByKey(token.key))
 
     def test_getByKey_returns_none_for_unused_key(self):
@@ -106,21 +105,23 @@
     """Tests for OAuth request token objects."""
 
     def test_newRequestToken(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, secret = self.consumer.newRequestToken()
         verifyObject(IOAuthRequestToken, request_token)
+        self.assertIsInstance(secret, unicode)
+        self.assertEqual(request_token.secret, secret)
 
     def test_key_and_secret_automatically_generated(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, secret = self.consumer.newRequestToken()
         self.assertEqual(len(request_token.key), 20)
         self.assertEqual(len(request_token.secret), 80)
 
     def test_date_created(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         now = datetime.now(pytz.timezone('UTC'))
         self.assertTrue(request_token.date_created <= now)
 
     def test_new_token_is_not_reviewed(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         self.assertFalse(request_token.is_reviewed)
         self.assertEqual(None, request_token.person)
         self.assertEqual(None, request_token.date_reviewed)
@@ -132,12 +133,12 @@
         self.assertEqual(None, request_token.context)
 
     def test_getRequestToken(self):
-        token_1 = self.consumer.newRequestToken()
+        token_1, _ = self.consumer.newRequestToken()
         token_2 = self.consumer.getRequestToken(token_1.key)
         self.assertEqual(token_1, token_2)
 
     def test_getRequestToken_for_wrong_consumer_returns_none(self):
-        token_1 = self.consumer.newRequestToken()
+        token_1, _ = self.consumer.newRequestToken()
         consumer_2 = self.factory.makeOAuthConsumer()
         self.assertEquals(
             None, consumer_2.getRequestToken(token_1.key))
@@ -147,12 +148,12 @@
             None, self.consumer.getRequestToken("no-such-token"))
 
     def test_isSecretValid(self):
-        token = self.consumer.newRequestToken()
-        self.assertTrue(token.isSecretValid(token.secret))
-        self.assertFalse(token.isSecretValid(token.secret + 'a'))
+        token, secret = self.consumer.newRequestToken()
+        self.assertTrue(token.isSecretValid(secret))
+        self.assertFalse(token.isSecretValid(secret + 'a'))
 
     def test_token_review(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
 
         request_token.review(self.person, OAuthPermission.WRITE_PUBLIC)
         now = datetime.now(pytz.timezone('UTC'))
@@ -170,7 +171,7 @@
         self.assertEquals(request_token.date_expires, None)
 
     def test_token_review_as_unauthorized(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(self.person, OAuthPermission.UNAUTHORIZED)
 
         # This token has been reviewed, but it may not be used for any
@@ -182,7 +183,7 @@
     def test_review_with_expiration_date(self):
         # A request token may be associated with an expiration date
         # upon review.
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(
             self.person, OAuthPermission.WRITE_PUBLIC,
             date_expires=self.in_a_while)
@@ -196,7 +197,7 @@
         #
         # Setting a request token's date_expires to a date in the past
         # is not a good idea, but it won't expire the request token.
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(
             self.person, OAuthPermission.WRITE_PUBLIC,
             date_expires=self.a_long_time_ago)
@@ -205,7 +206,7 @@
 
     def _reviewed_token_for_context(self, context_factory):
         """Create and review a request token with a given context."""
-        token = self.consumer.newRequestToken()
+        token, _ = self.consumer.newRequestToken()
         name = self.factory.getUniqueString('context')
         context = context_factory(name)
         token.review(
@@ -269,17 +270,19 @@
         # a) to show how a request token is exchanged for an access
         # token, b) acquire a reference to the request token that was
         # used to create the access token.
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(self.person, OAuthPermission.WRITE_PRIVATE)
-        access_token = request_token.createAccessToken()
-        return request_token, access_token
+        access_token, access_secret = request_token.createAccessToken()
+        return request_token, access_token, access_secret
 
     def test_exchange_request_token_for_access_token(self):
         # Make sure the basic exchange of request token for access
         # token works.
-        request_token, access_token = (
+        request_token, access_token, access_secret = (
             self._exchange_request_token_for_access_token())
         verifyObject(IOAuthAccessToken, access_token)
+        self.assertIsInstance(access_secret, unicode)
+        self.assertEqual(access_token.secret, access_secret)
 
         # Make sure the security notification email went out that the new
         # token was created.
@@ -289,7 +292,7 @@
         self.assertIn('@example.com', to_addr[0])
 
     def test_access_token_inherits_data_fields_from_request_token(self):
-        request_token, access_token = (
+        request_token, access_token, _ = (
             self._exchange_request_token_for_access_token())
 
         self.assertEquals(request_token.consumer, access_token.consumer)
@@ -309,30 +312,30 @@
         # Make sure that specific fields like context and expiration
         # date are passed down from request token to access token.
         context = self.factory.makeProduct()
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(
             self.person, OAuthPermission.WRITE_PRIVATE,
             context=context, date_expires=self.in_a_while)
-        access_token = request_token.createAccessToken()
+        access_token, _ = request_token.createAccessToken()
         self.assertEquals(request_token.context, access_token.context)
         self.assertEquals(
             request_token.date_expires, access_token.date_expires)
 
     def test_request_token_disappears_when_exchanged(self):
-        request_token, access_token = (
+        request_token, access_token, _ = (
             self._exchange_request_token_for_access_token())
         self.assertEquals(
             None, self.consumer.getRequestToken(request_token.key))
 
     def test_cant_exchange_unreviewed_request_token(self):
         # An unreviewed request token cannot be exchanged for an access token.
-        token = self.consumer.newRequestToken()
+        token, _ = self.consumer.newRequestToken()
         self.assertRaises(OAuthValidationError, token.createAccessToken)
 
     def test_cant_exchange_unauthorized_request_token(self):
         # A request token associated with the UNAUTHORIZED
         # OAuthPermission cannot be exchanged for an access token.
-        token = self.consumer.newRequestToken()
+        token, _ = self.consumer.newRequestToken()
         token.review(self.person, OAuthPermission.UNAUTHORIZED)
         self.assertRaises(OAuthValidationError, token.createAccessToken)
 
@@ -347,7 +350,7 @@
 
     def test_write_permission(self):
         """An access token can only be modified by its creator."""
-        access_token = self.factory.makeOAuthAccessToken()
+        access_token, _ = self.factory.makeOAuthAccessToken()
 
         def try_to_set():
             access_token.permission = AccessLevel.WRITE_PUBLIC
@@ -358,11 +361,11 @@
         try_to_set()
 
     def test_isSecretValid(self):
-        request_token = self.consumer.newRequestToken()
+        request_token, _ = self.consumer.newRequestToken()
         request_token.review(self.person, OAuthPermission.WRITE_PRIVATE)
-        token = request_token.createAccessToken()
-        self.assertTrue(token.isSecretValid(token.secret))
-        self.assertFalse(token.isSecretValid(token.secret + 'a'))
+        token, secret = request_token.createAccessToken()
+        self.assertTrue(token.isSecretValid(secret))
+        self.assertFalse(token.isSecretValid(secret + 'a'))
 
     def test_get_access_tokens_for_person(self):
         """It's possible to get a person's access tokens."""
@@ -375,7 +378,7 @@
     def test_expired_access_token_disappears_from_list(self):
         person = self.factory.makePerson()
         self.assertEquals(person.oauth_access_tokens.count(), 0)
-        access_token = self.factory.makeOAuthAccessToken(
+        access_token, _ = self.factory.makeOAuthAccessToken(
             self.consumer, person)
         self.assertEquals(person.oauth_access_tokens.count(), 1)
 
@@ -400,25 +403,9 @@
             self.context)
         self.assertEquals(person.oauth_access_tokens.count(), 1)
 
-    def test_oauth_access_token_for_retrieves_existing_token(self):
-        # If there's already a token for a
-        # user/consumer key/permission/context, it's retrieved.
-        person = self.factory.makePerson()
-        self.assertEquals(person.oauth_access_tokens.count(), 0)
-        access_token = oauth_access_token_for(
-            self.consumer.key, person, OAuthPermission.WRITE_PUBLIC,
-            self.context)
-        self.assertEquals(person.oauth_access_tokens.count(), 1)
-
-        access_token_2 = oauth_access_token_for(
-            access_token.consumer.key, access_token.person,
-            access_token.permission, access_token.context)
-        self.assertEquals(person.oauth_access_tokens.count(), 1)
-        self.assertTrue(sameProxiedObjects(access_token, access_token_2))
-
     def test_oauth_access_token_string_permission(self):
         """You can pass in a string instead of an OAuthPermission."""
-        access_token = oauth_access_token_for(
+        access_token, _ = oauth_access_token_for(
             self.consumer.key, self.person, 'WRITE_PUBLIC')
         self.assertEqual(access_token.permission, AccessLevel.WRITE_PUBLIC)
 

=== modified file 'lib/lp/services/webapp/doc/webapp-publication.txt'
--- lib/lp/services/webapp/doc/webapp-publication.txt	2014-02-27 08:39:44 +0000
+++ lib/lp/services/webapp/doc/webapp-publication.txt	2014-06-27 08:22:29 +0000
@@ -919,9 +919,9 @@
 
     >>> salgado = getUtility(IPersonSet).getByName('salgado')
     >>> consumer = getUtility(IOAuthConsumerSet).getByKey('foobar123451432')
-    >>> token = consumer.newRequestToken()
+    >>> token, _ = consumer.newRequestToken()
     >>> token.review(salgado, OAuthPermission.WRITE_PUBLIC)
-    >>> access_token = token.createAccessToken()
+    >>> access_token, access_secret = token.createAccessToken()
     >>> policy = getUtility(IStoreSelector).pop()
     >>> transaction.commit()
     >>> form = dict(
@@ -929,7 +929,7 @@
     ...     oauth_token=access_token.key,
     ...     oauth_version='1.0',
     ...     oauth_signature_method='PLAINTEXT',
-    ...     oauth_signature="&".join([consumer.secret, access_token.secret]),
+    ...     oauth_signature="&".join(['', access_secret]),
     ...     oauth_timestamp=time.time(),
     ...     oauth_nonce='1234572616e48616d6d65724c61686176')
     >>> test_request, publication = get_request_and_publication(
@@ -1107,16 +1107,16 @@
     >>> getUtility(IStoreSelector).push(MasterDatabasePolicy())
     >>> salgado = getUtility(IPersonSet).getByName('salgado')
     >>> consumer = getUtility(IOAuthConsumerSet).getByKey('foobar123451432')
-    >>> token = consumer.newRequestToken()
+    >>> token, _ = consumer.newRequestToken()
     >>> firefox = getUtility(IProductSet)['firefox']
     >>> token.review(salgado, OAuthPermission.WRITE_PUBLIC, context=firefox)
-    >>> access_token = token.createAccessToken()
+    >>> access_token, access_secret = token.createAccessToken()
     >>> form = dict(
     ...     oauth_consumer_key='foobar123451432',
     ...     oauth_token=access_token.key,
     ...     oauth_version='1.0',
     ...     oauth_signature_method='PLAINTEXT',
-    ...     oauth_signature="&".join([consumer.secret, access_token.secret]),
+    ...     oauth_signature="&".join(['', access_secret]),
     ...     oauth_timestamp=time.time(),
     ...     oauth_nonce='4572616e48616d6d65724c61686176')
     >>> policy = getUtility(IStoreSelector).pop()

=== modified file 'lib/lp/services/webapp/tests/test_authentication.py'
--- lib/lp/services/webapp/tests/test_authentication.py	2014-06-27 08:22:29 +0000
+++ lib/lp/services/webapp/tests/test_authentication.py	2014-06-27 08:22:29 +0000
@@ -60,35 +60,35 @@
         return LaunchpadTestRequest(form=form)
 
     def test_valid(self):
-        token = self.factory.makeOAuthAccessToken()
-        request = self.makeRequest('&%s' % token.secret)
+        token, secret = self.factory.makeOAuthAccessToken()
+        request = self.makeRequest('&%s' % secret)
         self.assertTrue(check_oauth_signature(request, token.consumer, token))
 
     def test_bad_secret(self):
-        token = self.factory.makeOAuthAccessToken()
-        request = self.makeRequest('&%slol' % token.secret)
+        token, secret = self.factory.makeOAuthAccessToken()
+        request = self.makeRequest('&%slol' % secret)
         self.assertFalse(check_oauth_signature(request, token.consumer, token))
         self.assertEqual(401, request.response.getStatus())
 
     def test_valid_no_token(self):
-        token = self.factory.makeOAuthAccessToken()
+        token, _ = self.factory.makeOAuthAccessToken()
         request = self.makeRequest('&')
         self.assertTrue(check_oauth_signature(request, token.consumer, None))
 
     def test_bad_secret_no_token(self):
-        token = self.factory.makeOAuthAccessToken()
+        token, _ = self.factory.makeOAuthAccessToken()
         request = self.makeRequest('&lol')
         self.assertFalse(check_oauth_signature(request, token.consumer, None))
         self.assertEqual(401, request.response.getStatus())
 
     def test_not_plaintext(self):
-        token = self.factory.makeOAuthAccessToken()
+        token, _ = self.factory.makeOAuthAccessToken()
         request = self.makeRequest('&lol', method='HMAC-SHA1')
         self.assertFalse(check_oauth_signature(request, token.consumer, token))
         self.assertEqual(400, request.response.getStatus())
 
     def test_bad_signature_format(self):
-        token = self.factory.makeOAuthAccessToken()
+        token, _ = self.factory.makeOAuthAccessToken()
         request = self.makeRequest('lol')
         self.assertFalse(check_oauth_signature(request, token.consumer, token))
         self.assertEqual(401, request.response.getStatus())

=== modified file 'lib/lp/services/webapp/tests/test_publication.py'
--- lib/lp/services/webapp/tests/test_publication.py	2013-06-20 05:50:00 +0000
+++ lib/lp/services/webapp/tests/test_publication.py	2014-06-27 08:22:29 +0000
@@ -100,10 +100,10 @@
 
         # Create an access token for our new person.
         consumer = getUtility(IOAuthConsumerSet).new('test-consumer')
-        request_token = consumer.newRequestToken()
+        request_token, _ = consumer.newRequestToken()
         request_token.review(
             person, permission=OAuthPermission.READ_PUBLIC, context=None)
-        access_token = request_token.createAccessToken()
+        access_token, _ = request_token.createAccessToken()
 
         # Use oauth.OAuthRequest just to generate a dictionary containing all
         # the parameters we need to use in a valid OAuth request, using the

=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py	2013-04-10 09:12:32 +0000
+++ lib/lp/testing/_webservice.py	2014-06-27 08:22:29 +0000
@@ -60,7 +60,7 @@
     :param context: The OAuth context for the credentials (or a string
         designating same).
 
-    :return: An OAuthAccessToken object.
+    :return: A tuple of an OAuthAccessToken object and its secret.
     """
     if isinstance(person, basestring):
         # Look up a person by name.
@@ -79,23 +79,11 @@
     consumer = consumer_set.getByKey(consumer_name)
     if consumer is None:
         consumer = consumer_set.new(consumer_name)
-    else:
-        # We didn't have to create the consumer. Maybe this user
-        # already has an access token for this
-        # consumer+person+permission?
-        existing_token = [token for token in person.oauth_access_tokens
-                          if (token.consumer == consumer
-                              and token.permission == permission
-                              and token.context == context)]
-        if len(existing_token) >= 1:
-            return existing_token[0]
 
-    # There is no existing access token for this
-    # consumer+person+permission+context. Create one and review it.
-    request_token = consumer.newRequestToken()
+    request_token, _ = consumer.newRequestToken()
     request_token.review(person, permission, context)
-    access_token = request_token.createAccessToken()
-    return access_token
+    access_token, access_secret = request_token.createAccessToken()
+    return access_token, access_secret
 
 
 def launchpadlib_credentials_for(
@@ -117,11 +105,10 @@
     # PageTestLayer, when a Launchpad instance is running for
     # launchpadlib to use.
     login(ANONYMOUS)
-    access_token = oauth_access_token_for(
+    access_token, access_secret = oauth_access_token_for(
         consumer_name, person, permission, context)
     logout()
-    launchpadlib_token = AccessToken(
-        access_token.key, access_token.secret)
+    launchpadlib_token = AccessToken(access_token.key, access_secret)
     return Credentials(consumer_name=consumer_name,
                        access_token=launchpadlib_token)
 

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2014-06-20 10:53:51 +0000
+++ lib/lp/testing/factory.py	2014-06-27 08:22:29 +0000
@@ -4166,7 +4166,7 @@
         """Create a (possibly reviewed) OAuth request token."""
         if consumer is None:
             consumer = self.makeOAuthConsumer()
-        token = consumer.newRequestToken()
+        token, _ = consumer.newRequestToken()
 
         if reviewed_by is not None:
             # Review the token before modifying the date_created,

=== modified file 'lib/lp/testing/pages.py'
--- lib/lp/testing/pages.py	2013-09-27 04:13:23 +0000
+++ lib/lp/testing/pages.py	2014-06-27 08:22:29 +0000
@@ -715,9 +715,9 @@
     consumer = oacs.getByKey(consumer_key)
     if consumer is None:
         consumer = oacs.new(consumer_key)
-    request_token = consumer.newRequestToken()
+    request_token, _ = consumer.newRequestToken()
     request_token.review(person, permission, context)
-    access_token = request_token.createAccessToken()
+    access_token, _ = request_token.createAccessToken()
     logout()
     service = LaunchpadWebServiceCaller(consumer_key, access_token.key)
     service.user = person


Follow ups