Skip to content

Commit cded99c

Browse files
authored
[Rust-Axum] Support Authentication (Cookie, API Key In Header) (#20017)
* [Rust-Axum] Support Cookie Authentication & API Key In Header * Fix * Fix * Fix Header Params & Responses
1 parent 06547b7 commit cded99c

File tree

30 files changed

+2677
-75
lines changed

30 files changed

+2677
-75
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
generatorName: rust-axum
2+
outputDir: samples/server/petstore/rust-axum/output/apikey-auths
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/jetbrains/CheckoutBasicBearerCookieQueryHeaderBasicBearer.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/rust-axum
5+
generateAliasAsModel: true
6+
additionalProperties:
7+
hideGenerationTimestamp: "true"
8+
packageName: apikey-auths
9+
globalProperties:
10+
skipFormModel: false
11+
enablePostProcessFile: true

docs/generators/rust-axum.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
198198
|Body|✓|OAS2
199199
|FormUnencoded|✓|OAS2
200200
|FormMultipart|✓|OAS2
201-
|Cookie||OAS3
201+
|Cookie||OAS3
202202

203203
### Schema Support Feature
204204
| Name | Supported | Defined By |
@@ -215,14 +215,14 @@ These options may be applied as additional-properties (cli) or configOptions (pl
215215
### Security Feature
216216
| Name | Supported | Defined By |
217217
| ---- | --------- | ---------- |
218-
|BasicAuth||OAS2,OAS3
218+
|BasicAuth||OAS2,OAS3
219219
|ApiKey|✓|OAS2,OAS3
220220
|OpenIDConnect|✗|OAS3
221-
|BearerToken||OAS3
222-
|OAuth2_Implicit||OAS2,OAS3
223-
|OAuth2_Password||OAS2,OAS3
224-
|OAuth2_ClientCredentials||OAS2,OAS3
225-
|OAuth2_AuthorizationCode||OAS2,OAS3
221+
|BearerToken||OAS3
222+
|OAuth2_Implicit||OAS2,OAS3
223+
|OAuth2_Password||OAS2,OAS3
224+
|OAuth2_ClientCredentials||OAS2,OAS3
225+
|OAuth2_AuthorizationCode||OAS2,OAS3
226226
|SignatureAuth|✗|OAS3
227227
|AWSV4Signature|✗|ToolingExtension
228228

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@
2525
import io.swagger.v3.oas.models.parameters.RequestBody;
2626
import io.swagger.v3.oas.models.responses.ApiResponse;
2727
import io.swagger.v3.oas.models.servers.Server;
28+
import io.swagger.v3.oas.models.tags.Tag;
2829
import org.apache.commons.io.FilenameUtils;
2930
import org.apache.commons.lang3.StringUtils;
3031
import org.openapitools.codegen.*;
3132
import org.openapitools.codegen.meta.GeneratorMetadata;
3233
import org.openapitools.codegen.meta.Stability;
33-
import org.openapitools.codegen.meta.features.GlobalFeature;
34-
import org.openapitools.codegen.meta.features.ParameterFeature;
35-
import org.openapitools.codegen.meta.features.SchemaSupportFeature;
36-
import org.openapitools.codegen.meta.features.WireFormatFeature;
34+
import org.openapitools.codegen.meta.features.*;
3735
import org.openapitools.codegen.model.ModelMap;
3836
import org.openapitools.codegen.model.ModelsMap;
3937
import org.openapitools.codegen.model.OperationMap;
@@ -53,7 +51,6 @@
5351

5452
public class RustAxumServerCodegen extends AbstractRustCodegen implements CodegenConfig {
5553
public static final String PROJECT_NAME = "openapi-server";
56-
private static final String apiPath = "rust-axum";
5754

5855
private String packageName;
5956
private String packageVersion;
@@ -99,6 +96,9 @@ public RustAxumServerCodegen() {
9996
WireFormatFeature.JSON,
10097
WireFormatFeature.Custom
10198
))
99+
.securityFeatures(EnumSet.of(
100+
SecurityFeature.ApiKey
101+
))
102102
.excludeGlobalFeatures(
103103
GlobalFeature.Info,
104104
GlobalFeature.ExternalDocumentation,
@@ -113,9 +113,6 @@ public RustAxumServerCodegen() {
113113
.excludeSchemaSupportFeatures(
114114
SchemaSupportFeature.Polymorphism
115115
)
116-
.excludeParameterFeatures(
117-
ParameterFeature.Cookie
118-
)
119116
);
120117

121118
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
@@ -436,7 +433,10 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
436433
}
437434
pathMethodOpMap
438435
.computeIfAbsent(axumPath, (key) -> new ArrayList<>())
439-
.add(new MethodOperation(op.httpMethod.toLowerCase(Locale.ROOT), underscoredOperationId));
436+
.add(new MethodOperation(
437+
op.httpMethod.toLowerCase(Locale.ROOT),
438+
underscoredOperationId,
439+
op.vendorExtensions));
440440
}
441441

442442
// Determine the types that this operation produces. `getProducesInfo`
@@ -488,7 +488,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
488488
);
489489
rsp.vendorExtensions.put("x-response-id", responseId);
490490
}
491-
491+
492492
if (rsp.dataType != null) {
493493
// Get the mimetype which is produced by this response. Note
494494
// that although in general responses produces a set of
@@ -562,19 +562,39 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
562562
}
563563
}
564564
}
565+
566+
for (CodegenProperty header : rsp.headers) {
567+
if (uuidType.equals(header.dataType)) {
568+
additionalProperties.put("apiUsesUuid", true);
569+
}
570+
header.nameInPascalCase = toModelName(header.baseName);
571+
header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT);
572+
}
573+
}
574+
575+
for (CodegenParameter header : op.headerParams) {
576+
header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT);
577+
}
578+
579+
for (CodegenProperty header : op.responseHeaders) {
580+
if (uuidType.equals(header.dataType)) {
581+
additionalProperties.put("apiUsesUuid", true);
582+
}
583+
header.nameInPascalCase = toModelName(header.baseName);
584+
header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT);
565585
}
566586

