[backend] feat(sc): post-creation redirection and CTAs for security coverage (#4676)#4980
[backend] feat(sc): post-creation redirection and CTAs for security coverage (#4676)#4980GaetanSantucci wants to merge 2 commits intorelease/currentfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release/current #4980 +/- ##
=====================================================
+ Coverage 56.38% 56.53% +0.15%
- Complexity 4505 4514 +9
=====================================================
Files 999 999
Lines 29847 29856 +9
Branches 2177 2177
=====================================================
+ Hits 16828 16878 +50
+ Misses 12058 12013 -45
- Partials 961 965 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR extends the STIX security coverage ingestion flow to push an updated STIX object back to OpenCTI containing an external URL to the generated OpenAEV scenario, enabling post-creation CTAs/redirection from OpenCTI.
Changes:
- Add
SecurityCoverageService#pushCoverageToOpenCTI(...)to injectexternal_uriinto the Security Coverage SDO and push a STIX bundle to OpenCTI. - Update
StixService#processBundle(...)to call the OpenCTI push after building the scenario. - Adjust integration test fixtures and STIX API integration tests to enable OpenCTI mode and prepare a “manual” injector contract.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| openaev-api/src/test/java/io/openaev/utils/fixtures/opencti/TestBeanConnector.java | Makes the test connector URL configurable via Spring property injection. |
| openaev-api/src/test/java/io/openaev/utils/fixtures/InjectorContractFixture.java | Adds a helper to ensure a well-known manual injector contract exists for tests. |
| openaev-api/src/test/java/io/openaev/api/stix_process/StixApiTest.java | Enables OpenCTI mode for integration tests and adds a MockServer to emulate OpenCTI endpoints. |
| openaev-api/src/main/java/io/openaev/service/stix/StixService.java | Pushes coverage back to OpenCTI as part of STIX bundle processing. |
| openaev-api/src/main/java/io/openaev/service/stix/SecurityCoverageService.java | Implements the OpenCTI push: parse coverage SDO, set external_uri, wrap into bundle, send via OpenCTIConnectorService. |
Comments suppressed due to low confidence (3)
openaev-api/src/main/java/io/openaev/service/stix/SecurityCoverageService.java:325
- The Javadoc for
pushCoverageToOpenCTIlistsParsingExceptionandConnectorError, but the method signature also throwsIOException(from the underlying OpenCTI client). Update the Javadoc to documentIOExceptionas well so callers understand the full error contract.
/**
* Pushes the security coverage to OpenCTI. This injects the OpenAEV scenario external URL into
* the STIX object.
*
* @param scenario The scenario containing the security coverage.
* @throws ParsingException If STIX parsing fails.
* @throws ConnectorError If the OpenCTI push fails.
*/
public void pushCoverageToOpenCTI(Scenario scenario)
throws ParsingException, ConnectorError, IOException {
SecurityCoverage coverage = scenario.getSecurityCoverage();
openaev-api/src/test/java/io/openaev/api/stix_process/StixApiTest.java:136
- Because
setUp()runs before every test, the twomockServer.when(...)expectations are appended repeatedly. In this file (which contains many tests), that can grow the expectation list and slow down or destabilize the suite. Consider callingmockServer.reset()(ormockServer.resetExpectations()) at the start ofsetUp()before adding new expectations.
@BeforeEach
void setUp() throws Exception {
attackPatternComposer.reset();
openaev-api/src/main/java/io/openaev/service/stix/StixService.java:40
processBundlenow propagatesConnectorError, but the current STIX controller (StixApi#processBundle) only treatsBadRequestException | ParsingException | IOExceptionas a 400 and will handleConnectorErrorvia the genericExceptionbranch (500). If connector errors should be returned/acknowledged differently (or not fail the whole operation), handleConnectorErrorexplicitly (either here or in the controller) and decide whether scenario creation should roll back on OpenCTI push failures.
public Scenario processBundle(String stixJson)
throws IOException, ParsingException, ConnectorError {
try {
// Update securityCoverage with the last bundle
SecurityCoverage securityCoverage =
securityCoverageService.processAndBuildStixToSecurityCoverage(stixJson);
// Update Scenario using the last SecurityCoverage
Scenario scenario =
securityCoverageService.buildScenarioFromSecurityCoverage(securityCoverage);
securityCoverageService.pushCoverageToOpenCTI(scenario);
return scenario;
| // 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(); |
There was a problem hiding this comment.
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 /graphql or /. 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.
| openCTIConnectorService.registerOrPingAllConnectors(); | ||
|
|
||
| mockServer | ||
| .when(request().withMethod("POST").withPath("graphql")) | ||
| .respond(response().withStatusCode(200)); |
There was a problem hiding this comment.
registerOrPingAllConnectors() is invoked before the /graphql expectation is configured. Since connector registration calls the GraphQL endpoint, the registration HTTP call won’t be stubbed here and may fail, leaving the connector unregistered and breaking later pushes. Configure the GraphQL expectation before calling registerOrPingAllConnectors() (or move the registration call after all relevant expectations are set).
| 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(); |
|
|
||
| mockServer | ||
| .when(request().withMethod("POST").withPath("graphql")) | ||
| .respond(response().withStatusCode(200)); |
There was a problem hiding this comment.
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\":{}}")); |
As a SOC Analyst
I want 2 clear CTA after creating a Security Coverage, one displayed in the creation report and one available in the Security Coverage Overview, when I am redirected there
So that I can immediately review my generated security coverage
Proposed changes
Added a method returning a Stix bundle with the external URL leading to the scenario created from the security coverage, necessary for redirection to OpenAEV from OpenCTI.
Testing Instructions
Related issues