From 21b411652c763a4f735cd657cb8276656ebe5218 Mon Sep 17 00:00:00 2001 From: David Gouldin Date: Wed, 5 May 2010 11:03:04 -0500 Subject: [PATCH 01/17] Adding Client class for OAuth 2.0 draft spec: https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-3.5.2.1 --- oauth2/__init__.py | 142 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 5f60b6e5..6288d92b 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -711,3 +711,145 @@ def sign(self, request, consumer, token): key, raw = self.signing_base(request, consumer, token) return raw +class Client2(object): + """Client for OAuth 2.0 draft spec + https://svn.tools.ietf.org/html/draft-hammer-oauth2-00 + """ + + def __init__(self, client_id, client_secret, oauth_base_url, + redirect_uri=None, cache=None, timeout=None, proxy_info=None): + + self.client_id = client_id + self.client_secret = client_secret + self.redirect_uri = redirect_uri + self.oauth_base_url = oauth_base_url + + if self.client_id is None or self.client_secret is None or \ + self.oauth_base_url is None: + raise ValueError("Client_id and client_secret must be set.") + + self.http = httplib2.Http(cache=cache, timeout=timeout, + proxy_info=proxy_info) + + @staticmethod + def _split_url_string(param_str): + """Turn URL string into parameters.""" + parameters = parse_qs(param_str, keep_blank_values=False) + for key, val in parameters.iteritems(): + parameters[key] = urllib.unquote(val[0]) + return parameters + + def authorization_url(self, redirect_uri=None, params=None, state=None, + immediate=None, endpoint='authorize'): + """Get the URL to redirect the user for client authorization + https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-3.5.2.1 + """ + + # prepare required args + args = { + 'type': 'web_server', + 'client_id': self.client_id, + } + + # prepare optional args + redirect_uri = redirect_uri or self.redirect_uri + if redirect_uri is not None: + args['redirect_uri'] = redirect_uri + if state is not None: + args['state'] = state + if immediate is not None: + args['immediate'] = str(immediate).lower() + + args.update(params or {}) + + return '%s?%s' % (urlparse.urljoin(self.oauth_base_url, endpoint), + urllib.urlencode(args)) + + def access_token(self, code, redirect_uri, secret_type=None, + endpoint='access_token'): + """Get an access token from the supplied code + https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-3.5.2.2 + """ + + # prepare required args + if code is None: + raise ValueError("Code must be set.") + if redirect_uri is None: + raise ValueError("Redirect_uri must be set.") + args = { + 'type': 'web_server', + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'code': code, + 'redirect_uri': redirect_uri, + } + + # prepare optional args + if secret_type is not None: + args['secret_type'] = secret_type + + uri = urlparse.urljoin(self.oauth_base_url, endpoint) + body = urllib.urlencode(args) + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + } + + response, content = self.http.request(uri, method='POST', body=body, + headers=headers) + if not response.status == 200: + raise Error(content) + response_args = Client2._split_url_string(content) + + error = response_args.pop('error', None) + if error is not None: + raise Error(error) + + refresh_token = response_args.pop('refresh_token', None) + if refresh_token is not None: + response_args = self.refresh(refresh_token, secret_type=secret_type) + return response_args + + def refresh(self, refresh_token, secret_type=None, endpoint='access_token'): + """Get a new access token from the supplied refresh token + https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-4 + """ + + if refresh_token is None: + raise ValueError("Refresh_token must be set.") + + # prepare required args + args = { + 'type': 'refresh', + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'refresh_token': refresh_token, + } + + # prepare optional args + if secret_type is not None: + args['secret_type'] = secret_type + + uri = urlparse.urljoin(self.oauth_base_url, endpoint) + body = urllib.urlencode(args) + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + } + + response, content = self.http.request(uri, method='POST', body=body, + headers=headers) + if not response.status == 200: + raise Error(content) + + response_args = Client2._split_url_string(content) + return response_args + + def request(self, base_uri, access_token=None, method='GET', body=None, + headers=None, params=None, token_param='oauth_token'): + """Make a request to the OAuth API""" + + args = {} + args.update(params or {}) + if access_token is not None and method == 'GET': + args[token_param] = access_token + uri = '%s?%s' % (base_uri, urllib.urlencode(args)) + return self.http.request(uri, method=method, body=body, headers=headers) From f5f5b5dbe097ff9c8d93fcba5f2eb7ccecbfde2b Mon Sep 17 00:00:00 2001 From: David Gouldin Date: Fri, 11 Feb 2011 11:26:52 -0600 Subject: [PATCH 02/17] Removing trailing whitespace. --- oauth2/__init__.py | 114 ++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 6288d92b..de1d8bca 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -85,11 +85,11 @@ def generate_verifier(length=8): class Consumer(object): """A consumer of OAuth-protected services. - + The OAuth consumer is a "third-party" service that wants to access protected resources from an OAuth service provider on behalf of an end user. It's kind of the OAuth client. - + Usually a consumer must be registered with the service provider by the developer of the consumer software. As part of that process, the service provider gives the consumer a *key* and a *secret* with which the consumer @@ -97,7 +97,7 @@ class Consumer(object): key in each request to identify itself, but will use its secret only when signing requests, to prove that the request is from that particular registered consumer. - + Once registered, the consumer can then use its consumer credentials to ask the service provider for a request token, kicking off the OAuth authorization process. @@ -125,12 +125,12 @@ def __str__(self): class Token(object): """An OAuth credential used to request authorization or a protected resource. - + Tokens in OAuth comprise a *key* and a *secret*. The key is included in requests to identify the token being used, but the secret is used only in the signature, to prove that the requester is who the server gave the token to. - + When first negotiating the authorization, the consumer asks for a *request token* that the live user authorizes with the service provider. The consumer then exchanges the request token for an *access token* that can @@ -175,7 +175,7 @@ def get_callback_url(self): def to_string(self): """Returns this token as a plain string, suitable for storage. - + The resulting string includes the token's secret, so you should never send or store this string where a third party can read it. """ @@ -188,7 +188,7 @@ def to_string(self): if self.callback_confirmed is not None: data['oauth_callback_confirmed'] = self.callback_confirmed return urllib.urlencode(data) - + @staticmethod def from_string(s): """Deserializes a token from a string like one returned by @@ -209,7 +209,7 @@ def from_string(s): try: secret = params['oauth_token_secret'][0] except Exception: - raise ValueError("'oauth_token_secret' not found in " + raise ValueError("'oauth_token_secret' not found in " "OAuth request.") token = Token(key, secret) @@ -225,39 +225,39 @@ def __str__(self): def setter(attr): name = attr.__name__ - + def getter(self): try: return self.__dict__[name] except KeyError: raise AttributeError(name) - + def deleter(self): del self.__dict__[name] - + return property(getter, attr, deleter) class Request(dict): - + """The parameters and information for an HTTP request, suitable for authorizing with OAuth credentials. - + When a consumer wants to access a service's protected resources, it does so using a signed HTTP request identifying itself (the consumer) with its key, and providing an access token authorized by the end user to access those resources. - + """ - + version = VERSION - + def __init__(self, method=HTTP_METHOD, url=None, parameters=None): self.method = method self.url = url if parameters is not None: self.update(parameters) - + @setter def url(self, value): self.__dict__['url'] = value @@ -277,40 +277,40 @@ def url(self, value): else: self.normalized_url = None self.__dict__['url'] = None - + @setter def method(self, value): self.__dict__['method'] = value.upper() - + def _get_timestamp_nonce(self): return self['oauth_timestamp'], self['oauth_nonce'] - + def get_nonoauth_parameters(self): """Get any non-OAuth parameters.""" - return dict([(k, v) for k, v in self.iteritems() + return dict([(k, v) for k, v in self.iteritems() if not k.startswith('oauth_')]) - + def to_header(self, realm=''): """Serialize as a header for an HTTPAuth request.""" - oauth_params = ((k, v) for k, v in self.items() + oauth_params = ((k, v) for k, v in self.items() if k.startswith('oauth_')) stringy_params = ((k, escape(str(v))) for k, v in oauth_params) header_params = ('%s="%s"' % (k, v) for k, v in stringy_params) params_header = ', '.join(header_params) - + auth_header = 'OAuth realm="%s"' % realm if params_header: auth_header = "%s, %s" % (auth_header, params_header) - + return {'Authorization': auth_header} - + def to_postdata(self): """Serialize as post data for a POST request.""" # tell urlencode to deal with sequence values and map them correctly # to resulting querystring. for example self["k"] = ["v1", "v2"] will # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D return urllib.urlencode(self, True) - + def to_url(self): """Serialize as a URL for a GET request.""" base_url = urlparse.urlparse(self.url) @@ -327,7 +327,7 @@ def get_parameter(self, parameter): raise Error('Parameter not found: %s' % parameter) return ret - + def get_normalized_parameters(self): """Return a string that contains the parameters that must be signed.""" items = [] @@ -351,7 +351,7 @@ def get_normalized_parameters(self): # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6) # Spaces must be encoded with "%20" instead of "+" return encoded_str.replace('+', '%20') - + def sign_request(self, signature_method, consumer, token): """Set the signature parameter to the result of sign.""" @@ -363,24 +363,24 @@ def sign_request(self, signature_method, consumer, token): self['oauth_signature_method'] = signature_method.name self['oauth_signature'] = signature_method.sign(self, consumer, token) - + @classmethod def make_timestamp(cls): """Get seconds since epoch (UTC).""" return str(int(time.time())) - + @classmethod def make_nonce(cls): """Generate pseudorandom number.""" return str(random.randint(0, 100000000)) - + @classmethod def from_request(cls, http_method, http_url, headers=None, parameters=None, query_string=None): """Combines multiple parameter sources.""" if parameters is None: parameters = {} - + # Headers if headers and 'Authorization' in headers: auth_header = headers['Authorization'] @@ -394,59 +394,59 @@ def from_request(cls, http_method, http_url, headers=None, parameters=None, except: raise Error('Unable to parse OAuth parameters from ' 'Authorization header.') - + # GET or POST query string. if query_string: query_params = cls._split_url_string(query_string) parameters.update(query_params) - + # URL parameters. param_str = urlparse.urlparse(http_url)[4] # query url_params = cls._split_url_string(param_str) parameters.update(url_params) - + if parameters: return cls(http_method, http_url, parameters) - + return None - + @classmethod def from_consumer_and_token(cls, consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} - + defaults = { 'oauth_consumer_key': consumer.key, 'oauth_timestamp': cls.make_timestamp(), 'oauth_nonce': cls.make_nonce(), 'oauth_version': cls.version, } - + defaults.update(parameters) parameters = defaults - + if token: parameters['oauth_token'] = token.key if token.verifier: parameters['oauth_verifier'] = token.verifier - + return Request(http_method, http_url, parameters) - + @classmethod - def from_token_and_callback(cls, token, callback=None, + def from_token_and_callback(cls, token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): if not parameters: parameters = {} - + parameters['oauth_token'] = token.key - + if callback: parameters['oauth_callback'] = callback - + return cls(http_method, http_url, parameters) - + @staticmethod def _split_header(header): """Turn Authorization: header into parameters.""" @@ -463,7 +463,7 @@ def _split_header(header): # Remove quotes and unescape the value. params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) return params - + @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" @@ -476,7 +476,7 @@ def _split_url_string(param_str): class Server(object): """A skeletal implementation of a service provider, providing protected resources to requests from authorized consumers. - + This class implements the logic to check requests for authorization. You can use it with your web server or web framework to protect certain resources with OAuth. @@ -552,7 +552,7 @@ def _check_signature(self, request, consumer, token): if not valid: key, base = signature_method.signing_base(request, consumer, token) - raise Error('Invalid signature. Expected signature base ' + raise Error('Invalid signature. Expected signature base ' 'string: %s' % base) built = signature_method.sign(request, consumer, token) @@ -583,7 +583,7 @@ def __init__(self, consumer, token=None, cache=None, timeout=None, self.token = token self.method = SignatureMethod_HMAC_SHA1() - httplib2.Http.__init__(self, cache=cache, timeout=timeout, + httplib2.Http.__init__(self, cache=cache, timeout=timeout, proxy_info=proxy_info) def set_signature_method(self, method): @@ -592,7 +592,7 @@ def set_signature_method(self, method): self.method = method - def request(self, uri, method="GET", body=None, headers=None, + def request(self, uri, method="GET", body=None, headers=None, redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded' @@ -623,14 +623,14 @@ def request(self, uri, method="GET", body=None, headers=None, else: headers.update(req.to_header()) - return httplib2.Http.request(self, uri, method=method, body=body, - headers=headers, redirections=redirections, + return httplib2.Http.request(self, uri, method=method, body=body, + headers=headers, redirections=redirections, connection_type=connection_type) class SignatureMethod(object): """A way of signing requests. - + The OAuth protocol lets consumers and service providers pick a way to sign requests. This interface shows the methods expected by the other `oauth` modules for signing requests. Subclass it and implement its methods to @@ -666,7 +666,7 @@ def check(self, request, consumer, token, signature): class SignatureMethod_HMAC_SHA1(SignatureMethod): name = 'HMAC-SHA1' - + def signing_base(self, request, consumer, token): sig = ( escape(request.method), From c2bcc54919b6a43d1f3032d6942b372e076a910e Mon Sep 17 00:00:00 2001 From: David Gouldin Date: Fri, 11 Feb 2011 11:29:15 -0600 Subject: [PATCH 03/17] Adding params kwarg to access_token and refresh as an escape hatch for OAuth 2.0 providers who don't strictly adhere to the draft spec. --- oauth2/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index de1d8bca..a3a4f687 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -765,7 +765,7 @@ def authorization_url(self, redirect_uri=None, params=None, state=None, return '%s?%s' % (urlparse.urljoin(self.oauth_base_url, endpoint), urllib.urlencode(args)) - def access_token(self, code, redirect_uri, secret_type=None, + def access_token(self, code, redirect_uri, params=None, secret_type=None, endpoint='access_token'): """Get an access token from the supplied code https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-3.5.2.2 @@ -788,6 +788,8 @@ def access_token(self, code, redirect_uri, secret_type=None, if secret_type is not None: args['secret_type'] = secret_type + args.update(params or {}) + uri = urlparse.urljoin(self.oauth_base_url, endpoint) body = urllib.urlencode(args) headers = { @@ -809,7 +811,8 @@ def access_token(self, code, redirect_uri, secret_type=None, response_args = self.refresh(refresh_token, secret_type=secret_type) return response_args - def refresh(self, refresh_token, secret_type=None, endpoint='access_token'): + def refresh(self, refresh_token, params=None, secret_type=None, + endpoint='access_token'): """Get a new access token from the supplied refresh token https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-4 """ @@ -829,6 +832,8 @@ def refresh(self, refresh_token, secret_type=None, endpoint='access_token'): if secret_type is not None: args['secret_type'] = secret_type + args.update(params or {}) + uri = urlparse.urljoin(self.oauth_base_url, endpoint) body = urllib.urlencode(args) headers = { From ea8bc5f62b6db773088c534340047e089e00c637 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 12:32:41 -0800 Subject: [PATCH 04/17] Handle json responses for oauth2 access token requests --- oauth2/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index a3a4f687..49c5a670 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -739,6 +739,12 @@ def _split_url_string(param_str): parameters[key] = urllib.unquote(val[0]) return parameters + @staticmethod + def _get_json(data): + """Turn json response into hash.""" + return json.loads(data) + + def authorization_url(self, redirect_uri=None, params=None, state=None, immediate=None, endpoint='authorize'): """Get the URL to redirect the user for client authorization @@ -800,7 +806,11 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, headers=headers) if not response.status == 200: raise Error(content) - response_args = Client2._split_url_string(content) + + if resp['content-type'] == 'application/json': + response_args = Client2._get_json(content) + else: + response_args = Client2._split_url_string(content) error = response_args.pop('error', None) if error is not None: From fe07f538db11320700148f441c391affdfcda5f8 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 12:34:53 -0800 Subject: [PATCH 05/17] Oops, copy and paste error --- oauth2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 49c5a670..a202ab32 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -807,7 +807,7 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) - if resp['content-type'] == 'application/json': + if response['content-type'] == 'application/json': response_args = Client2._get_json(content) else: response_args = Client2._split_url_string(content) From fc38975673c89ac7f66ea6560979013843b017b9 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 12:36:51 -0800 Subject: [PATCH 06/17] Need to include json lib to parse json responses from modern servers --- oauth2/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index a202ab32..490dddf3 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -29,6 +29,7 @@ import hmac import binascii import httplib2 +import json try: from urlparse import parse_qs, parse_qsl From 11f6ee2d334da340d0e1b6d55629dd753c75352d Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 12:59:03 -0800 Subject: [PATCH 07/17] Better way to test for json in response --- oauth2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 490dddf3..911c033a 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -808,7 +808,7 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) - if response['content-type'] == 'application/json': + if "json" in response['content-type']: response_args = Client2._get_json(content) else: response_args = Client2._split_url_string(content) From b26f6a82a49fe557d6ee53189806948b78201e59 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:04:13 -0800 Subject: [PATCH 08/17] A little test --- oauth2/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 911c033a..18bf3b57 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -808,10 +808,10 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) - if "json" in response['content-type']: - response_args = Client2._get_json(content) - else: - response_args = Client2._split_url_string(content) +# if "json" in response['content-type']: + response_args = Client2._get_json(content) +# else: +# response_args = Client2._split_url_string(content) error = response_args.pop('error', None) if error is not None: From d71c8c6014940f85c88e2b9c883c2aa5951d8824 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:07:00 -0800 Subject: [PATCH 09/17] Revert "A little test" This reverts commit b26f6a82a49fe557d6ee53189806948b78201e59. --- oauth2/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 18bf3b57..911c033a 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -808,10 +808,10 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) -# if "json" in response['content-type']: - response_args = Client2._get_json(content) -# else: -# response_args = Client2._split_url_string(content) + if "json" in response['content-type']: + response_args = Client2._get_json(content) + else: + response_args = Client2._split_url_string(content) error = response_args.pop('error', None) if error is not None: From 574c8d2438744fd78fbc2a2aecf1d01623f5d8ed Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:32:24 -0800 Subject: [PATCH 10/17] Assign to var before returning for debugging --- oauth2/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 911c033a..9f4b45ba 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -743,7 +743,8 @@ def _split_url_string(param_str): @staticmethod def _get_json(data): """Turn json response into hash.""" - return json.loads(data) + result = json.loads(data) + return result def authorization_url(self, redirect_uri=None, params=None, state=None, From 6a47f1c18e1d120e5c99c21c93b7d5e5a0f0ba7d Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:37:33 -0800 Subject: [PATCH 11/17] Assign to var before returning for debugging --- oauth2/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 9f4b45ba..7a2d7b18 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -809,6 +809,8 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) + print response['content-type'] + if "json" in response['content-type']: response_args = Client2._get_json(content) else: From 53d5b63883ba3f55f6dc97fab4f72862de10a11c Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:40:08 -0800 Subject: [PATCH 12/17] Assign to var before returning for debugging --- oauth2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 7a2d7b18..298b2eda 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -809,7 +809,7 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) - print response['content-type'] + print content if "json" in response['content-type']: response_args = Client2._get_json(content) From 479dee78e38469a0342509785c8ab685cbba17cf Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:42:27 -0800 Subject: [PATCH 13/17] More debugging --- oauth2/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 298b2eda..c55433c6 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -809,10 +809,9 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if not response.status == 200: raise Error(content) - print content - if "json" in response['content-type']: response_args = Client2._get_json(content) + print response_args else: response_args = Client2._split_url_string(content) From 1dac8048ecf761968a60280f47bc61970c880213 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 13:53:50 -0800 Subject: [PATCH 14/17] Remove debugging info --- oauth2/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index c55433c6..9f4b45ba 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -811,7 +811,6 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if "json" in response['content-type']: response_args = Client2._get_json(content) - print response_args else: response_args = Client2._split_url_string(content) From d13f203fd3fdcaa64615ce35f6ad0f03ae7f59f3 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 15:12:13 -0800 Subject: [PATCH 15/17] Update readme with OAuth2 usage --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 20f3123f..72fad64c 100644 --- a/README.md +++ b/README.md @@ -295,3 +295,20 @@ and code here might need to be updated if you are using Python 2.6+. * You'll likely want to set `LOGIN_URL` to `/login/` so that users are properly redirected to your Twitter login handler when you use `@login_required` in other parts of your Django app. * You can also set `AUTH_PROFILE_MODULE = 'mytwitterapp.Profile'` so that you can easily access the Twitter OAuth token/secret for that user using the `User.get_profile()` method in Django. + +# OAuth2 Example + +You might have thought from the name "oauth2" that this libary supported +the OAuth2 standard. You would have been wrong, except for the fine efforts +of https://github.com/dgouldin. Here is how you use it: + + import oauth2 + client = oauth2.Client2(CONSUMER_KEY, CONSUMER_SECRET, AUTHORIZATION_URL) + auth_url = client.authorization_url(redirect_uri = CALLBACK_URL) + print auth_url + # navigate to auth_url, to obtain code + token = client.access_token(code, CALLBACK_URL, endpoint='token')["access_token"] + print token + # use token to call secure APIs + (headers, content) = client.request(RESOURCE_URL, access_token=token) + ... From cb49c6f0e98965aeddaba35f91cc6aba4b558c23 Mon Sep 17 00:00:00 2001 From: deadprogrammer Date: Mon, 9 Jan 2012 12:32:41 -0800 Subject: [PATCH 16/17] Handle json responses for oauth2 access token requests, and update README with OAuth2 example --- README.md | 17 +++++++++++++++++ oauth2/__init__.py | 14 +++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20f3123f..72fad64c 100644 --- a/README.md +++ b/README.md @@ -295,3 +295,20 @@ and code here might need to be updated if you are using Python 2.6+. * You'll likely want to set `LOGIN_URL` to `/login/` so that users are properly redirected to your Twitter login handler when you use `@login_required` in other parts of your Django app. * You can also set `AUTH_PROFILE_MODULE = 'mytwitterapp.Profile'` so that you can easily access the Twitter OAuth token/secret for that user using the `User.get_profile()` method in Django. + +# OAuth2 Example + +You might have thought from the name "oauth2" that this libary supported +the OAuth2 standard. You would have been wrong, except for the fine efforts +of https://github.com/dgouldin. Here is how you use it: + + import oauth2 + client = oauth2.Client2(CONSUMER_KEY, CONSUMER_SECRET, AUTHORIZATION_URL) + auth_url = client.authorization_url(redirect_uri = CALLBACK_URL) + print auth_url + # navigate to auth_url, to obtain code + token = client.access_token(code, CALLBACK_URL, endpoint='token')["access_token"] + print token + # use token to call secure APIs + (headers, content) = client.request(RESOURCE_URL, access_token=token) + ... diff --git a/oauth2/__init__.py b/oauth2/__init__.py index a3a4f687..9f4b45ba 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -29,6 +29,7 @@ import hmac import binascii import httplib2 +import json try: from urlparse import parse_qs, parse_qsl @@ -739,6 +740,13 @@ def _split_url_string(param_str): parameters[key] = urllib.unquote(val[0]) return parameters + @staticmethod + def _get_json(data): + """Turn json response into hash.""" + result = json.loads(data) + return result + + def authorization_url(self, redirect_uri=None, params=None, state=None, immediate=None, endpoint='authorize'): """Get the URL to redirect the user for client authorization @@ -800,7 +808,11 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, headers=headers) if not response.status == 200: raise Error(content) - response_args = Client2._split_url_string(content) + + if "json" in response['content-type']: + response_args = Client2._get_json(content) + else: + response_args = Client2._split_url_string(content) error = response_args.pop('error', None) if error is not None: From 9348493e90bdd5b392a16b8605ba32c68f70ab72 Mon Sep 17 00:00:00 2001 From: Artem Rizhov Date: Mon, 27 Feb 2012 22:02:14 +0200 Subject: [PATCH 17/17] Removed call to refresh() in access_token() --- oauth2/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index b49e39ed..f95a8c6c 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -967,9 +967,6 @@ def access_token(self, code, redirect_uri, params=None, secret_type=None, if error is not None: raise Error(error) - refresh_token = response_args.pop('refresh_token', None) - if refresh_token is not None: - response_args = self.refresh(refresh_token, secret_type=secret_type) return response_args def refresh(self, refresh_token, params=None, secret_type=None,