summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Huot <JonathanHuot@users.noreply.github.com>2018-12-13 14:08:00 +0100
committerGitHub <noreply@github.com>2018-12-13 14:08:00 +0100
commit5d9a9c90ba04f85477c7859a3cc7b13577fc24f9 (patch)
tree6b63ff661f35855b7d95d14ecad76bd7e25470b9
parent9130131f0718d783c7ae326b786c3aa95b269047 (diff)
parentc0e9f781e82bf70f904a7ceda5f46212c09f961e (diff)
downloadoauthlib-5d9a9c90ba04f85477c7859a3cc7b13577fc24f9.tar.gz
Merge pull request #623 from oauthlib/264-status401
Handle 401 with WWW-Authenticate. Moved wrong 401 into 400.
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/introspect.py13
-rw-r--r--oauthlib/oauth2/rfc6749/endpoints/revocation.py8
-rw-r--r--oauthlib/oauth2/rfc6749/errors.py29
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/authorization_code.py1
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/client_credentials.py1
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/refresh_token.py2
-rw-r--r--oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py1
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py18
-rw-r--r--tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py23
9 files changed, 75 insertions, 21 deletions
diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py
index 7613acc..4a531e4 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py
@@ -57,24 +57,25 @@ class IntrospectEndpoint(BaseEndpoint):
an introspection response indicating the token is not active
as described in Section 2.2.
"""
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ }
request = Request(uri, http_method, body, headers)
try:
self.validate_introspect_request(request)
log.debug('Token introspect valid for %r.', request)
except OAuth2Error as e:
log.debug('Client error during validation of %r. %r.', request, e)
- return {}, e.json, e.status_code
+ headers.update(e.headers)
+ return headers, e.json, e.status_code
claims = self.request_validator.introspect_token(
request.token,
request.token_type_hint,
request
)
- headers = {
- 'Content-Type': 'application/json',
- 'Cache-Control': 'no-store',
- 'Pragma': 'no-cache',
- }
if claims is None:
return headers, json.dumps(dict(active=False)), 200
if "active" in claims:
diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py
index d5b5b78..f7e591d 100644
--- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py
+++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py
@@ -59,6 +59,11 @@ class RevocationEndpoint(BaseEndpoint):
An invalid token type hint value is ignored by the authorization server
and does not influence the revocation response.
"""
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ }
request = Request(
uri, http_method=http_method, body=body, headers=headers)
try:
@@ -69,7 +74,8 @@ class RevocationEndpoint(BaseEndpoint):
response_body = e.json
if self.enable_jsonp and request.callback:
response_body = '%s(%s);' % (request.callback, response_body)
- return {}, response_body, e.status_code
+ headers.update(e.headers)
+ return headers, response_body, e.status_code
self.request_validator.revoke_token(request.token,
request.token_type_hint, request)
diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py
index 678fcff..ec2b0d1 100644
--- a/oauthlib/oauth2/rfc6749/errors.py
+++ b/oauthlib/oauth2/rfc6749/errors.py
@@ -96,6 +96,27 @@ class OAuth2Error(Exception):
def json(self):
return json.dumps(dict(self.twotuples))
+ @property
+ def headers(self):
+ if self.status_code == 401:
+ """
+ https://tools.ietf.org/html/rfc6750#section-3
+
+ All challenges defined by this specification MUST use the auth-scheme
+ value "Bearer". This scheme MUST be followed by one or more
+ auth-param values.
+ """
+ authvalues = [
+ "Bearer",
+ 'error="{}"'.format(self.error)
+ ]
+ if self.description:
+ authvalues.append('error_description="{}"'.format(self.description))
+ if self.uri:
+ authvalues.append('error_uri="{}"'.format(self.uri))
+ return {"WWW-Authenticate": ", ".join(authvalues)}
+ return {}
+
class TokenExpiredError(OAuth2Error):
error = 'token_expired'
@@ -185,7 +206,6 @@ class AccessDeniedError(OAuth2Error):
The resource owner or authorization server denied the request.
"""
error = 'access_denied'
- status_code = 401
class UnsupportedResponseTypeError(OAuth2Error):
@@ -198,12 +218,12 @@ class UnsupportedResponseTypeError(OAuth2Error):
class InvalidScopeError(OAuth2Error):
"""
- The requested scope is invalid, unknown, or malformed.
+ The requested scope is invalid, unknown, or malformed, or
+ exceeds the scope granted by the resource owner.
https://tools.ietf.org/html/rfc6749#section-5.2
"""
error = 'invalid_scope'
- status_code = 400
class ServerError(OAuth2Error):
@@ -261,7 +281,6 @@ class UnauthorizedClientError(OAuth2Error):
grant type.
"""
error = 'unauthorized_client'
- status_code = 401
class UnsupportedGrantTypeError(OAuth2Error):
@@ -318,7 +337,6 @@ class ConsentRequired(OAuth2Error):
completed without displaying a user interface for End-User consent.
"""
error = 'consent_required'
- status_code = 401
class LoginRequired(OAuth2Error):
@@ -330,7 +348,6 @@ class LoginRequired(OAuth2Error):
completed without displaying a user interface for End-User authentication.
"""
error = 'login_required'
- status_code = 401
class CustomOAuth2Error(OAuth2Error):
diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
index 8ebae49..850d70a 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -243,6 +243,7 @@ class AuthorizationCodeGrant(GrantTypeBase):
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)
+ headers.update(e.headers)
return headers, e.json, e.status_code
token = token_handler.create_token(request, refresh_token=self.refresh_token, save_token=False)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
index 7d4f74c..0e4f545 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
@@ -77,6 +77,7 @@ class ClientCredentialsGrant(GrantTypeBase):
self.validate_token_request(request)
except errors.OAuth2Error as e:
log.debug('Client error in token request. %s.', e)
+ headers.update(e.headers)
return headers, e.json, e.status_code
token = token_handler.create_token(request, refresh_token=False, save_token=False)
diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
index 5f7382a..67d65a7 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
@@ -63,6 +63,8 @@ class RefreshTokenGrant(GrantTypeBase):
log.debug('Validating refresh token request, %r.', request)
self.validate_token_request(request)
except errors.OAuth2Error as e:
+ log.debug('Client error in token request, %s.', e)
+ headers.update(e.headers)
return headers, e.json, e.status_code
token = token_handler.create_token(request,
diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
index 87e8015..cb5a4ca 100644
--- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
+++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
@@ -105,6 +105,7 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
self.validate_token_request(request)
except errors.OAuth2Error as e:
log.debug('Client error in token request, %s.', e)
+ headers.update(e.headers)
return headers, e.json, e.status_code
token = token_handler.create_token(request, self.refresh_token, save_token=False)
diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py
index 7ec8190..f92652b 100644
--- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py
+++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py
@@ -86,7 +86,12 @@ class IntrospectEndpointTest(TestCase):
('token_type_hint', 'access_token')])
h, b, s = self.endpoint.create_introspect_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ "WWW-Authenticate": 'Bearer, error="invalid_client"'
+ })
self.assertEqual(loads(b)['error'], 'invalid_client')
self.assertEqual(s, 401)
@@ -109,7 +114,12 @@ class IntrospectEndpointTest(TestCase):
('token_type_hint', 'access_token')])
h, b, s = self.endpoint.create_introspect_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ "WWW-Authenticate": 'Bearer, error="invalid_client"'
+ })
self.assertEqual(loads(b)['error'], 'invalid_client')
self.assertEqual(s, 401)
@@ -121,12 +131,12 @@ class IntrospectEndpointTest(TestCase):
('token_type_hint', 'refresh_token')])
h, b, s = endpoint.create_introspect_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, self.resp_h)
self.assertEqual(loads(b)['error'], 'unsupported_token_type')
self.assertEqual(s, 400)
h, b, s = endpoint.create_introspect_response(self.uri,
headers=self.headers, body='')
- self.assertEqual(h, {})
+ self.assertEqual(h, self.resp_h)
self.assertEqual(loads(b)['error'], 'invalid_request')
self.assertEqual(s, 400)
diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py
index 77f5662..2a24177 100644
--- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py
+++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py
@@ -24,6 +24,11 @@ class RevocationEndpointTest(TestCase):
self.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
+ self.resp_h = {
+ 'Cache-Control': 'no-store',
+ 'Content-Type': 'application/json',
+ 'Pragma': 'no-cache'
+ }
def test_revoke_token(self):
for token_type in ('access_token', 'refresh_token', 'invalid'):
@@ -49,7 +54,12 @@ class RevocationEndpointTest(TestCase):
('token_type_hint', 'access_token')])
h, b, s = self.endpoint.create_revocation_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ "WWW-Authenticate": 'Bearer, error="invalid_client"'
+ })
self.assertEqual(loads(b)['error'], 'invalid_client')
self.assertEqual(s, 401)
@@ -72,7 +82,12 @@ class RevocationEndpointTest(TestCase):
('token_type_hint', 'access_token')])
h, b, s = self.endpoint.create_revocation_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ 'Pragma': 'no-cache',
+ "WWW-Authenticate": 'Bearer, error="invalid_client"'
+ })
self.assertEqual(loads(b)['error'], 'invalid_client')
self.assertEqual(s, 401)
@@ -96,12 +111,12 @@ class RevocationEndpointTest(TestCase):
('token_type_hint', 'refresh_token')])
h, b, s = endpoint.create_revocation_response(self.uri,
headers=self.headers, body=body)
- self.assertEqual(h, {})
+ self.assertEqual(h, self.resp_h)
self.assertEqual(loads(b)['error'], 'unsupported_token_type')
self.assertEqual(s, 400)
h, b, s = endpoint.create_revocation_response(self.uri,
headers=self.headers, body='')
- self.assertEqual(h, {})
+ self.assertEqual(h, self.resp_h)
self.assertEqual(loads(b)['error'], 'invalid_request')
self.assertEqual(s, 400)