Merge lp:~mbp/launchpad/701545-oauth into lp:launchpad
- 701545-oauth
- Merge into devel
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~mbp/launchpad/701545-oauth | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
620 lines (+12/-537) 5 files modified
lib/canonical/launchpad/testing/pages.py (+2/-2) lib/canonical/launchpad/webapp/authentication.py (+3/-2) lib/canonical/launchpad/webapp/tests/test_authentication.py (+5/-2) lib/canonical/launchpad/webapp/tests/test_publication.py (+2/-2) lib/contrib/oauth.py (+0/-529) |
||||
To merge this branch: | bzr merge lp:~mbp/launchpad/701545-oauth | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
j.c.sackett (community) | code* | Approve | |
Review via email: mp+48431@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-02-13.
Commit message
[r=jcsackett,
Description of the change
Remove redundant copy of oauth.py.
Launchpad contains a copy of oauth.py, which is a bit out of date. It also has a proper copy of the python-oauth module in lp-sourcedeps.
This doesn't specifically fix any user-visible bugs that I know of, but it removes some cruft, and if the duplication hadn't been there in the first place we would have avoided at least one oauth bug that existed for years.
This branch just deletes the shipped copy, and updates import statements to fetch from the real copy. It passes a smoketest of './bin/test -m oauth' and I'm running a full test.
If this is acceptable would someone please sponsor landing of it?
- 12314. By Launchpad PQM Bot
-
[r=sinzui]
[ui=sinzui] [bug=708436] Fixed the width of the tag list so
it doesn't wrap (bug #708436). - 12315. By Launchpad PQM Bot
-
[r=bac,
benji][ui=none][bug=699719] A (currently disabled) JS overlay has
been added for advanced direct bug subscriptions. - 12316. By Launchpad PQM Bot
-
[r=gmb][bug=711901] Implement LIFECYCLE notification level for direct
subscriptions. - 12317. By Launchpad PQM Bot
-
[r=mthaddon, stub][bug=712282,
712285][no-qa] Improve nightly.sh transparency,
and remove update-pkgcache.py from it. - 12318. By Launchpad PQM Bot
-
[bug=75976] [r=abentley]
[ui=none] When someone asks to merge two
accounts, Launchpad sends an email to the primary account asking for
confirmation. That email was hard to understand and badly
written. This branch rewrites that email's text and introduces
a test that the email is sent with the correct text.
Curtis Hovey (sinzui) wrote : | # |
Hi Martin.
Thank you for cleaning up the code. We normally write copyrights as a range. Are you aware of utilities/
- 12319. By Launchpad PQM Bot
-
[no-qa] [r=allenap]
[ui=none] [bug=707103] Clean-up of lint after
bugs-with-blueprints-bug-707103.
Francis J. Lacoste (flacoste) wrote : | # |
On February 3, 2011, Martin Pool wrote:
> If this is acceptable would someone please sponsor landing of it?
All Canonical Bazaar Developers can land branches directly through PQM
nowawayds :-)
- 12320. By Launchpad PQM Bot
-
[r=deryck]
[ui=none] [no-qa] Provide translation sharing information. - 12321. By Launchpad PQM Bot
-
[r=jtv][bug=705652] Log warnings in the remove_translations script if
current translations are removed. Removed the theoretically
impossible case of finding a diverged translation being current
in upstream which is linked to a POTemplate having a diverged
translation being current in Ubuntu. - 12322. By Launchpad PQM Bot
-
[r=leonardr][bug=705342] Fail nonvirtual builders in the ABORTED
state rather than cleaning them,
since we can't guarantee that there's no active build. - 12323. By Launchpad PQM Bot
-
[r=lifeless][bug=709717] Remove an unnecessary join to
sourcepackagerelease when counting packages that are being
built/waiting to be built in ArchiveView.num_pkgs_ building
Martin Pool (mbp) wrote : | # |
Thanks, Curtis. I was not aware of that. I will send this to pqm.
Martin Pool (mbp) wrote : | # |
On 4 February 2011 03:52, Francis J. Lacoste <email address hidden> wrote:
> On February 3, 2011, Martin Pool wrote:
>> If this is acceptable would someone please sponsor landing of it?
>
> All Canonical Bazaar Developers can land branches directly through PQM
> nowawayds :-)
No, I still get a gpgv error from pqm.
- 12324. By Launchpad PQM Bot
-
[r=thumper][bug=316694] Add a web_link property to the JSON
representation of all web service entries that correspond to
some page on the Launchpad website.
Martin Pool (mbp) wrote : | # |
spm helped me sort this out and it's in the queue now.
- Martin
On 04/02/2011 11:35 AM, "Martin Pool" <email address hidden> wrote:
> On 4 February 2011 03:52, Francis J. Lacoste <email address hidden>
wrote:
>> On February 3, 2011, Martin Pool wrote:
>>> If this is acceptable would someone please sponsor landing of it?
>>
>> All Canonical Bazaar Developers can land branches directly through PQM
>> nowawayds :-)
>
> No, I still get a gpgv error from pqm.
- 12325. By Launchpad PQM Bot
-
[r=jcsackett,
sinzui][ui=none][bug=701545][no-qa] remove extra copy of oauth.py - 12326. By Launchpad PQM Bot
-
[r=lifeless]
[ui=none] [no-qa] Remove duplicated urgencymap in gina. - 12327. By Launchpad PQM Bot
-
[r=lifeless][bug=670452] Fix layout of related branches collapsible
fieldset - 12328. By Launchpad PQM Bot
-
[r=lifeless,
wallyworld][bug=712882][no-qa] Silence non-error output from
mirror-prober.sh. - 12329. By Launchpad PQM Bot
-
[testfix]
[r=me][ no-qa] Revert r12325 as the new oauth module is buggy
and causes a test to fail - 12330. By Launchpad PQM Bot
-
[r=henninge]
[ui=none] [no-qa] Add direct unit tests for
TM.shareIfPossible - 12331. By Launchpad PQM Bot
-
[r=allenap]
[ui=none] [bug=693689] Escape the token and title in
lp.app.widgets. itemswidgets renderer. - 12332. By Launchpad PQM Bot
-
[r=bac][bug=710054][bug=530476][bug=530477] Fix the protocol in the
gpg documentation and branch and MP api docs. - 12333. By Launchpad PQM Bot
-
[r=allenap][no-qa] Machinery for building documentation with sphinx
- 12334. By Launchpad PQM Bot
-
[r=henninge]
[ui=none] [bug=697294] Refactors create_ questionreopeni ng
to be called as a helper, rather than a listener to notifications;
this avoids the need for a slew of guards on the method, and keeps it
from doing work when we don't want it to. - 12335. By Launchpad PQM Bot
-
[testfix]
[rs=sinzui] Print the escaped text of the structured string. - 12336. By Launchpad PQM Bot
-
Merging db-stable at revno 12335
- 12337. By Launchpad PQM Bot
-
[release-
critical= wallyworld] [r=jtv] [bug=710591][incr] Provide
TranslationMessage.acceptFrom Import and
.acceptFromUpstreamImportOnPa ckage. - 12338. By Launchpad PQM Bot
-
[wallyworld,spm] merge db-devel into devel; needed for an RC DB patch
- 12339. By Launchpad PQM Bot
-
[r=gmb][bug=548] adds a simple way to control whether you want to
receive bug notifications for changes you generated. - 12340. By Launchpad PQM Bot
-
[r=gmb][no-qa] test, tweak, and exercise the dbuser test helper
- 12341. By Launchpad PQM Bot
-
[r=jcsackett, sinzui][ui=none][bug=344125,
712012] Remove obsolete method Bug.addChangeNotification( ). - 12342. By Launchpad PQM Bot
-
[r=leonardr][no-qa] Use itertools.tee() to simplify and speed up
CachingIterator. - 12343. By Launchpad PQM Bot
-
[r=gmb][bug=713392] make the +structural-
subscriptions UI correctly
show the effect of an event filter setting. - 12344. By Launchpad PQM Bot
-
[r=thumper, wallyworld]
[ui=none] [r=thumper, wallyworld][bug=661988,
714312] Reduce the time taken for distro scope bug searches by 50% by
using sequence-of-sets based eager loading rather than
wide-query eager loading. As part of making this fit cleanly
stop loading assignees during bug task search (they are not
rendered so not needed). - 12345. By Launchpad PQM Bot
-
[r=gmb][ui=none][bug=50616] Catch the ValueError while validating
image file. - 12346. By Launchpad PQM Bot
-
[r=leonardr][bug=714527] Properly escape labels in suggestion widgets.
- 12347. By Launchpad PQM Bot
-
[r=sinzui][bug=715474] Permit showing server side render times in the
visible page (controlled by feature flag visible_render_ time). - 12348. By Launchpad PQM Bot
-
[r=lifeless][bug=715000] Upgrade to a bzr with a fix for the graph
ancestry chained stacking issue. - 12349. By Launchpad PQM Bot
-
[r=lifeless][bug=713234] Remove duplicated queries issued when
calling ArchiveView.latest_ updates - 12350. By Launchpad PQM Bot
-
[r=leonardr][bug=715116] Add a big informational notice on the PPA
pages if publishing is disabled. - 12351. By Launchpad PQM Bot
-
[r=jtv][bug=715659] Use zope.exceptions to format logged exceptions
so that __traceback_info__ is reported. - 12352. By Launchpad PQM Bot
-
[r=adeuring][bug=181368] Gut BinaryPackageFi
lePublishing view. - 12353. By Launchpad PQM Bot
-
[r=jtv][bug=714820] Ignore malformed responses when probing remote
Bugzilla instances for XML-RPC capabilities. - 12354. By Launchpad PQM Bot
-
[r=allenap][bug=710603] The new direct subscription overlay allows
subscribing,
unsubscribing and editing subscriptions and is available to the
~malone-alpha team. - 12355. By Launchpad PQM Bot
-
[r=jtv][bug=705657] Fix mirror-prober.sh wrapper script to export the
correct config. (Fix bug 705657) - 12356. By Launchpad PQM Bot
-
[r=jtv] [ui=none][bug=316694][incr] Take advantage of web_link when
changing the URL in the browser bar,
rather than hacking self_link into web_link. - 12357. By Launchpad PQM Bot
-
[r=flacoste][bug=716109] Update Launchpad to r202 of lazr-js,
which includes an update to YUI 3.3. - 12358. By Launchpad PQM Bot
-
[r=lifeless][bug=457856] Added manual close to tag dialogue if it is
still open when tags are saved. - 12359. By Launchpad PQM Bot
-
[r=allenap][bug=528367] Added alt tag to remove icons.
- 12360. By Launchpad PQM Bot
-
[r=thumper][no-qa] Mechanical move of
canonical.launchpad. windmill. testing to lp.testing. windmill. - 12361. By Launchpad PQM Bot
-
[r=jcsackett,
sinzui] [bug=713382] test and fix for bug 713382 - 12362. By Launchpad PQM Bot
-
[r=jcsackett,
sinzui] [no-qa] Create a job for merging translations. - 12363. By Launchpad PQM Bot
-
[r=gmb][bug=632847] Adds check to prevent bugtasks for deactivated
products from showing up for people who can't see the product. - 12364. By Launchpad PQM Bot
-
[r=lifeless][bug=715992] Corrects use of canonical_url so that
requests for pillars via alias on the API do not redirect
outside of the API layer. - 12365. By Launchpad PQM Bot
-
[r=stub,
wallyworld][ui=none] [no-qa] Enforce BugMessage.index population on
all new BugMessage creation. - 12366. By Launchpad PQM Bot
-
[r=sinzui]
[ui=sinzui] [bug=710446] Unified the colours of the facets
(bugs, code etc.) so that they are all the same. - 12367. By Launchpad PQM Bot
-
[r=bac, benji][no-qa] move some code,
which lets you look at bug activity log entires more precisely,
from browser code to model code. This is a step along the path to
addressing bug 164196. - 12368. By Martin Pool
-
Remove dependency on oauth egg
- 12369. By Martin Pool
-
Use external python-oauth and remove contrib/oauth.py
- 12370. By Martin Pool
-
Also remove oauth from versions.cfg
Unmerged revisions
- 12370. By Martin Pool
-
Also remove oauth from versions.cfg
- 12369. By Martin Pool
-
Use external python-oauth and remove contrib/oauth.py
- 12368. By Martin Pool
-
Remove dependency on oauth egg
Preview Diff
1 | === modified file 'lib/canonical/launchpad/testing/pages.py' |
2 | --- lib/canonical/launchpad/testing/pages.py 2010-11-30 08:11:30 +0000 |
3 | +++ lib/canonical/launchpad/testing/pages.py 2011-02-04 00:32:51 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | """Testing infrastructure for page tests.""" |
10 | @@ -28,7 +28,7 @@ |
11 | SoupStrainer, |
12 | Tag, |
13 | ) |
14 | -from contrib.oauth import ( |
15 | +from oauth.oauth import ( |
16 | OAuthConsumer, |
17 | OAuthRequest, |
18 | OAuthSignatureMethod_PLAINTEXT, |
19 | |
20 | === modified file 'lib/canonical/launchpad/webapp/authentication.py' |
21 | --- lib/canonical/launchpad/webapp/authentication.py 2010-09-20 16:45:03 +0000 |
22 | +++ lib/canonical/launchpad/webapp/authentication.py 2011-02-04 00:32:51 +0000 |
23 | @@ -1,4 +1,4 @@ |
24 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
25 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
26 | # GNU Affero General Public License version 3 (see the file LICENSE). |
27 | |
28 | __metaclass__ = type |
29 | @@ -18,7 +18,8 @@ |
30 | import random |
31 | from UserDict import UserDict |
32 | |
33 | -from contrib.oauth import OAuthRequest |
34 | +from oauth.oauth import OAuthRequest |
35 | + |
36 | from zope.annotation.interfaces import IAnnotations |
37 | from zope.app.security.interfaces import ILoginPassword |
38 | from zope.app.security.principalregistry import UnauthenticatedPrincipal |
39 | |
40 | === modified file 'lib/canonical/launchpad/webapp/tests/test_authentication.py' |
41 | --- lib/canonical/launchpad/webapp/tests/test_authentication.py 2010-12-20 03:28:21 +0000 |
42 | +++ lib/canonical/launchpad/webapp/tests/test_authentication.py 2011-02-04 00:32:51 +0000 |
43 | @@ -1,4 +1,4 @@ |
44 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
45 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
46 | # GNU Affero General Public License version 3 (see the file LICENSE). |
47 | |
48 | """Tests authentication.py""" |
49 | @@ -10,7 +10,7 @@ |
50 | |
51 | from zope.app.security.principalregistry import UnauthenticatedPrincipal |
52 | |
53 | -from contrib.oauth import OAuthRequest |
54 | +from oauth.oauth import OAuthRequest |
55 | |
56 | from canonical.config import config |
57 | from canonical.launchpad.ftests import login |
58 | @@ -65,6 +65,9 @@ |
59 | # This was really a bug in the underlying contrib/oauth.py module, but |
60 | # it has no standalone test case. |
61 | # |
62 | + # Now we use the separate oauth module (bug 701545), and this has been |
63 | + # fixed upstream, but we might as well keep the test. |
64 | + # |
65 | # Note that the 'realm' parameter is not returned, because it's not |
66 | # included in the OAuth calculations. |
67 | headers = OAuthRequest._split_header( |
68 | |
69 | === modified file 'lib/canonical/launchpad/webapp/tests/test_publication.py' |
70 | --- lib/canonical/launchpad/webapp/tests/test_publication.py 2010-10-04 19:50:45 +0000 |
71 | +++ lib/canonical/launchpad/webapp/tests/test_publication.py 2011-02-04 00:32:51 +0000 |
72 | @@ -1,4 +1,4 @@ |
73 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
74 | +# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
75 | # GNU Affero General Public License version 3 (see the file LICENSE). |
76 | |
77 | """Tests publication.py""" |
78 | @@ -9,7 +9,7 @@ |
79 | import sys |
80 | import unittest |
81 | |
82 | -from contrib.oauth import ( |
83 | +from oauth.oauth import ( |
84 | OAuthRequest, |
85 | OAuthSignatureMethod_PLAINTEXT, |
86 | ) |
87 | |
88 | === removed file 'lib/contrib/oauth.py' |
89 | --- lib/contrib/oauth.py 2010-12-20 03:53:24 +0000 |
90 | +++ lib/contrib/oauth.py 1970-01-01 00:00:00 +0000 |
91 | @@ -1,529 +0,0 @@ |
92 | -# pylint: disable-msg=C0301,E0602,E0211,E0213,W0105,W0231,W0702 |
93 | - |
94 | -import cgi |
95 | -import urllib |
96 | -import time |
97 | -import random |
98 | -import urlparse |
99 | -import hmac |
100 | -import base64 |
101 | - |
102 | -VERSION = '1.0' # Hi Blaine! |
103 | -HTTP_METHOD = 'GET' |
104 | -SIGNATURE_METHOD = 'PLAINTEXT' |
105 | - |
106 | -# Generic exception class |
107 | -class OAuthError(RuntimeError): |
108 | - def __init__(self, message='OAuth error occured'): |
109 | - self.message = message |
110 | - |
111 | -# optional WWW-Authenticate header (401 error) |
112 | -def build_authenticate_header(realm=''): |
113 | - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} |
114 | - |
115 | -# url escape |
116 | -def escape(s): |
117 | - # escape '/' too |
118 | - return urllib.quote(s, safe='~') |
119 | - |
120 | -# util function: current timestamp |
121 | -# seconds since epoch (UTC) |
122 | -def generate_timestamp(): |
123 | - return int(time.time()) |
124 | - |
125 | -# util function: nonce |
126 | -# pseudorandom number |
127 | -def generate_nonce(length=8): |
128 | - return ''.join(str(random.randint(0, 9)) for i in range(length)) |
129 | - |
130 | -# OAuthConsumer is a data type that represents the identity of the Consumer |
131 | -# via its shared secret with the Service Provider. |
132 | -class OAuthConsumer(object): |
133 | - key = None |
134 | - secret = None |
135 | - |
136 | - def __init__(self, key, secret): |
137 | - self.key = key |
138 | - self.secret = secret |
139 | - |
140 | -# OAuthToken is a data type that represents an End User via either an access |
141 | -# or request token. |
142 | -class OAuthToken(object): |
143 | - # access tokens and request tokens |
144 | - key = None |
145 | - secret = None |
146 | - |
147 | - ''' |
148 | - key = the token |
149 | - secret = the token secret |
150 | - ''' |
151 | - def __init__(self, key, secret): |
152 | - self.key = key |
153 | - self.secret = secret |
154 | - |
155 | - def to_string(self): |
156 | - return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) |
157 | - |
158 | - # return a token from something like: |
159 | - # oauth_token_secret=digg&oauth_token=digg |
160 | - @staticmethod |
161 | - def from_string(s): |
162 | - params = cgi.parse_qs(s, keep_blank_values=False) |
163 | - key = params['oauth_token'][0] |
164 | - secret = params['oauth_token_secret'][0] |
165 | - return OAuthToken(key, secret) |
166 | - |
167 | - def __str__(self): |
168 | - return self.to_string() |
169 | - |
170 | -# OAuthRequest represents the request and can be serialized |
171 | -class OAuthRequest(object): |
172 | - ''' |
173 | - OAuth parameters: |
174 | - - oauth_consumer_key |
175 | - - oauth_token |
176 | - - oauth_signature_method |
177 | - - oauth_signature |
178 | - - oauth_timestamp |
179 | - - oauth_nonce |
180 | - - oauth_version |
181 | - ... any additional parameters, as defined by the Service Provider. |
182 | - ''' |
183 | - parameters = None # oauth parameters |
184 | - http_method = HTTP_METHOD |
185 | - http_url = None |
186 | - version = VERSION |
187 | - |
188 | - def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): |
189 | - self.http_method = http_method |
190 | - self.http_url = http_url |
191 | - self.parameters = parameters or {} |
192 | - |
193 | - def set_parameter(self, parameter, value): |
194 | - self.parameters[parameter] = value |
195 | - |
196 | - def get_parameter(self, parameter): |
197 | - try: |
198 | - return self.parameters[parameter] |
199 | - except: |
200 | - raise OAuthError('Parameter not found: %s' % parameter) |
201 | - |
202 | - def _get_timestamp_nonce(self): |
203 | - return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') |
204 | - |
205 | - # get any non-oauth parameters |
206 | - def get_nonoauth_parameters(self): |
207 | - parameters = {} |
208 | - for k, v in self.parameters.iteritems(): |
209 | - # ignore oauth parameters |
210 | - if k.find('oauth_') < 0: |
211 | - parameters[k] = v |
212 | - return parameters |
213 | - |
214 | - # serialize as a header for an HTTPAuth request |
215 | - def to_header(self, realm=''): |
216 | - auth_header = 'OAuth realm="%s"' % realm |
217 | - # add the oauth parameters |
218 | - if self.parameters: |
219 | - for k, v in self.parameters.iteritems(): |
220 | - auth_header += ', %s="%s"' % (k, v) |
221 | - return {'Authorization': auth_header} |
222 | - |
223 | - # serialize as post data for a POST request |
224 | - def to_postdata(self): |
225 | - return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()) |
226 | - |
227 | - # serialize as a url for a GET request |
228 | - def to_url(self): |
229 | - return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) |
230 | - |
231 | - # return a string that consists of all the parameters that need to be signed |
232 | - def get_normalized_parameters(self): |
233 | - params = self.parameters |
234 | - try: |
235 | - # exclude the signature if it exists |
236 | - del params['oauth_signature'] |
237 | - except: |
238 | - pass |
239 | - key_values = params.items() |
240 | - # sort lexicographically, first after key, then after value |
241 | - key_values.sort() |
242 | - # combine key value pairs in string and escape |
243 | - return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values) |
244 | - |
245 | - # just uppercases the http method |
246 | - def get_normalized_http_method(self): |
247 | - return self.http_method.upper() |
248 | - |
249 | - # parses the url and rebuilds it to be scheme://host/path |
250 | - def get_normalized_http_url(self): |
251 | - parts = urlparse.urlparse(self.http_url) |
252 | - url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path |
253 | - return url_string |
254 | - |
255 | - # set the signature parameter to the result of build_signature |
256 | - def sign_request(self, signature_method, consumer, token): |
257 | - # set the signature method |
258 | - self.set_parameter('oauth_signature_method', signature_method.get_name()) |
259 | - # set the signature |
260 | - self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) |
261 | - |
262 | - def build_signature(self, signature_method, consumer, token): |
263 | - # call the build signature method within the signature method |
264 | - return signature_method.build_signature(self, consumer, token) |
265 | - |
266 | - @staticmethod |
267 | - def from_request(http_method, http_url, headers=None, postdata=None, parameters=None): |
268 | - |
269 | - # let the library user override things however they'd like, if they know |
270 | - # which parameters to use then go for it, for example XMLRPC might want to |
271 | - # do this |
272 | - if parameters is not None: |
273 | - return OAuthRequest(http_method, http_url, parameters) |
274 | - |
275 | - # from the headers |
276 | - if headers is not None: |
277 | - try: |
278 | - auth_header = headers['Authorization'] |
279 | - # check that the authorization header is OAuth |
280 | - auth_header.index('OAuth') |
281 | - # get the parameters from the header |
282 | - parameters = OAuthRequest._split_header(auth_header) |
283 | - return OAuthRequest(http_method, http_url, parameters) |
284 | - except: |
285 | - raise OAuthError('Unable to parse OAuth parameters from Authorization header.') |
286 | - |
287 | - # from the parameter string (post body) |
288 | - if http_method == 'POST' and postdata is not None: |
289 | - parameters = OAuthRequest._split_url_string(postdata) |
290 | - |
291 | - # from the url string |
292 | - elif http_method == 'GET': |
293 | - param_str = urlparse.urlparse(http_url).query |
294 | - parameters = OAuthRequest._split_url_string(param_str) |
295 | - |
296 | - if parameters: |
297 | - return OAuthRequest(http_method, http_url, parameters) |
298 | - |
299 | - raise OAuthError('Missing all OAuth parameters. OAuth parameters must be in the headers, post body, or url.') |
300 | - |
301 | - @staticmethod |
302 | - def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): |
303 | - if not parameters: |
304 | - parameters = {} |
305 | - |
306 | - defaults = { |
307 | - 'oauth_consumer_key': oauth_consumer.key, |
308 | - 'oauth_timestamp': generate_timestamp(), |
309 | - 'oauth_nonce': generate_nonce(), |
310 | - 'oauth_version': OAuthRequest.version, |
311 | - } |
312 | - |
313 | - defaults.update(parameters) |
314 | - parameters = defaults |
315 | - |
316 | - if token: |
317 | - parameters['oauth_token'] = token.key |
318 | - |
319 | - return OAuthRequest(http_method, http_url, parameters) |
320 | - |
321 | - @staticmethod |
322 | - def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): |
323 | - if not parameters: |
324 | - parameters = {} |
325 | - |
326 | - parameters['oauth_token'] = token.key |
327 | - |
328 | - if callback: |
329 | - parameters['oauth_callback'] = escape(callback) |
330 | - |
331 | - return OAuthRequest(http_method, http_url, parameters) |
332 | - |
333 | - # util function: turn Authorization: header into parameters, has to do some unescaping |
334 | - @staticmethod |
335 | - def _split_header(header): |
336 | - params = {} |
337 | - header = header.lstrip() |
338 | - if not header.startswith('OAuth '): |
339 | - raise ValueError("not an OAuth header: %r" % header) |
340 | - header = header[6:] |
341 | - parts = header.split(',') |
342 | - for param in parts: |
343 | - # remove whitespace |
344 | - param = param.strip() |
345 | - # split key-value |
346 | - param_parts = param.split('=', 1) |
347 | - if param_parts[0] == 'realm': |
348 | - # Realm header is not an OAuth parameter according to rfc5849 |
349 | - # section 3.4.1.3.1. |
350 | - continue |
351 | - # remove quotes and unescape the value |
352 | - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) |
353 | - return params |
354 | - |
355 | - # util function: turn url string into parameters, has to do some unescaping |
356 | - @staticmethod |
357 | - def _split_url_string(param_str): |
358 | - parameters = cgi.parse_qs(param_str, keep_blank_values=False) |
359 | - for k, v in parameters.iteritems(): |
360 | - parameters[k] = urllib.unquote(v[0]) |
361 | - return parameters |
362 | - |
363 | -# OAuthServer is a worker to check a requests validity against a data store |
364 | -class OAuthServer(object): |
365 | - timestamp_threshold = 300 # in seconds, five minutes |
366 | - version = VERSION |
367 | - signature_methods = None |
368 | - data_store = None |
369 | - |
370 | - def __init__(self, data_store=None, signature_methods=None): |
371 | - self.data_store = data_store |
372 | - self.signature_methods = signature_methods or {} |
373 | - |
374 | - def set_data_store(self, oauth_data_store): |
375 | - self.data_store = oauth_data_store |
376 | - |
377 | - def get_data_store(self): |
378 | - return self.data_store |
379 | - |
380 | - def add_signature_method(self, signature_method): |
381 | - self.signature_methods[signature_method.get_name()] = signature_method |
382 | - return self.signature_methods |
383 | - |
384 | - # process a request_token request |
385 | - # returns the request token on success |
386 | - def fetch_request_token(self, oauth_request): |
387 | - try: |
388 | - # get the request token for authorization |
389 | - token = self._get_token(oauth_request, 'request') |
390 | - except: |
391 | - # no token required for the initial token request |
392 | - version = self._get_version(oauth_request) |
393 | - consumer = self._get_consumer(oauth_request) |
394 | - self._check_signature(oauth_request, consumer, None) |
395 | - # fetch a new token |
396 | - token = self.data_store.fetch_request_token(consumer) |
397 | - return token |
398 | - |
399 | - # process an access_token request |
400 | - # returns the access token on success |
401 | - def fetch_access_token(self, oauth_request): |
402 | - version = self._get_version(oauth_request) |
403 | - consumer = self._get_consumer(oauth_request) |
404 | - # get the request token |
405 | - token = self._get_token(oauth_request, 'request') |
406 | - self._check_signature(oauth_request, consumer, token) |
407 | - new_token = self.data_store.fetch_access_token(consumer, token) |
408 | - return new_token |
409 | - |
410 | - # verify an api call, checks all the parameters |
411 | - def verify_request(self, oauth_request): |
412 | - # -> consumer and token |
413 | - version = self._get_version(oauth_request) |
414 | - consumer = self._get_consumer(oauth_request) |
415 | - # get the access token |
416 | - token = self._get_token(oauth_request, 'access') |
417 | - self._check_signature(oauth_request, consumer, token) |
418 | - parameters = oauth_request.get_nonoauth_parameters() |
419 | - return consumer, token, parameters |
420 | - |
421 | - # authorize a request token |
422 | - def authorize_token(self, token, user): |
423 | - return self.data_store.authorize_request_token(token, user) |
424 | - |
425 | - # get the callback url |
426 | - def get_callback(self, oauth_request): |
427 | - return oauth_request.get_parameter('oauth_callback') |
428 | - |
429 | - # optional support for the authenticate header |
430 | - def build_authenticate_header(self, realm=''): |
431 | - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} |
432 | - |
433 | - # verify the correct version request for this server |
434 | - def _get_version(self, oauth_request): |
435 | - try: |
436 | - version = oauth_request.get_parameter('oauth_version') |
437 | - except: |
438 | - version = VERSION |
439 | - if version and version != self.version: |
440 | - raise OAuthError('OAuth version %s not supported' % str(version)) |
441 | - return version |
442 | - |
443 | - # figure out the signature with some defaults |
444 | - def _get_signature_method(self, oauth_request): |
445 | - try: |
446 | - signature_method = oauth_request.get_parameter('oauth_signature_method') |
447 | - except: |
448 | - signature_method = SIGNATURE_METHOD |
449 | - try: |
450 | - # get the signature method object |
451 | - signature_method = self.signature_methods[signature_method] |
452 | - except: |
453 | - signature_method_names = ', '.join(self.signature_methods.keys()) |
454 | - raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) |
455 | - |
456 | - return signature_method |
457 | - |
458 | - def _get_consumer(self, oauth_request): |
459 | - consumer_key = oauth_request.get_parameter('oauth_consumer_key') |
460 | - if not consumer_key: |
461 | - raise OAuthError('Invalid consumer key') |
462 | - consumer = self.data_store.lookup_consumer(consumer_key) |
463 | - if not consumer: |
464 | - raise OAuthError('Invalid consumer') |
465 | - return consumer |
466 | - |
467 | - # try to find the token for the provided request token key |
468 | - def _get_token(self, oauth_request, token_type='access'): |
469 | - token_field = oauth_request.get_parameter('oauth_token') |
470 | - token = self.data_store.lookup_token(token_type, token_field) |
471 | - if not token: |
472 | - raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) |
473 | - return token |
474 | - |
475 | - def _check_signature(self, oauth_request, consumer, token): |
476 | - timestamp, nonce = oauth_request._get_timestamp_nonce() |
477 | - self._check_timestamp(timestamp) |
478 | - self._check_nonce(consumer, token, nonce) |
479 | - signature_method = self._get_signature_method(oauth_request) |
480 | - try: |
481 | - signature = oauth_request.get_parameter('oauth_signature') |
482 | - except: |
483 | - raise OAuthError('Missing signature') |
484 | - # attempt to construct the same signature |
485 | - built = signature_method.build_signature(oauth_request, consumer, token) |
486 | - if signature != built: |
487 | - key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) |
488 | - raise OAuthError('Signature does not match. Expected: %s Got: %s Expected signature base string: %s' % (built, signature, base)) |
489 | - |
490 | - def _check_timestamp(self, timestamp): |
491 | - # verify that timestamp is recentish |
492 | - timestamp = int(timestamp) |
493 | - now = int(time.time()) |
494 | - lapsed = now - timestamp |
495 | - if lapsed > self.timestamp_threshold: |
496 | - raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) |
497 | - |
498 | - def _check_nonce(self, consumer, token, nonce): |
499 | - # verify that the nonce is uniqueish |
500 | - try: |
501 | - self.data_store.lookup_nonce(consumer, token, nonce) |
502 | - raise OAuthError('Nonce already used: %s' % str(nonce)) |
503 | - except: |
504 | - pass |
505 | - |
506 | -# OAuthClient is a worker to attempt to execute a request |
507 | -class OAuthClient(object): |
508 | - consumer = None |
509 | - token = None |
510 | - |
511 | - def __init__(self, oauth_consumer, oauth_token): |
512 | - self.consumer = oauth_consumer |
513 | - self.token = oauth_token |
514 | - |
515 | - def get_consumer(self): |
516 | - return self.consumer |
517 | - |
518 | - def get_token(self): |
519 | - return self.token |
520 | - |
521 | - def fetch_request_token(self, oauth_request): |
522 | - # -> OAuthToken |
523 | - raise NotImplementedError |
524 | - |
525 | - def fetch_access_token(self, oauth_request): |
526 | - # -> OAuthToken |
527 | - raise NotImplementedError |
528 | - |
529 | - def access_resource(self, oauth_request): |
530 | - # -> some protected resource |
531 | - raise NotImplementedError |
532 | - |
533 | -# OAuthDataStore is a database abstraction used to lookup consumers and tokens |
534 | -class OAuthDataStore(object): |
535 | - |
536 | - def lookup_consumer(self, key): |
537 | - # -> OAuthConsumer |
538 | - raise NotImplementedError |
539 | - |
540 | - def lookup_token(self, oauth_consumer, token_type, token_token): |
541 | - # -> OAuthToken |
542 | - raise NotImplementedError |
543 | - |
544 | - def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): |
545 | - # -> OAuthToken |
546 | - raise NotImplementedError |
547 | - |
548 | - def fetch_request_token(self, oauth_consumer): |
549 | - # -> OAuthToken |
550 | - raise NotImplementedError |
551 | - |
552 | - def fetch_access_token(self, oauth_consumer, oauth_token): |
553 | - # -> OAuthToken |
554 | - raise NotImplementedError |
555 | - |
556 | - def authorize_request_token(self, oauth_token, user): |
557 | - # -> OAuthToken |
558 | - raise NotImplementedError |
559 | - |
560 | -# OAuthSignatureMethod is a strategy class that implements a signature method |
561 | -class OAuthSignatureMethod(object): |
562 | - def get_name(): |
563 | - # -> str |
564 | - raise NotImplementedError |
565 | - |
566 | - def build_signature_base_string(oauth_request, oauth_consumer, oauth_token): |
567 | - # -> str key, str raw |
568 | - raise NotImplementedError |
569 | - |
570 | - def build_signature(oauth_request, oauth_consumer, oauth_token): |
571 | - # -> str |
572 | - raise NotImplementedError |
573 | - |
574 | -class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): |
575 | - |
576 | - def get_name(self): |
577 | - return 'HMAC-SHA1' |
578 | - |
579 | - def build_signature_base_string(self, oauth_request, consumer, token): |
580 | - sig = ( |
581 | - escape(oauth_request.get_normalized_http_method()), |
582 | - escape(oauth_request.get_normalized_http_url()), |
583 | - escape(oauth_request.get_normalized_parameters()), |
584 | - ) |
585 | - |
586 | - key = '%s&' % escape(consumer.secret) |
587 | - if token: |
588 | - key += escape(token.secret) |
589 | - raw = '&'.join(sig) |
590 | - return key, raw |
591 | - |
592 | - def build_signature(self, oauth_request, consumer, token): |
593 | - # build the base signature string |
594 | - key, raw = self.build_signature_base_string(oauth_request, consumer, token) |
595 | - |
596 | - # hmac object |
597 | - try: |
598 | - import hashlib # 2.5 |
599 | - hashed = hmac.new(key, raw, hashlib.sha1) |
600 | - except: |
601 | - import sha # deprecated |
602 | - hashed = hmac.new(key, raw, sha) |
603 | - |
604 | - # calculate the digest base 64 |
605 | - return base64.b64encode(hashed.digest()) |
606 | - |
607 | -class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): |
608 | - |
609 | - def get_name(self): |
610 | - return 'PLAINTEXT' |
611 | - |
612 | - def build_signature_base_string(self, oauth_request, consumer, token): |
613 | - # concatenate the consumer key and secret |
614 | - sig = escape(consumer.secret) |
615 | - if token: |
616 | - sig = '&'.join((sig, escape(token.secret))) |
617 | - return sig |
618 | - |
619 | - def build_signature(self, oauth_request, consumer, token): |
620 | - return self.build_signature_base_string(oauth_request, consumer, token) |
Martin--
This looks good. Thanks!