Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cf0c012
[backend/frontend] fix(security-platforms): deduplication security pl…
savacano28 Nov 21, 2025
b90b03d
chore: rebase
savacano28 Dec 17, 2025
98663bb
[backend] fix(securityplatforms): wip
savacano28 Jan 7, 2026
cb0423c
[backend] fix(securityplatforms): wip
savacano28 Jan 7, 2026
12cd800
[backend] fix(securityplatforms): wip
savacano28 Jan 8, 2026
145bb5a
[backend] fix(securityplatforms): wip
savacano28 Jan 8, 2026
4529891
[backend] fix(securityplatforms): wip
savacano28 Jan 9, 2026
a877ad0
[backend] fix(securityplatforms): wip
savacano28 Jan 9, 2026
cff2954
[backend] fix(securityplatforms): wip
savacano28 Jan 9, 2026
4c19659
[backend] fix(securityplatforms): wip
savacano28 Jan 9, 2026
79b9e25
[backend] fix(securityplatforms): wip
savacano28 Jan 9, 2026
a12d022
[frontend] fix: update ui to show platform in expectation results
savacano28 Jan 9, 2026
ba1e077
[frontend] fix: update ui to show platform in expectation results
savacano28 Jan 9, 2026
085846c
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
d7cfa6b
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
7930d48
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
dd5d53d
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
048599b
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
08b7653
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
d5d5ac1
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
edc5845
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
4b09d36
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
2d15a45
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
bea7118
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
874bd16
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
dd2854b
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
e474ded
[backend] fix(securityplatform): add tests
savacano28 Jan 12, 2026
f9a8ea0
[backend] fix(securityplatform): add tests
savacano28 Jan 12, 2026
7916353
[backend] fix(securityplatform): wip
savacano28 Jan 12, 2026
926d454
[backend] fix(securityplatform): fix test
savacano28 Jan 12, 2026
f43bea1
[backend] fix(securityplatform): fix test
savacano28 Jan 12, 2026
3cb8895
[backend] fix(securityplatform): fix test
savacano28 Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package io.openaev.migration;

import java.sql.Statement;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.stereotype.Component;

