def __init__(self, url, http_method='GET', params=None, headers={}, version=OAUTH_VERSION, timestamp_threshold=TIMESTAMP_THRESHOLD, nonce_length=NONCE_LENGTH): if params and not isinstance(params, collections.Mapping): # if its not a mapping, it must be a string params = parse_qs(params) elif not params: params = {} if 'Authorization' in headers: auth_header = headers['Authorization'] # check that the authorization header is OAuth if auth_header.index('OAuth') > -1: try: header_params = OAuthRequest._parse_auth_header(auth_header) params.update(header_params) except ValueError: raise OAuthError('Unable to parse OAuth parameters from Authorization header.') # URL parameters parts = urlparse.urlparse(url) url = '%s://%s%s' % (parts.scheme, parts.netloc, parts.path) params.update(parse_qs(parts.query)) #FIXME should this be a merge? self.http_method = http_method.upper() self.url = url self.params = params.copy() self.version = version self.timestamp_threshold = timestamp_threshold self.nonce_length = nonce_length
def need_authorization(self, request): """ The view that triggers the access token flow by sending the user to the authorization url. If you wish to show the user a message, you may provide a template named: `django_oauth_consumer/{NAME}/need_authorization.html` The template will be provided an `authorization_url` in the context. If you do not provide a template, the user will be redirected there immediately. """ response = self.make_signed_req(self.request_token_url) body = unicode(response.read(), 'utf8').strip() request_token = urlencoding.parse_qs(body) request.session[self.name + '_request_token'] = request_token qs = urlencoding.compose_qs({ 'oauth_token': request_token['oauth_token'], 'oauth_callback': request.build_absolute_uri( reverse(self.SUCCESS_VIEW_NAME, kwargs={'oauth_token': request_token['oauth_token']})), }) url = self.authorization_url if '?' in url: if url[-1] == '&': url += qs else: url += '&' + qs else: url += '?' + qs if self._has_needs_auth_template: try: return self._render('need_authorization', request, {'authorization_url': url}) except TemplateDoesNotExist: self._has_needs_auth_template = False return HttpResponseRedirect(url)
def need_authorization(self, request): """ The view that triggers the access token flow by sending the user to the authorization url. If you wish to show the user a message, you may provide a template named: `django_oauth_consumer/{NAME}/need_authorization.html` The template will be provided an `authorization_url` in the context. If you do not provide a template, the user will be redirected there immediately. """ response = self.make_signed_req(self.request_token_url) body = unicode(response.read(), "utf8").strip() request_token = urlencoding.parse_qs(body) request.session[self.name + "_request_token"] = request_token qs = urlencoding.compose_qs( { "oauth_token": request_token["oauth_token"], "oauth_callback": request.build_absolute_uri( reverse(self.SUCCESS_VIEW_NAME, kwargs={"oauth_token": request_token["oauth_token"]}) ), } ) url = self.authorization_url if "?" in url: if url[-1] == "&": url += qs else: url += "&" + qs else: url += "?" + qs if self._has_needs_auth_template: try: return self._render("need_authorization", request, {"authorization_url": url}) except TemplateDoesNotExist: self._has_needs_auth_template = False return HttpResponseRedirect(url)
def success_auth(self, request, oauth_token=None): """ The view that handles a successful OAuth Authorization flow from the user's side. The Service Provider redirect returns the user here. If you wish to show the user a message here before continuing to the original URL that triggered the access token flow, you may provide a template named: `django_oauth_consumer/{NAME}/successful_authorization.html` The template will be provided the `access_token` and the `next_url` (the original URL the user visited that triggered the access token flow). If you do not provide a template, the view will simply redirect the user back to the original URL. """ request_token = request.session[self.REQUEST_TOKEN_NAME] if request_token['oauth_token'] != oauth_token: logging.error('request token in session and url dont match') response = self.make_signed_req(self.access_token_url, token=request_token) body = unicode(response.read(), 'utf8').strip() access_token = urlencoding.parse_qs(body) self.store_access_token(request, access_token) del request.session[self.REQUEST_TOKEN_NAME] try: next_url = request.session.pop(self.NEXT_URL_NAME) except KeyError: next_url = '/' if self._has_success_auth_template: try: return self._render('successful_authorization', request, { 'access_token': access_token, 'next_url': next_url, }) except TemplateDoesNotExist: self._has_success_auth_template = False return HttpResponseRedirect(next_url)
def make_signed_req(self, url, method='GET', content={}, headers={}, token=None, request=None): """ Identical to the make_request API, and accepts an additional (optional) token parameter and request object (required if dealing with Scalable OAuth service providers). It adds the OAuth Authorization header based on the consumer set on this instance. If content not a Mapping object, it will be ignored with respect to signing. This means you need to either pass the query parameters as a dict/Mapping object, or pass a encoded query string as part of the URL which will be extracted and included in the signature. http://oauth.net/core/1.0/#rfc.section.7 Arguments: `url` The URL - query parameters will be parsed out. `method` The HTTP method to use. `content` A dict of key/values or string/unicode value. `headers` A dict of headers. `token` An optional access token. If this is provided, you will be making a 3-legged request. If it is missing, you will be making a 2-legged request. `request` *Optional*. Needed if using Scalable OAuth in order to transparently handle access token renewal. http://wiki.oauth.net/ScalableOAuth#AccessTokenRenewal """ if isinstance(content, collections.Mapping): params = content else: params = {} orequest = oauth.OAuthRequest(url, method, params) orequest.sign_request(self.sig_method, self.consumer, token) headers['Authorization'] = orequest.to_header(self.realm) response = make_request(url, method=method, content=content, headers=headers) # check if we got a scalable oauth token_expired error # we will fetch a new access token using the oauth_session_handle in # the current token and redo the request in this case. # FIXME: infinite loop www_auth = response.getheader('www-authenticate', None) if www_auth and response.status == 401 and 'token_expired' in www_auth: response = self.make_signed_req( self.access_token_url, content={'oauth_session_handle': token['oauth_session_handle']}, token=token, request=request ) body = unicode(response.read(), 'utf8').strip() new_token = urlencoding.parse_qs(body) self.store_access_token(request, new_token) return self.make_signed_req(url, method, content, headers, new_token, request) else: return response
def test_simple(self): self.assertEqual(urlencoding.parse_qs('a=1'), {'a': '1'})
def test_array_value(self): self.assertEqual( urlencoding.parse_qs('a=1&a=2'), {'a': ['1', '2']} )
def test_square_index(self): self.assertEqual( urlencoding.parse_qs('a%5Bb%5D=1&a%5Bc%5D=2'), {'a[b]': '1', 'a[c]': '2'} )
def test_space(self): self.assertEqual( urlencoding.parse_qs('a=1&b=2&c=%20d'), {'a': '1', 'b': '2', 'c': ' d'} )
def test_multi(self): self.assertEqual(urlencoding.parse_qs('a=1&b=2'), {'a': '1', 'b': '2'})
def make_signed_req(self, url, method='GET', content={}, headers={}, token=None, request=None): """ Identical to the make_request API, and accepts an additional (optional) token parameter and request object (required if dealing with Scalable OAuth service providers). It adds the OAuth Authorization header based on the consumer set on this instance. If content not a Mapping object, it will be ignored with respect to signing. This means you need to either pass the query parameters as a dict/Mapping object, or pass a encoded query string as part of the URL which will be extracted and included in the signature. http://oauth.net/core/1.0/#rfc.section.7 Arguments: `url` The URL - query parameters will be parsed out. `method` The HTTP method to use. `content` A dict of key/values or string/unicode value. `headers` A dict of headers. `token` An optional access token. If this is provided, you will be making a 3-legged request. If it is missing, you will be making a 2-legged request. `request` *Optional*. Needed if using Scalable OAuth in order to transparently handle access token renewal. http://wiki.oauth.net/ScalableOAuth#AccessTokenRenewal """ if isinstance(content, collections.Mapping): params = content else: params = {} orequest = oauth.OAuthRequest(url, method, params) orequest.sign_request(self.sig_method, self.consumer, token) headers['Authorization'] = orequest.to_header(self.realm) response = make_request(url, method=method, content=content, headers=headers) # check if we got a scalable oauth token_expired error # we will fetch a new access token using the oauth_session_handle in # the current token and redo the request in this case. # FIXME: infinite loop www_auth = response.getheader('www-authenticate', None) if www_auth and response.status == 401 and 'token_expired' in www_auth: response = self.make_signed_req(self.access_token_url, content={ 'oauth_session_handle': token['oauth_session_handle'] }, token=token, request=request) body = unicode(response.read(), 'utf8').strip() new_token = urlencoding.parse_qs(body) self.store_access_token(request, new_token) return self.make_signed_req(url, method, content, headers, new_token, request) else: return response
def prepare_request(url, method='GET', content=None, headers={}): """ This is the generic processing logic used by the various APIs. If content is a Mapping object, parameters will be processed. In this case, query parameters from the URL will be processed and merged with the content dict. They will then be appended to the URL or sent as the body based on the method. Arguments: `url` The URL - query parameters will be parsed out. `method` The HTTP method to use. `content` A dict of key/values or string/unicode value. `headers` A dict of headers. """ # we potentially modify them headers = headers.copy() parts = urlparse(url) url = parts.path if parts.params: url += ';' + parts.params # we dont do much with the url/body unless content is a Mapping object if isinstance(content, collections.Mapping): # merge the parameters in the query string if parts.query: qs_params = parse_qs(parts.query) qs_params.update(content) content = qs_params # put the content in the url or convert to string body if content: content = compose_qs(content) if method in ('HEAD', 'GET'): url += '?' + content content = None else: if 'Content-Type' not in headers: headers['Content-Type'] = 'application/x-www-form-urlencoded' else: if parts.query: url += '?' + parts.query # add Content-Length if needed if content and 'Content-Length' not in headers: headers['Content-Length'] = len(content) return { 'scheme': parts.scheme, 'netloc': parts.netloc, 'url': url, 'method': method, 'content': content, 'headers': headers, }
def prepare_request(url, method='GET', content=None, headers={}): """ This is the generic processing logic used by the various APIs. If content is a Mapping object, parameters will be processed. In this case, query parameters from the URL will be processed and merged with the content dict. They will then be appended to the URL or sent as the body based on the method. Arguments: `url` The URL - query parameters will be parsed out. `method` The HTTP method to use. `content` A dict of key/values or string/unicode value. `headers` A dict of headers. """ # we potentially modify them headers = headers.copy() parts = urlparse(url) url = parts.path if parts.params: url += ';' + parts.params # we dont do much with the url/body unless content is a Mapping object if isinstance(content, collections.Mapping): # merge the parameters in the query string if parts.query: qs_params = parse_qs(parts.query) qs_params.update(content) content = qs_params # put the content in the url or convert to string body if content: content = compose_qs(content) if method in ('HEAD', 'GET'): url += '?' + content content = None else: if 'Content-Type' not in headers: headers[ 'Content-Type'] = 'application/x-www-form-urlencoded' else: if parts.query: url += '?' + parts.query # add Content-Length if needed if content and 'Content-Length' not in headers: headers['Content-Length'] = len(content) return { 'scheme': parts.scheme, 'netloc': parts.netloc, 'url': url, 'method': method, 'content': content, 'headers': headers, }