Skip to content
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

Fix Side Drawer Advance Filters (CRASM-967) #740

Merged
merged 21 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
186bbdc
Edited DrawerInterior.tsx to fix filter sort
hawkishpolicy Dec 5, 2024
938777f
Edited severityFacet logic and syncdb script
hawkishpolicy Dec 10, 2024
48a5907
Edited Severity Facet Filter in DrawerInterior.tsx
hawkishpolicy Dec 12, 2024
7e9723f
Cleaned up code
hawkishpolicy Dec 12, 2024
443ef42
Added sort logic to root domain filter
hawkishpolicy Dec 12, 2024
eadf409
Updated backend snapshots
hawkishpolicy Dec 12, 2024
563f4c6
Commented out args in searchSync test
hawkishpolicy Dec 12, 2024
b856c97
Merge remote-tracking branch 'origin/develop' into Fix-Sort-of-Side-D…
hawkishpolicy Dec 17, 2024
537da7e
Fixes search-sync test
Dec 18, 2024
86bb333
Removed console.logs and commented out code
hawkishpolicy Dec 18, 2024
73b1a8e
Refined if check for N/A severity level values
hawkishpolicy Dec 23, 2024
2f9db50
Merge remote-tracking branch 'origin/develop' into Fix-Sort-of-Side-D…
hawkishpolicy Jan 21, 2025
6718c12
empty commit
hawkishpolicy Jan 21, 2025
f2da131
Updated search endpoint to use Elasti Search aggs
hawkishpolicy Jan 22, 2025
dd7bce4
Merge remote-tracking branch 'origin/develop' into Fix-Sort-of-Side-D…
hawkishpolicy Jan 22, 2025
7a40058
Edited elastic search helper functions
hawkishpolicy Jan 23, 2025
504ad0a
Merge remote-tracking branch 'origin/develop' into Fix-Sort-of-Side-D…
hawkishpolicy Jan 23, 2025
98077cf
Refinement to severity formatting logic
hawkishpolicy Jan 24, 2025
f02d9cb
Restored CVE as a facet filter
hawkishpolicy Jan 27, 2025
a63f1f8
Removed accidental trivial conditional
hawkishpolicy Jan 27, 2025
dbc1897
Merge remote-tracking branch 'origin/develop' into Fix-Sort-of-Side-D…
hawkishpolicy Feb 11, 2025
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
1 change: 1 addition & 0 deletions backend/src/xfd_django/xfd_api/api_methods/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ async def search_post(search_body: DomainSearchBody, current_user):
for hit in response["hits"]["hits"]
],
},
"aggregations": response.get("aggregations", {}),
}

return result
Expand Down
88 changes: 85 additions & 3 deletions backend/src/xfd_django/xfd_api/helpers/elastic_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ def get_term_filter_value(field, field_value):
"""
Determine the appropriate term filter value based on the field and its value.

Handles specific cases for boolean values, 'organization.regionId', numeric values,
and the 'name' field.
Handles specific cases for boolean values, 'organization.regionId', numeric values, the 'name' field, and 'vulnerabilities.severity'.
"""
if field_value in ["false", "true"]:
return {field: field_value == "true"}
Expand All @@ -55,6 +54,8 @@ def get_term_filter_value(field, field_value):
return {field: field_value}
if field == "name" and field_value and "*" not in field_value:
field_value = "*{}*".format(field_value)
if field == "vulnerabilities.severity":
return {field: field_value.lower()}
return {"{}.keyword".format(field): field_value}


Expand All @@ -75,6 +76,28 @@ def get_term_filter(term_filter):
elif term_filter["field"] == "organization.regionId":
search_type = "terms"

reg_values = [
"Low",
"low",
"Medium",
"medium",
"High",
"high",
"Critical",
"critical",
]
na_values = [
"N/A",
"n/a",
"Null",
"null",
"None",
"none",
"",
"Undefined",
"undefined",
]

if term_filter["type"] == "any":
if term_filter["field"] == "organization.regionId" and term_filter["values"]:
search = {
Expand All @@ -89,6 +112,61 @@ def get_term_filter(term_filter):
"minimum_should_match": 1,
}
}

# Handle grouping of N/A values for 'vulnerabilities.severity' field. #

elif (
term_filter["field"] == "vulnerabilities.severity"
and "N/A" in term_filter["values"]
):
search = {
"bool": {
"should": [
{"terms": {"vulnerabilities.severity.keyword": na_values}},
{
"bool": {
"must_not": [
{
"exists": {
"field": "vulnerabilities.severity.keyword"
}
}
]
}
},
],
"minimum_should_match": 1,
}
}
# Handle grouping of 'Other' values for 'vulnerabilities.severity' field. #
elif (
term_filter["field"] == "vulnerabilities.severity"
and "Other" in term_filter["values"]
):
search = {
"bool": {
"must_not": [
{
"terms": {
"vulnerabilities.severity.keyword": reg_values
+ na_values
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "vulnerabilities.severity.keyword"
}
}
]
}
},
],
}
}

