-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new fuzzer to check for security schemes definition
- Loading branch information
Showing
5 changed files
with
288 additions
and
4 deletions.
There are no files selected for viewing
64 changes: 64 additions & 0 deletions
64
src/main/java/com/endava/cats/fuzzer/contract/SecuritySchemesContractInfoFuzzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package com.endava.cats.fuzzer.contract; | ||
|
||
import com.endava.cats.fuzzer.ContractInfoFuzzer; | ||
import com.endava.cats.http.HttpMethod; | ||
import com.endava.cats.model.FuzzingData; | ||
import com.endava.cats.report.TestCaseListener; | ||
import io.github.ludovicianul.prettylogger.PrettyLogger; | ||
import io.github.ludovicianul.prettylogger.PrettyLoggerFactory; | ||
import io.swagger.v3.oas.models.Components; | ||
import io.swagger.v3.oas.models.Operation; | ||
import io.swagger.v3.oas.models.security.SecurityRequirement; | ||
import io.swagger.v3.oas.models.security.SecurityScheme; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
@ContractInfoFuzzer | ||
@Component | ||
public class SecuritySchemesContractInfoFuzzer extends BaseContractInfoFuzzer { | ||
private final PrettyLogger log = PrettyLoggerFactory.getLogger(this.getClass()); | ||
|
||
protected SecuritySchemesContractInfoFuzzer(TestCaseListener tcl) { | ||
super(tcl); | ||
} | ||
|
||
@Override | ||
public void process(FuzzingData data) { | ||
testCaseListener.addScenario(log, "Scenario: Check if the current path has security schemes defined either globally or at path level"); | ||
testCaseListener.addExpectedResult(log, "[at least a security schemes] must be present either globally or at path level"); | ||
|
||
Map<String, SecurityScheme> securitySchemeMap = Optional.ofNullable(data.getOpenApi().getComponents()).orElse(new Components()).getSecuritySchemes(); | ||
List<SecurityRequirement> securityRequirementList = Optional.ofNullable(data.getOpenApi().getSecurity()).orElse(Collections.emptyList()); | ||
|
||
boolean hasTopLevelSecuritySchemes = !securityRequirementList.isEmpty(); | ||
boolean areGlobalSecuritySchemesDefined = securityRequirementList.stream().allMatch(securityRequirement -> securitySchemeMap.keySet().containsAll(securityRequirement.keySet())); | ||
|
||
Operation operation = HttpMethod.getOperation(data.getMethod(), data.getPathItem()); | ||
|
||
boolean hasSecuritySchemesAtTagLevel = !Optional.ofNullable(operation.getSecurity()).orElse(Collections.emptyList()).isEmpty(); | ||
|
||
if (hasTopLevelSecuritySchemes || hasSecuritySchemesAtTagLevel) { | ||
if (areGlobalSecuritySchemesDefined) { | ||
testCaseListener.reportInfo(log, "The current path has security scheme(s) properly defined"); | ||
} else { | ||
testCaseListener.reportWarn(log, "The current path has security scheme(s) defined, but they are not present in the [components->securitySchemes] contract element"); | ||
} | ||
} else { | ||
testCaseListener.reportError(log, "The current path does not have security scheme(s) defined and there are none defined globally"); | ||
} | ||
} | ||
|
||
@Override | ||
protected String runKey(FuzzingData data) { | ||
return data.getPath() + data.getMethod(); | ||
} | ||
|
||
@Override | ||
public String description() { | ||
return "verifies if the OpenApi contract contains valid security schemas for all paths, either globally configured or per path"; | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/test/java/com/endava/cats/fuzzer/contract/SecuritySchemesContractInfoFuzzerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package com.endava.cats.fuzzer.contract; | ||
|
||
import com.endava.cats.http.HttpMethod; | ||
import com.endava.cats.io.TestCaseExporter; | ||
import com.endava.cats.model.FuzzingData; | ||
import com.endava.cats.report.ExecutionStatisticsListener; | ||
import com.endava.cats.report.TestCaseListener; | ||
import io.swagger.parser.OpenAPIParser; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import org.assertj.core.api.Assertions; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.Mockito; | ||
import org.springframework.boot.info.BuildProperties; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.boot.test.mock.mockito.SpyBean; | ||
import org.springframework.test.context.junit.jupiter.SpringExtension; | ||
|
||
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.util.Collections; | ||
|
||
@ExtendWith(SpringExtension.class) | ||
class SecuritySchemesContractInfoFuzzerTest { | ||
@SpyBean | ||
private TestCaseListener testCaseListener; | ||
|
||
@MockBean | ||
private ExecutionStatisticsListener executionStatisticsListener; | ||
|
||
@MockBean | ||
private TestCaseExporter testCaseExporter; | ||
|
||
@SpyBean | ||
private BuildProperties buildProperties; | ||
|
||
private SecuritySchemesContractInfoFuzzer securitySchemesContractInfoFuzzer; | ||
|
||
@BeforeAll | ||
static void init() { | ||
System.setProperty("name", "cats"); | ||
System.setProperty("version", "4.3.2"); | ||
System.setProperty("time", "100011111"); | ||
} | ||
|
||
@BeforeEach | ||
void setup() { | ||
securitySchemesContractInfoFuzzer = new SecuritySchemesContractInfoFuzzer(testCaseListener); | ||
} | ||
|
||
@Test | ||
void shouldNotReportAnyError() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/openapi.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").tags(Collections.singletonList("pet")).method(HttpMethod.POST).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportInfo(Mockito.any(), Mockito.eq("The current path has security scheme(s) properly defined")); | ||
} | ||
|
||
@Test | ||
void shouldReportError() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/contract-no-security.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").tags(Collections.singletonList("pet")).method(HttpMethod.PUT).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportError(Mockito.any(), Mockito.eq("The current path does not have security scheme(s) defined and there are none defined globally")); | ||
} | ||
|
||
@Test | ||
void shouldNotReportErrorWithSecurityAtPathLevel() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/contract-no-path-tags.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").method(HttpMethod.PUT).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportInfo(Mockito.any(), Mockito.eq("The current path has security scheme(s) properly defined")); | ||
} | ||
|
||
@Test | ||
void shouldNotReportErrorWithSecurityGlobal() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/contract-path-tags-mismatch.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").method(HttpMethod.PUT).tags(Collections.singletonList("petsCats")).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportInfo(Mockito.any(), Mockito.eq("The current path has security scheme(s) properly defined")); | ||
} | ||
|
||
@Test | ||
void shouldReportWarningWithSecurityGlobalAndSchemeNotDefined() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/contract-security-mismatch-schemes.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").method(HttpMethod.PUT).tags(Collections.singletonList("petsCats")).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportWarn(Mockito.any(), Mockito.eq("The current path has security scheme(s) defined, but they are not present in the [components->securitySchemes] contract element")); | ||
} | ||
|
||
@Test | ||
void shouldNotRunOnSecondAttempt() throws Exception { | ||
OpenAPI openAPI = new OpenAPIParser().readContents(new String(Files.readAllBytes(Paths.get("src/test/resources/openapi.yml"))), null, null).getOpenAPI(); | ||
FuzzingData data = FuzzingData.builder().openApi(openAPI).path("/pet").method(HttpMethod.POST).tags(Collections.singletonList("pet")).pathItem(openAPI.getPaths().get("/pet")).build(); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
|
||
Mockito.verify(testCaseListener, Mockito.times(1)).reportInfo(Mockito.any(), Mockito.eq("The current path has security scheme(s) properly defined")); | ||
|
||
Mockito.reset(testCaseListener); | ||
securitySchemesContractInfoFuzzer.fuzz(data); | ||
Mockito.verify(testCaseListener, Mockito.times(0)).reportInfo(Mockito.any(), Mockito.eq("The current path has security scheme(s) properly defined")); | ||
} | ||
|
||
@Test | ||
void shouldReturnSimpleClassNameForToString() { | ||
Assertions.assertThat(securitySchemesContractInfoFuzzer).hasToString(securitySchemesContractInfoFuzzer.getClass().getSimpleName()); | ||
} | ||
|
||
@Test | ||
void shouldReturnMeaningfulDescription() { | ||
Assertions.assertThat(securitySchemesContractInfoFuzzer.description()).isEqualTo("verifies if the OpenApi contract contains valid security schemas for all paths, either globally configured or per path"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
openapi: 3.0.0 | ||
info: | ||
title: OpenAPI Petstore | ||
description: 'This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. For OAuth2 flow, you may use `user` as both username and password when asked to login.' | ||
license: | ||
name: Apache-2.0 | ||
url: 'http://www.apache.org/licenses/LICENSE-2.0.html' | ||
version: 1.0.0 | ||
contact: | ||
name: CATS team | ||
url: https://github.com/Endava/cats | ||
email: [email protected] | ||
externalDocs: | ||
description: Find out more about OpenAPI generator | ||
url: 'https://openapi-generator.tech' | ||
servers: | ||
- url: /v3 | ||
description: This is the production server | ||
tags: | ||
- name: pet | ||
description: Everything about your Pets | ||
- name: store | ||
description: Access to Petstore orders | ||
- name: user | ||
description: Operations about the user | ||
paths: | ||
/pet: | ||
put: | ||
summary: Update an existing pet | ||
operationId: updatePet | ||
requestBody: | ||
$ref: '#/components/requestBodies/Pet' | ||
responses: | ||
'400': | ||
description: Invalid ID supplied | ||
'404': | ||
description: Pet not found | ||
'405': | ||
description: Validation exception | ||
x-accepts: application/json | ||
x-tags: | ||
- tag: pet | ||
x-contentType: application/json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
openapi: 3.0.0 | ||
info: | ||
title: OpenAPI Petstore | ||
description: 'This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. For OAuth2 flow, you may use `user` as both username and password when asked to login.' | ||
license: | ||
name: Apache-2.0 | ||
url: 'http://www.apache.org/licenses/LICENSE-2.0.html' | ||
version: 1.0.0 | ||
contact: | ||
name: CATS team | ||
url: https://github.com/Endava/cats | ||
email: [email protected] | ||
externalDocs: | ||
description: Find out more about OpenAPI generator | ||
url: 'https://openapi-generator.tech' | ||
servers: | ||
- url: /v3 | ||
description: This is the production server | ||
tags: | ||
- name: pet | ||
description: Everything about your Pets | ||
- name: store | ||
description: Access to Petstore orders | ||
- name: user | ||
description: Operations about the user | ||
paths: | ||
/pet: | ||
put: | ||
tags: | ||
- petCATS | ||
summary: Update an existing pet | ||
operationId: updatePet | ||
requestBody: | ||
$ref: '#/components/requestBodies/Pet' | ||
responses: | ||
'400': | ||
description: Invalid ID supplied | ||
'404': | ||
description: Pet not found | ||
'405': | ||
description: Validation exception | ||
x-accepts: application/json | ||
x-tags: | ||
- tag: pet | ||
x-contentType: application/json | ||
components: | ||
securitySchemes: | ||
bearerAuth: | ||
type: http | ||
scheme: bearer | ||
bearerFormat: JWT | ||
security: | ||
- mismatch: [ ] |