From 5161bceeb00565c4f3f11414873a8dcfda2360a3 Mon Sep 17 00:00:00 2001 From: Dan Lipsitt <578773+DanLipsitt@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:07:14 -0800 Subject: [PATCH 1/2] Test bearer token. --- tests/process/security_schemes/openapi.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/process/security_schemes/openapi.yaml b/tests/process/security_schemes/openapi.yaml index 8f441c95..2aa69dbd 100644 --- a/tests/process/security_schemes/openapi.yaml +++ b/tests/process/security_schemes/openapi.yaml @@ -14,3 +14,6 @@ components: name: Authorization type: apiKey in: cookie + bearerToken: + type: http + scheme: bearer From 94c5843c72323546377e3cf197604b51efad1ec5 Mon Sep 17 00:00:00 2001 From: Dan Lipsitt <578773+DanLipsitt@users.noreply.github.com> Date: Thu, 15 Jan 2026 13:16:50 -0800 Subject: [PATCH 2/2] Implement bearer token. dev --- src/lapidary_render/model/conv_cst.py | 16 ++++++++++++ src/lapidary_render/model/conv_openapi.py | 25 ++++++++++++++++--- src/lapidary_render/model/python/__init__.py | 1 + src/lapidary_render/model/python/model.py | 9 +++++++ .../dummy/lapidary/openapi/dummy.yaml | 4 +++ .../expected/dummy/src/test_dummy/client.py | 1 + .../test_dummy/components/securitySchemes.py | 7 ++++++ .../initial/dummy/lapidary/openapi/dummy.yaml | 4 +++ tests/process/test_security_scheme.py | 9 +++++++ 9 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/lapidary_render/model/conv_cst.py b/src/lapidary_render/model/conv_cst.py index 470a0adb..21f0d0c8 100644 --- a/src/lapidary_render/model/conv_cst.py +++ b/src/lapidary_render/model/conv_cst.py @@ -491,6 +491,8 @@ def mk_security_fn(auth: python.Auth) -> cst.BaseStatement: fn_name = cst.Name(f'{auth.type}_{auth.python_name}') match auth: + case python.HttpBearerAuth(): # check first because it's a subclass of ApiKeyAuth + return mk_auth_http_bearer(auth, fn_name) case python.ApiKeyAuth(): return mk_auth_api_key(cast(python.ApiKeyAuth, auth), fn_name) case python.HttpBasicAuth(): @@ -660,6 +662,20 @@ def mk_auth_api_key(auth: python.ApiKeyAuth, fn_name: cst.Name): auth_key=str_literal(auth.key), ) +def mk_auth_http_bearer(auth: python.HttpBearerAuth, fn_name: cst.Name): + param_name = auth.location.value + '_name' + return cst.helpers.parse_template_statement( + """def {fn_name}(api_key: str) -> lapidary.runtime.NamedAuth: + return {auth_name}, {auth_class}( + api_key="Bearer " + api_key, + {param_name}={auth_key}, + )""", + fn_name=fn_name, + auth_name=str_literal(auth.name), + auth_class=mk_name('lapidary', 'runtime', 'auth', auth.location.value.capitalize() + 'ApiKey'), + param_name=cst.Name(param_name), + auth_key=str_literal(auth.key), + ) def mk_security_module(module: python.SecurityModule) -> cst.Module: return cst.Module( diff --git a/src/lapidary_render/model/conv_openapi.py b/src/lapidary_render/model/conv_openapi.py index 0150046f..92f09dd1 100644 --- a/src/lapidary_render/model/conv_openapi.py +++ b/src/lapidary_render/model/conv_openapi.py @@ -401,12 +401,14 @@ def process_security_requirement( # need separate method to resolve references before calling a single-dispatched method @resolve_ref def process_security_scheme(self, value: openapi.SecurityScheme, stack: Stack) -> None: - match value.type: - case 'apiKey': + match value.type, value.scheme: + case 'apiKey', _: self.process_security_scheme_api_key(value, stack) - case 'oauth2': + case 'oauth2', _: self.process_security_scheme_oauth2(value, stack) - case 'http': + case 'http', 'bearer': + self.process_security_scheme_http_bearer(value, stack) + case 'http', _: self.process_security_scheme_http(value, stack) def process_security_scheme_api_key(self, value: openapi.SecurityScheme, stack: Stack) -> None: @@ -507,6 +509,20 @@ def process_security_scheme_http(self, value: openapi.SecurityScheme, stack: Sta except KeyError: raise NotImplementedError(stack.push('scheme'), value.scheme) from None + def process_security_scheme_http_bearer(self, value: openapi.SecurityScheme, stack: Stack) -> None: + logger.debug('Process HTTP Bearer security scheme %s', stack) + auth_name = stack.top() + flow_name = f'http_{auth_name}' + + if flow_name in self.target.security_schemes: + return + + self.target.security_schemes[flow_name] = python.HttpBearerAuth( + name=auth_name, + python_name=names.maybe_mangle_name(auth_name), + format=value.format, + ) + def param_style( style: str | None, @@ -568,4 +584,5 @@ def map_process( HTTP_SCHEMES = { 'basic': python.HttpBasicAuth, 'digest': python.HttpDigestAuth, + 'bearer': python.HttpBearerAuth, } diff --git a/src/lapidary_render/model/python/__init__.py b/src/lapidary_render/model/python/__init__.py index 276a5fe8..7e8fe045 100644 --- a/src/lapidary_render/model/python/__init__.py +++ b/src/lapidary_render/model/python/__init__.py @@ -16,6 +16,7 @@ ClientInit, HttpBasicAuth, HttpDigestAuth, + HttpBearerAuth, ImplicitOAuth2Flow, MetadataModel, MimeMap, diff --git a/src/lapidary_render/model/python/model.py b/src/lapidary_render/model/python/model.py index cb127376..95390b16 100644 --- a/src/lapidary_render/model/python/model.py +++ b/src/lapidary_render/model/python/model.py @@ -62,6 +62,15 @@ class HttpBasicAuth(Auth): type: str = 'http_basic' +@dc.dataclass(kw_only=True, frozen=True) +class HttpBearerAuth(ApiKeyAuth): + type: str = 'http' + scheme: str = 'bearer' + key: str = 'Authorization' + location: ParamLocation = ParamLocation.HEADER + format: str | None = 'bearer_token' + bearer_format: str | None = 'bearer_token' + @dc.dataclass(kw_only=True, frozen=True) class HttpDigestAuth(Auth): scheme: str = 'digest' diff --git a/tests/e2e/render/expected/dummy/lapidary/openapi/dummy.yaml b/tests/e2e/render/expected/dummy/lapidary/openapi/dummy.yaml index aa09fb60..060e498e 100644 --- a/tests/e2e/render/expected/dummy/lapidary/openapi/dummy.yaml +++ b/tests/e2e/render/expected/dummy/lapidary/openapi/dummy.yaml @@ -231,6 +231,9 @@ components: http_digest: type: http scheme: digest + http_bearer: + type: http + scheme: bearer responses: default: content: @@ -265,3 +268,4 @@ security: - api-key-query: [ ] - http_basic: [ ] - http_digest: [ ] +- http_bearer: [ ] diff --git a/tests/e2e/render/expected/dummy/src/test_dummy/client.py b/tests/e2e/render/expected/dummy/src/test_dummy/client.py index f0989e93..3dc6e56d 100644 --- a/tests/e2e/render/expected/dummy/src/test_dummy/client.py +++ b/tests/e2e/render/expected/dummy/src/test_dummy/client.py @@ -37,6 +37,7 @@ def __init__( {'api-key-query': ()}, {'http_basic': ()}, {'http_digest': ()}, + {'http_bearer': ()}, ), base_url=base_url, **kwargs, diff --git a/tests/e2e/render/expected/dummy/src/test_dummy/components/securitySchemes.py b/tests/e2e/render/expected/dummy/src/test_dummy/components/securitySchemes.py index 8db47d72..44fdd358 100644 --- a/tests/e2e/render/expected/dummy/src/test_dummy/components/securitySchemes.py +++ b/tests/e2e/render/expected/dummy/src/test_dummy/components/securitySchemes.py @@ -134,3 +134,10 @@ def http_digest_http_digest( username=user_name, password=password, ) + + +def http_http_bearer(api_key: str) -> lapidary.runtime.NamedAuth: + return 'http_bearer', lapidary.runtime.auth.HeaderApiKey( + api_key="Bearer " + api_key, + header_name='Authorization', + ) diff --git a/tests/e2e/render/initial/dummy/lapidary/openapi/dummy.yaml b/tests/e2e/render/initial/dummy/lapidary/openapi/dummy.yaml index aa09fb60..060e498e 100644 --- a/tests/e2e/render/initial/dummy/lapidary/openapi/dummy.yaml +++ b/tests/e2e/render/initial/dummy/lapidary/openapi/dummy.yaml @@ -231,6 +231,9 @@ components: http_digest: type: http scheme: digest + http_bearer: + type: http + scheme: bearer responses: default: content: @@ -265,3 +268,4 @@ security: - api-key-query: [ ] - http_basic: [ ] - http_digest: [ ] +- http_bearer: [ ] diff --git a/tests/process/test_security_scheme.py b/tests/process/test_security_scheme.py index 009f564a..6d8840e2 100644 --- a/tests/process/test_security_scheme.py +++ b/tests/process/test_security_scheme.py @@ -29,3 +29,12 @@ def test_process_security_schemes(path: Path): location=python.ParamLocation.COOKIE, format='{}', ) + assert converter.target.security_schemes['http_bearerToken'] == python.HttpBearerAuth( + type='http', + scheme='bearer', + name='bearerToken', + python_name='bearerToken', + key='Authorization', + location=python.ParamLocation.HEADER, + format = '{}' + )