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

Severity Level Validation Improvements (CRASM-1057) #747

Merged
merged 20 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3f7de25
Refined Severity Levels filtering in Bar Chart
hawkishpolicy Dec 23, 2024
e5a8017
Added to-do for N/A values array
hawkishpolicy Dec 23, 2024
c562310
Refined Severity Level sorting and grouping
hawkishpolicy Dec 27, 2024
2da4510
Removed unnecessary if statement from Comparator
hawkishpolicy Dec 27, 2024
cbd704d
Removed unused console.logs and variables
hawkishpolicy Dec 27, 2024
1afe5c3
Refactored if statement for N/A values
hawkishpolicy Dec 30, 2024
ffa2013
Removed console.logs and unused variables
hawkishpolicy Dec 30, 2024
d0d6694
Merge remote-tracking branch 'origin/develop' into Severity-Level-Imp…
hawkishpolicy Jan 8, 2025
8489044
Merge remote-tracking branch 'origin/develop' into Severity-Level-Imp…
hawkishpolicy Jan 10, 2025
57b4da5
Irregular Severity Levels Improvements
hawkishpolicy Jan 13, 2025
096cfe3
Removed console.logs and prints
hawkishpolicy Jan 13, 2025
0bb820b
Refactored boolean logic in filter_helpers.py
hawkishpolicy Jan 13, 2025
382d408
Removed commented out code
hawkishpolicy Jan 13, 2025
eadd96a
hawkishpolicy Jan 15, 2025
bc76c2f
Removed console.logs
hawkishpolicy Jan 16, 2025
185ca6f
Updated import of sanitize in app.ts
hawkishpolicy Jan 16, 2025
cacba6d
Changes to dompurify import
hawkishpolicy Jan 16, 2025
c6eff3a
Reverted changes to ResultCard
hawkishpolicy Jan 16, 2025
1eb0117
Merge remote-tracking branch 'origin/develop' into Severity-Level-Imp…
hawkishpolicy Jan 16, 2025
3d16c52
Merge remote-tracking branch 'origin/develop' into Severity-Level-Imp…
hawkishpolicy Jan 21, 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
5 changes: 3 additions & 2 deletions backend/src/api/vulnerabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ class VulnerabilitySearch {
if (this.filters?.severity) {
if (this.filters.severity === 'N/A') {
qs.andWhere(
"vulnerability.severity IS NULL OR vulnerability.severity = ''"
"vulnerability.severity IS NULL OR vulnerability.severity = '' OR vulnerability.severity ILIKE 'N/A' OR vulnerability.severity ILIKE 'NULL'"
);
} else if (this.filters.severity === 'Other') {
qs.andWhere(
`vulnerability.severity NOT ILIKE 'N/A' AND
`vulnerability.severity NOT ILIKE 'NULL' AND
vulnerability.severity NOT ILIKE 'N/A' AND
vulnerability.severity NOT ILIKE 'Low' AND
vulnerability.severity NOT ILIKE 'Medium' AND
vulnerability.severity NOT ILIKE 'High' AND
Expand Down
10 changes: 9 additions & 1 deletion backend/src/xfd_django/xfd_api/api_methods/vulnerability.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,21 @@ def search_vulnerabilities(vulnerability_search: VulnerabilitySearch, current_us
)

# Permissions check
if not is_global_view_admin(current_user):
if (
not is_global_view_admin(current_user)
and not current_user.userType == "regionalAdmin"
):
org_ids = get_org_memberships(current_user)
if not org_ids:
return [], 0 # User has no accessible organizations
vulnerabilities = vulnerabilities.filter(
domain__organization_id__in=org_ids
)
# Regional Admins can only view vulnerabilities in their region
if current_user.userType == "regionalAdmin" and current_user.regionId:
vulnerabilities = vulnerabilities.filter(
domain__organization__regionId=current_user.regionId
)

# Apply custom FCEB and CIDR filter
vulnerabilities = vulnerabilities.filter(
Expand Down
1 change: 1 addition & 0 deletions backend/src/xfd_django/xfd_api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ def get_stats_org_ids(current_user, filters):
is_global_view_admin(current_user)
or (is_regional_admin_for_organization(current_user, org_id))
or (is_org_admin(current_user, org_id))
or (get_org_memberships(current_user))
):
organization_ids.add(org_id)

Expand Down
43 changes: 42 additions & 1 deletion backend/src/xfd_django/xfd_api/helpers/filter_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
from ..models import Vulnerability
from ..schema_models.vulnerability import VulnerabilityFilters

# Define the severity levels
SEVERITY_LEVELS = ["Low", "Medium", "High", "Critical"]
NULL_VALUES = ["None", "Null", "N/A", "Undefined", ""]


def format_severity(severity: str) -> str:
"""Format severity to classify as 'N/A', standard severity, or 'Other'."""
if severity is None or severity in NULL_VALUES:
return "N/A"
elif severity.title() in SEVERITY_LEVELS:
return severity.title()
else:
return "Other"


def sort_direction(sort, order):
"""
Expand Down Expand Up @@ -100,7 +114,34 @@ def apply_vuln_filters(

# Partial match on severity
if vulnerability_filters.severity:
q &= Q(severity__icontains=vulnerability_filters.severity)
severity_category = format_severity(vulnerability_filters.severity)

if severity_category == "N/A":
q &= (
Q(severity=None)
| Q(severity__icontains="none")
| Q(severity__icontains="null")
| Q(severity__icontains="n/a")
| Q(severity__icontains="undefined")
| Q(severity="")
)

elif severity_category == "Other":
q &= ~(
Q(severity=None)
| Q(severity__icontains="none")
| Q(severity__icontains="null")
| Q(severity__icontains="undefined")
| Q(severity="")
| Q(severity__icontains="N/A")
| Q(severity__icontains="Low")
| Q(severity__icontains="Medium")
| Q(severity__icontains="High")
| Q(severity__icontains="Critical")
)

elif severity_category in SEVERITY_LEVELS:
q &= Q(severity__icontains=severity_category)

# Partial match on cpe
if vulnerability_filters.cpe:
Expand Down
9 changes: 8 additions & 1 deletion backend/src/xfd_django/xfd_api/helpers/stats_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ async def get_stats_count_from_cache(redis_client, redis_key_prefix, filtered_or
if response:
stats_list = json.loads(response)
for stat in stats_list:
aggregated_stats[stat["id"]] += stat["value"]
stat_id = stat["id"]
# Handle the case where the stat ID is None.
# None/Null values come from Redis as "0" and cannot be incremented.
if stat_id in [None, "None"]:
stat_id = "None"
if stat["value"] == 0:
stat["value"] = 1
aggregated_stats[stat_id] += stat["value"]

return [
{"id": stat_id, "value": value, "label": stat_id}
Expand Down
25 changes: 24 additions & 1 deletion backend/src/xfd_django/xfd_api/tasks/syncdb_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,30 @@ def create_sample_services_and_vulnerabilities(domain):
domain=domain,
service=None,
description="Sample description",
severity=random.choice(["Low", "Medium", "High"]),
severity=random.choice(
[
None,
"N/A",
"n/a",
"Null",
"null",
"Undefined",
"undefined",
"",
"Low",
"Medium",
"High",
"Critical",
"Other",
"!@#$%^&*()",
1234,
"low",
"medium",
"high",
"critical",
"other",
]
),
needsPopulation=True,
state="open",
substate="unconfirmed",
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/hooks/useDomainApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ export const useDomainApi = (showAll?: boolean) => {
tableFilters['organization'] = currentOrganization.id;
}

console.log('filters here', tableFilters);

const { result, count, url } = await apiPost<ApiResponse>(
doExport ? '/domain/export' : '/domain/search',
{
Expand Down
22 changes: 12 additions & 10 deletions frontend/src/pages/Risk/VulnerabilityBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,15 @@ const VulnerabilityBarChart = (props: {
</>
);

// Place null values in "N/A" and capitalize the first letter of each word in the data.
// Capitalize the first letter of each id.
const titleCaseData: BarData[] = data.map((d) => {
if (d.id === 'null' || d.id === null || d.id === '') {
return { id: 'N/A', value: d.value };
} else {
return {
id: d.id[0]?.toUpperCase() + d.id.slice(1)?.toLowerCase(),
value: d.value
};
}
return {
id: d.id[0]?.toUpperCase() + d.id.slice(1)?.toLowerCase(),
value: d.value
};
});

// Group the data by severity level and "Other". Sum the values for each group.
// Sort irregular ids into N/A and Other. Sum the values for each group.
const groupedData = titleCaseData
.map((d) => {
const severityLevels = [
Expand All @@ -98,6 +94,12 @@ const VulnerabilityBarChart = (props: {
];
if (severityLevels.includes(d.id)) {
return d;
}
if (
!d.id ||
['None', 'Null', 'N/a', 'Undefined', 'undefined', ''].includes(d.id)
) {
return { id: 'N/A', value: d.value };
} else {
return { id: 'Other', value: d.value };
}
Expand Down
51 changes: 37 additions & 14 deletions frontend/src/pages/Vulnerabilities/Vulnerabilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
children?: React.ReactNode;
groupBy?: string;
}) => {
const { currentOrganization, apiPost, apiPut } = useAuthContext();
const { currentOrganization, apiPost, apiPut, user } = useAuthContext();
const [vulnerabilities, setVulnerabilities] = useState<Vulnerability[]>([]);
const [totalResults, setTotalResults] = useState(0);
const [loadingError, setLoadingError] = useState(false);
Expand Down Expand Up @@ -150,7 +150,12 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
userOrgIsExcluded = true;
}
});
if (currentOrganization && !userOrgIsExcluded) {

if (
currentOrganization &&
!userOrgIsExcluded &&
user?.userType === 'standard'
) {
tableFilters['organization'] = currentOrganization.id;
}
if (tableFilters['isKev']) {
Expand All @@ -174,7 +179,7 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
return;
}
},
[apiPost, currentOrganization]
[apiPost, currentOrganization, user?.userType]
);

const fetchVulnerabilities = useCallback(
Expand Down Expand Up @@ -312,19 +317,33 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
const titleCase = (str: string) =>
str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();

const severityLevels: string[] = ['Low', 'Medium', 'High', 'Critical'];
const severityLevels: string[] = [
'N/A',
'Low',
'Medium',
'High',
'Critical',
'Other'
];

const formatSeverity = (severity: string) => {
if (severity === null || severity === '' || severity === 'N/A') {
const formatSeverity = (severity?: any) => {
const titleCaseSev = titleCase(severity);
if (severityLevels.includes(titleCaseSev)) {
return titleCaseSev;
}
if (
!titleCaseSev ||
['None', 'Null', 'N/a', 'Undefined', 'undefined', ''].includes(
titleCaseSev
)
) {
return 'N/A';
} else if (severityLevels.includes(titleCase(severity))) {
return titleCase(severity);
} else {
return 'Other';
}
};

const severity = formatSeverity(vuln.severity ?? '');
const severity = formatSeverity(vuln.severity ?? 'N/A');

return {
id: vuln.id,
Expand Down Expand Up @@ -388,21 +407,25 @@ export const Vulnerabilities: React.FC<{ groupBy?: string }> = ({
flex: 0.5,
sortComparator: (v1, v2, cellParams1, cellParams2) => {
const severityLevels: Record<string, number> = {
Low: 1,
Medium: 2,
High: 3,
Critical: 4
'N/A': 1,
Low: 2,
Medium: 3,
High: 4,
Critical: 5,
Other: 6
};
return (
severityLevels[cellParams1.value] - severityLevels[cellParams2.value]
);
},
renderCell: (cellValues: GridRenderCellParams) => {
const severityLevels: Record<string, number> = {
NA: 0,
Low: 1,
Medium: 2,
High: 3,
Critical: 4
Critical: 4,
Other: 5
};
return (
<Stack>
Expand Down
Loading