mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-06 04:19:26 +00:00
Inject current request in security handlers (#1883)
Fixes #1881 Fixes #1880 Fixes #1876 Alternative to #1750 This PR makes the current request available to the security handlers by injecting it as a keyword. I think this is a proper alternative to #1750, since this is the only place in the default middleware stack where I expect this to be needed.
This commit is contained in:
@@ -67,6 +67,7 @@ NO_VALUE = object()
|
|||||||
class AbstractSecurityHandler:
|
class AbstractSecurityHandler:
|
||||||
|
|
||||||
required_scopes_kw = "required_scopes"
|
required_scopes_kw = "required_scopes"
|
||||||
|
request_kw = "request"
|
||||||
client = None
|
client = None
|
||||||
security_definition_key: str
|
security_definition_key: str
|
||||||
"""The key which contains the value for the function name to resolve."""
|
"""The key which contains the value for the function name to resolve."""
|
||||||
@@ -106,12 +107,12 @@ class AbstractSecurityHandler:
|
|||||||
return default
|
return default
|
||||||
|
|
||||||
def _generic_check(self, func, exception_msg):
|
def _generic_check(self, func, exception_msg):
|
||||||
need_to_add_required_scopes = self._need_to_add_scopes(func)
|
|
||||||
|
|
||||||
async def wrapper(request, *args, required_scopes=None):
|
async def wrapper(request, *args, required_scopes=None):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if need_to_add_required_scopes:
|
if self._accepts_kwarg(func, self.required_scopes_kw):
|
||||||
kwargs[self.required_scopes_kw] = required_scopes
|
kwargs[self.required_scopes_kw] = required_scopes
|
||||||
|
if self._accepts_kwarg(func, self.request_kw):
|
||||||
|
kwargs[self.request_kw] = request
|
||||||
token_info = func(*args, **kwargs)
|
token_info = func(*args, **kwargs)
|
||||||
while asyncio.iscoroutine(token_info):
|
while asyncio.iscoroutine(token_info):
|
||||||
token_info = await token_info
|
token_info = await token_info
|
||||||
@@ -140,10 +141,11 @@ class AbstractSecurityHandler:
|
|||||||
raise OAuthProblem(detail="Invalid authorization header")
|
raise OAuthProblem(detail="Invalid authorization header")
|
||||||
return auth_type.lower(), value
|
return auth_type.lower(), value
|
||||||
|
|
||||||
def _need_to_add_scopes(self, func):
|
@staticmethod
|
||||||
|
def _accepts_kwarg(func: t.Callable, keyword: str) -> bool:
|
||||||
|
"""Check if the function accepts the provided keyword argument."""
|
||||||
arguments, has_kwargs = inspect_function_arguments(func)
|
arguments, has_kwargs = inspect_function_arguments(func)
|
||||||
need_required_scopes = has_kwargs or self.required_scopes_kw in arguments
|
return has_kwargs or keyword in arguments
|
||||||
return need_required_scopes
|
|
||||||
|
|
||||||
def _resolve_func(self, security_scheme):
|
def _resolve_func(self, security_scheme):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ The function should accept the following arguments:
|
|||||||
- username
|
- username
|
||||||
- password
|
- password
|
||||||
- required_scopes (optional)
|
- required_scopes (optional)
|
||||||
|
- request (optional)
|
||||||
|
|
||||||
You can find a `minimal Basic Auth example application`_ in Connexion's "examples" folder.
|
You can find a `minimal Basic Auth example application`_ in Connexion's "examples" folder.
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ The function should accept the following arguments:
|
|||||||
|
|
||||||
- token
|
- token
|
||||||
- required_scopes (optional)
|
- required_scopes (optional)
|
||||||
|
- request (optional)
|
||||||
|
|
||||||
You can find a `minimal Bearer example application`_ in Connexion's "examples" folder.
|
You can find a `minimal Bearer example application`_ in Connexion's "examples" folder.
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ The function should accept the following arguments:
|
|||||||
|
|
||||||
- apikey
|
- apikey
|
||||||
- required_scopes (optional)
|
- required_scopes (optional)
|
||||||
|
- request (optional)
|
||||||
|
|
||||||
You can find a `minimal API Key example application`_ in Connexion's "examples" folder.
|
You can find a `minimal API Key example application`_ in Connexion's "examples" folder.
|
||||||
|
|
||||||
@@ -115,6 +118,7 @@ The function should accept the following arguments:
|
|||||||
|
|
||||||
- token
|
- token
|
||||||
- required_scopes (optional)
|
- required_scopes (optional)
|
||||||
|
- request (optional)
|
||||||
|
|
||||||
As alternative to an ``x-tokenInfoFunc`` definition, you can set an ``x-tokenInfoUrl`` definition or
|
As alternative to an ``x-tokenInfoFunc`` definition, you can set an ``x-tokenInfoUrl`` definition or
|
||||||
``TOKENINFO_URL`` environment variable, and connexion will call the url instead of a local
|
``TOKENINFO_URL`` environment variable, and connexion will call the url instead of a local
|
||||||
@@ -132,6 +136,7 @@ The function should accept the following arguments:
|
|||||||
|
|
||||||
- required_scopes
|
- required_scopes
|
||||||
- token_scopes
|
- token_scopes
|
||||||
|
- request (optional)
|
||||||
|
|
||||||
and return a boolean indicating if the validation was successful.
|
and return a boolean indicating if the validation was successful.
|
||||||
|
|
||||||
|
|||||||
@@ -328,3 +328,52 @@ def test_raise_most_specific(errors, most_specific):
|
|||||||
security_handler_factory = SecurityHandlerFactory()
|
security_handler_factory = SecurityHandlerFactory()
|
||||||
with pytest.raises(most_specific):
|
with pytest.raises(most_specific):
|
||||||
security_handler_factory._raise_most_specific(errors)
|
security_handler_factory._raise_most_specific(errors)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_optional_kwargs_injected():
|
||||||
|
"""Test that optional keyword arguments 'required_scopes' and 'request' are injected when
|
||||||
|
defined as arguments in the user security function. This test uses the ApiKeySecurityHandler,
|
||||||
|
but the tested behavior is generic across handlers."""
|
||||||
|
security_handler_factory = ApiKeySecurityHandler()
|
||||||
|
|
||||||
|
request = ConnexionRequest(
|
||||||
|
scope={"type": "http", "headers": [[b"x-auth", b"foobar"]]}
|
||||||
|
)
|
||||||
|
|
||||||
|
def apikey_info_no_kwargs(key):
|
||||||
|
"""Will fail if additional keywords are injected."""
|
||||||
|
return {"sub": "no_kwargs"}
|
||||||
|
|
||||||
|
wrapped_func_no_kwargs = security_handler_factory._get_verify_func(
|
||||||
|
apikey_info_no_kwargs, "header", "X-Auth"
|
||||||
|
)
|
||||||
|
assert await wrapped_func_no_kwargs(request) == {"sub": "no_kwargs"}
|
||||||
|
|
||||||
|
def apikey_info_request(key, request):
|
||||||
|
"""Will fail if request is not injected."""
|
||||||
|
return {"sub": "request"}
|
||||||
|
|
||||||
|
wrapped_func_request = security_handler_factory._get_verify_func(
|
||||||
|
apikey_info_request, "header", "X-Auth"
|
||||||
|
)
|
||||||
|
assert await wrapped_func_request(request) == {"sub": "request"}
|
||||||
|
|
||||||
|
def apikey_info_scopes(key, required_scopes):
|
||||||
|
"""Will fail if required_scopes is not injected."""
|
||||||
|
return {"sub": "scopes"}
|
||||||
|
|
||||||
|
wrapped_func_scopes = security_handler_factory._get_verify_func(
|
||||||
|
apikey_info_scopes, "header", "X-Auth"
|
||||||
|
)
|
||||||
|
assert await wrapped_func_scopes(request) == {"sub": "scopes"}
|
||||||
|
|
||||||
|
def apikey_info_kwargs(key, **kwargs):
|
||||||
|
"""Will fail if request and required_scopes are not injected."""
|
||||||
|
assert "request" in kwargs
|
||||||
|
assert "required_scopes" in kwargs
|
||||||
|
return {"sub": "kwargs"}
|
||||||
|
|
||||||
|
wrapped_func_kwargs = security_handler_factory._get_verify_func(
|
||||||
|
apikey_info_kwargs, "header", "X-Auth"
|
||||||
|
)
|
||||||
|
assert await wrapped_func_kwargs(request) == {"sub": "kwargs"}
|
||||||
|
|||||||
Reference in New Issue
Block a user