summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmer Katz <omer.drow@gmail.com>2018-12-13 18:30:08 +0200
committerGitHub <noreply@github.com>2018-12-13 18:30:08 +0200
commit9cb1448314d950cdee57d0d2ff78ea1b2e503c3d (patch)
tree2e13003f25aa59c7406fa4a34184e5707a73ae01
parentf5c8630d9d5b0e889bb72099948c5fe26722eef2 (diff)
parent8aca902011981a236cedf32d0c859078c7881b71 (diff)
downloadoauthlib-9cb1448314d950cdee57d0d2ff78ea1b2e503c3d.tar.gz
Merge branch 'master' into bandit
-rw-r--r--oauthlib/oauth1/rfc5849/__init__.py4
-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
10 files changed, 78 insertions, 22 deletions
diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py
index 887ab69..7313286 100644
--- a/oauthlib/oauth1/rfc5849/__init__.py
+++ b/oauthlib/oauth1/rfc5849/__init__.py
@@ -173,10 +173,12 @@ class Client(object):
params.append(('oauth_verifier', self.verifier))
# providing body hash for requests other than x-www-form-urlencoded
- # as described in http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
+ # as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
# 4.1.1. When to include the body hash
# * [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
# * [...] SHOULD include the oauth_body_hash parameter on all other requests.
+ # Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2
+ # At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension.
content_type = request.headers.get('Content-Type', None)
content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
if request.body is not None and content_type_eligible:
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)