Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to CLI, App and add_api to disable security verification. #1874

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading