Skip to content

Commit

Permalink
Merge pull request #201 from squirly/#200
Browse files Browse the repository at this point in the history
OAuth2 API cleanup to match OAuth1. Proposal for Issue #200
  • Loading branch information
ib-lundgren committed Aug 3, 2013
2 parents b79af65 + f538ea8 commit 1122945
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 146 deletions.
23 changes: 10 additions & 13 deletions docs/oauth2/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ Grant and the Client Credentials Grant.

except FatalClientError as e:
# this is your custom error page
from your_views import authorization_error_page_uri
# Use in_uri to embed error code and description in the redirect uri
redirect(e.in_uri(authorization_error_page_uri))
from your_view_helpers import error_to_response
return error_to_response(e)


**Post Authorization Request**
Expand All @@ -107,23 +106,22 @@ Grant and the Client Credentials Grant.
scopes = request.POST.get('scopes')

from oauthlib.oauth2 import FatalClientError, OAuth2Error
from your_framework import redirect
from your_framework import http_response
http_response(body, status=status, headers=headers)
try:
uri, headers, body, status = server.create_authorization_response(
headers, body, status = server.create_authorization_response(
uri, http_method, body, headers, scopes, credentials)
# uri = https://foo.com/welcome_back?code=somerandomstring&state=xyz
# headers = {}, this might change to include suggested headers related
# headers = {'Location': 'https://foo.com/welcome_back?code=somerandomstring&state=xyz'}, this might change to include suggested headers related
# to cache best practices etc.
# body = '', this might be set in future custom grant types
# status = 302, suggested HTTP status code

redirect(uri, headers=headers, status=status, body=body)
return http_response(body, status=status, headers=headers)

except FatalClientError as e:
# this is your custom error page
from your_views import authorization_error_page_uri
# Use in_uri to embed error code and description in the redirect uri
redirect(e.in_uri(authorization_error_page_uri))
from your_view_helpers import error_to_response
return error_to_response(e)

except OAuth2Error as e:
# Less grave errors will be reported back to client
Expand Down Expand Up @@ -181,10 +179,9 @@ tokens which unless you are certain you need them, are a bad idea.
# Extra credentials you wish to include
credentials = {'client_ip': '1.2.3.4'}

uri, headers, body, status = server.create_token_response(
headers, body, status = server.create_token_response(
uri, http_method, body, headers, credentials)

# uri is not used by most grant types
# headers will contain some suggested headers to add to your response
{
'Content-Type': 'application/json;charset=UTF-8',
Expand Down
34 changes: 16 additions & 18 deletions docs/oauth2/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ as well as provide an interface for a backend to store tokens, clients, etc.
def __init__(self):
# Using the server from previous section
self._authorization_endpoint = server
self._error_uri = '/error'
def get(self, request):
# You need to define extract_params and make sure it does not
Expand Down Expand Up @@ -288,7 +287,7 @@ as well as provide an interface for a backend to store tokens, clients, etc.
# Errors that should be shown to the user on the provider website
except errors.FatalClientError as e:
return HttpResponseRedirect(e.in_uri(self._error_uri))
return response_from_error(e)
# Errors embedded in the redirect URI back to the client
except errors.OAuth2Error as e:
Expand All @@ -297,7 +296,7 @@ as well as provide an interface for a backend to store tokens, clients, etc.
@csrf_exempt
def post(self, request):
uri, http_method, body, headers = extract_params(request)
# The scopes the user actually authorized, i.e. checkboxes
# that were selected.
scopes = request.POST.getlist(['scopes'])
Expand All @@ -309,15 +308,12 @@ as well as provide an interface for a backend to store tokens, clients, etc.
credentials.update(request.session.get('oauth2_credentials', {}))
try:
url, headers, body, status = self._authorization_endpoint.create_authorization_response(
headers, body, status = self._authorization_endpoint.create_authorization_response(
uri, http_method, body, headers, scopes, credentials)
return HttpResponseRedirect(url)
return response_from_return(headers, body, status)
except errors.FatalClientError as e:
return HttpResponseRedirect(e.in_uri(self._error_uri))
except errors.OAuth2Error as e:
return HttpResponseRedirect(e.in_uri(redirect_uri))
return response_from_error(e)
# Handles requests to /token
class TokenView(View):
Expand All @@ -333,21 +329,23 @@ as well as provide an interface for a backend to store tokens, clients, etc.
# use in the validator, do so here.
credentials = {'foo': 'bar'}
url, headers, body, status = self._token_endpoint.create_token_response(
headers, body, status = self._token_endpoint.create_token_response(
uri, http_method, body, headers, credentials)
# All requests to /token will return a json response, no redirection.
response = HttpResponse(content=body, status=status)
for k, v in headers.items():
response[k] = v
return response
return response_from_return(headers, body, status)
class ErrorView(View):
response = HttpResponse()
response.write('Evil client is unable to send a proper request.')
def response_from_return(headers, body, status):
response = HttpResponse(content=body, status=status)
for k, v in headers.items():
response[k] = v
return response
def response_from_error(e)
return HttpResponseBadRequest('Evil client is unable to send a proper request. Error is: ' + e.description)
**5. Protect your APIs using scopes**

Let's define a decorator we can use to protect the views.
Expand Down
4 changes: 2 additions & 2 deletions oauthlib/oauth2/rfc6749/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def wrapper(endpoint, uri, *args, **kwargs):
if not endpoint.available:
e = TemporarilyUnavailableError()
log.info('Endpoint unavailable, ignoring request %s.' % uri)
return None, {}, e.json, 503
return {}, e.json, 503

if endpoint.catch_errors:
try:
Expand All @@ -55,7 +55,7 @@ def wrapper(endpoint, uri, *args, **kwargs):
except Exception as e:
error = ServerError()
log.warning('Exception caught while processing request, %s.' % e)
return None, {}, error.json, 500
return {}, error.json, 500
else:
return f(endpoint, uri, *args, **kwargs)
return wrapper
4 changes: 2 additions & 2 deletions oauthlib/oauth2/rfc6749/endpoints/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def wrapper(endpoint, uri, *args, **kwargs):
if not endpoint.available:
e = TemporarilyUnavailableError()
log.info('Endpoint unavailable, ignoring request %s.' % uri)
return None, {}, e.json, 503
return {}, e.json, 503

if endpoint.catch_errors:
try:
Expand All @@ -56,7 +56,7 @@ def wrapper(endpoint, uri, *args, **kwargs):
except Exception as e:
error = ServerError()
log.warning('Exception caught while processing request, %s.' % e)
return None, {}, error.json, 500
return {}, error.json, 500
else:
return f(endpoint, uri, *args, **kwargs)
return wrapper
10 changes: 5 additions & 5 deletions oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def create_authorization_response(self, request, token_handler):
:param request: oauthlib.commong.Request
:param token_handler: A token handler instace, for example of type
oauthlib.oauth2.BearerToken.
:returns: uri, headers, body, status
:returns: headers, body, status
:raises: FatalClientError on invalid redirect URI or client id.
ValueError if scopes are not set on the request object.
Expand Down Expand Up @@ -203,12 +203,12 @@ def create_authorization_response(self, request, token_handler):
except errors.OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
request.redirect_uri = request.redirect_uri or self.error_uri
return common.add_params_to_uri(request.redirect_uri, e.twotuples), None, None, e.status_code
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples)}, None, 302

grant = self.create_authorization_code(request)
log.debug('Saving grant %r for %r.', grant, request)
self.request_validator.save_authorization_code(request.client_id, grant, request)
return common.add_params_to_uri(request.redirect_uri, grant.items()), None, None, 302
return {'Location': common.add_params_to_uri(request.redirect_uri, grant.items())}, None, 302

def create_token_response(self, request, token_handler):
"""Validate the authorization code.
Expand All @@ -229,12 +229,12 @@ def create_token_response(self, request, token_handler):
log.debug('Token request validation ok for %r.', request)
except errors.OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
return None, headers, e.json, e.status_code
return headers, e.json, e.status_code

token = token_handler.create_token(request, refresh_token=True)
self.request_validator.invalidate_authorization_code(
request.client_id, request.code, request)
return None, headers, json.dumps(token), 200
return headers, json.dumps(token), 200

def validate_authorization_request(self, request):
"""Check the authorization request for normal and fatal errors.
Expand Down
4 changes: 2 additions & 2 deletions oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ def create_token_response(self, request, token_handler):
self.validate_token_request(request)
except errors.OAuth2Error as e:
log.debug('Client error in token request. %s.', e)
return None, {}, e.json, e.status_code
return {}, e.json, e.status_code

token = token_handler.create_token(request, refresh_token=False)
log.debug('Issuing token to client id %r (%r), %r.',
request.client_id, request.client, token)
return None, {}, json.dumps(token), 200
return {}, json.dumps(token), 200

def validate_token_request(self, request):
if not getattr(request, 'grant_type'):
Expand Down
8 changes: 4 additions & 4 deletions oauthlib/oauth2/rfc6749/grant_types/implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,12 @@ def create_token_response(self, request, token_handler):
# http://tools.ietf.org/html/rfc6749#appendix-B
except errors.OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
return common.add_params_to_uri(request.redirect_uri, e.twotuples,
fragment=True), {}, None, e.status_code
return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
fragment=True)}, None, 302

token = token_handler.create_token(request, refresh_token=False)
return common.add_params_to_uri(request.redirect_uri, token.items(),
fragment=True), {}, None, 302
return {'Location': common.add_params_to_uri(request.redirect_uri, token.items(),
fragment=True)}, None, 302

def validate_authorization_request(self, request):
return self.validate_token_request(request)
Expand Down
4 changes: 2 additions & 2 deletions oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ def create_token_response(self, request, token_handler):
log.debug('Validating refresh token request, %r.', request)
self.validate_token_request(request)
except errors.OAuth2Error as e:
return None, headers, e.json, e.status_code
return headers, e.json, e.status_code

token = token_handler.create_token(request,
refresh_token=self.issue_new_refresh_tokens)
log.debug('Issuing new token to client id %r (%r), %r.',
request.client_id, request.client, token)
return None, headers, json.dumps(token), 200
return headers, json.dumps(token), 200

def validate_token_request(self, request):
# REQUIRED. Value MUST be set to "refresh_token".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ def create_token_response(self, request, token_handler,
self.validate_token_request(request)
except errors.OAuth2Error as e:
log.debug('Client error in token request, %s.', e)
return None, headers, e.json, e.status_code
return headers, e.json, e.status_code

token = token_handler.create_token(request, refresh_token=True)
log.debug('Issuing token %r to client id %r (%r) and username %s.',
token, request.client_id, request.client, request.username)
return None, headers, json.dumps(token), 200
return headers, json.dumps(token), 200

def validate_token_request(self, request):
"""
Expand Down
22 changes: 12 additions & 10 deletions tests/oauth2/rfc6749/test_grant_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def test_create_authorization_grant(self):

def test_create_token_response(self):
bearer = BearerToken(self.mock_validator)
u, h, token, s = self.auth.create_token_response(self.request, bearer)
h, token, s = self.auth.create_token_response(self.request, bearer)
token = json.loads(token)
self.assertIn('access_token', token)
self.assertIn('refresh_token', token)
Expand Down Expand Up @@ -122,10 +122,12 @@ def test_create_token_response(self):
orig_generate_token = common.generate_token
self.addCleanup(setattr, common, 'generate_token', orig_generate_token)
common.generate_token = lambda *args, **kwargs: '1234'
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
correct_uri = 'https://b.c/p#access_token=1234&token_type=Bearer&expires_in=1800&state=xyz&scope=hello+world'
self.assertURLEqual(uri, correct_uri, parse_fragment=True)
self.assertEqual(status_code, 302)
self.assertIn('Location', headers)
self.assertURLEqual(headers['Location'], correct_uri, parse_fragment=True)

def test_error_response(self):
pass
Expand All @@ -148,7 +150,7 @@ def setUp(self):

def test_create_token_response(self):
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertIn('access_token', token)
Expand Down Expand Up @@ -178,7 +180,7 @@ def setUp(self):

def test_create_token_response(self):
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertIn('access_token', token)
Expand Down Expand Up @@ -210,7 +212,7 @@ def setUp(self):
def test_create_token_response(self):
self.mock_validator.get_original_scopes.return_value = ['foo', 'bar']
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertIn('access_token', token)
Expand All @@ -222,7 +224,7 @@ def test_create_token_inherit_scope(self):
self.request.scope = None
self.mock_validator.get_original_scopes.return_value = ['foo', 'bar']
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertIn('access_token', token)
Expand All @@ -233,7 +235,7 @@ def test_create_token_inherit_scope(self):
def test_invalid_scope(self):
self.mock_validator.get_original_scopes.return_value = ['baz']
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertEqual(token['error'], 'invalid_scope')
Expand All @@ -242,7 +244,7 @@ def test_invalid_scope(self):
def test_invalid_token(self):
self.mock_validator.validate_refresh_token.return_value = False
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertEqual(token['error'], 'invalid_grant')
Expand All @@ -251,7 +253,7 @@ def test_invalid_token(self):
def test_invalid_client(self):
self.mock_validator.authenticate_client.return_value = False
bearer = BearerToken(self.mock_validator)
uri, headers, body, status_code = self.auth.create_token_response(
headers, body, status_code = self.auth.create_token_response(
self.request, bearer)
token = json.loads(body)
self.assertEqual(token['error'], 'invalid_client')
Expand Down
Loading

0 comments on commit 1122945

Please sign in to comment.