diff --git a/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx b/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx index cf6fb88ae..c559e07c4 100644 --- a/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx +++ b/application/frontend/src/pages/BrowseRootCres/browseRootCres.tsx @@ -47,9 +47,7 @@ export const BrowseRootCres = () => { {!loading && !error && (
-
- {display && } -
+
{display && }
)} diff --git a/application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx b/application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx index 2b952ce82..be6041207 100644 --- a/application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx +++ b/application/frontend/src/pages/GapAnalysis/GapAnalysis.tsx @@ -65,10 +65,10 @@ export const GapAnalysis = () => { }, [BaseStandard, CompareStandard, setGapAnalysis]); const handleAccordionClick = (e, titleProps) => { - const { index } = titleProps - const newIndex = activeIndex === index ? -1 : index - SetActiveIndex(newIndex) - } + const { index } = titleProps; + const newIndex = activeIndex === index ? -1 : index; + SetActiveIndex(newIndex); + }; return (
@@ -107,65 +107,67 @@ export const GapAnalysis = () => { - - - {gapAnalysis[key].paths.sort((a, b) => a.score - b.score).slice(0, 3).map((path) => { - let segmentID = gapAnalysis[key].start.id; - return ( - <> - { - const { text, nextID } = GetSegmentText(segment, segmentID); - segmentID = nextID; - return text; - }) - .join('')} - trigger={ - - {path.end.name} {path.end.sectionID} {path.end.section} {path.end.subsection}{' '} - {path.end.description}{' '}({path.score}) - - } - /> -
- - ); - })} + + + {gapAnalysis[key].paths + .sort((a, b) => a.score - b.score) + .slice(0, 3) + .map((path) => { + let segmentID = gapAnalysis[key].start.id; + return ( + <> + { + const { text, nextID } = GetSegmentText(segment, segmentID); + segmentID = nextID; + return text; + }) + .join('')} + trigger={ + + {path.end.name} {path.end.sectionID} {path.end.section}{' '} + {path.end.subsection} {path.end.description} ({path.score}) + + } + /> +
+ + ); + })} (Total Links: {gapAnalysis[key].paths.length})
- {gapAnalysis[key].paths.sort((a, b) => a.score - b.score).slice(2, gapAnalysis[key].paths.length).map((path) => { - let segmentID = gapAnalysis[key].start.id; - return ( - <> - { - const { text, nextID } = GetSegmentText(segment, segmentID); - segmentID = nextID; - return text; - }) - .join('')} - trigger={ - - {path.end.name} {path.end.sectionID} {path.end.section} {path.end.subsection}{' '} - {path.end.description}{' '}({path.score}) - - } - /> -
- - ); - })} + {gapAnalysis[key].paths + .sort((a, b) => a.score - b.score) + .slice(2, gapAnalysis[key].paths.length) + .map((path) => { + let segmentID = gapAnalysis[key].start.id; + return ( + <> + { + const { text, nextID } = GetSegmentText(segment, segmentID); + segmentID = nextID; + return text; + }) + .join('')} + trigger={ + + {path.end.name} {path.end.sectionID} {path.end.section}{' '} + {path.end.subsection} {path.end.description} ({path.score}) + + } + /> +
+ + ); + })}
diff --git a/application/frontend/src/pages/Search/components/BodyText.tsx b/application/frontend/src/pages/Search/components/BodyText.tsx index a43c101ee..59fd4e4bd 100644 --- a/application/frontend/src/pages/Search/components/BodyText.tsx +++ b/application/frontend/src/pages/Search/components/BodyText.tsx @@ -24,14 +24,16 @@ export const SearchBody = () => { topics.

-

Do you have an OWASP account? Try talking to our CRE Chatbot

+

+ Do you have an OWASP account? Try talking to our CRE Chatbot +

HOW?

OpenCRE links each section of a resource (like a standard or guideline) to a shared topic, known as a Common Requirement, causing that section to also link with all other resources that link to the same - topic. This 1) enables users to find all combined information from relevant sources, 2) facilitates - a shared and better understanding of cyber security, and 3) allows standard makers to have links - that keep working and offer all the information that readers need, alleviating their need to cover + topic. This 1) enables users to find all combined information from relevant sources, 2) facilitates a + shared and better understanding of cyber security, and 3) allows standard makers to have links that + keep working and offer all the information that readers need, alleviating their need to cover everything themselves. OpenCRE maintains itself: links to OpenCRE in the standard text are scanned automatically. Furthermore, topics are linked with related other topics, creating a semantic web for security to explore. @@ -43,17 +45,17 @@ export const SearchBody = () => {

WHO?

- OpenCRE is the independent brainchild of software security professionals Spyros Gasteratos and Rob - van der Veer, who joined forces to tackle the complexities and segmentation in current security - standards and guidelines. They collaborated closely with many initiatives, including SKF, OpenSSF and the Owasp Top 10 - project. OpenCRE is an open-source platform overseen by the OWASP foundation through the + OpenCRE is the independent brainchild of software security professionals Spyros Gasteratos and Rob van + der Veer, who joined forces to tackle the complexities and segmentation in current security standards + and guidelines. They collaborated closely with many initiatives, including SKF, OpenSSF and the Owasp + Top 10 project. OpenCRE is an open-source platform overseen by the OWASP foundation through the OWASP Integration standard project . The goal is to foster better coordination among security initiatives.

OpenCRE currently links OWASP standards (Top 10, ASVS, Proactive Controls, Cheat sheets, Testing - guide, ZAP, Juice shop, SAMM), plus several other sources (CWE, CAPEC, NIST-800 53, NIST-800 63b, Cloud Control - Matrix, ISO27001, ISO27002, and NIST SSDF). + guide, ZAP, Juice shop, SAMM), plus several other sources (CWE, CAPEC, NIST-800 53, NIST-800 63b, + Cloud Control Matrix, ISO27001, ISO27002, and NIST SSDF).

