Skip to content

Commit cc2a444

Browse files
committed
Detect AWS IAM Auth by looking for common patterns
1 parent b57c23b commit cc2a444

File tree

3 files changed

+189
-14
lines changed

3 files changed

+189
-14
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class CodegenSecurity {
3030
// Those are to differentiate basic and bearer authentication
3131
// isHttpSignature is to support HTTP signature authorization scheme.
3232
// https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
33-
public Boolean isBasicBasic, isBasicBearer, isHttpSignature;
33+
public Boolean isBasicBasic, isBasicBearer, isHttpSignature, isAWSV4Signature;
3434
public String bearerFormat;
3535
public Map<String, Object> vendorExtensions = new HashMap<String, Object>();
3636
// ApiKey specific
@@ -56,6 +56,7 @@ public CodegenSecurity(CodegenSecurity original) {
5656
this.isBasic = original.isBasic;
5757
this.isBasicBasic = original.isBasicBasic;
5858
this.isHttpSignature = original.isHttpSignature;
59+
this.isAWSV4Signature = original.isAWSV4Signature;
5960
this.bearerFormat = original.bearerFormat;
6061
this.isBasicBearer = original.isBasicBearer;
6162
this.isApiKey = original.isApiKey;
@@ -134,6 +135,7 @@ public boolean equals(Object o) {
134135
Objects.equals(isApiKey, that.isApiKey) &&
135136
Objects.equals(isBasicBasic, that.isBasicBasic) &&
136137
Objects.equals(isHttpSignature, that.isHttpSignature) &&
138+
Objects.equals(isAWSV4Signature, that.isAWSV4Signature) &&
137139
Objects.equals(isBasicBearer, that.isBasicBearer) &&
138140
Objects.equals(bearerFormat, that.bearerFormat) &&
139141
Objects.equals(vendorExtensions, that.vendorExtensions) &&
@@ -157,7 +159,7 @@ public boolean equals(Object o) {
157159
public int hashCode() {
158160

159161
return Objects.hash(name, description, type, scheme, isBasic, isOAuth, isOpenId, isApiKey,
160-
isBasicBasic, isHttpSignature, isBasicBearer, bearerFormat, vendorExtensions,
162+
isBasicBasic, isHttpSignature, isAWSV4Signature, isBasicBearer, bearerFormat, vendorExtensions,
161163
keyParamName, isKeyInQuery, isKeyInHeader, isKeyInCookie, flow,
162164
authorizationUrl, tokenUrl, refreshUrl, scopes, isCode, isPassword, isApplication, isImplicit,
163165
openIdConnectUrl);
@@ -176,6 +178,7 @@ public String toString() {
176178
sb.append(", isApiKey=").append(isApiKey);
177179
sb.append(", isBasicBasic=").append(isBasicBasic);
178180
sb.append(", isHttpSignature=").append(isHttpSignature);
181+
sb.append(", isAWSV4Signature=").append(isAWSV4Signature);
179182
sb.append(", isBasicBearer=").append(isBasicBearer);
180183
sb.append(", bearerFormat='").append(bearerFormat).append('\'');
181184
sb.append(", vendorExtensions=").append(vendorExtensions);

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

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
package org.openapitools.codegen.languages;
1919

20-
import io.swagger.v3.oas.models.media.Schema;
21-
import io.swagger.v3.parser.util.SchemaTypeUtil;
22-
import lombok.Getter;
23-
import lombok.Setter;
20+
import java.io.File;
21+
import java.util.List;
22+
import java.util.Locale;
23+
import java.util.Map;
24+
import java.util.TreeSet;
25+
2426
import org.apache.commons.lang3.StringUtils;
2527
import org.openapitools.codegen.*;
2628
import org.openapitools.codegen.meta.features.DocumentationFeature;
@@ -30,12 +32,15 @@
3032
import org.openapitools.codegen.model.OperationMap;
3133
import org.openapitools.codegen.model.OperationsMap;
3234
import org.openapitools.codegen.utils.ModelUtils;
35+
import org.slf4j.Logger;
36+
import org.slf4j.LoggerFactory;
3337

34-
import java.io.File;
35-
import java.util.List;
36-
import java.util.Locale;
37-
import java.util.Map;
38-
import java.util.TreeSet;
38+
import io.swagger.v3.oas.models.OpenAPI;
39+
import io.swagger.v3.oas.models.media.Schema;
40+
import io.swagger.v3.oas.models.security.SecurityScheme;
41+
import io.swagger.v3.parser.util.SchemaTypeUtil;
42+
import lombok.Getter;
43+
import lombok.Setter;
3944

4045
public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodegen {
4146

@@ -53,6 +58,8 @@ public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodege
5358
public static final String AXIOS_VERSION = "axiosVersion";
5459
public static final String DEFAULT_AXIOS_VERSION = "^1.6.1";
5560

61+
private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptAxiosClientCodegen.class);
62+
5663
@Getter @Setter
5764
protected String npmRepository = null;
5865
protected Boolean stringEnums = false;
@@ -121,6 +128,76 @@ private static String getRelativeToRoot(String path) {
121128
return sb.toString();
122129
}
123130

131+
@Override
132+
public void preprocessOpenAPI(OpenAPI openAPI) {
133+
super.preprocessOpenAPI(openAPI);
134+
135+
boolean hasAwsV4Signature = detectAwsV4Signature(openAPI);
136+
additionalProperties.put("withAWSV4Signature", hasAwsV4Signature);
137+
}
138+
139+
/**
140+
* Detects if the OpenAPI specification uses AWS V4 signature authentication
141+
* by checking for common patterns used in AWS API Gateway specifications.
142+
*/
143+
private boolean detectAwsV4Signature(OpenAPI openAPI) {
144+
// Check security schemes for AWS V4 signature patterns
145+
if (openAPI.getComponents() != null && openAPI.getComponents().getSecuritySchemes() != null) {
146+
return openAPI.getComponents().getSecuritySchemes().entrySet().stream()
147+
.anyMatch(entry -> isAwsV4SecurityScheme(entry.getKey(), entry.getValue(), openAPI));
148+
}
149+
150+
return false;
151+
}
152+
153+
/**
154+
* Determines if a security scheme represents AWS V4 signature authentication
155+
*/
156+
private boolean isAwsV4SecurityScheme(String schemeName, SecurityScheme scheme, OpenAPI openAPI) {
157+
if (scheme == null) {
158+
return false;
159+
}
160+
161+
// Pattern 1: Check for AWS-specific extension
162+
if (scheme.getExtensions() != null) {
163+
Object authType = scheme.getExtensions().get("x-amazon-apigateway-authtype");
164+
if ("awsSigv4".equals(authType)) {
165+
return true;
166+
}
167+
}
168+
169+
// Pattern 2: Check for common AWS V4 signature scheme names
170+
String lowerSchemeName = schemeName.toLowerCase(Locale.ROOT);
171+
if (lowerSchemeName.contains("sigv4") ||
172+
lowerSchemeName.contains("aws") ||
173+
lowerSchemeName.contains("iam")) {
174+
175+
// Additional validation: should be apiKey type with Authorization header
176+
if (SecurityScheme.Type.APIKEY.equals(scheme.getType()) &&
177+
"Authorization".equalsIgnoreCase(scheme.getName()) &&
178+
SecurityScheme.In.HEADER.equals(scheme.getIn())) {
179+
return true;
180+
}
181+
}
182+
183+
// Pattern 3: Check for AWS API Gateway URL patterns in servers
184+
if (openAPI.getServers() != null) {
185+
boolean hasAwsApiGatewayUrl = openAPI.getServers().stream()
186+
.anyMatch(server -> server.getUrl() != null &&
187+
(server.getUrl().contains("execute-api") && server.getUrl().contains("amazonaws.com")));
188+
189+
// If we have AWS API Gateway URL and an Authorization header scheme, likely AWS V4
190+
if (hasAwsApiGatewayUrl &&
191+
SecurityScheme.Type.APIKEY.equals(scheme.getType()) &&
192+
"Authorization".equalsIgnoreCase(scheme.getName()) &&
193+
SecurityScheme.In.HEADER.equals(scheme.getIn())) {
194+
return true;
195+
}
196+
}
197+
198+
return false;
199+
}
200+
124201
@Override
125202
public void processOpts() {
126203
super.processOpts();
@@ -182,7 +259,6 @@ public void processOpts() {
182259
setAxiosVersion(additionalProperties.get(AXIOS_VERSION).toString());
183260
}
184261
additionalProperties.put("axiosVersion", getAxiosVersion());
185-
186262
}
187263

188264
@Override
@@ -363,4 +439,22 @@ protected void addImport(Schema composed, Schema childSchema, CodegenModel model
363439
// import everything (including child schema of a composed schema)
364440
addImport(model, modelName);
365441
}
442+
443+
@Override
444+
public List<CodegenSecurity> fromSecurity(Map<String, SecurityScheme> securitySchemeMap) {
445+
List<CodegenSecurity> securities = super.fromSecurity(securitySchemeMap);
446+
447+
// Post-process security schemes to detect AWS V4 signature
448+
for (CodegenSecurity security : securities) {
449+
if (securitySchemeMap.containsKey(security.name)) {
450+
SecurityScheme scheme = securitySchemeMap.get(security.name);
451+
if (isAwsV4SecurityScheme(security.name, scheme, this.openAPI)) {
452+
security.isAWSV4Signature = true;
453+
LOGGER.info("Detected AWS V4 signature security scheme: {}", security.name);
454+
}
455+
}
456+
}
457+
458+
return securities;
459+
}
366460
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
package org.openapitools.codegen.typescript.axios;
22

3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.testng.Assert.assertEquals;
5+
import static org.testng.Assert.assertFalse;
6+
import static org.testng.Assert.assertTrue;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
311
import org.openapitools.codegen.CodegenConstants;
12+
import org.openapitools.codegen.CodegenSecurity;
413
import org.openapitools.codegen.SupportingFile;
14+
import org.openapitools.codegen.TestUtils;
515
import org.openapitools.codegen.languages.TypeScriptAxiosClientCodegen;
616
import org.openapitools.codegen.typescript.TypeScriptGroups;
717
import org.testng.annotations.Test;
818

9-
import static org.assertj.core.api.Assertions.assertThat;
10-
import static org.testng.Assert.assertEquals;
19+
import io.swagger.v3.oas.models.OpenAPI;
20+
import io.swagger.v3.oas.models.security.SecurityScheme;
1121

1222
@Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_AXIOS})
1323
public class TypeScriptAxiosClientCodegenTest {
@@ -130,4 +140,72 @@ public void testAppliesCustomAxiosVersion() {
130140

131141
assertEquals(codegen.additionalProperties().get("axiosVersion"), "^1.2.3");
132142
}
143+
144+
@Test
145+
public void testDetectsAwsIamAuthenticationWithExtension() {
146+
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/typescript-axios/with-aws-iam.yaml");
147+
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
148+
149+
// Call preprocessOpenAPI which will detect AWS and set the property
150+
codegen.preprocessOpenAPI(openAPI);
151+
152+
// Should detect AWS V4 signature due to x-amazon-apigateway-authtype extension
153+
assertTrue((Boolean) codegen.additionalProperties().get(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT));
154+
}
155+
156+
@Test
157+
public void testDetectsAwsIamAuthenticationWithNaming() {
158+
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/typescript-axios/with-aws-iam.yaml");
159+
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
160+
161+
// Call preprocessOpenAPI which will detect AWS and set the property
162+
codegen.preprocessOpenAPI(openAPI);
163+
164+
// Should detect AWS V4 signature due to scheme name patterns and AWS API Gateway URL
165+
assertTrue((Boolean) codegen.additionalProperties().get(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT));
166+
}
167+
168+
@Test
169+
public void testDoesNotDetectAwsIamInRegularPetstore() {
170+
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore.yaml");
171+
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
172+
173+
// Call preprocessOpenAPI
174+
codegen.preprocessOpenAPI(openAPI);
175+
176+
// Should NOT detect AWS V4 signature in regular petstore
177+
assertFalse(codegen.additionalProperties().containsKey(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT) &&
178+
(Boolean) codegen.additionalProperties().get(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT));
179+
}
180+
181+
@Test
182+
public void testDoesNotDetectAwsIamInSwagger2Petstore() {
183+
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/petstore.yaml");
184+
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
185+
186+
// Call preprocessOpenAPI
187+
codegen.preprocessOpenAPI(openAPI);
188+
189+
// Should NOT detect AWS V4 signature in Swagger 2.0 petstore either
190+
assertFalse(codegen.additionalProperties().containsKey(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT) &&
191+
(Boolean) codegen.additionalProperties().get(CodegenConstants.WITH_AWSV4_SIGNATURE_COMMENT));
192+
}
193+
194+
@Test
195+
public void testFromSecuritySetsAwsV4Flag() {
196+
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/typescript-axios/with-aws-iam.yaml");
197+
TypeScriptAxiosClientCodegen codegen = new TypeScriptAxiosClientCodegen();
198+
199+
// Call preprocessOpenAPI first to set up the openAPI object
200+
codegen.preprocessOpenAPI(openAPI);
201+
202+
// Test fromSecurity method directly
203+
Map<String, SecurityScheme> securitySchemes =
204+
openAPI.getComponents().getSecuritySchemes();
205+
List<CodegenSecurity> securities = codegen.fromSecurity(securitySchemes);
206+
207+
// Should have AWS V4 signature flagged on individual security schemes
208+
boolean hasAwsV4 = securities.stream().anyMatch(s -> Boolean.TRUE.equals(s.isAWSV4Signature));
209+
assertTrue(hasAwsV4, "fromSecurity should set isAWSV4Signature=true for AWS schemes");
210+
}
133211
}

0 commit comments

Comments
 (0)