mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-06 04:19:26 +00:00
Fail on invalid token when checking multiple OR security schemes
This commit is contained in:
@@ -558,24 +558,15 @@ class SecurityHandlerFactory:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def verify_security(cls, auth_funcs):
|
def verify_security(cls, auth_funcs):
|
||||||
async def verify_fn(request):
|
async def verify_fn(request):
|
||||||
token_info = NO_VALUE
|
|
||||||
errors = []
|
|
||||||
for func in auth_funcs:
|
for func in auth_funcs:
|
||||||
try:
|
token_info = func(request)
|
||||||
token_info = func(request)
|
while asyncio.iscoroutine(token_info):
|
||||||
while asyncio.iscoroutine(token_info):
|
token_info = await token_info
|
||||||
token_info = await token_info
|
if token_info is not NO_VALUE:
|
||||||
if token_info is not NO_VALUE:
|
break
|
||||||
break
|
|
||||||
except Exception as err:
|
|
||||||
errors.append(err)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if errors != []:
|
logger.info("... No auth provided. Aborting with 401.")
|
||||||
cls._raise_most_specific(errors)
|
raise OAuthProblem(detail="No authorization token provided")
|
||||||
else:
|
|
||||||
logger.info("... No auth provided. Aborting with 401.")
|
|
||||||
raise OAuthProblem(detail="No authorization token provided")
|
|
||||||
|
|
||||||
request.context.update(
|
request.context.update(
|
||||||
{
|
{
|
||||||
@@ -586,34 +577,3 @@ class SecurityHandlerFactory:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return verify_fn
|
return verify_fn
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _raise_most_specific(exceptions: t.List[Exception]) -> None:
|
|
||||||
"""Raises the most specific error from a list of exceptions by status code.
|
|
||||||
|
|
||||||
The status codes are expected to be either in the `code`
|
|
||||||
or in the `status` attribute of the exceptions.
|
|
||||||
|
|
||||||
The order is as follows:
|
|
||||||
- 403: valid credentials but not enough privileges
|
|
||||||
- 401: no or invalid credentials
|
|
||||||
- for other status codes, the smallest one is selected
|
|
||||||
|
|
||||||
:param errors: List of exceptions.
|
|
||||||
:type errors: t.List[Exception]
|
|
||||||
"""
|
|
||||||
if not exceptions:
|
|
||||||
return
|
|
||||||
# We only use status code attributes from exceptions
|
|
||||||
# We use 600 as default because 599 is highest valid status code
|
|
||||||
status_to_exc = {
|
|
||||||
getattr(exc, "status_code", getattr(exc, "status", 600)): exc
|
|
||||||
for exc in exceptions
|
|
||||||
}
|
|
||||||
if 403 in status_to_exc:
|
|
||||||
raise status_to_exc[403]
|
|
||||||
elif 401 in status_to_exc:
|
|
||||||
raise status_to_exc[401]
|
|
||||||
else:
|
|
||||||
lowest_status_code = min(status_to_exc)
|
|
||||||
raise status_to_exc[lowest_status_code]
|
|
||||||
|
|||||||
@@ -152,13 +152,30 @@ Multiple Authentication Schemes
|
|||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
With Connexion, it is also possible to combine multiple authentication schemes
|
With Connexion, it is also possible to combine multiple authentication schemes
|
||||||
as described in the `OpenAPI specification`_. When multiple authentication
|
as described in the `OpenAPI specification`_.
|
||||||
schemes are combined using logical AND, the ``token_info`` argument will
|
|
||||||
consist of a dictionary mapping the names of the security scheme to their
|
AND
|
||||||
|
```
|
||||||
|
|
||||||
|
When multiple authentication schemes are combined using logical AND, the ``token_info`` argument
|
||||||
|
will consist of a dictionary mapping the names of the security scheme to their
|
||||||
corresponding ``token_info``.
|
corresponding ``token_info``.
|
||||||
|
|
||||||
Multiple OAuth2 security schemes in AND fashion are not supported.
|
Multiple OAuth2 security schemes in AND fashion are not supported.
|
||||||
|
|
||||||
|
OR
|
||||||
|
``
|
||||||
|
|
||||||
|
When multiple authentication schemes are combined using logical OR, the schemes are checked in
|
||||||
|
the order they are listed in the OpenAPI spec. For each security scheme the following logic is
|
||||||
|
applied:
|
||||||
|
|
||||||
|
- If a token matching the security schem is found in the request and is valid, allow the request
|
||||||
|
to pass
|
||||||
|
- If a token matching the security scheme is found in the request and is invalid, return an error
|
||||||
|
- If no token matching the security scheme is found in the request, move on to the next scheme,
|
||||||
|
or return a 401 error if this was the last scheme
|
||||||
|
|
||||||
.. _OpenAPI specification: https://swagger.io/docs/specification/authentication/#multiple
|
.. _OpenAPI specification: https://swagger.io/docs/specification/authentication/#multiple
|
||||||
|
|
||||||
Custom security handlers
|
Custom security handlers
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ def test_security(oauth_requests, secure_endpoint_app):
|
|||||||
assert response.text == '"Authenticated"\n'
|
assert response.text == '"Authenticated"\n'
|
||||||
headers = {"X-AUTH": "wrong-key"}
|
headers = {"X-AUTH": "wrong-key"}
|
||||||
response = app_client.get("/v1.0/optional-auth", headers=headers)
|
response = app_client.get("/v1.0/optional-auth", headers=headers)
|
||||||
assert response.text == '"Unauthenticated"\n'
|
assert response.json()["title"] == "Unauthorized"
|
||||||
assert response.status_code == 200
|
assert response.status_code == 401
|
||||||
|
|
||||||
# security function throws exception
|
# security function throws exception
|
||||||
response = app_client.get("/v1.0/auth-exception", headers={"X-Api-Key": "foo"})
|
response = app_client.get("/v1.0/auth-exception", headers={"X-Api-Key": "foo"})
|
||||||
|
|||||||
@@ -299,32 +299,3 @@ async def test_verify_security_oauthproblem():
|
|||||||
|
|
||||||
assert exc_info.value.status_code == 401
|
assert exc_info.value.status_code == 401
|
||||||
assert exc_info.value.detail == "No authorization token provided"
|
assert exc_info.value.detail == "No authorization token provided"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"errors, most_specific",
|
|
||||||
[
|
|
||||||
([OAuthProblem()], OAuthProblem),
|
|
||||||
([OAuthProblem(), OAuthScopeProblem([], [])], OAuthScopeProblem),
|
|
||||||
(
|
|
||||||
[OAuthProblem(), OAuthScopeProblem([], []), BadRequestProblem],
|
|
||||||
OAuthScopeProblem,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
[
|
|
||||||
OAuthProblem(),
|
|
||||||
OAuthScopeProblem([], []),
|
|
||||||
BadRequestProblem,
|
|
||||||
ConnexionException,
|
|
||||||
],
|
|
||||||
OAuthScopeProblem,
|
|
||||||
),
|
|
||||||
([BadRequestProblem(), ConnexionException()], BadRequestProblem),
|
|
||||||
([ConnexionException()], ConnexionException),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_raise_most_specific(errors, most_specific):
|
|
||||||
"""Tests whether most specific exception is raised from a list."""
|
|
||||||
security_handler_factory = SecurityHandlerFactory()
|
|
||||||
with pytest.raises(most_specific):
|
|
||||||
security_handler_factory._raise_most_specific(errors)
|
|
||||||
|
|||||||
@@ -526,9 +526,8 @@ def more_than_one_scope_defined(**kwargs):
|
|||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
def optional_auth(**kwargs):
|
def optional_auth(context_):
|
||||||
key = apikey_info(request.headers.get("X-AUTH"))
|
if not context_.get("token_info"):
|
||||||
if key is None:
|
|
||||||
return "Unauthenticated"
|
return "Unauthenticated"
|
||||||
else:
|
else:
|
||||||
return "Authenticated"
|
return "Authenticated"
|
||||||
|
|||||||
Reference in New Issue
Block a user