Skip to content

Commit

Permalink
Add option to CLI, App and add_api to disable security verification.
Browse files Browse the repository at this point in the history
  • Loading branch information
mjp4 committed Feb 10, 2024
1 parent f8f461c commit f0fd410
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 3 deletions.
10 changes: 10 additions & 0 deletions connexion/apps/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = None,
) -> None:
"""
:param import_name: The name of the package or module that this object belongs to. If you
Expand Down Expand Up @@ -84,6 +85,9 @@ def __init__(
:obj:`validators.VALIDATOR_MAP`.
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
"""
self.middleware = ConnexionMiddleware(
self._middleware_app,
Expand All @@ -103,6 +107,7 @@ def __init__(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
)

def add_middleware(
Expand Down Expand Up @@ -137,6 +142,7 @@ def add_api(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = None,
**kwargs,
) -> t.Any:
"""
Expand Down Expand Up @@ -171,6 +177,9 @@ def add_api(
:obj:`validators.VALIDATOR_MAP`
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
:param kwargs: Additional keyword arguments to pass to the `add_api` method of the managed
middlewares. This can be used to pass arguments to middlewares added beyond the default
ones.
Expand All @@ -193,6 +202,7 @@ def add_api(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
**kwargs,
)

Expand Down
5 changes: 5 additions & 0 deletions connexion/apps/asynchronous.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def __init__(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = None,
) -> None:
"""
:param import_name: The name of the package or module that this object belongs to. If you
Expand Down Expand Up @@ -177,6 +178,9 @@ def __init__(
:obj:`validators.VALIDATOR_MAP`.
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
"""
self._middleware_app: AsyncASGIApp = AsyncASGIApp()

Expand All @@ -197,6 +201,7 @@ def __init__(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
)

def add_url_rule(
Expand Down
5 changes: 5 additions & 0 deletions connexion/apps/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def __init__(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = None,
):
"""
:param import_name: The name of the package or module that this object belongs to. If you
Expand Down Expand Up @@ -213,6 +214,9 @@ def __init__(
:obj:`validators.VALIDATOR_MAP`.
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
"""
self._middleware_app = FlaskASGIApp(import_name, server_args or {})

Expand All @@ -233,6 +237,7 @@ def __init__(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
)

self.app = self._middleware_app.app
Expand Down
10 changes: 9 additions & 1 deletion connexion/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ def run(app: AbstractApp, args: argparse.Namespace):
action="count",
default=0,
)
run_parser.add_argument(
"--no-security",
help="Disable security checks.",
action="store_true",
)
run_parser.add_argument("--base-path", help="Override the basePath in the API spec.")
run_parser.add_argument(
"--app-framework",
Expand Down Expand Up @@ -126,10 +131,13 @@ def create_app(args: t.Optional[argparse.Namespace] = None) -> AbstractApp:
if args.stub:
resolver_error = 501

api_extra_args = {}
api_extra_args: t.Dict[str, t.Any] = {}
if args.mock:
resolver = MockResolver(mock_all=args.mock == "all")
api_extra_args["resolver"] = resolver
if args.no_security:
logger.warning("Disabling security checks on the API.")
api_extra_args["no_security"] = True

app_cls = connexion.utils.get_function_from_name(AVAILABLE_APPS[args.app_framework])

Expand Down
11 changes: 11 additions & 0 deletions connexion/middleware/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class _Options:
validate_responses: t.Optional[bool] = False
validator_map: t.Optional[dict] = None
security_map: t.Optional[dict] = None
no_security: t.Optional[bool] = False

def __post_init__(self):
self.resolver = (
Expand Down Expand Up @@ -212,6 +213,7 @@ def __init__(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = None,
):
"""
:param import_name: The name of the package or module that this object belongs to. If you
Expand Down Expand Up @@ -244,6 +246,9 @@ def __init__(
:obj:`validators.VALIDATOR_MAP`.
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`.
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
"""
import_name = import_name or str(pathlib.Path.cwd())
self.root_path = utils.get_root_path(import_name)
Expand Down Expand Up @@ -277,6 +282,7 @@ def __init__(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
)

self.extra_files: t.List[str] = []
Expand Down Expand Up @@ -365,6 +371,7 @@ def add_api(
validate_responses: t.Optional[bool] = None,
validator_map: t.Optional[dict] = None,
security_map: t.Optional[dict] = None,
no_security: t.Optional[bool] = False,
**kwargs,
) -> None:
"""
Expand Down Expand Up @@ -399,6 +406,9 @@ def add_api(
:obj:`validators.VALIDATOR_MAP`
:param security_map: A dictionary of security handlers to use. Defaults to
:obj:`security.SECURITY_HANDLERS`
:param no_security: Disable security verification. Useful for prototyping
or if security is handled by an API Gateway in front of your application. Defaults to
False.
:param kwargs: Additional keyword arguments to pass to the `add_api` method of the managed
middlewares. This can be used to pass arguments to middlewares added beyond the default
ones.
Expand Down Expand Up @@ -431,6 +441,7 @@ def add_api(
validate_responses=validate_responses,
validator_map=validator_map,
security_map=security_map,
no_security=no_security,
)

api = API(
Expand Down
17 changes: 15 additions & 2 deletions connexion/middleware/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def from_operation(
*,
next_app: ASGIApp,
security_handler_factory: SecurityHandlerFactory,
no_security: bool = False,
) -> "SecurityOperation":
"""Create a SecurityOperation from an Operation of Specification instance
Expand All @@ -47,10 +48,15 @@ def from_operation(
:param security_handler_factory: The factory to be used to generate security handlers for
the different security schemes.
"""
if no_security:
security = []
else:
security = operation.security

return cls(
next_app=next_app,
security_handler_factory=security_handler_factory,
security=operation.security,
security=security,
security_schemes=operation.security_schemes,
)

Expand Down Expand Up @@ -113,11 +119,17 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:

class SecurityAPI(RoutedAPI[SecurityOperation]):
def __init__(
self, *args, auth_all_paths: bool = False, security_map: dict = None, **kwargs
self,
*args,
auth_all_paths: bool = False,
security_map: dict = None,
no_security: bool = False,
**kwargs,
):
super().__init__(*args, **kwargs)

self.security_handler_factory = SecurityHandlerFactory(security_map)
self.no_security = no_security

if auth_all_paths:
self.add_auth_on_not_found()
Expand Down Expand Up @@ -145,6 +157,7 @@ def make_operation(
operation,
next_app=self.next_app,
security_handler_factory=self.security_handler_factory,
no_security=self.no_security,
)


Expand Down
11 changes: 11 additions & 0 deletions tests/api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ def secure_endpoint_app(spec, app_class):
)


@pytest.fixture(scope="session")
def secure_endpoint_app_no_security(spec, app_class):
return build_app_from_fixture(
"secure_endpoint",
app_class=app_class,
spec_file=spec,
validate_responses=True,
no_security=True,
)


@pytest.fixture(scope="session")
def secure_endpoint_strict_app(spec, app_class):
return build_app_from_fixture(
Expand Down
10 changes: 10 additions & 0 deletions tests/api/test_secure_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ def test_security(oauth_requests, secure_endpoint_app):
assert response.status_code == 401


def test_disabled_security(secure_endpoint_app_no_security):
# Test that disabling security allows unauthenticated access to an otherwise
# secure endpoint.
app_client = secure_endpoint_app_no_security.test_client()

get_bye_no_auth = app_client.get("/v1.0/byesecure-ignoring-context/jsantos")
assert get_bye_no_auth.status_code == 200
assert get_bye_no_auth.text == "Goodbye jsantos (Secure!)"


def test_checking_that_client_token_has_all_necessary_scopes(
oauth_requests, secure_endpoint_app
):
Expand Down

0 comments on commit f0fd410

Please sign in to comment.