Contact us via (rob.vanderveer [at] owasp.org) to join the movement. Currently, a stakeholder group is @@ -65,12 +67,9 @@ export const SearchBody = () => { {' '} CRE explanation document{' '} - , follow our - - {' '} - LinkedIn page{' '} - {' '} - or click the diagram below. + + , follow our + LinkedIn page or click the diagram below.

diff --git a/application/frontend/src/utils/document.ts b/application/frontend/src/utils/document.ts index 54007d167..07b5a784d 100644 --- a/application/frontend/src/utils/document.ts +++ b/application/frontend/src/utils/document.ts @@ -1,4 +1,10 @@ -import { DOCUMENT_TYPES, DOCUMENT_TYPE_NAMES, TYPE_IS_PART_OF, TYPE_LINKED_FROM, TYPE_LINKED_TO } from '../const'; +import { + DOCUMENT_TYPES, + DOCUMENT_TYPE_NAMES, + TYPE_IS_PART_OF, + TYPE_LINKED_FROM, + TYPE_LINKED_TO, +} from '../const'; import { Document, LinkedDocument } from '../types'; export const getDocumentDisplayName = (document: Document) => { @@ -73,10 +79,13 @@ export const getApiEndpoint = (doc: Document, apiUrl: string): string => { return `${apiUrl}/id/${doc.id}`; }; -export const getDocumentTypeText = (linkType, docType, parentDocType = ""): string => { +export const getDocumentTypeText = (linkType, docType, parentDocType = ''): string => { let docText = DOCUMENT_TYPE_NAMES[linkType]; if (linkType === TYPE_LINKED_TO && docType === DOCUMENT_TYPES.TYPE_CRE) { - docText = parentDocType === DOCUMENT_TYPES.TYPE_STANDARD ? DOCUMENT_TYPE_NAMES[TYPE_LINKED_FROM] : DOCUMENT_TYPE_NAMES[TYPE_IS_PART_OF]; + docText = + parentDocType === DOCUMENT_TYPES.TYPE_STANDARD + ? DOCUMENT_TYPE_NAMES[TYPE_LINKED_FROM] + : DOCUMENT_TYPE_NAMES[TYPE_IS_PART_OF]; } return docText; }; diff --git a/application/tests/gap_analysis_test.py b/application/tests/gap_analysis_test.py new file mode 100644 index 000000000..a1414c6d9 --- /dev/null +++ b/application/tests/gap_analysis_test.py @@ -0,0 +1,103 @@ +import unittest + +from application.utils.gap_analysis import ( + get_path_score, + get_relation_direction, + get_next_id, + PENALTIES +) + + +class TestGapAnalysis(unittest.TestCase): + def tearDown(self) -> None: + return None + + def setUp(self) -> None: + return None + + def test_get_relation_direction_UP(self): + step = {"start": {"id": "123"}, "end": {"id": "234"}} + self.assertEqual(get_relation_direction(step, "123"), "UP") + + def test_get_relation_direction_DOWN(self): + step = {"start": {"id": "123"}, "end": {"id": "234"}} + self.assertEqual(get_relation_direction(step, "234"), "DOWN") + + def test_get_next_id_start(self): + step = {"start": {"id": "123"}, "end": {"id": "234"}} + self.assertEqual(get_next_id(step, "234"), "123") + + def test_get_next_id_end(self): + step = {"start": {"id": "123"}, "end": {"id": "234"}} + self.assertEqual(get_next_id(step, "123"), "234") + + def test_get_path_score_direct_siblings_zero(self): + path = { + "start": { + "id": "029f7cd7-ef2f-4f25-b0d2-3227cde4b34b", + }, + "end": { + "id": "7d030730-14cc-4c43-8927-f2d0f5fbcf5d", + }, + "path": [ + { + "end": { + "id": "029f7cd7-ef2f-4f25-b0d2-3227cde4b34b", + }, + "relationship": "LINKED_TO", + "start": { + "id": "07bc9f6f-5387-4dc6-b277-0022ed76049f", + }, + }, + { + "end": { + "id": "7d030730-14cc-4c43-8927-f2d0f5fbcf5d", + }, + "relationship": "LINKED_TO", + "start": { + "id": "e2ac59b2-c1d8-4525-a6b3-155d480aecc9", + }, + }, + ], + } + self.assertEqual(get_path_score(path), 0) + + def test_get_path_score_one_up_zero(self): + path = { + "start": { + "id": "029f7cd7-ef2f-4f25-b0d2-3227cde4b34b", + }, + "end": { + "id": "7d030730-14cc-4c43-8927-f2d0f5fbcf5d", + }, + "path": [ + { + "end": { + "id": "029f7cd7-ef2f-4f25-b0d2-3227cde4b34b", + }, + "relationship": "LINKED_TO", + "start": { + "id": "07bc9f6f-5387-4dc6-b277-0022ed76049f", + }, + }, + { + "end": { + "id": "123", + }, + "relationship": "CONTAINS", + "start": { + "id": "07bc9f6f-5387-4dc6-b277-0022ed76049f", + }, + }, + { + "end": { + "id": "7d030730-14cc-4c43-8927-f2d0f5fbcf5d", + }, + "relationship": "LINKED_TO", + "start": { + "id": "123", + }, + }, + ], + } + self.assertEqual(get_path_score(path), PENALTIES['CONTAINS_UP']) diff --git a/application/utils/gap_analysis.py b/application/utils/gap_analysis.py index fe3be0e39..308714592 100644 --- a/application/utils/gap_analysis.py +++ b/application/utils/gap_analysis.py @@ -1,5 +1,26 @@ -import random +PENALTIES = {"RELATED": 20, "CONTAINS_UP": 2, "CONTAINS_DOWN": 1, "LINKED_TO": 0} -def get_path_score(path): - return random.randint(10, 100) \ No newline at end of file +def get_path_score(path, start_id): + score = 0 + previous_id = start_id + for step in path["path"]: + penalty_type = step["relationship"] + + if step["relationship"] == "CONTAINS": + penalty_type = f"CONTAINS_{get_relation_direction(step, previous_id)}" + score += PENALTIES[penalty_type] + previous_id = get_next_id(step, previous_id) + return score + + +def get_relation_direction(step, previous_id): + if step["start"]["id"] == previous_id: + return "UP" + return "DOWN" + + +def get_next_id(step, previous_id): + if step["start"]["id"] == previous_id: + return step["end"]["id"] + return step["start"]["id"] diff --git a/application/web/web_main.py b/application/web/web_main.py index fc4a985cf..3ac93f8e5 100644 --- a/application/web/web_main.py +++ b/application/web/web_main.py @@ -215,7 +215,7 @@ def gap_analysis() -> Any: # TODO (spyros): add export result to spreadsheet key = path["start"]["id"] if key not in grouped_paths: grouped_paths[key] = {"start": path["start"], "paths": []} - path['score'] = get_path_score(path) + path["score"] = get_path_score(path) del path["start"] grouped_paths[key]["paths"].append(path)