|
7 | 7 | import json |
8 | 8 | import unittest |
9 | 9 | import tempfile |
| 10 | +from types import SimpleNamespace |
10 | 11 | from unittest.mock import patch |
11 | 12 |
|
12 | 13 | import redis |
@@ -902,6 +903,195 @@ def test_gap_analysis_returns_direct_cre_overlap_when_backends_fail( |
902 | 903 | self.assertIn(compare.id, payload["result"][base.id]["paths"]) |
903 | 904 | db_mock.return_value.add_gap_analysis_result.assert_called_once() |
904 | 905 |
|
| 906 | + @patch.object(web_main.gap_analysis, "schedule") |
| 907 | + @patch.object(db, "Node_collection") |
| 908 | + def test_gap_analysis_supports_opencre_as_standard( |
| 909 | + self, db_mock, schedule_mock |
| 910 | + ) -> None: |
| 911 | + compare = defs.Standard( |
| 912 | + name="OWASP Web Security Testing Guide (WSTG)", |
| 913 | + section="WSTG-CRYP-04", |
| 914 | + ) |
| 915 | + opencre = defs.CRE(id="170-772", name="Cryptography", description="") |
| 916 | + opencre.add_link( |
| 917 | + defs.Link(ltype=defs.LinkTypes.LinkedTo, document=compare.shallow_copy()) |
| 918 | + ) |
| 919 | + |
| 920 | + db_mock.return_value.get_gap_analysis_result.return_value = None |
| 921 | + db_mock.return_value.gap_analysis_exists.return_value = False |
| 922 | + db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: ( |
| 923 | + [compare] if name == "OWASP Web Security Testing Guide (WSTG)" else [] |
| 924 | + ) |
| 925 | + db_mock.return_value.session.query.return_value.all.return_value = [ |
| 926 | + SimpleNamespace(id="cre-internal-1") |
| 927 | + ] |
| 928 | + db_mock.return_value.get_CREs.return_value = [opencre] |
| 929 | + |
| 930 | + with self.app.test_client() as client: |
| 931 | + response = client.get( |
| 932 | + "/rest/v1/map_analysis?standard=OpenCRE&standard=OWASP%20Web%20Security%20Testing%20Guide%20(WSTG)", |
| 933 | + headers={"Content-Type": "application/json"}, |
| 934 | + ) |
| 935 | + |
| 936 | + payload = json.loads(response.data) |
| 937 | + self.assertEqual(200, response.status_code) |
| 938 | + self.assertIn("result", payload) |
| 939 | + self.assertIn(opencre.id, payload["result"]) |
| 940 | + self.assertEqual(1, len(payload["result"][opencre.id]["paths"])) |
| 941 | + path = next(iter(payload["result"][opencre.id]["paths"].values())) |
| 942 | + self.assertEqual(compare.id, path["end"]["id"]) |
| 943 | + schedule_mock.assert_not_called() |
| 944 | + |
| 945 | + @patch.object(web_main.gap_analysis, "schedule") |
| 946 | + @patch.object(db, "Node_collection") |
| 947 | + def test_gap_analysis_returns_only_direct_opencre_mappings( |
| 948 | + self, db_mock, schedule_mock |
| 949 | + ) -> None: |
| 950 | + compare = defs.Standard( |
| 951 | + name="CWE", |
| 952 | + sectionID="1004", |
| 953 | + section="Sensitive Cookie Without 'HttpOnly' Flag", |
| 954 | + ) |
| 955 | + direct_cre = defs.CRE( |
| 956 | + id="804-220", |
| 957 | + name="Set httponly attribute for cookie-based session tokens", |
| 958 | + description="", |
| 959 | + ) |
| 960 | + direct_cre.add_link( |
| 961 | + defs.Link(ltype=defs.LinkTypes.LinkedTo, document=compare.shallow_copy()) |
| 962 | + ) |
| 963 | + auto_linked_cres = [] |
| 964 | + for i, cre_id in enumerate( |
| 965 | + [ |
| 966 | + "117-371", |
| 967 | + "166-151", |
| 968 | + "284-521", |
| 969 | + "368-633", |
| 970 | + "612-252", |
| 971 | + "664-080", |
| 972 | + "801-310", |
| 973 | + ], |
| 974 | + start=1, |
| 975 | + ): |
| 976 | + cre = defs.CRE( |
| 977 | + id=cre_id, |
| 978 | + name=f"Automatically mapped CRE {i}", |
| 979 | + description="", |
| 980 | + ) |
| 981 | + cre.add_link( |
| 982 | + defs.Link( |
| 983 | + ltype=defs.LinkTypes.AutomaticallyLinkedTo, |
| 984 | + document=compare.shallow_copy(), |
| 985 | + ) |
| 986 | + ) |
| 987 | + auto_linked_cres.append(cre) |
| 988 | + |
| 989 | + opencre_documents = [direct_cre] + auto_linked_cres |
| 990 | + internal_ids = [ |
| 991 | + SimpleNamespace(id=f"cre-internal-{i}") |
| 992 | + for i in range(len(opencre_documents)) |
| 993 | + ] |
| 994 | + |
| 995 | + db_mock.return_value.get_gap_analysis_result.return_value = None |
| 996 | + db_mock.return_value.gap_analysis_exists.return_value = False |
| 997 | + db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: ( |
| 998 | + [compare] if name == "CWE" else [] |
| 999 | + ) |
| 1000 | + db_mock.return_value.session.query.return_value.all.return_value = internal_ids |
| 1001 | + db_mock.return_value.get_CREs.side_effect = lambda internal_id=None, **kwargs: [ |
| 1002 | + next( |
| 1003 | + cre |
| 1004 | + for index, cre in enumerate(opencre_documents) |
| 1005 | + if internal_id == f"cre-internal-{index}" |
| 1006 | + ) |
| 1007 | + ] |
| 1008 | + |
| 1009 | + with self.app.test_client() as client: |
| 1010 | + response = client.get( |
| 1011 | + "/rest/v1/map_analysis?standard=CWE&standard=OpenCRE", |
| 1012 | + headers={"Content-Type": "application/json"}, |
| 1013 | + ) |
| 1014 | + |
| 1015 | + payload = json.loads(response.data) |
| 1016 | + self.assertEqual(200, response.status_code) |
| 1017 | + self.assertIn("result", payload) |
| 1018 | + self.assertEqual([compare.id], list(payload["result"].keys())) |
| 1019 | + self.assertEqual(1, len(payload["result"][compare.id]["paths"])) |
| 1020 | + path = next(iter(payload["result"][compare.id]["paths"].values())) |
| 1021 | + self.assertEqual(compare.id, payload["result"][compare.id]["start"]["id"]) |
| 1022 | + self.assertEqual(direct_cre.id, path["end"]["id"]) |
| 1023 | + self.assertEqual(direct_cre.name, path["end"]["name"]) |
| 1024 | + self.assertEqual("", path["path"][0]["start"]["id"]) |
| 1025 | + self.assertEqual(direct_cre.id, path["path"][0]["end"]["id"]) |
| 1026 | + schedule_mock.assert_not_called() |
| 1027 | + |
| 1028 | + @patch.object(web_main.gap_analysis, "schedule") |
| 1029 | + @patch.object(db, "Node_collection") |
| 1030 | + def test_gap_analysis_returns_only_direct_opencre_mappings_when_opencre_is_left( |
| 1031 | + self, db_mock, schedule_mock |
| 1032 | + ) -> None: |
| 1033 | + compare = defs.Standard( |
| 1034 | + name="CWE", |
| 1035 | + sectionID="1004", |
| 1036 | + section="Sensitive Cookie Without 'HttpOnly' Flag", |
| 1037 | + ) |
| 1038 | + direct_cre = defs.CRE( |
| 1039 | + id="804-220", |
| 1040 | + name="Set httponly attribute for cookie-based session tokens", |
| 1041 | + description="", |
| 1042 | + ) |
| 1043 | + direct_cre.add_link( |
| 1044 | + defs.Link(ltype=defs.LinkTypes.LinkedTo, document=compare.shallow_copy()) |
| 1045 | + ) |
| 1046 | + indirect_cre = defs.CRE( |
| 1047 | + id="117-371", |
| 1048 | + name="Use a centralized access control mechanism", |
| 1049 | + description="", |
| 1050 | + ) |
| 1051 | + indirect_cre.add_link( |
| 1052 | + defs.Link( |
| 1053 | + ltype=defs.LinkTypes.AutomaticallyLinkedTo, |
| 1054 | + document=compare.shallow_copy(), |
| 1055 | + ) |
| 1056 | + ) |
| 1057 | + |
| 1058 | + opencre_documents = [direct_cre, indirect_cre] |
| 1059 | + internal_ids = [ |
| 1060 | + SimpleNamespace(id=f"cre-internal-{i}") |
| 1061 | + for i in range(len(opencre_documents)) |
| 1062 | + ] |
| 1063 | + |
| 1064 | + db_mock.return_value.get_gap_analysis_result.return_value = None |
| 1065 | + db_mock.return_value.gap_analysis_exists.return_value = False |
| 1066 | + db_mock.return_value.get_nodes.side_effect = lambda name=None, **kwargs: ( |
| 1067 | + [compare] if name == "CWE" else [] |
| 1068 | + ) |
| 1069 | + db_mock.return_value.session.query.return_value.all.return_value = internal_ids |
| 1070 | + db_mock.return_value.get_CREs.side_effect = lambda internal_id=None, **kwargs: [ |
| 1071 | + next( |
| 1072 | + cre |
| 1073 | + for index, cre in enumerate(opencre_documents) |
| 1074 | + if internal_id == f"cre-internal-{index}" |
| 1075 | + ) |
| 1076 | + ] |
| 1077 | + |
| 1078 | + with self.app.test_client() as client: |
| 1079 | + response = client.get( |
| 1080 | + "/rest/v1/map_analysis?standard=OpenCRE&standard=CWE", |
| 1081 | + headers={"Content-Type": "application/json"}, |
| 1082 | + ) |
| 1083 | + |
| 1084 | + payload = json.loads(response.data) |
| 1085 | + self.assertEqual(200, response.status_code) |
| 1086 | + self.assertEqual([direct_cre.id], list(payload["result"].keys())) |
| 1087 | + self.assertEqual(1, len(payload["result"][direct_cre.id]["paths"])) |
| 1088 | + path = next(iter(payload["result"][direct_cre.id]["paths"].values())) |
| 1089 | + self.assertEqual(direct_cre.id, payload["result"][direct_cre.id]["start"]["id"]) |
| 1090 | + self.assertEqual(compare.id, path["end"]["id"]) |
| 1091 | + self.assertEqual(direct_cre.id, path["path"][0]["start"]["id"]) |
| 1092 | + self.assertEqual(compare.id, path["path"][0]["end"]["id"]) |
| 1093 | + schedule_mock.assert_not_called() |
| 1094 | + |
905 | 1095 | @patch.object(web_main.cre_main, "fetch_upstream_json") |
906 | 1096 | @patch.object(web_main.gap_analysis, "schedule") |
907 | 1097 | @patch.object(db, "Node_collection") |
|
0 commit comments