diff --git a/backend/src/main/java/com/park/utmstack/domain/ip_info/GeoIp.java b/backend/src/main/java/com/park/utmstack/domain/ip_info/GeoIp.java deleted file mode 100644 index 583481876..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/ip_info/GeoIp.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.park.utmstack.domain.ip_info; - -import org.springframework.util.StringUtils; - -public class GeoIp { - private String network; - private String latitude; - private String longitude; - private String localeCode; - private String continentCode; - private String continentName; - private String countryIsoCode; - private String countryName; - private String subdivision1IsoCode; - private String subdivision1IsoName; - private String subdivision2IsoCode; - private String subdivision2IsoName; - private String cityName; - private String metroCode; - private String timeZone; - - public String getNetwork() { - return network; - } - - public void setNetwork(String network) { - this.network = network; - } - - public Double getLatitude() { - return Double.valueOf(latitude); - } - - public void setLatitude(String latitude) { - this.latitude = latitude; - } - - public Double getLongitude() { - return Double.valueOf(longitude); - } - - public void setLongitude(String longitude) { - this.longitude = longitude; - } - - public String getLocaleCode() { - return localeCode; - } - - public void setLocaleCode(String localeCode) { - this.localeCode = localeCode; - } - - public String getContinentCode() { - return continentCode; - } - - public void setContinentCode(String continentCode) { - this.continentCode = continentCode; - } - - public String getContinentName() { - return continentName; - } - - public void setContinentName(String continentName) { - this.continentName = continentName; - } - - public String getCountryIsoCode() { - return countryIsoCode; - } - - public void setCountryIsoCode(String countryIsoCode) { - this.countryIsoCode = countryIsoCode; - } - - public String getCountryName() { - return countryName; - } - - public void setCountryName(String countryName) { - this.countryName = countryName; - } - - public String getSubdivision1IsoCode() { - return subdivision1IsoCode; - } - - public void setSubdivision1IsoCode(String subdivision1IsoCode) { - this.subdivision1IsoCode = subdivision1IsoCode; - } - - public String getSubdivision1IsoName() { - return subdivision1IsoName; - } - - public void setSubdivision1IsoName(String subdivision1IsoName) { - this.subdivision1IsoName = subdivision1IsoName; - } - - public String getSubdivision2IsoCode() { - return subdivision2IsoCode; - } - - public void setSubdivision2IsoCode(String subdivision2IsoCode) { - this.subdivision2IsoCode = subdivision2IsoCode; - } - - public String getSubdivision2IsoName() { - return subdivision2IsoName; - } - - public void setSubdivision2IsoName(String subdivision2IsoName) { - this.subdivision2IsoName = subdivision2IsoName; - } - - public String getCityName() { - return cityName; - } - - public void setCityName(String cityName) { - this.cityName = cityName; - } - - public String getMetroCode() { - return metroCode; - } - - public void setMetroCode(String metroCode) { - this.metroCode = metroCode; - } - - public String getTimeZone() { - return timeZone; - } - - public void setTimeZone(String timeZone) { - this.timeZone = timeZone; - } - - @Override - public String toString() { - return "Continent Name: " + (StringUtils.hasText(continentName) ? continentName : "-") + "\n" + - "Continent Code: " + (StringUtils.hasText(continentCode) ? continentCode : "-") + "\n" + - "Country Name: " + (StringUtils.hasText(countryName) ? countryName : "-") + "\n" + - "Country ISO Code: " + (StringUtils.hasText(countryIsoCode) ? countryIsoCode : "-") + "\n" + - "City Name: " + (StringUtils.hasText(cityName) ? cityName : "-") + "\n" + - "Network: " + (StringUtils.hasText(network) ? network : "-") + "\n" + - "Latitude: " + (StringUtils.hasText(latitude) ? latitude : "-") + "\n" + - "Longitude: " + (StringUtils.hasText(longitude) ? longitude : "-") + "\n"; - } -} diff --git a/backend/src/main/java/com/park/utmstack/service/ip_info/IpInfoService.java b/backend/src/main/java/com/park/utmstack/service/ip_info/IpInfoService.java deleted file mode 100644 index 3ece554cb..000000000 --- a/backend/src/main/java/com/park/utmstack/service/ip_info/IpInfoService.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.park.utmstack.service.ip_info; - -import com.park.utmstack.config.ApplicationProperties; -import com.park.utmstack.domain.chart_builder.types.query.FilterType; -import com.park.utmstack.domain.chart_builder.types.query.OperatorType; -import com.park.utmstack.domain.ip_info.GeoIp; -import com.park.utmstack.service.elasticsearch.ElasticsearchService; -import com.park.utmstack.service.elasticsearch.SearchUtil; -import com.park.utmstack.util.exceptions.UtmIpInfoException; -import org.opensearch.client.opensearch.core.SearchRequest; -import org.opensearch.client.opensearch.core.search.HitsMetadata; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; - -@Service -public class IpInfoService { - private static final String CLASSNAME = "IpInfoService"; - - private final ApplicationProperties applicationProperties; - private final ElasticsearchService elasticsearchService; - - public IpInfoService(ApplicationProperties applicationProperties, - ElasticsearchService elasticsearchService) { - this.applicationProperties = applicationProperties; - this.elasticsearchService = elasticsearchService; - } - - /** - * Get information about an Ip - * - * @param ip The ip to get the related information - * @return A ${@link GeoIp} object with the ip information - */ - public GeoIp getIpInfo(String ip) throws UtmIpInfoException { - final String ctx = CLASSNAME + "getIpV4Info"; - try { - List filters = new ArrayList<>(); - filters.add(new FilterType("network", OperatorType.IS, ip)); - - SearchRequest sr = SearchRequest.of(s -> s.index(applicationProperties.getChartBuilder().getIpInfoIndexName()) - .query(SearchUtil.toQuery(filters)).size(1)); - - HitsMetadata hits = elasticsearchService.search(sr, GeoIp.class).hits(); - - if (hits.total().value() <= 0) - return null; - - return hits.hits().get(0).source(); - } catch (Exception e) { - throw new RuntimeException(ctx + ": " + e.getLocalizedMessage()); - } - } -} diff --git a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java index a5c35aae6..ac20f4a15 100644 --- a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java +++ b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java @@ -23,6 +23,11 @@ public class RequestDsl { private static final String CLASSNAME = "RequestDsl"; + public static final String GEO_HIT_AGG = "_geo_hit"; + public static final List GEO_SOURCE_INCLUDES = List.of( + "origin.geolocation.latitude", "origin.geolocation.longitude", + "source.geolocation.latitude", "source.geolocation.longitude", + "destination.geolocation.latitude", "destination.geolocation.longitude"); private final SearchRequest.Builder searchRequestBuilder; private final UtmVisualization visualization; @@ -130,6 +135,14 @@ private void buildAggregation() throws UtmElasticsearchException { Map bucketAggregations = buildBucketAggregation(bucket); Map metricAggregations = buildMetricAggregation(metrics); + if (visualization.getChartType() == ChartType.COORDINATE_MAP_CHART) { + Map withGeo = new LinkedHashMap<>(metricAggregations); + withGeo.put(GEO_HIT_AGG, Aggregation.of(a -> a.topHits(th -> th + .size(1) + .source(s -> s.filter(f -> f.includes(GEO_SOURCE_INCLUDES)))))); + metricAggregations = withGeo; + } + if (!CollectionUtils.isEmpty(bucketAggregations)) { Map root = new LinkedHashMap<>(); if (bucketAggregations.size() > 1) { diff --git a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/responses/impl/coordinate_map/ResponseParserForCoordinateMapChart.java b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/responses/impl/coordinate_map/ResponseParserForCoordinateMapChart.java index d3605edae..ca5406d7d 100644 --- a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/responses/impl/coordinate_map/ResponseParserForCoordinateMapChart.java +++ b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/responses/impl/coordinate_map/ResponseParserForCoordinateMapChart.java @@ -1,17 +1,18 @@ package com.park.utmstack.util.chart_builder.elasticsearch_dsl.responses.impl.coordinate_map; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.park.utmstack.domain.chart_builder.UtmVisualization; import com.park.utmstack.domain.chart_builder.types.aggregation.AggregationType; import com.park.utmstack.domain.chart_builder.types.aggregation.Bucket; import com.park.utmstack.domain.chart_builder.types.aggregation.Metric; -import com.park.utmstack.domain.ip_info.GeoIp; -import com.park.utmstack.service.ip_info.IpInfoService; +import com.park.utmstack.util.chart_builder.elasticsearch_dsl.requests.RequestDsl; import com.park.utmstack.util.chart_builder.elasticsearch_dsl.responses.ResponseParser; -import com.park.utmstack.util.exceptions.UtmIpInfoException; import com.utmstack.opensearch_connector.parsers.TermAggregateParser; import com.utmstack.opensearch_connector.types.BucketAggregation; import com.utmstack.opensearch_connector.types.SearchSqlResponse; +import org.opensearch.client.opensearch._types.aggregations.Aggregate; +import org.opensearch.client.opensearch._types.aggregations.TopHitsAggregate; import org.opensearch.client.opensearch.core.SearchResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,15 +28,10 @@ @Component public class ResponseParserForCoordinateMapChart implements ResponseParser { private static final String CLASSNAME = "ResponseParserForCoordinateMapChart"; + private static final List GEO_PREFIXES = List.of("origin", "source", "destination"); private final Logger log = LoggerFactory.getLogger(ResponseParserForCoordinateMapChart.class); - private final IpInfoService ipInfoService; - - public ResponseParserForCoordinateMapChart(IpInfoService ipInfoService) { - this.ipInfoService = ipInfoService; - } - @Override public List parse(UtmVisualization visualization, SearchResponse result) { final String ctx = CLASSNAME + ".parse"; @@ -53,20 +49,13 @@ public List parse(UtmVisualization visualization, Sear for (BucketAggregation entry : entries) { - GeoIp ipV4Info; - try { - ipV4Info = ipInfoService.getIpInfo(entry.getKey()); - - if (ipV4Info == null) - continue; - } catch (UtmIpInfoException e) { - log.error(e.getMessage()); + Double[] latLon = extractLatLongFromTopHits(entry.getSubAggregations()); + if (latLon == null) continue; - } CoordinateMapChartResult value = new CoordinateMapChartResult(); value.setName(entry.getKey()); - value.addLatitude(ipV4Info.getLatitude()).addLongitude(ipV4Info.getLongitude()); + value.addLatitude(latLon[0]).addLongitude(latLon[1]); switch (metric.getAggregation()) { case COUNT: @@ -140,20 +129,14 @@ public List parse(UtmVisualization visualization, Sear } if (!StringUtils.hasText(ip)) continue; - GeoIp ipInfo; - try { - ipInfo = ipInfoService.getIpInfo(ip); - if (ipInfo == null) continue; - } catch (UtmIpInfoException e) { - log.error(e.getMessage()); - continue; - } + Double[] latLon = extractLatLongFromRow(row); + if (latLon == null) continue; CoordinateMapChartResult chartResult = new CoordinateMapChartResult(); chartResult.setName(ip); chartResult.setValue(new Double[] { - ipInfo.getLatitude(), - ipInfo.getLongitude(), + latLon[0], + latLon[1], (double) i }); @@ -165,4 +148,59 @@ public List parse(UtmVisualization visualization, Sear throw new RuntimeException(ctx + ": " + e.getMessage(), e); } } + + private Double[] extractLatLongFromTopHits(Map subAggs) { + if (subAggs == null) return null; + Aggregate geoAgg = subAggs.get(RequestDsl.GEO_HIT_AGG); + if (geoAgg == null || !geoAgg.isTopHits()) return null; + TopHitsAggregate topHits = geoAgg.topHits(); + if (topHits.hits() == null || topHits.hits().hits().isEmpty()) return null; + var hit = topHits.hits().hits().get(0); + if (hit.source() == null) return null; + try { + ObjectNode source = hit.source().to(ObjectNode.class); + return extractLatLongFromJson(source); + } catch (Exception e) { + log.warn("Failed to deserialize top_hits source for geolocation: {}", e.getMessage()); + return null; + } + } + + private Double[] extractLatLongFromJson(ObjectNode source) { + if (source == null) return null; + for (String prefix : GEO_PREFIXES) { + JsonNode geo = source.path(prefix).path("geolocation"); + JsonNode lat = geo.path("latitude"); + JsonNode lon = geo.path("longitude"); + if (lat.isNumber() && lon.isNumber()) { + return new Double[]{lat.asDouble(), lon.asDouble()}; + } + } + return null; + } + + private Double[] extractLatLongFromRow(Map row) { + Double lat = null; + Double lon = null; + for (Map.Entry e : row.entrySet()) { + if (e.getValue() == null) continue; + String key = e.getKey(); + if (lat == null && (key.endsWith(".geolocation.latitude") || key.equals("latitude"))) { + lat = toDouble(e.getValue()); + } else if (lon == null && (key.endsWith(".geolocation.longitude") || key.equals("longitude"))) { + lon = toDouble(e.getValue()); + } + } + if (lat == null || lon == null) return null; + return new Double[]{lat, lon}; + } + + private Double toDouble(Object val) { + if (val instanceof Number) return ((Number) val).doubleValue(); + try { + return Double.parseDouble(val.toString()); + } catch (NumberFormatException e) { + return null; + } + } } diff --git a/backend/src/main/resources/config/application-prod.yml b/backend/src/main/resources/config/application-prod.yml index 33bacc55c..d8e2c8d1d 100644 --- a/backend/src/main/resources/config/application-prod.yml +++ b/backend/src/main/resources/config/application-prod.yml @@ -64,8 +64,6 @@ jhipster: base-url: application: - chart-builder: # Chart builder configuration - ip-info-index-name: .utm-geoip incident-response: asset-verification-interval: 300