Skip to content

Commit 47caa32

Browse files
author
Jack Diederich
committed
comply with the RFC and submit useless and unused information when redeeming the access code.
1 parent 630532b commit 47caa32

File tree

2 files changed

+80
-48
lines changed

2 files changed

+80
-48
lines changed

foauth2.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,17 @@
3232
THE SOFTWARE.
3333
"""
3434

35-
import random
36-
import time
3735
import urllib
3836
import urllib2
39-
import urlparse
37+
import StringIO
4038

4139
try:
4240
import simplejson
4341
except ImportError:
4442
# Have django or are running in the Google App Engine?
4543
from django.utils import simplejson
4644

47-
VERSION = '1.0'
45+
VERSION = '1.1'
4846

4947
class Error(RuntimeError):
5048
"""Generic exception class."""
@@ -70,23 +68,26 @@ class Client(object):
7068

7169
def __init__(self, client_id, client_secret, access_token=None,
7270
refresh_token=None, timeout=None):
73-
7471
if not client_id or not client_secret:
7572
raise ValueError("Client_id and client_secret must be set.")
76-
7773
self.client_id = client_id
7874
self.client_secret = client_secret
7975
self.timeout = timeout
8076
self.access_token = access_token
8177
self.refresh_token = refresh_token
78+
self._authorization_redirect_uri = None
8279

8380
def authorization_url(self, auth_uri=None, redirect_uri=None, scope=None, state=None,
8481
access_type='offline', approval_prompt=None):
8582
""" Get the URL to redirect the user for client authorization """
8683
if redirect_uri is None:
8784
redirect_uri = self.redirect_uri
85+
if redirect_uri:
86+
self._authorization_redirect_uri = redirect_uri
8887
if auth_uri is None:
8988
auth_uri = self.auth_uri
89+
if not auth_uri:
90+
raise ValueError("an auth_uri is required")
9091
if scope is None:
9192
scope = self.scope
9293

@@ -111,12 +112,12 @@ def authorization_url(self, auth_uri=None, redirect_uri=None, scope=None, state=
111112

112113
def redeem_code(self, refresh_uri=None, redirect_uri=None, code=None, scope=None):
113114
"""Get an access token from the supplied code """
114-
115-
# prepare required args
116115
if code is None:
117-
raise ValueError("Code must be set.")
116+
raise ValueError("Code must be set. see see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.3")
118117
if redirect_uri is None:
119118
redirect_uri = self.redirect_uri
119+
if self._authorization_redirect_uri and redirect_uri != self._authorization_redirect_uri:
120+
raise ValueError("redirect_uri mismatch. see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.3")
120121
if refresh_uri is None:
121122
refresh_uri = self.refresh_uri
122123
if scope is None:
@@ -126,10 +127,10 @@ def redeem_code(self, refresh_uri=None, redirect_uri=None, code=None, scope=None
126127
'client_id': self.client_id,
127128
'client_secret': self.client_secret,
128129
'code': code,
129-
'redirect_uri': redirect_uri,
130130
'grant_type' : 'authorization_code',
131131
}
132-
132+
if redirect_uri is not None:
133+
data['redirect_uri'] = redirect_uri
133134
if scope is not None:
134135
data['scope'] = scope
135136
body = urllib.urlencode(data)
@@ -225,6 +226,8 @@ def request(self, uri, body, headers, method='GET'):
225226
raise ValueError(response.read())
226227

227228
def handle_rate_limit(self):
229+
import random
230+
import time
228231
time.sleep(1 + random.random() * 3)
229232

230233

@@ -252,6 +255,44 @@ def stats(self, short_url):
252255
return self.request(stat_url, None, headers)
253256

254257

258+
class BufferAPI(GooglAPI):
259+
auth_uri = 'https://bufferapp.com/oauth2/authorize'
260+
refresh_uri = 'https://api.bufferapp.com/1/oauth2/token.json'
261+
scope = None
262+
service = 'buffer'
263+
data_uri = 'https://api.bufferapp.com/1/'
264+
265+
def authorization_url(self, **kwargs):
266+
# Buffer doesn't use access_type
267+
kwargs['access_type'] = None
268+
return super(BufferAPI, self).authorization_url(**kwargs)
269+
270+
def get_profiles(self):
271+
url = self.data_uri + 'profiles.json'
272+
headers = {'Content-Type' : 'application/json'}
273+
return self.request(url, None, headers)
274+
275+
def get_info(self):
276+
url = self.data_uri + 'info/configuration.json'
277+
headers = {'Content-Type' : 'application/json'}
278+
return self.request(url, None, headers)
279+
280+
def get_pending(self, profile_id):
281+
url = self.data_uri + 'profiles/%s/updates/pending.json' % profile_id
282+
headers = {'Content-Type' : 'application/json'}
283+
return self.request(url, None, headers)
284+
285+
def post_update(self, profile_ids, message):
286+
url = self.data_uri + 'updates/create.json'
287+
import urllib
288+
data = [('text', urllib.urlencode(message)), ('shorten', 1)]
289+
for pid in profile_ids:
290+
data.append(('profile_ids[]', pid))
291+
body = urllib.urlencode(data)
292+
headers = {'Content-type' : 'application/x-www-form-urlencoded'}
293+
return self.request(url, body, headers)
294+
295+
255296
class GAnalyticsAPI(GooglAPI):
256297
# OAuth API
257298
refresh_uri = 'https://accounts.google.com/o/oauth2/token'
@@ -305,40 +346,3 @@ def list(self, uid, count):
305346
headers = {'Content-Type': 'application/json'}
306347
data = self.request(url, None, headers)
307348
return data
308-
309-
310-
class BufferAPI(GooglAPI):
311-
auth_uri = 'https://bufferapp.com/oauth2/authorize'
312-
refresh_uri = 'https://api.bufferapp.com/1/oauth2/token.json'
313-
scope = None
314-
service = 'buffer'
315-
data_uri = 'https://api.bufferapp.com/1/'
316-
317-
def refresh_access_token(self, refresh_uri=None, refresh_token=None, grant_type='authorization_code'):
318-
# Buffer wants a different grant_type than Google
319-
return super(BufferAPI, self).refresh_access_token(refresh_uri=refresh_uri, refresh_token=refresh_token, grant_type=grant_type)
320-
321-
# data API
322-
def get_profiles(self):
323-
url = self.data_uri + 'profiles.json'
324-
headers = {'Content-Type' : 'application/json'}
325-
return self.request(url, None, headers)
326-
327-
def get_info(self):
328-
url = self.data_uri + 'info/configuration.json'
329-
headers = {'Content-Type' : 'application/json'}
330-
return self.request(url, None, headers)
331-
332-
def get_pending(profile_id):
333-
url = self.data_uri + 'profiles/%s/updates/pending.json' % profile_id
334-
headers = {'Content-Type' : 'application/json'}
335-
return self.request(url, None, headers)
336-
337-
def post_update(profile_ids, message):
338-
url = self.data_uri + 'updates/create.json' % profile_id
339-
import urllib
340-
data = [('text', urllib.urlencode(message)), ('shorten', 1)]
341-
for pid in profile_ids:
342-
data.append(('profile_ids[]', pid))
343-
body = urllib.urlencode(data)
344-
return self.request(url, body, headers)

test_foauth.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import mock
2+
import unittest
3+
4+
import foauth2
5+
6+
7+
class TestFoauth2(unittest.TestCase):
8+
def test_section_4_1_3(self):
9+
# test the parts we care about from http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-4.1.3
10+
client = foauth2.Client('client_id', 'client_secret')
11+
self.assertRaises(ValueError, client.authorization_url)
12+
redirect_uri = 'http://example.com/'
13+
client.authorization_url('http://example.com/oauth', redirect_uri=redirect_uri)
14+
15+
self.assertRaises(ValueError, client.redeem_code)
16+
self.assertRaises(ValueError, client.redeem_code, code='code')
17+
client._request = mock.Mock()
18+
client._request().read._return_value = '{"access_token": "token", "code": "xyzzy"}'
19+
client._request().code = 200
20+
self.assertRaises(ValueError, client.redeem_code, code='code', redirect_uri='mismatch')
21+
client.redeem_code(code='code', redirect_uri=redirect_uri)
22+
23+
client._authorization_redirect_uri = None
24+
client.redeem_code(code='code')
25+
26+
27+
if __name__ == '__main__':
28+
unittest.main()

0 commit comments

Comments
 (0)