Merge lp:~leonardr/launchpadlib/anonymous-access into lp:launchpadlib
- anonymous-access
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Guilherme Salgado |
Approved revision: | 76 |
Merged at revision: | not available |
Proposed branch: | lp:~leonardr/launchpadlib/anonymous-access |
Merge into: | lp:launchpadlib |
Diff against target: |
269 lines (+130/-36) 5 files modified
src/launchpadlib/NEWS.txt (+4/-2) src/launchpadlib/__init__.py (+1/-1) src/launchpadlib/credentials.py (+10/-0) src/launchpadlib/docs/introduction.txt (+69/-15) src/launchpadlib/launchpad.py (+46/-18) |
To merge this branch: | bzr merge lp:~leonardr/launchpadlib/anonymous-access |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guilherme Salgado (community) | Approve | ||
Review via email: mp+16213@code.launchpad.net |
Commit message
Description of the change
Leonard Richardson (leonardr) wrote : | # |
Guilherme Salgado (salgado) wrote : | # |
Hi Leonard,
Just a couple nitpicks, but it looks good to go.
status approved
review approve
On Tue, 2009-12-15 at 18:33 +0000, Leonard Richardson wrote:
> Leonard Richardson has proposed merging
> === modified file 'src/launchpadl
> --- src/launchpadli
> +++ src/launchpadli
> @@ -19,6 +19,7 @@
> __metaclass__ = type
> __all__ = [
> 'AccessToken',
> + 'AnonymousAcces
> 'RequestTokenAu
> 'Consumer',
> 'Credentials',
> @@ -169,6 +170,14 @@
> return cls(key, secret, context)
>
>
> +class AnonymousAccess
> + """An OAuth access token that doesn't authenticate anybody.
> +
> + This token can be used for anonymous access."""
PEP-8 says the closing triple quotes should be on a line by itself for
multi-line docstrings.
> + def __init__(self):
> + super(Anonymous
> +
> +
> class SimulatedLaunch
> """A programmable substitute for a human-operated web browser.
>
> === modified file 'src/launchpadl
> --- src/launchpadli
> +++ src/launchpadli
> @@ -32,7 +32,8 @@
> from lazr.restfulcli
> CollectionWithK
> from launchpadlib.
> - AccessToken, Credentials, AuthorizeReques
> + AccessToken, AnonymousAccess
> + AuthorizeReques
> from oauth.oauth import OAuthRequest, OAuthSignatureM
> from launchpadlib import uris
>
> @@ -170,6 +171,19 @@
> return cls(credentials, service_root, cache, timeout, proxy_info)
>
> @classmethod
> + def login_anonymously(
> + cls, consumer_name, service_
> + launchpadlib_
> + """Get access to Launchpad without providing any credentials.
> +
> + """
You could fit that whole docstring on a single line, no?
> + service_root_dir, cache_path = cls._get_paths(
> + service_root, launchpadlib_dir)
> + token = AnonymousAccess
> + credentials = Credentials(
> + return cls(credentials, service_root, cache_path, timeout, proxy_info)
> +
> + @classmethod
> def login_with(cls, consumer_name,
> service_
> launchpadlib_
--
Guilherme Salgado <email address hidden>
Leonard Richardson (leonardr) wrote : | # |
Here's an incremental diff. I noticed that one of the tests was failing because it _is_ now possible to access Launchpad without any credentials. I replaced it with a more specific test, but I'm not convinced the new test adds any new test coverage. Let me know if you think I should keep it.
- 77. By Leonard Richardson
-
Response to feedback, and fixed a broken test.
- 78. By Leonard Richardson
-
Prep for release.
Preview Diff
1 | === modified file 'src/launchpadlib/NEWS.txt' |
2 | --- src/launchpadlib/NEWS.txt 2009-11-03 14:45:05 +0000 |
3 | +++ src/launchpadlib/NEWS.txt 2009-12-17 13:18:16 +0000 |
4 | @@ -2,8 +2,10 @@ |
5 | NEWS for launchpadlib |
6 | ===================== |
7 | |
8 | -Development |
9 | -=========== |
10 | +1.5.4 (2009-12-17) |
11 | +================== |
12 | + |
13 | +- Made it easy to get anonymous access to a Launchpad instance. |
14 | |
15 | - Made it easy to plug in different clients that take the user's |
16 | Launchpad login and password for purposes of authorizing a request |
17 | |
18 | === modified file 'src/launchpadlib/__init__.py' |
19 | --- src/launchpadlib/__init__.py 2009-10-22 16:02:25 +0000 |
20 | +++ src/launchpadlib/__init__.py 2009-12-17 13:18:16 +0000 |
21 | @@ -14,4 +14,4 @@ |
22 | # You should have received a copy of the GNU Lesser General Public License |
23 | # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>. |
24 | |
25 | -__version__ = '1.5.3' |
26 | +__version__ = '1.5.4' |
27 | |
28 | === modified file 'src/launchpadlib/credentials.py' |
29 | --- src/launchpadlib/credentials.py 2009-11-02 21:03:53 +0000 |
30 | +++ src/launchpadlib/credentials.py 2009-12-17 13:18:16 +0000 |
31 | @@ -19,6 +19,7 @@ |
32 | __metaclass__ = type |
33 | __all__ = [ |
34 | 'AccessToken', |
35 | + 'AnonymousAccessToken', |
36 | 'RequestTokenAuthorizationEngine', |
37 | 'Consumer', |
38 | 'Credentials', |
39 | @@ -169,6 +170,15 @@ |
40 | return cls(key, secret, context) |
41 | |
42 | |
43 | +class AnonymousAccessToken(_AccessToken): |
44 | + """An OAuth access token that doesn't authenticate anybody. |
45 | + |
46 | + This token can be used for anonymous access. |
47 | + """ |
48 | + def __init__(self): |
49 | + super(AnonymousAccessToken, self).__init__('','') |
50 | + |
51 | + |
52 | class SimulatedLaunchpadBrowser(object): |
53 | """A programmable substitute for a human-operated web browser. |
54 | |
55 | |
56 | === modified file 'src/launchpadlib/docs/introduction.txt' |
57 | --- src/launchpadlib/docs/introduction.txt 2009-11-03 13:50:27 +0000 |
58 | +++ src/launchpadlib/docs/introduction.txt 2009-12-17 13:18:16 +0000 |
59 | @@ -147,20 +147,71 @@ |
60 | 'test' |
61 | |
62 | |
63 | +Anonymous access |
64 | +================ |
65 | + |
66 | +An anonymous access token doesn't authenticate any particular |
67 | +user. Using it will give a client read-only access to the public parts |
68 | +of the Launchpad dataset. |
69 | + |
70 | + >>> from launchpadlib.credentials import AnonymousAccessToken |
71 | + >>> anonymous_token = AnonymousAccessToken() |
72 | + |
73 | + >>> from launchpadlib.credentials import Credentials |
74 | + >>> credentials = Credentials( |
75 | + ... consumer_name="a consumer", access_token=anonymous_token) |
76 | + >>> launchpad = Launchpad(credentials=credentials) |
77 | + |
78 | + >>> salgado = launchpad.people['salgado'] |
79 | + >>> print salgado.display_name |
80 | + Guilherme Salgado |
81 | + |
82 | +An anonymous client can't modify the dataset, or read any data that's |
83 | +permission-controlled or scoped to a particular user. |
84 | + |
85 | + >>> launchpad.me |
86 | + Traceback (most recent call last): |
87 | + ... |
88 | + HTTPError: HTTP Error 401: Unauthorized |
89 | + ... |
90 | + |
91 | + >>> salgado.display_name = "This won't work." |
92 | + >>> salgado.lp_save() |
93 | + Traceback (most recent call last): |
94 | + ... |
95 | + HTTPError: HTTP Error 401: Unauthorized |
96 | + ... |
97 | + |
98 | Convenience |
99 | =========== |
100 | |
101 | -When the consumer name, access token and access secret are all known up-front, |
102 | -a convenience method is available for logging into the web service in one |
103 | -function call. |
104 | +When you want anonymous access, a convenience method is available for |
105 | +setting up a web service connection in one function call. All you have |
106 | +to provide is the consumer name. |
107 | + |
108 | + >>> launchpad = Launchpad.login_anonymously('launchpad-library') |
109 | + >>> sorted(launchpad.people) |
110 | + [...] |
111 | + |
112 | + >>> launchpad.me |
113 | + Traceback (most recent call last): |
114 | + ... |
115 | + HTTPError: HTTP Error 401: Unauthorized |
116 | + ... |
117 | + |
118 | +Another function call is useful when the consumer name, access token |
119 | +and access secret are all known up-front. |
120 | |
121 | >>> launchpad = Launchpad.login( |
122 | ... 'launchpad-library', 'salgado-change-anything', 'test') |
123 | >>> sorted(launchpad.people) |
124 | [...] |
125 | |
126 | -If that is not the case the application should obtain authorization from |
127 | -the user and get the credentials directly from Launchpad. |
128 | + >>> print launchpad.me.name |
129 | + salgado |
130 | + |
131 | +Otherwise, the application should obtain authorization from the user |
132 | +and get a new set of credentials directly from Launchpad. |
133 | |
134 | First we must get a request token. |
135 | |
136 | @@ -402,16 +453,6 @@ |
137 | Bad credentials |
138 | =============== |
139 | |
140 | -The application is not allowed to access Launchpad if there are no |
141 | -credentials. |
142 | - |
143 | - >>> credentials = Credentials(consumer) |
144 | - >>> launchpad = Launchpad(credentials=credentials) |
145 | - Traceback (most recent call last): |
146 | - ... |
147 | - HTTPError: HTTP Error 401: Unauthorized |
148 | - ... |
149 | - |
150 | The application is not allowed to access Launchpad with a bad access token. |
151 | |
152 | >>> access_token = AccessToken('bad', 'no-secret') |
153 | @@ -424,6 +465,19 @@ |
154 | HTTPError: HTTP Error 401: Unauthorized |
155 | ... |
156 | |
157 | +The application is not allowed to access Launchpad with a consumer |
158 | +name that doesn't match the credentials. |
159 | + |
160 | + >>> access_token = AccessToken('salgado-change-anything', 'test') |
161 | + >>> credentials = Credentials( |
162 | + ... consumer_name='not-the-launchpad-library', |
163 | + ... access_token=access_token) |
164 | + >>> launchpad = Launchpad(credentials=credentials) |
165 | + Traceback (most recent call last): |
166 | + ... |
167 | + HTTPError: HTTP Error 401: Unauthorized |
168 | + ... |
169 | + |
170 | The application is not allowed to access Launchpad with a bad access secret. |
171 | |
172 | >>> access_token = AccessToken('hgm2VK35vXD6rLg5pxWw', 'bad-secret') |
173 | |
174 | === modified file 'src/launchpadlib/launchpad.py' |
175 | --- src/launchpadlib/launchpad.py 2009-12-03 14:38:29 +0000 |
176 | +++ src/launchpadlib/launchpad.py 2009-12-17 13:18:16 +0000 |
177 | @@ -32,7 +32,8 @@ |
178 | from lazr.restfulclient.resource import ( |
179 | CollectionWithKeyBasedLookup, HostedFile, ServiceRoot) |
180 | from launchpadlib.credentials import ( |
181 | - AccessToken, Credentials, AuthorizeRequestTokenWithBrowser) |
182 | + AccessToken, AnonymousAccessToken, Credentials, |
183 | + AuthorizeRequestTokenWithBrowser) |
184 | from oauth.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT |
185 | from launchpadlib import uris |
186 | |
187 | @@ -170,6 +171,17 @@ |
188 | return cls(credentials, service_root, cache, timeout, proxy_info) |
189 | |
190 | @classmethod |
191 | + def login_anonymously( |
192 | + cls, consumer_name, service_root=uris.STAGING_SERVICE_ROOT, |
193 | + launchpadlib_dir=None, timeout=None, proxy_info=None): |
194 | + """Get access to Launchpad without providing any credentials.""" |
195 | + service_root_dir, cache_path = cls._get_paths( |
196 | + service_root, launchpadlib_dir) |
197 | + token = AnonymousAccessToken() |
198 | + credentials = Credentials(consumer_name, access_token=token) |
199 | + return cls(credentials, service_root, cache_path, timeout, proxy_info) |
200 | + |
201 | + @classmethod |
202 | def login_with(cls, consumer_name, |
203 | service_root=uris.STAGING_SERVICE_ROOT, |
204 | launchpadlib_dir=None, timeout=None, proxy_info=None, |
205 | @@ -210,27 +222,13 @@ |
206 | :rtype: `Launchpad` |
207 | |
208 | """ |
209 | - if launchpadlib_dir is None: |
210 | - home_dir = os.environ['HOME'] |
211 | - launchpadlib_dir = os.path.join(home_dir, '.launchpadlib') |
212 | - launchpadlib_dir = os.path.expanduser(launchpadlib_dir) |
213 | - if not os.path.exists(launchpadlib_dir): |
214 | - os.makedirs(launchpadlib_dir,0700) |
215 | - os.chmod(launchpadlib_dir,0700) |
216 | - # Determine the real service root. |
217 | - service_root = uris.lookup_service_root(service_root) |
218 | - # Each service root has its own cache and credential dirs. |
219 | - scheme, host_name, path, query, fragment = urlparse.urlsplit( |
220 | - service_root) |
221 | - service_root_dir = os.path.join(launchpadlib_dir, host_name) |
222 | - cache_path = os.path.join(service_root_dir, 'cache') |
223 | - if not os.path.exists(cache_path): |
224 | - os.makedirs(cache_path) |
225 | + service_root_dir, cache_path = cls._get_paths( |
226 | + service_root, launchpadlib_dir) |
227 | credentials_path = os.path.join(service_root_dir, 'credentials') |
228 | if not os.path.exists(credentials_path): |
229 | os.makedirs(credentials_path) |
230 | if credentials_file is None: |
231 | - consumer_credentials_path = os.path.join(credentials_path, |
232 | + consumer_credentials_path = os.path.join(credentials_path, |
233 | consumer_name) |
234 | else: |
235 | consumer_credentials_path = credentials_file |
236 | @@ -250,3 +248,33 @@ |
237 | launchpad.credentials.save_to_path(consumer_credentials_path) |
238 | os.chmod(consumer_credentials_path, stat.S_IREAD | stat.S_IWRITE) |
239 | return launchpad |
240 | + |
241 | + @classmethod |
242 | + def _get_paths(cls, service_root, launchpadlib_dir=None): |
243 | + """Locate launchpadlib-related user paths and ensure they exist. |
244 | + |
245 | + This is a helper function used by login_with() and |
246 | + login_anonymously(). |
247 | + |
248 | + :param service_root: The service root the user wants to connect to. |
249 | + :param launchpadlib_dir: The user's base launchpadlib directory, |
250 | + if known. |
251 | + :return: A 2-tuple: (cache_dir, service_root_dir) |
252 | + """ |
253 | + if launchpadlib_dir is None: |
254 | + home_dir = os.environ['HOME'] |
255 | + launchpadlib_dir = os.path.join(home_dir, '.launchpadlib') |
256 | + launchpadlib_dir = os.path.expanduser(launchpadlib_dir) |
257 | + if not os.path.exists(launchpadlib_dir): |
258 | + os.makedirs(launchpadlib_dir,0700) |
259 | + os.chmod(launchpadlib_dir,0700) |
260 | + # Determine the real service root. |
261 | + service_root = uris.lookup_service_root(service_root) |
262 | + # Each service root has its own cache and credential dirs. |
263 | + scheme, host_name, path, query, fragment = urlparse.urlsplit( |
264 | + service_root) |
265 | + service_root_dir = os.path.join(launchpadlib_dir, host_name) |
266 | + cache_path = os.path.join(service_root_dir, 'cache') |
267 | + if not os.path.exists(cache_path): |
268 | + os.makedirs(cache_path) |
269 | + return (cache_path, service_root_dir) |
This branch introduces a new _AccessToken subclass, AnonymousAccess Token. You can use it to get read-only public access to the Launchpad web service, without authenticating as any particular user.
I also added a login_anonymously() convenience method to Launchpad. Because anonymous access doesn't go through the normal credential- obtaining process, I couldn't have this method defer to login(). I refactored some login() code into _get_paths() so that it could be used by both login() and login_anonymous ly().
Anonymous credentials are not stored on disk.