567-
// Include renderUuidConversionImpl exactly once in the vendorExtensions map when
568-
// at least one `uuid::Uuid` converted from a header value in the resulting Rust code.
569-
final Boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType));
570-
if (renderUuidConversionImpl) {
587+
// Include renderUuidConversionImpl exactly once in the vendorExtensions map when
588+
// at least one `uuid::Uuid` converted from a header value in the resulting Rust code.
589+
final boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType));
590+
if (renderUuidConversionImpl)
571591
additionalProperties.put("renderUuidConversionImpl", "true");
572-
}
592+
573593
return op;
574594
}
575595

576596
@Override
577-
public OperationsMap postProcessOperationsWithModels(OperationsMap operationsMap, List<ModelMap> allModels) {
597+
public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List<ModelMap> allModels) {
578598
OperationMap operations = operationsMap.getOperations();
579599
operations.put("classnamePascalCase", camelize(operations.getClassname()));
580600
List<CodegenOperation> operationList = operations.getOperation();
@@ -586,7 +606,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operationsMap
586606
return operationsMap;
587607
}
588608

589-
private void postProcessOperationWithModels(CodegenOperation op) {
609+
private void postProcessOperationWithModels(final CodegenOperation op) {
590610
boolean consumesJson = false;
591611
boolean consumesPlainText = false;
592612
boolean consumesFormUrlEncoded = false;
@@ -641,6 +661,22 @@ private void postProcessOperationWithModels(CodegenOperation op) {
641661
param.vendorExtensions.put("x-consumes-json", true);
642662
}
643663
}
664+
665+
if (op.authMethods != null) {
666+
for (CodegenSecurity s : op.authMethods) {
667+
if (s.isApiKey && (s.isKeyInCookie || s.isKeyInHeader)) {
668+
if (s.isKeyInCookie) {
669+
op.vendorExtensions.put("x-has-cookie-auth-methods", "true");
670+
op.vendorExtensions.put("x-api-key-cookie-name", toModelName(s.keyParamName));
671+
} else {
672+
op.vendorExtensions.put("x-has-header-auth-methods", "true");
673+
op.vendorExtensions.put("x-api-key-header-name", toModelName(s.keyParamName));
674+
}
675+
676+
op.vendorExtensions.put("x-has-auth-methods", "true");
677+
}
678+
}
679+
}
644680
}
645681

646682
@Override
@@ -671,14 +707,16 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
671707
return;
672708
}
673709

674-
// Get all tags sorted by name
675-
final List<String> tags = op.tags.stream().map(t -> t.getName()).sorted().collect(Collectors.toList());
676-
// Combine into a single group
677-
final String combinedTag = tags.stream().collect(Collectors.joining("-"));
710+
// Get all tags sorted by name & Combine into a single group
711+
final String combinedTag = op.tags.stream()
712+
.map(Tag::getName).sorted()
713+
.collect(Collectors.joining("-"));
678714
// Add to group
679715
super.addOperationToGroup(combinedTag, resourcePath, operation, op, operations);
716+
680717
return;
681718
}
719+
682720
super.addOperationToGroup(tag, resourcePath, operation, op, operations);
683721
}
684722

