Merge lp:~leonardr/launchpadlib/oauth-in-restfulclient into lp:launchpadlib

Proposed by Leonard Richardson
Status: Merged
Approved by: Gavin Panella
Approved revision: 61
Merged at revision: not available
Proposed branch: lp:~leonardr/launchpadlib/oauth-in-restfulclient
Merge into: lp:launchpadlib
Diff against target: 333 lines
6 files modified
setup.py (+1/-1)
src/launchpadlib/NEWS.txt (+6/-0)
src/launchpadlib/__init__.py (+1/-1)
src/launchpadlib/credentials.py (+9/-126)
src/launchpadlib/launchpad.py (+0/-28)
src/launchpadlib/tests/test_credentials.py (+0/-60)
To merge this branch: bzr merge lp:~leonardr/launchpadlib/oauth-in-restfulclient
Reviewer Review Type Date Requested Status
Gavin Panella Approve
Review via email: mp+13790@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

This branch removes a lot of code from launchpadlib that now lives in lazr.restfulclient. There should be no changes visible from outside. I've run the launchpadlib tests under a Launchpad installation and test-driven launchpadlib using bin/py to go through the token request process.

61. By Leonard Richardson

Prep for release.

Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'setup.py'
--- setup.py 2009-07-08 18:48:28 +0000
+++ setup.py 2009-10-22 16:05:21 +0000
@@ -60,7 +60,7 @@
60 license='LGPL v3',60 license='LGPL v3',
61 install_requires=[61 install_requires=[
62 'httplib2',62 'httplib2',
63 'lazr.restfulclient',63 'lazr.restfulclient>=0.9.9',
64 'lazr.uri',64 'lazr.uri',
65 'oauth',65 'oauth',
66 'setuptools',66 'setuptools',
6767
=== modified file 'src/launchpadlib/NEWS.txt'
--- src/launchpadlib/NEWS.txt 2009-10-01 19:15:41 +0000
+++ src/launchpadlib/NEWS.txt 2009-10-22 16:05:21 +0000
@@ -2,6 +2,12 @@
2NEWS for launchpadlib2NEWS for launchpadlib
3=====================3=====================
44
51.5.3 (2009-10-22)
6==================
7
8- Moved some more code from launchpadlib into the more generic
9 lazr.restfulclient.
10
51.5.2 (2009-10-01)111.5.2 (2009-10-01)
6==================12==================
713
814
=== modified file 'src/launchpadlib/__init__.py'
--- src/launchpadlib/__init__.py 2009-10-01 19:15:41 +0000
+++ src/launchpadlib/__init__.py 2009-10-22 16:05:21 +0000
@@ -14,4 +14,4 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.15# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
1616
17__version__ = '1.5.2'17__version__ = '1.5.3'
1818
=== modified file 'src/launchpadlib/credentials.py'
--- src/launchpadlib/credentials.py 2009-03-26 21:07:35 +0000
+++ src/launchpadlib/credentials.py 2009-10-22 16:05:21 +0000
@@ -23,23 +23,22 @@
23 'Credentials',23 'Credentials',
24 ]24 ]
2525
26from ConfigParser import SafeConfigParser
27import cgi26import cgi
28import httplib227import httplib2
29from oauth.oauth import OAuthConsumer, OAuthToken
30from urllib import urlencode28from urllib import urlencode
3129
32from lazr.restfulclient.errors import CredentialsFileError, HTTPError30from lazr.restfulclient.errors import HTTPError
3331from lazr.restfulclient.authorize.oauth import (
3432 AccessToken as _AccessToken, Consumer, OAuthAuthorizer)
35CREDENTIALS_FILE_VERSION = '1'33
34
36STAGING_WEB_ROOT = 'https://staging.launchpad.net/'35STAGING_WEB_ROOT = 'https://staging.launchpad.net/'
37request_token_page = '+request-token'36request_token_page = '+request-token'
38access_token_page = '+access-token'37access_token_page = '+access-token'
39authorize_token_page = '+authorize-token'38authorize_token_page = '+authorize-token'
4039
4140
42class Credentials:41class Credentials(OAuthAuthorizer):
43 """Standard credentials storage and usage class.42 """Standard credentials storage and usage class.
4443
45 :ivar consumer: The consumer (application)44 :ivar consumer: The consumer (application)
@@ -49,107 +48,6 @@
49 """48 """
50 _request_token = None49 _request_token = None
5150
52 def __init__(self, consumer_name=None, consumer_secret='',
53 access_token=None):
54 """The user's Launchpad API credentials.
55
56 :param consumer_name: The name of the consumer (application)
57 :param consumer_secret: The secret of the consumer
58 :param access_token: The authenticated user access token
59 :type access_token: `AccessToken`
60 """
61 self.consumer = None
62 if consumer_name is not None:
63 self.consumer = Consumer(consumer_name, consumer_secret)
64 self.access_token = access_token
65
66 def load(self, readable_file):
67 """Load credentials from a file-like object.
68
69 This overrides the consumer and access token given in the constructor
70 and replaces them with the values read from the file.
71
72 :param readable_file: A file-like object to read the credentials from
73 :type readable_file: Any object supporting the file-like `read()`
74 method
75 """
76 # Attempt to load the access token from the file.
77 parser = SafeConfigParser()
78 parser.readfp(readable_file)
79 # Check the version number and extract the access token and
80 # secret. Then convert these to the appropriate instances.
81 if not parser.has_section(CREDENTIALS_FILE_VERSION):
82 raise CredentialsFileError('No configuration for version %s' %
83 CREDENTIALS_FILE_VERSION)
84 consumer_key = parser.get(
85 CREDENTIALS_FILE_VERSION, 'consumer_key')
86 consumer_secret = parser.get(
87 CREDENTIALS_FILE_VERSION, 'consumer_secret')
88 self.consumer = Consumer(consumer_key, consumer_secret)
89 access_token = parser.get(
90 CREDENTIALS_FILE_VERSION, 'access_token')
91 access_secret = parser.get(
92 CREDENTIALS_FILE_VERSION, 'access_secret')
93 self.access_token = AccessToken(access_token, access_secret)
94
95 @classmethod
96 def load_from_path(cls, path):
97 """Convenience method for loading credentials from a file.
98
99 Open the file, create the Credentials and load from the file,
100 and finally close the file and return the newly created
101 Credentials instance.
102
103 :param path: In which file the credential file should be saved.
104 :type path: string
105 :return: The loaded Credentials instance.
106 :rtype: `Credentials`
107 """
108 credentials = cls()
109 credentials_file = open(path, 'r')
110 credentials.load(credentials_file)
111 credentials_file.close()
112 return credentials
113
114 def save(self, writable_file):
115 """Write the credentials to the file-like object.
116
117 :param writable_file: A file-like object to write the credentials to
118 :type writable_file: Any object supporting the file-like `write()`
119 method
120 :raise CredentialsFileError: when there is either no consumer or no
121 access token
122 """
123 if self.consumer is None:
124 raise CredentialsFileError('No consumer')
125 if self.access_token is None:
126 raise CredentialsFileError('No access token')
127
128 parser = SafeConfigParser()
129 parser.add_section(CREDENTIALS_FILE_VERSION)
130 parser.set(CREDENTIALS_FILE_VERSION,
131 'consumer_key', self.consumer.key)
132 parser.set(CREDENTIALS_FILE_VERSION,
133 'consumer_secret', self.consumer.secret)
134 parser.set(CREDENTIALS_FILE_VERSION,
135 'access_token', self.access_token.key)
136 parser.set(CREDENTIALS_FILE_VERSION,
137 'access_secret', self.access_token.secret)
138 parser.write(writable_file)
139
140 def save_to_path(self, path):
141 """Convenience method for saving credentials to a file.
142
143 Create the file, call self.save(), and close the file. Existing
144 files are overwritten.
145
146 :param path: In which file the credential file should be saved.
147 :type path: string
148 """
149 credentials_file = open(path, 'w')
150 self.save(credentials_file)
151 credentials_file.close()
152
153 def get_request_token(self, context=None, web_root=STAGING_WEB_ROOT):51 def get_request_token(self, context=None, web_root=STAGING_WEB_ROOT):
154 """Request an OAuth token to Launchpad.52 """Request an OAuth token to Launchpad.
15553
@@ -162,7 +60,7 @@
162 validity within Launchpad.60 validity within Launchpad.
163 :param web_root: The URL of the website on which the token61 :param web_root: The URL of the website on which the token
164 should be requested.62 should be requested.
165 :return: The URL for the user to authorize the `OAuthToken` provided63 :return: The URL for the user to authorize the `AccessToken` provided
166 by Launchpad.64 by Launchpad.
167 """65 """
168 assert self.consumer is not None, "Consumer not specified."66 assert self.consumer is not None, "Consumer not specified."
@@ -176,7 +74,7 @@
176 url, method='POST', body=urlencode(params))74 url, method='POST', body=urlencode(params))
177 if response.status != 200:75 if response.status != 200:
178 raise HTTPError(response, content)76 raise HTTPError(response, content)
179 self._request_token = OAuthToken.from_string(content)77 self._request_token = AccessToken.from_string(content)
180 url = '%s%s?oauth_token=%s' % (web_root, authorize_token_page,78 url = '%s%s?oauth_token=%s' % (web_root, authorize_token_page,
181 self._request_token.key)79 self._request_token.key)
182 if context is not None:80 if context is not None:
@@ -210,24 +108,9 @@
210 self.access_token = AccessToken.from_string(content)108 self.access_token = AccessToken.from_string(content)
211109
212110
213# These two classes are provided for convenience (so applications don't need111class AccessToken(_AccessToken):
214# to import from launchpadlib._oauth.oauth), and to provide a default argument
215# for secret.
216
217class Consumer(OAuthConsumer):
218 """An OAuth consumer (application)."""
219
220 def __init__(self, key, secret=''):
221 super(Consumer, self).__init__(key, secret)
222
223
224class AccessToken(OAuthToken):
225 """An OAuth access token."""112 """An OAuth access token."""
226113
227 def __init__(self, key, secret='', context=None):
228 super(AccessToken, self).__init__(key, secret)
229 self.context = context
230
231 @classmethod114 @classmethod
232 def from_string(cls, query_string):115 def from_string(cls, query_string):
233 """Create and return a new `AccessToken` from the given string."""116 """Create and return a new `AccessToken` from the given string."""
234117
=== modified file 'src/launchpadlib/launchpad.py'
--- src/launchpadlib/launchpad.py 2009-09-29 14:54:55 +0000
+++ src/launchpadlib/launchpad.py 2009-10-22 16:05:21 +0000
@@ -107,9 +107,6 @@
107 super(Launchpad, self).__init__(107 super(Launchpad, self).__init__(
108 credentials, service_root, cache, timeout, proxy_info)108 credentials, service_root, cache, timeout, proxy_info)
109109
110 def httpFactory(self, credentials, cache, timeout, proxy_info):
111 return OAuthSigningHttp(credentials, cache, timeout, proxy_info)
112
113 @classmethod110 @classmethod
114 def login(cls, consumer_name, token_string, access_secret,111 def login(cls, consumer_name, token_string, access_secret,
115 service_root=STAGING_SERVICE_ROOT,112 service_root=STAGING_SERVICE_ROOT,
@@ -252,28 +249,3 @@
252 os.path.join(credentials_path, consumer_name),249 os.path.join(credentials_path, consumer_name),
253 stat.S_IREAD | stat.S_IWRITE)250 stat.S_IREAD | stat.S_IWRITE)
254 return launchpad251 return launchpad
255
256
257class OAuthSigningHttp(RestfulHttp):
258 """A client that signs every outgoing request with OAuth credentials."""
259
260 def _request(self, conn, host, absolute_uri, request_uri, method, body,
261 headers, redirections, cachekey):
262 """Sign a request with OAuth credentials before sending it."""
263 oauth_request = OAuthRequest.from_consumer_and_token(
264 self.restful_credentials.consumer,
265 self.restful_credentials.access_token,
266 http_url=absolute_uri)
267 oauth_request.sign_request(
268 OAuthSignatureMethod_PLAINTEXT(),
269 self.restful_credentials.consumer,
270 self.restful_credentials.access_token)
271 if headers.has_key('authorization'):
272 # There's an authorization header left over from a
273 # previous request that resulted in a redirect. Remove it
274 # and start again.
275 del headers['authorization']
276 headers.update(oauth_request.to_header(OAUTH_REALM))
277 return super(OAuthSigningHttp, self)._request(
278 conn, host, absolute_uri, request_uri, method, body, headers,
279 redirections, cachekey)
280252
=== removed file 'src/launchpadlib/tests/test_credentials.py'
--- src/launchpadlib/tests/test_credentials.py 2009-07-09 12:54:19 +0000
+++ src/launchpadlib/tests/test_credentials.py 1970-01-01 00:00:00 +0000
@@ -1,60 +0,0 @@
1# Copyright 2009 Canonical Ltd.
2
3# This file is part of launchpadlib.
4#
5# launchpadlib is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Lesser General Public License as published by the
7# Free Software Foundation, version 3 of the License.
8#
9# launchpadlib is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12# for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
16
17"""Tests for the Credentials class."""
18
19__metaclass__ = type
20
21
22import os
23import os.path
24import shutil
25import tempfile
26import unittest
27
28from launchpadlib.credentials import AccessToken, Credentials
29
30
31class TestCredentialsSaveAndLoad(unittest.TestCase):
32 """Test for saving and loading credentials."""
33
34 def setUp(self):
35 self.temp_dir = tempfile.mkdtemp()
36
37 def tearDown(self):
38 shutil.rmtree(self.temp_dir)
39
40 def test_save_to_and_load_from__path(self):
41 # Credentials can be saved to and loaded from a file using
42 # save_to_path() and load_from_path().
43 credentials_path = os.path.join(self.temp_dir, 'credentials')
44 credentials = Credentials(
45 'consumer.key', consumer_secret='consumer.secret',
46 access_token=AccessToken('access.key', 'access.secret'))
47 credentials.save_to_path(credentials_path)
48 self.assertTrue(os.path.exists(credentials_path))
49
50 loaded_credentials = Credentials.load_from_path(credentials_path)
51 self.assertEqual(loaded_credentials.consumer.key, 'consumer.key')
52 self.assertEqual(
53 loaded_credentials.consumer.secret, 'consumer.secret')
54 self.assertEqual(
55 loaded_credentials.access_token.key, 'access.key')
56 self.assertEqual(
57 loaded_credentials.access_token.secret, 'access.secret')
58
59def test_suite():
60 return unittest.TestLoader().loadTestsFromName(__name__)

Subscribers

People subscribed via source and target branches