-
Notifications
You must be signed in to change notification settings - Fork 211
[backend] feat(sc): post-creation redirection and CTAs for security coverage (#4676) #4980
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,10 @@ | |||||||||||||||||||||||||||||||||||
| import static org.assertj.core.api.Assertions.assertThatNoException; | ||||||||||||||||||||||||||||||||||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||||||||||||||||||||||||||||||||
| import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; | ||||||||||||||||||||||||||||||||||||
| import static org.mockito.ArgumentMatchers.any; | ||||||||||||||||||||||||||||||||||||
| import static org.mockito.Mockito.*; | ||||||||||||||||||||||||||||||||||||
| import static org.mockserver.model.HttpRequest.request; | ||||||||||||||||||||||||||||||||||||
| import static org.mockserver.model.HttpResponse.response; | ||||||||||||||||||||||||||||||||||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||||||||||||||||||||||||||||||||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -28,9 +32,9 @@ | |||||||||||||||||||||||||||||||||||
| import io.openaev.database.repository.ScenarioRepository; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.database.repository.SecurityCoverageRepository; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.database.repository.TagRepository; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.integration.Manager; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.integration.impl.injectors.manual.ManualInjectorIntegrationFactory; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.opencti.connectors.service.OpenCTIConnectorService; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.service.AssetGroupService; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.service.stix.SecurityCoverageService; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.stix.objects.constants.CommonProperties; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.utils.constants.StixConstants; | ||||||||||||||||||||||||||||||||||||
| import io.openaev.utils.fixtures.*; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -47,15 +51,23 @@ | |||||||||||||||||||||||||||||||||||
| import java.util.*; | ||||||||||||||||||||||||||||||||||||
| import org.apache.commons.io.IOUtils; | ||||||||||||||||||||||||||||||||||||
| import org.junit.jupiter.api.*; | ||||||||||||||||||||||||||||||||||||
| import org.mockserver.configuration.Configuration; | ||||||||||||||||||||||||||||||||||||
| import org.mockserver.integration.ClientAndServer; | ||||||||||||||||||||||||||||||||||||
| import org.mockserver.socket.PortFactory; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.http.MediaType; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.test.context.DynamicPropertyRegistry; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.test.context.DynamicPropertySource; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.test.context.TestPropertySource; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.test.web.servlet.MockMvc; | ||||||||||||||||||||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @TestInstance(PER_CLASS) | ||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||
| @WithMockUser(withCapabilities = {Capability.MANAGE_STIX_BUNDLE}) | ||||||||||||||||||||||||||||||||||||
| @DisplayName("STIX API Integration Tests") | ||||||||||||||||||||||||||||||||||||
| @TestPropertySource(properties = {"openaev.xtm.opencti.enable=true"}) | ||||||||||||||||||||||||||||||||||||
| class StixApiTest extends IntegrationTest { | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public static final String T_1531 = "T1531"; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -80,9 +92,14 @@ class StixApiTest extends IntegrationTest { | |||||||||||||||||||||||||||||||||||
| @Autowired private InjectorContractComposer injectorContractComposer; | ||||||||||||||||||||||||||||||||||||
| @Autowired private TagComposer tagComposer; | ||||||||||||||||||||||||||||||||||||
| @Autowired private DomainComposer domainComposer; | ||||||||||||||||||||||||||||||||||||
| @Autowired private ConnectorInstanceComposer connectorInstanceComposer; | ||||||||||||||||||||||||||||||||||||
| @Autowired private ConnectorInstanceConfigurationComposer connectorInstanceConfigurationComposer; | ||||||||||||||||||||||||||||||||||||
| @Autowired private CatalogConnectorComposer catalogConnectorComposer; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Autowired private InjectorFixture injectorFixture; | ||||||||||||||||||||||||||||||||||||
| @Autowired private ManualInjectorIntegrationFactory manualInjectorIntegrationFactory; | ||||||||||||||||||||||||||||||||||||
| @Autowired private InjectorContractFixture injectorContractFixture; | ||||||||||||||||||||||||||||||||||||
| @MockitoSpyBean private SecurityCoverageService securityCoverageService; | ||||||||||||||||||||||||||||||||||||
| @Autowired private OpenCTIConnectorService openCTIConnectorService; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private JsonNode stixSecurityCoverage; | ||||||||||||||||||||||||||||||||||||
| private JsonNode stixSecurityCoverageNoDuration; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -93,9 +110,28 @@ class StixApiTest extends IntegrationTest { | |||||||||||||||||||||||||||||||||||
| private JsonNode stixSecurityCoverageOnlyVulns; | ||||||||||||||||||||||||||||||||||||
| private JsonNode stixSecurityCoverageWithDomainName; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private static ClientAndServer mockServer; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @DynamicPropertySource | ||||||||||||||||||||||||||||||||||||
| static void registerProperties(DynamicPropertyRegistry registry) { | ||||||||||||||||||||||||||||||||||||
| mockServer = new ClientAndServer(Configuration.configuration(), PortFactory.findFreePort()); | ||||||||||||||||||||||||||||||||||||
| registry.add( | ||||||||||||||||||||||||||||||||||||
| "openaev.xtm.opencti.url", | ||||||||||||||||||||||||||||||||||||
| () -> String.format("http://localhost:%d/", mockServer.getLocalPort())); | ||||||||||||||||||||||||||||||||||||
| registry.add( | ||||||||||||||||||||||||||||||||||||
| "openaev.test.connector.url", | ||||||||||||||||||||||||||||||||||||
| () -> String.format("http://localhost:%d/", mockServer.getLocalPort())); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @AfterAll | ||||||||||||||||||||||||||||||||||||
| void after() { | ||||||||||||||||||||||||||||||||||||
| if (mockServer != null) { | ||||||||||||||||||||||||||||||||||||
| mockServer.stop(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @BeforeEach | ||||||||||||||||||||||||||||||||||||
| void setUp() throws Exception { | ||||||||||||||||||||||||||||||||||||
| new Manager(List.of(manualInjectorIntegrationFactory)).monitorIntegrations(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| attackPatternComposer.reset(); | ||||||||||||||||||||||||||||||||||||
| vulnerabilityComposer.reset(); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -184,6 +220,29 @@ void setUp() throws Exception { | |||||||||||||||||||||||||||||||||||
| vulnerabilityComposer.forVulnerability( | ||||||||||||||||||||||||||||||||||||
| VulnerabilityFixture.createVulnerabilityInput("CVE-2025-56786"))) | ||||||||||||||||||||||||||||||||||||
| .persist(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| injectorContractComposer | ||||||||||||||||||||||||||||||||||||
| .forInjectorContract(injectorContractFixture.getWellKnownSingleManualContract()) | ||||||||||||||||||||||||||||||||||||
| .persist(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // need to mock unregistered connector to be use in process | ||||||||||||||||||||||||||||||||||||
| mockServer | ||||||||||||||||||||||||||||||||||||
| .when(request().withMethod("POST").withPath("")) | ||||||||||||||||||||||||||||||||||||
| .respond( | ||||||||||||||||||||||||||||||||||||
| response() | ||||||||||||||||||||||||||||||||||||
| .withStatusCode(200) | ||||||||||||||||||||||||||||||||||||
| .withHeader("Content-Type", "application/json") | ||||||||||||||||||||||||||||||||||||
| .withBody( | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| "data": {} | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| """)); | ||||||||||||||||||||||||||||||||||||
| openCTIConnectorService.registerOrPingAllConnectors(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| mockServer | ||||||||||||||||||||||||||||||||||||
| .when(request().withMethod("POST").withPath("graphql")) | ||||||||||||||||||||||||||||||||||||
| .respond(response().withStatusCode(200)); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+241
to
+245
|
||||||||||||||||||||||||||||||||||||
| openCTIConnectorService.registerOrPingAllConnectors(); | |
| mockServer | |
| .when(request().withMethod("POST").withPath("graphql")) | |
| .respond(response().withStatusCode(200)); | |
| mockServer | |
| .when(request().withMethod("POST").withPath("graphql")) | |
| .respond(response().withStatusCode(200)); | |
| openCTIConnectorService.registerOrPingAllConnectors(); |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MockServer response for the GraphQL endpoint is 200 with an empty body. OpenCTIClient expects a GraphQL-shaped JSON response containing at least a data or errors field; an empty body will be treated as an error response, causing registerConnector/pushStixBundle to throw ConnectorError. Return a minimal valid GraphQL JSON payload (e.g., { "data": {} }) for the stubbed /graphql requests.
| .respond(response().withStatusCode(200)); | |
| .respond( | |
| response() | |
| .withStatusCode(200) | |
| .withHeader("Content-Type", "application/json") | |
| .withBody("{\"data\":{}}")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MockServer expectations are set up with
withPath(""), but the OpenCTI GraphQL client posts to a URL ending with/graphql(see OpenCTIConfig#getApiUrl) and will therefore request path/graphqlor/. As written, this expectation likely won’t match the real requests, causing connector registration/push to fail and the test to be flaky/failing. Consider matching the actual paths (e.g./and/graphql) or using a broader matcher, and ensure the matching expectation is configured for the request you intend to intercept.