@@ -692,7 +730,7 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera
692730
// restore things to sensible values.
693731
@Override
694732
public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, String bodyParameterName) {
695-
Schema original_schema = ModelUtils.getSchemaFromRequestBody(body);
733+
final Schema original_schema = ModelUtils.getSchemaFromRequestBody(body);
696734
CodegenParameter codegenParameter = super.fromRequestBody(body, imports, bodyParameterName);
697735

698736
if (StringUtils.isNotBlank(original_schema.get$ref())) {
@@ -709,12 +747,12 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
709747
}
710748

711749
@Override
712-
public String toInstantiationType(Schema p) {
750+
public String toInstantiationType(final Schema p) {
713751
if (ModelUtils.isArraySchema(p)) {
714-
Schema inner = ModelUtils.getSchemaItems(p);
752+
final Schema inner = ModelUtils.getSchemaItems(p);
715753
return instantiationTypes.get("array") + "<" + getSchemaType(inner) + ">";
716754
} else if (ModelUtils.isMapSchema(p)) {
717-
Schema inner = ModelUtils.getAdditionalProperties(p);
755+
final Schema inner = ModelUtils.getAdditionalProperties(p);
718756
return instantiationTypes.get("map") + "<" + typeMapping.get("string") + ", " + getSchemaType(inner) + ">";
719757
} else {
720758
return null;
@@ -727,7 +765,7 @@ public Map<String, Object> postProcessSupportingFileData(Map<String, Object> bun
727765

728766
final List<PathMethodOperations> pathMethodOps = pathMethodOpMap.entrySet().stream()
729767
.map(entry -> {
730-
ArrayList<MethodOperation> methodOps = entry.getValue();
768+
final ArrayList<MethodOperation> methodOps = entry.getValue();
731769
methodOps.sort(Comparator.comparing(a -> a.method));
732770
return new PathMethodOperations(entry.getKey(), methodOps);
733771
})
@@ -739,7 +777,7 @@ public Map<String, Object> postProcessSupportingFileData(Map<String, Object> bun
739777
}
740778

741779
@Override
742-
public String toDefaultValue(Schema p) {
780+
public String toDefaultValue(final Schema p) {
743781
String defaultValue = null;
744782
if ((ModelUtils.isNullable(p)) && (p.getDefault() != null) && ("null".equalsIgnoreCase(p.getDefault().toString())))
745783
return "Nullable::Null";
@@ -899,7 +937,7 @@ protected void updateParameterForString(CodegenParameter codegenParameter, Schem
899937
}
900938

901939
@Override
902-
protected void updatePropertyForAnyType(CodegenProperty property, Schema p) {
940+
protected void updatePropertyForAnyType(final CodegenProperty property, final Schema p) {
903941
// The 'null' value is allowed when the OAS schema is 'any type'.
904942
// See https://github.com/OAI/OpenAPI-Specification/issues/1389
905943
if (Boolean.FALSE.equals(p.getNullable())) {
@@ -917,7 +955,7 @@ protected void updatePropertyForAnyType(CodegenProperty property, Schema p) {
917955
}
918956

919957
@Override
920-
protected String getParameterDataType(Parameter parameter, Schema schema) {
958+
protected String getParameterDataType(final Parameter parameter, final Schema schema) {
921959
if (parameter.get$ref() != null) {
922960
String refName = ModelUtils.getSimpleRef(parameter.get$ref());
923961
return toModelName(refName);
@@ -938,10 +976,12 @@ static class PathMethodOperations {
938976
static class MethodOperation {
939977
public String method;
940978
public String operationID;
979+
public Map<String, Object> vendorExtensions;
941980

942-
MethodOperation(String method, String operationID) {
981+
MethodOperation(String method, String operationID, Map<String, Object> vendorExtensions) {
943982
this.method = method;
944983
this.operationID = operationID;
984+
this.vendorExtensions = vendorExtensions;
945985
}
946986
}
947987
}

modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,19 @@ pub mod {{classFilename}};
44
{{/apis}}
55
{{/apiInfo}}
66

7+
{{#authMethods}}
8+
{{#isApiKey}}
9+
{{#isKeyInCookie}}
10+
/// Cookie Authentication.
11+
pub trait CookieAuthentication {
12+
fn extract_token_from_cookie(&self, cookies: &axum_extra::extract::CookieJar, key: &str) -> Option<String>;
13+
}
14+
{{/isKeyInCookie}}
15+
{{#isKeyInHeader}}
16+
/// API Key Authentication - Header.
17+
pub trait ApiKeyAuthHeader {
18+
fn extract_token_from_header(&self, headers: &axum::http::header::HeaderMap, key: &str) -> Option<String>;
19+
}
20+
{{/isKeyInHeader}}
21+
{{/isApiKey}}
22+
{{/authMethods}}

modules/openapi-generator/src/main/resources/rust-axum/apis.mustache

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ pub trait {{classnamePascalCase}} {
3030
method: Method,
3131
host: Host,
3232
cookies: CookieJar,
33+
{{#vendorExtensions}}
34+
{{#x-has-cookie-auth-methods}}
35+
token_in_cookie: Option<String>,
36+
{{/x-has-cookie-auth-methods}}
37+
{{#x-has-header-auth-methods}}
38+
token_in_header: Option<String>,
39+
{{/x-has-header-auth-methods}}
40+
{{/vendorExtensions}}
3341
{{#headerParams.size}}
3442
header_params: models::{{{operationIdCamelCase}}}HeaderParams,
3543
{{/headerParams.size}}

modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}<I, A>(
77
{{#headerParams.size}}
88
headers: HeaderMap,
99
{{/headerParams.size}}
10+
{{^headerParams.size}}
11+
{{#vendorExtensions}}
12+
{{#x-has-header-auth-methods}}
13+
headers: HeaderMap,
14+
{{/x-has-header-auth-methods}}
15+
{{/vendorExtensions}}
16+
{{/headerParams.size}}
1017
{{#pathParams.size}}
1118
Path(path_params): Path<models::{{{operationIdCamelCase}}}PathParams>,
1219
{{/pathParams.size}}
@@ -47,14 +54,34 @@ async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}<I, A>(
4754
) -> Result<Response, StatusCode>
4855
where
4956
I: AsRef<A> + Send + Sync,
50-
A: apis::{{classFilename}}::{{classnamePascalCase}},
57+
A: apis::{{classFilename}}::{{classnamePascalCase}}{{#vendorExtensions}}{{#x-has-cookie-auth-methods}}+ apis::CookieAuthentication{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}+ apis::ApiKeyAuthHeader{{/x-has-header-auth-methods}}{{/vendorExtensions}},
5158
{
59+
{{#vendorExtensions}}
60+
{{#x-has-auth-methods}}
61+
// Authentication
62+
{{/x-has-auth-methods}}
63+
{{#x-has-cookie-auth-methods}}
64+
let token_in_cookie = api_impl.as_ref().extract_token_from_cookie(&cookies, "{{x-api-key-cookie-name}}");
65+
{{/x-has-cookie-auth-methods}}
66+
{{#x-has-header-auth-methods}}
67+
let token_in_header = api_impl.as_ref().extract_token_from_header(&headers, "{{x-api-key-header-name}}");
68+
{{/x-has-header-auth-methods}}
69+
{{#x-has-auth-methods}}
70+
if let ({{#x-has-cookie-auth-methods}}None,{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}None,{{/x-has-header-auth-methods}}) = ({{#x-has-cookie-auth-methods}}&token_in_cookie,{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}&token_in_header,{{/x-has-header-auth-methods}}) {
71+
return Response::builder()
72+
.status(StatusCode::UNAUTHORIZED)
73+
.body(Body::empty())
74+
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
75+
}
76+
{{/x-has-auth-methods}}
77+
{{/vendorExtensions}}
78+
5279
{{#headerParams}}
5380
{{#-first}}
5481
// Header parameters
5582
let header_params = {
5683
{{/-first}}
57-
let header_{{{paramName}}} = headers.get(HeaderName::from_static("{{{nameInLowerCase}}}"));
84+
let header_{{{paramName}}} = headers.get(HeaderName::from_static("{{{baseName}}}"));
5885

5986
let header_{{{paramName}}} = match header_{{{paramName}}} {
6087
Some(v) => match header::IntoHeaderValue::<{{{dataType}}}>::try_from((*v).clone()) {
@@ -154,6 +181,14 @@ where
154181
method,
155182
host,
156183
cookies,
184+
{{#vendorExtensions}}
185+
{{#x-has-cookie-auth-methods}}
186+
token_in_cookie,
187+
{{/x-has-cookie-auth-methods}}
188+
{{#x-has-header-auth-methods}}
189+
token_in_header,
190+
{{/x-has-header-auth-methods}}
191+
{{/vendorExtensions}}
157192
{{#headerParams.size}}
158193
header_params,
159194
{{/headerParams.size}}
@@ -232,7 +267,7 @@ where
232267
{
233268
let mut response_headers = response.headers_mut().unwrap();
234269
response_headers.insert(
235-
HeaderName::from_static("{{{nameInLowerCase}}}"),
270+
HeaderName::from_static("{{{baseName}}}"),
236271
{{name}}
237272
);
238273
}

0 commit comments

Comments
 (0)