@Component
public class V4_55__Add_unique_constraint_name_security_platform extends BaseJavaMigration {

@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
try {
// 1. Create temp mapping table: old_id -> new_id (case-insensitive)
stmt.execute(
"""
CREATE TEMP TABLE temp_security_platform_mapping AS
SELECT a.asset_id AS old_id,
c.canonical_id AS new_id
FROM assets a
JOIN (
SELECT LOWER(asset_name) AS asset_name_norm,
security_platform_type,
MIN(asset_id) AS canonical_id
FROM assets
WHERE asset_type = 'SecurityPlatform'
GROUP BY LOWER(asset_name), security_platform_type
) c
ON LOWER(a.asset_name) = c.asset_name_norm
AND a.security_platform_type = c.security_platform_type;
""");

// 2. Update assets_tags safely (drop PK, update, deduplication, recreate PK)
stmt.execute("ALTER TABLE assets_tags DROP CONSTRAINT IF EXISTS assets_tags_pkey;");
stmt.execute(
"""
UPDATE assets_tags at
SET asset_id = m.new_id
FROM temp_security_platform_mapping m
WHERE at.asset_id = m.old_id
AND m.old_id <> m.new_id;
""");
stmt.execute(
"""
DELETE FROM assets_tags a
USING assets_tags b
WHERE a.asset_id = b.asset_id
AND a.tag_id = b.tag_id
AND a.ctid > b.ctid;
""");
stmt.execute(
"""
ALTER TABLE assets_tags
ADD CONSTRAINT assets_tags_pkey PRIMARY KEY (asset_id, tag_id);
""");

// 3. Update injects_expectations_traces
stmt.execute(
"""
UPDATE injects_expectations_traces t
SET inject_expectation_trace_source_id = m.new_id
FROM temp_security_platform_mapping m
WHERE t.inject_expectation_trace_source_id = m.old_id
AND m.old_id <> m.new_id;
""");

// 4. Update collectors
stmt.execute(
"""
UPDATE collectors c
SET collector_security_platform = m.new_id
FROM temp_security_platform_mapping m
WHERE c.collector_security_platform = m.old_id
AND m.old_id <> m.new_id;
""");

// 5. Update inject_expectation_results JSON (add sourcePlatform as string, nullable)
stmt.execute(
"""
UPDATE injects_expectations ie
SET inject_expectation_results = sub.new_results::json
FROM (
SELECT
ie2.inject_expectation_id,
jsonb_agg(
jsonb_set(
r.elem,
'{sourceId}',
to_jsonb(canonical.asset_id::text),
true
) || jsonb_set(
r.elem,
'{sourceName}',
to_jsonb(canonical.asset_name),
true
)
) AS new_results
FROM injects_expectations ie2
CROSS JOIN LATERAL jsonb_array_elements(ie2.inject_expectation_results::jsonb) r(elem)
JOIN assets a
ON a.asset_id::text = r.elem->>'sourceId' --This will fetch only results from source type security-platform
LEFT JOIN temp_security_platform_mapping m
ON m.old_id = a.asset_id
JOIN assets canonical
ON canonical.asset_id = COALESCE(m.new_id, a.asset_id)
WHERE a.asset_type = 'SecurityPlatform'
GROUP BY ie2.inject_expectation_id
) sub
WHERE ie.inject_expectation_id = sub.inject_expectation_id;
""");

stmt.execute(
"""
UPDATE injects_expectations ie
SET inject_expectation_results = sub.new_results::json
FROM (
SELECT ie2.inject_expectation_id,
jsonb_agg(
CASE
-- SecurityPlatform sourceType
WHEN r.elem->>'sourceType' = 'security-platform' AND a.asset_id IS NOT NULL THEN
jsonb_set(r.elem, '{sourcePlatform}', to_jsonb(a.security_platform_type), true)
-- Collector sourceType
WHEN r.elem->>'sourceType' = 'collector' AND c.collector_id IS NOT NULL AND sp.asset_id IS NOT NULL THEN
jsonb_set(r.elem, '{sourcePlatform}', to_jsonb(sp.security_platform_type), true)
-- Other sourceType
ELSE
jsonb_set(r.elem, '{sourcePlatform}', 'null'::jsonb, true)
END
) AS new_results
FROM injects_expectations ie2
CROSS JOIN LATERAL jsonb_array_elements(ie2.inject_expectation_results::jsonb) r(elem)
-- Join assets if sourceType = SecurityPlatform
LEFT JOIN assets a
ON r.elem->>'sourceType' = 'security-platform'
AND a.asset_id::text = r.elem->>'sourceId'
-- Join collectors
LEFT JOIN collectors c
ON r.elem->>'sourceType' = 'collector'
AND c.collector_id::text = r.elem->>'sourceId'
-- Join collector's security platform
LEFT JOIN assets sp
ON c.collector_security_platform = sp.asset_id
GROUP BY ie2.inject_expectation_id
) sub
WHERE ie.inject_expectation_id = sub.inject_expectation_id;
""");

// 6. Delete duplicate SecurityPlatform assets
stmt.execute(
"""
DELETE FROM assets
WHERE asset_id IN (
SELECT old_id
FROM temp_security_platform_mapping
WHERE old_id <> new_id
);
""");

// 7. Add unique index to prevent future duplicates
stmt.execute(
"""
CREATE UNIQUE INDEX unique_security_platform_name_type_ci_idx
ON assets (
lower(asset_name::text),
asset_type,
security_platform_type
)
WHERE asset_type::text = 'SecurityPlatform';
""");

// 8. Cleanup
stmt.execute(
"""
DROP TABLE temp_security_platform_mapping;
""");

} finally {
stmt.execute(
"""
DROP TABLE IF EXISTS temp_security_platform_mapping;
""");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class ExpectationUpdateInput {
@NotNull
private String sourceName;

@JsonProperty("source_platform")
private String sourcePlatform;

@JsonProperty("expectation_score")
@NotNull
private Double score;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private List<InjectExpectation> propagateToTeam(
expectationsForPlayers,
expectationForTeams,
injectExpectation,
(score) -> buildForPlayerManualValidation(result, score));
score -> buildForPlayerManualValidation(result, score));
return expectationForTeams;
}

Expand Down Expand Up @@ -330,7 +330,7 @@ public void computeTechnicalExpectation(
updated,
false,
shouldPropagateLastInjectExpectationResult
? (score) -> updated.getResults().getLast()
? score -> updated.getResults().getLast()
: null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class InjectExpectationResultUtils {

private static final String NOT_APPLICABLE = null;

private InjectExpectationResultUtils() {}

// -- SCORE --
Expand Down Expand Up @@ -50,18 +53,30 @@ public static Double computeScore(
// -- SETUP --

private static InjectExpectationResult setUp(
@NotNull final String sourceId, @NotNull final String sourceName) {
@NotNull final String sourceId,
@NotNull final String sourceName,
@NotNull final String sourcePlatform) {
return InjectExpectationResult.builder()
.sourceId(sourceId)
.sourceType(COLLECTOR)
.date(String.valueOf(Instant.now()))
.sourceName(sourceName)
.sourcePlatform(sourcePlatform)
.date(String.valueOf(Instant.now()))
.build();
}

public static List<InjectExpectationResult> setUpFromCollectors(
@NotNull final List<Collector> collectors) {
return collectors.stream().map(c -> setUp(c.getId(), c.getName())).toList();
return collectors.stream()
.map(
c ->
setUp(
c.getId(),
c.getName(),
Optional.ofNullable(c.getSecurityPlatform())
.map(sp -> sp.getSecurityPlatformType().name())
.orElse(null)))
.toList();
}

// -- BUILD --
Expand All @@ -81,6 +96,7 @@ public static void addResult(
.sourceId(input.getSourceId())
.sourceType(input.getSourceType())
.sourceName(input.getSourceName())
.sourcePlatform(input.getSourcePlatform())
.result(resultMsg)
.date(now().toString())
.score(input.getScore())
Expand Down Expand Up @@ -109,6 +125,10 @@ public static void addResult(
.sourceId(collector.getId())
.sourceType(COLLECTOR)
.sourceName(collector.getName())
.sourcePlatform(
Optional.ofNullable(collector.getSecurityPlatform())
.map(sp -> sp.getSecurityPlatformType().name())
.orElse(null))
.result(input.getResult())
.date(Instant.now().toString())
.score(score)
Expand All @@ -133,6 +153,7 @@ public static InjectExpectationResult buildForMediaPressure(
.sourceId("media-pressure")
.sourceType("media-pressure")
.sourceName("Media pressure read")
.sourcePlatform(NOT_APPLICABLE)
.result(Instant.now().toString())
.date(Instant.now().toString())
.score(injectExpectation.getExpectedScore())
Expand All @@ -144,6 +165,7 @@ public static InjectExpectationResult buildForVulnerabilityManager() {
.sourceId(EXPECTATIONS_VULNERABILITY_COLLECTOR_ID)
.sourceType(EXPECTATIONS_VULNERABILITY_COLLECTOR_TYPE)
.sourceName(EXPECTATIONS_VULNERABILITY_COLLECTOR_NAME)
.sourcePlatform(NOT_APPLICABLE)
.score(0.0)
.result(VULNERABILITY.failureLabel)
.date(String.valueOf(Instant.now()))
Expand All @@ -156,6 +178,7 @@ public static InjectExpectationResult buildForPlayerManualValidation(
.sourceId("player-manual-validation")
.sourceType("player-manual-validation")
.sourceName("Player Manual Validation")
.sourcePlatform(NOT_APPLICABLE)
.result(result)
.score(score)
.date(String.valueOf(Instant.now()))
Expand All @@ -168,6 +191,7 @@ public static InjectExpectationResult buildForTeamManualValidation(
.sourceId("team-manual-validation")
.sourceType("team-manual-validation")
.sourceName("Team Manual Validation")
.sourcePlatform(NOT_APPLICABLE)
.result(result)
.score(score)
.date(String.valueOf(Instant.now()))
Expand Down
Loading