else:
search = {
"bool": {
Expand Down Expand Up @@ -230,7 +308,11 @@ def build_request(state, options: Dict[str, Any]) -> Dict[str, Any]:
"nested": {"path": "vulnerabilities"},
"aggs": {
"severity": {
"terms": {"field": "vulnerabilities.severity.keyword"}
"terms": {
"field": "vulnerabilities.severity.keyword",
"missing": "null",
"size": 50,
}
},
"cve": {"terms": {"field": "vulnerabilities.cve.keyword"}},
},
Expand Down
1 change: 1 addition & 0 deletions backend/src/xfd_django/xfd_api/schema_models/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SearchResponse(BaseModel):
timed_out: bool
_shards: Any
hits: Any
aggregations: Any


class DomainSearchBody(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions backend/src/xfd_django/xfd_api/tasks/searchSync.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def handler(command_options):
"title": vulnerability.title,
"cvss": vulnerability.cvss,
"severity": vulnerability.severity,
"cve": vulnerability.cve,
"state": vulnerability.state,
"substate": vulnerability.substate,
"description": vulnerability.description,
Expand Down
12 changes: 12 additions & 0 deletions backend/src/xfd_django/xfd_api/tasks/syncdb_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ def create_sample_services_and_vulnerabilities(domain):
"other",
]
),
cve="CVE-"
+ random.choice(
[
"2024-47421",
"2021-22501",
"2024-53959",
"2024-47422",
"2024-47423",
"2020-28163",
"2020-29312",
]
),
needsPopulation=True,
state="open",
substate="unconfirmed",
Expand Down
86 changes: 74 additions & 12 deletions frontend/src/components/DrawerInterior.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ interface Props {
initialFilters: any[];
}

interface SeverityData {
value: string;
count: number;
}

interface GroupedData {
[key: string]: number;
}

const FiltersApplied: React.FC = () => {
return (
<div className={classes.applied}>
Expand Down Expand Up @@ -151,26 +160,79 @@ export const DrawerInterior: React.FC<Props> = (props) => {
);

const portFacet: any[] = facets['services.port']
? facets['services.port'][0].data
? facets['services.port'][0].data.sort(
(a: { value: number }, b: { value: number }) => a.value - b.value
)
: [];

const fromDomainFacet: any[] = facets['fromRootDomain']
? facets['fromRootDomain'][0].data
? facets['fromRootDomain'][0].data.sort(
(a: { value: string }, b: { value: string }) =>
a.value.localeCompare(b.value)
)
: [];

const cveFacet: any[] = facets['vulnerabilities.cve']
? facets['vulnerabilities.cve'][0].data
? facets['vulnerabilities.cve'][0].data.sort(
(a: { value: string }, b: { value: string }) =>
a.value.localeCompare(b.value)
)
: [];

const severityFacet: any[] = facets['vulnerabilities.severity']
? facets['vulnerabilities.severity'][0].data
// To-Do: Create array(s) to handle permutations of null and N/A values
const titleCaseSeverityFacet = facets['vulnerabilities.severity']
? facets['vulnerabilities.severity'][0].data.map(
(d: { value: string; count: number }) => {
if (d.value === null || d.value === undefined) {
return { value: 'N/A', count: d.count };
} else {
return {
value:
d.value[0]?.toUpperCase() + d.value.slice(1)?.toLowerCase(),
count: d.count
};
}
}
)
: [];

// Always show all severities
for (const value of ['Critical', 'High', 'Medium', 'Low']) {
if (!severityFacet.find((severity) => value === severity.value))
severityFacet.push({ value, count: 0 });
}
const groupedData: GroupedData = titleCaseSeverityFacet
.map((d: SeverityData) => {
const severityLevels = [
'N/A',
'Low',
'Medium',
'High',
'Critical',
'Other'
];
if (severityLevels.includes(d.value)) {
return d;
}
if (
!d.value ||
['None', 'Null', 'N/a', 'Undefined', 'undefined'].includes(d.value)
) {
return { value: 'N/A', count: d.count };
} else {
return { value: 'Other', count: d.count };
}
})
.reduce((acc: GroupedData, curr: SeverityData) => {
if (acc[curr.value]) {
acc[curr.value] += curr.count;
} else {
acc[curr.value] = curr.count;
}
return acc;
}, {});

const sortedSeverityFacets = Object.entries(groupedData)
.map(([value, count]) => ({ value, count }))
.sort((a, b) => {
const order = ['N/A', 'Low', 'Medium', 'High', 'Critical', 'Other'];
return order.indexOf(a.value) - order.indexOf(b.value);
});

return (
<StyledWrapper style={{ overflowY: 'auto' }}>
Expand Down Expand Up @@ -360,7 +422,7 @@ export const DrawerInterior: React.FC<Props> = (props) => {
</AccordionDetails>
</Accordion>
)}
{severityFacet.length > 0 && (
{sortedSeverityFacets.length > 0 && (
<Accordion
elevation={0}
square
Expand All @@ -386,7 +448,7 @@ export const DrawerInterior: React.FC<Props> = (props) => {
</AccordionSummary>
<AccordionDetails classes={{ root: classes.details }}>
<FacetFilter
options={severityFacet}
options={sortedSeverityFacets}
selected={filtersByColumn['vulnerabilities.severity'] ?? []}
onSelect={(value) =>
addFilter('vulnerabilities.severity', value, 'any')
Expand Down