psengine 2.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. psengine/__init__.py +22 -0
  2. psengine/_sdk_id.py +16 -0
  3. psengine/_version.py +14 -0
  4. psengine/analyst_notes/__init__.py +32 -0
  5. psengine/analyst_notes/constants.py +15 -0
  6. psengine/analyst_notes/errors.py +42 -0
  7. psengine/analyst_notes/helpers.py +90 -0
  8. psengine/analyst_notes/models.py +219 -0
  9. psengine/analyst_notes/note.py +149 -0
  10. psengine/analyst_notes/note_mgr.py +400 -0
  11. psengine/base_http_client.py +285 -0
  12. psengine/classic_alerts/__init__.py +24 -0
  13. psengine/classic_alerts/classic_alert.py +275 -0
  14. psengine/classic_alerts/classic_alert_mgr.py +507 -0
  15. psengine/classic_alerts/constants.py +31 -0
  16. psengine/classic_alerts/errors.py +38 -0
  17. psengine/classic_alerts/helpers.py +87 -0
  18. psengine/classic_alerts/markdown/__init__.py +13 -0
  19. psengine/classic_alerts/markdown/markdown.py +359 -0
  20. psengine/classic_alerts/models.py +141 -0
  21. psengine/collective_insights/__init__.py +29 -0
  22. psengine/collective_insights/collective_insights.py +164 -0
  23. psengine/collective_insights/constants.py +44 -0
  24. psengine/collective_insights/errors.py +18 -0
  25. psengine/collective_insights/insight.py +89 -0
  26. psengine/collective_insights/models.py +81 -0
  27. psengine/common_models.py +89 -0
  28. psengine/config/__init__.py +15 -0
  29. psengine/config/config.py +284 -0
  30. psengine/config/errors.py +18 -0
  31. psengine/constants.py +63 -0
  32. psengine/detection/__init__.py +17 -0
  33. psengine/detection/detection_mgr.py +135 -0
  34. psengine/detection/detection_rule.py +85 -0
  35. psengine/detection/errors.py +26 -0
  36. psengine/detection/helpers.py +56 -0
  37. psengine/detection/models.py +47 -0
  38. psengine/endpoints.py +98 -0
  39. psengine/enrich/__init__.py +28 -0
  40. psengine/enrich/constants.py +73 -0
  41. psengine/enrich/errors.py +26 -0
  42. psengine/enrich/lookup.py +299 -0
  43. psengine/enrich/lookup_mgr.py +341 -0
  44. psengine/enrich/models/__init__.py +13 -0
  45. psengine/enrich/models/base_enriched_entity.py +43 -0
  46. psengine/enrich/models/lookup.py +271 -0
  47. psengine/enrich/models/soar.py +138 -0
  48. psengine/enrich/soar.py +89 -0
  49. psengine/enrich/soar_mgr.py +176 -0
  50. psengine/entity_lists/__init__.py +16 -0
  51. psengine/entity_lists/constants.py +19 -0
  52. psengine/entity_lists/entity_list.py +435 -0
  53. psengine/entity_lists/entity_list_mgr.py +185 -0
  54. psengine/entity_lists/errors.py +26 -0
  55. psengine/entity_lists/models.py +87 -0
  56. psengine/entity_match/__init__.py +16 -0
  57. psengine/entity_match/entity_match.py +90 -0
  58. psengine/entity_match/entity_match_mgr.py +235 -0
  59. psengine/entity_match/errors.py +18 -0
  60. psengine/entity_match/models.py +22 -0
  61. psengine/errors.py +41 -0
  62. psengine/helpers/__init__.py +23 -0
  63. psengine/helpers/helpers.py +471 -0
  64. psengine/logger/__init__.py +15 -0
  65. psengine/logger/constants.py +39 -0
  66. psengine/logger/errors.py +18 -0
  67. psengine/logger/rf_logger.py +148 -0
  68. psengine/markdown/__init__.py +21 -0
  69. psengine/markdown/markdown.py +169 -0
  70. psengine/markdown/models.py +22 -0
  71. psengine/playbook_alerts/__init__.py +34 -0
  72. psengine/playbook_alerts/constants.py +35 -0
  73. psengine/playbook_alerts/errors.py +35 -0
  74. psengine/playbook_alerts/helpers.py +80 -0
  75. psengine/playbook_alerts/mappings.py +44 -0
  76. psengine/playbook_alerts/markdown/__init__.py +13 -0
  77. psengine/playbook_alerts/markdown/markdown.py +98 -0
  78. psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
  79. psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
  80. psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
  81. psengine/playbook_alerts/models/__init__.py +36 -0
  82. psengine/playbook_alerts/models/common_models.py +18 -0
  83. psengine/playbook_alerts/models/panel_log.py +329 -0
  84. psengine/playbook_alerts/models/panel_status.py +70 -0
  85. psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
  86. psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
  87. psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
  88. psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
  89. psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
  90. psengine/playbook_alerts/models/search_endpoint.py +68 -0
  91. psengine/playbook_alerts/pa_category.py +37 -0
  92. psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
  93. psengine/playbook_alerts/playbook_alerts.py +393 -0
  94. psengine/rf_client.py +430 -0
  95. psengine/risklists/__init__.py +17 -0
  96. psengine/risklists/constants.py +15 -0
  97. psengine/risklists/errors.py +20 -0
  98. psengine/risklists/models.py +65 -0
  99. psengine/risklists/risklist_mgr.py +156 -0
  100. psengine/stix2/__init__.py +21 -0
  101. psengine/stix2/base_stix_entity.py +62 -0
  102. psengine/stix2/complex_entity.py +372 -0
  103. psengine/stix2/constants.py +81 -0
  104. psengine/stix2/enriched_indicator.py +261 -0
  105. psengine/stix2/errors.py +22 -0
  106. psengine/stix2/helpers.py +68 -0
  107. psengine/stix2/rf_bundle.py +240 -0
  108. psengine/stix2/simple_entity.py +145 -0
  109. psengine/stix2/util.py +53 -0
  110. psengine-2.0.4.dist-info/METADATA +189 -0
  111. psengine-2.0.4.dist-info/RECORD +115 -0
  112. psengine-2.0.4.dist-info/WHEEL +5 -0
  113. psengine-2.0.4.dist-info/entry_points.txt +2 -0
  114. psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
  115. psengine-2.0.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from .markdown import (
15
+ MarkdownMaker,
16
+ clean_text,
17
+ divider,
18
+ escape_pipe_characters,
19
+ html_textarea,
20
+ table_from_rows,
21
+ )
@@ -0,0 +1,169 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ import html
15
+ from typing import Union
16
+
17
+ import markdown_strings
18
+ from markdown_strings import header
19
+ from pydantic import validate_call
20
+
21
+ from .models import Section
22
+
23
+ TITLE_HEADER_LEVEL = 2
24
+
25
+ ###############################################################################
26
+ # Helpers
27
+ ###############################################################################
28
+
29
+
30
+ def divider() -> str:
31
+ """Return divider."""
32
+ return '\n---\n'
33
+
34
+
35
+ def table_from_rows(table_list) -> str:
36
+ r"""Return a formatted table, using each list as the list. The specifics are
37
+ the same as those for the table function.
38
+
39
+ >>> table_from_rows([["1","2","3"],["4","5","6"],["7","8","9"]])
40
+ '| 1 | 2 | 3 |\\n| --- | --- | --- |\\n| 4 | 5 | 6 |\\n| 7 | 8 | 9 |'
41
+ """
42
+ longest_list = max(table_list, key=len)
43
+ number_of_columns = len(longest_list)
44
+ columns = [
45
+ [
46
+ str(row[column]).replace('|', ' ').replace('\n', ' ') if column < len(row) else ''
47
+ for row in table_list
48
+ ]
49
+ for column in range(number_of_columns)
50
+ ]
51
+ return markdown_strings.table(columns)
52
+
53
+
54
+ def clean_text(text: str) -> str:
55
+ """Cleanup legacy alerts data from markdown and html text special chars."""
56
+ text = (
57
+ text.replace('\n', '')
58
+ .replace('#', '\\#')
59
+ .replace('_', '\\_')
60
+ .replace('-', '\\-')
61
+ .replace('*', '•')
62
+ .replace('\u2028', ' ')
63
+ )
64
+ return html.escape(text)
65
+
66
+
67
+ def escape_pipe_characters(text: str) -> str:
68
+ """Escape pipe characters in text."""
69
+ return text.replace('|', '\\|')
70
+
71
+
72
+ def html_textarea(text: str) -> str:
73
+ """Wrap text in a markdown code block."""
74
+ return f'<textarea readonly="true">{text}</textarea>'
75
+
76
+
77
+ class MarkdownMaker:
78
+ """Class to manage markdown inputs and formatting of markdown output."""
79
+
80
+ def __init__(
81
+ self,
82
+ addendum: str = '',
83
+ character_limit: int = None,
84
+ defang_iocs: bool = False,
85
+ iocs_to_defang: list = None,
86
+ ):
87
+ self.title = None
88
+ self.sections = []
89
+ self.addendum = addendum
90
+ if character_limit is not None and character_limit < len(addendum):
91
+ raise ValueError(f'Character limit must be at least {len(addendum)}')
92
+ self.character_limit = character_limit
93
+ self.defang_iocs = defang_iocs
94
+ if self.defang_iocs and iocs_to_defang is None:
95
+ raise ValueError('If `defang_iocs` is True, you need to supply `iocs_to_defang`.')
96
+ self.iocs_to_defang = iocs_to_defang
97
+
98
+ @validate_call
99
+ def add_title(self, title: str) -> None:
100
+ """Add title to the markdown."""
101
+ self.title = title
102
+
103
+ def validate_section(self, title: str, content: Union[list[dict], list[str]]) -> Section:
104
+ """Recursive function to validate a section and its content.
105
+
106
+ Args:
107
+ title (str): Section title
108
+ content (Union[list[dict], list[str]]): Section content
109
+
110
+ Raises:
111
+ ValueError: Raised if the section content is empty.
112
+
113
+ Returns:
114
+ Section: Returns a validated section object.
115
+ """
116
+ if len(content) == 0:
117
+ raise ValueError('Section content cannot be empty.')
118
+ if isinstance(content[0], str):
119
+ return Section(title=title, content=content)
120
+ if isinstance(content[0], dict):
121
+ return Section(
122
+ title=title, content=[self.validate_section(**section) for section in content]
123
+ )
124
+
125
+ @validate_call
126
+ def add_section(self, title: str, content: Union[list[dict], list[str]]) -> None:
127
+ """Add a section to the markdown."""
128
+ self.sections.append(self.validate_section(title, content))
129
+
130
+ def format_section(self, section: Section, header_level: int) -> str:
131
+ """Recursive function to format a section and its content."""
132
+ md_str = header(section.title, header_level) + '\n\n'
133
+ if isinstance(section.content[0], str):
134
+ md_str += '\n'.join(section.content)
135
+ else:
136
+ for sub_section in section.content:
137
+ md_str += self.format_section(sub_section, header_level + 1)
138
+
139
+ md_str += '\n\n'
140
+
141
+ return md_str
142
+
143
+ def format_defang_iocs(self, entities: set, md_str: str) -> str:
144
+ """Replace `.` with `[.]` for every IOC found."""
145
+ for entity in entities:
146
+ defanged = entity.replace('.', '[.]')
147
+ md_str = md_str.replace(entity, defanged)
148
+
149
+ return md_str
150
+
151
+ def format_output(self) -> str:
152
+ """Format the markdown output.
153
+
154
+ Call this method after adding a title and any sections.
155
+ """
156
+ md_str = ''
157
+ if self.title is not None:
158
+ md_str += header(self.title, TITLE_HEADER_LEVEL) + '\n\n'
159
+
160
+ for section in self.sections:
161
+ md_str += self.format_section(section, TITLE_HEADER_LEVEL + 1)
162
+
163
+ if self.defang_iocs:
164
+ md_str = self.format_defang_iocs(self.iocs_to_defang, md_str)
165
+
166
+ if self.character_limit is not None and len(md_str) > self.character_limit:
167
+ md_str = md_str[: self.character_limit - len(self.addendum)] + self.addendum
168
+
169
+ return md_str
@@ -0,0 +1,22 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+
15
+ from typing import Union
16
+
17
+ from ..common_models import RFBaseModel
18
+
19
+
20
+ class Section(RFBaseModel):
21
+ title: str
22
+ content: Union[list['Section'], list[str]]
@@ -0,0 +1,34 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from .errors import (
15
+ PlaybookAlertError,
16
+ PlaybookAlertFetchError,
17
+ PlaybookAlertRetrieveImageError,
18
+ PlaybookAlertSearchError,
19
+ PlaybookAlertUpdateError,
20
+ )
21
+ from .models import SearchResponse
22
+ from .pa_category import PACategory
23
+ from .playbook_alert_mgr import PlaybookAlertMgr
24
+ from .playbook_alerts import (
25
+ PBA_CodeRepoLeakage,
26
+ PBA_CyberVulnerability,
27
+ PBA_DomainAbuse,
28
+ PBA_Generic,
29
+ PBA_IdentityNovelExposure,
30
+ PBA_ThirdPartyRisk,
31
+ PreviewAlertOut,
32
+ SearchIn,
33
+ UpdateAlertIn,
34
+ )
@@ -0,0 +1,35 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+ from typing import Union
14
+
15
+ from .playbook_alerts import (
16
+ PBA_CodeRepoLeakage,
17
+ PBA_CyberVulnerability,
18
+ PBA_DomainAbuse,
19
+ PBA_Generic,
20
+ PBA_IdentityNovelExposure,
21
+ PBA_ThirdPartyRisk,
22
+ )
23
+
24
+ STATUS_PANEL_NAME = 'status'
25
+ DEFAULT_PBA_OUTPUT_DIR = 'playbook_alerts'
26
+ DEFAULT_PBA_FILE_NAME = 'playbook_alerts_{}.json'
27
+
28
+ PLAYBOOK_ALERT_TYPE = Union[
29
+ PBA_Generic,
30
+ PBA_CodeRepoLeakage,
31
+ PBA_CyberVulnerability,
32
+ PBA_DomainAbuse,
33
+ PBA_IdentityNovelExposure,
34
+ PBA_ThirdPartyRisk,
35
+ ]
@@ -0,0 +1,35 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+
15
+ from ..errors import RecordedFutureError
16
+
17
+
18
+ class PlaybookAlertError(RecordedFutureError):
19
+ """Error raised when playbook alert fails."""
20
+
21
+
22
+ class PlaybookAlertSearchError(PlaybookAlertError):
23
+ """Error raised when playbook alert search query fails."""
24
+
25
+
26
+ class PlaybookAlertUpdateError(PlaybookAlertError):
27
+ """Error raised when playbook alert update fails."""
28
+
29
+
30
+ class PlaybookAlertFetchError(PlaybookAlertError):
31
+ """Error raised when playbook alert fetch fails."""
32
+
33
+
34
+ class PlaybookAlertRetrieveImageError(PlaybookAlertError):
35
+ """Error raised when playbook alert image fetch fails."""
@@ -0,0 +1,80 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Union
16
+
17
+ from ..errors import WriteFileError
18
+ from ..helpers import OSHelpers, debug_call
19
+ from .constants import DEFAULT_PBA_OUTPUT_DIR
20
+ from .playbook_alerts import PBA_DomainAbuse
21
+
22
+ LOG = logging.getLogger('psengine.playbook_alerts.helpers')
23
+
24
+
25
+ @debug_call
26
+ def save_pba_images(
27
+ playbook_alerts: Union[PBA_DomainAbuse, list[PBA_DomainAbuse]],
28
+ output_directory: str = DEFAULT_PBA_OUTPUT_DIR,
29
+ ) -> None:
30
+ """Save Domain Abuse images/screenshots to disk as a .png file.
31
+
32
+ Args:
33
+ playbook_alerts (Union[PBA_DomainAbuse, List[PBA_DomainAbuse]]): Domain Abuse alert(s)
34
+ output_directory (str): The directory to save the images to
35
+
36
+ Raises:
37
+ TypeError: If alerts are not PBA_DomainAbuse objects
38
+ WriteFileError: If the image save fails with an OSError
39
+ """
40
+ if not isinstance(playbook_alerts, (list, PBA_DomainAbuse)):
41
+ raise TypeError('Image saving is only supported by Domain Abuse alerts')
42
+
43
+ playbook_alerts = playbook_alerts if isinstance(playbook_alerts, list) else [playbook_alerts]
44
+ if not all(isinstance(alert, PBA_DomainAbuse) for alert in playbook_alerts):
45
+ raise TypeError('Image saving is only supported by Domain Abuse alerts')
46
+
47
+ for alert in playbook_alerts:
48
+ LOG.info(f'Saving {len(alert.images)} image(s) to disk for alert {alert.playbook_alert_id}')
49
+ for image_id, meta in alert.images.items():
50
+ file_name = f'{alert.playbook_alert_id[5:]}_{image_id[4:]}'
51
+ _save_image(
52
+ file_name=file_name,
53
+ image_bytes=meta['image_bytes'],
54
+ output_directory=output_directory,
55
+ )
56
+
57
+
58
+ def _save_image(
59
+ file_name: str, image_bytes: bytes, output_directory: Union[str, Path] = DEFAULT_PBA_OUTPUT_DIR
60
+ ) -> None:
61
+ """Save image to disk as a .png file.
62
+
63
+ Args:
64
+ file_name (str): File name without the extension
65
+ image_bytes (bytes): Image bytes
66
+ output_directory (str or Path): The directory to save the image to
67
+
68
+ Raises:
69
+ WriteFileError: If the image save fails with an OSError
70
+ """
71
+ try:
72
+ LOG.debug(f"Saving image '{file_name}' to disk")
73
+ dir_path = OSHelpers.mkdir(output_directory)
74
+ image_filepath = dir_path / f'{file_name}.png'
75
+ with image_filepath.open('wb') as file:
76
+ file.write(image_bytes)
77
+ except OSError as err:
78
+ raise WriteFileError(
79
+ f'Failed to save playbook alert image to disk. Cause: {err.args}',
80
+ ) from err
@@ -0,0 +1,44 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from ..endpoints import (
15
+ EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
16
+ EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
17
+ EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
18
+ EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
19
+ EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
20
+ )
21
+ from .pa_category import PACategory
22
+ from .playbook_alerts import (
23
+ PBA_CodeRepoLeakage,
24
+ PBA_CyberVulnerability,
25
+ PBA_DomainAbuse,
26
+ PBA_IdentityNovelExposure,
27
+ PBA_ThirdPartyRisk,
28
+ )
29
+
30
+ CATEGORY_ENDPOINTS = {
31
+ PACategory.CODE_REPO_LEAKAGE.value: EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
32
+ PACategory.CYBER_VULNERABILITY.value: EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
33
+ PACategory.DOMAIN_ABUSE.value: EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
34
+ PACategory.IDENTITY_NOVEL_EXPOSURES.value: EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
35
+ PACategory.THIRD_PARTY_RISK.value: EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
36
+ }
37
+
38
+ CATEGORY_TO_OBJECT_MAP = {
39
+ PACategory.CODE_REPO_LEAKAGE.value: PBA_CodeRepoLeakage,
40
+ PACategory.CYBER_VULNERABILITY.value: PBA_CyberVulnerability,
41
+ PACategory.DOMAIN_ABUSE.value: PBA_DomainAbuse,
42
+ PACategory.IDENTITY_NOVEL_EXPOSURES.value: PBA_IdentityNovelExposure,
43
+ PACategory.THIRD_PARTY_RISK.value: PBA_ThirdPartyRisk,
44
+ }
@@ -0,0 +1,13 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
@@ -0,0 +1,98 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from markdown_strings import bold, link
15
+
16
+ from ...constants import TIMESTAMP_STR, TRUNCATE_COMMENT
17
+ from ...markdown import (
18
+ MarkdownMaker,
19
+ )
20
+ from ..models.common_models import ResolvedEntity
21
+ from ..pa_category import PACategory
22
+ from .markdown_code_repo import _code_repo_markdown
23
+ from .markdown_domain_abuse import _domain_abuse_markdown
24
+ from .markdown_identity_exposure import _identity_exposure_markdown
25
+
26
+ PORTAL_URL = 'https://app.recordedfuture.com/portal/playbook-alerts/{}'
27
+ API_URL = 'https://api.recordedfuture.com/playbook-alert/{}/{}'
28
+
29
+
30
+ MARKDOWN_BY_PBA_TYPE = {
31
+ PACategory.CODE_REPO_LEAKAGE.value: _code_repo_markdown,
32
+ PACategory.DOMAIN_ABUSE.value: _domain_abuse_markdown,
33
+ PACategory.IDENTITY_NOVEL_EXPOSURES.value: _identity_exposure_markdown,
34
+ }
35
+
36
+
37
+ def _generic_pba_summary(pba, md_maker: MarkdownMaker):
38
+ md_maker.add_title(pba.panel_status.case_rule_label)
39
+ id_ = pba.playbook_alert_id
40
+ general_info = [
41
+ f'{bold("ID:")} {id_}',
42
+ f'{bold("Created:")} {pba.panel_status.created.strftime(TIMESTAMP_STR)}',
43
+ f'{bold("Updated:")} {pba.panel_status.updated.strftime(TIMESTAMP_STR)}',
44
+ f'{bold("Status:")} {pba.panel_status.status}',
45
+ f'{bold("Priority:")} {pba.panel_status.priority}',
46
+ '{} | {}'.format(
47
+ link('API', API_URL.format(pba.category, id_)), link('Portal', PORTAL_URL.format(id_))
48
+ ),
49
+ ]
50
+ md_maker.add_section('Summary', general_info)
51
+
52
+
53
+ def _unmapped_pba(pba, md_maker: MarkdownMaker, *args, **kwargs) -> str: # noqa: ARG001
54
+ md_maker.sections.insert(
55
+ 0,
56
+ md_maker.validate_section(
57
+ 'Warning',
58
+ [
59
+ (
60
+ '> 🚨 The markdown of this alert is not fully supported yet. '
61
+ 'For full details check the portal 🚨'
62
+ )
63
+ ],
64
+ ),
65
+ )
66
+
67
+ if targets := pba.panel_status.targets:
68
+ if any(isinstance(t, str) for t in targets):
69
+ md_maker.add_section('Targets', ', '.join(t for t in targets))
70
+ elif any(isinstance(t, ResolvedEntity) for t in targets):
71
+ md_maker.add_section('Targets', [t.name for t in targets])
72
+
73
+ return md_maker.format_output()
74
+
75
+
76
+ def _markdown_playbook_alert(
77
+ playbook_alert,
78
+ html_tags: bool = True,
79
+ character_limit: int = None,
80
+ defang_iocs: bool = False,
81
+ iocs_to_defang: list = None,
82
+ ) -> str:
83
+ md_maker = MarkdownMaker(
84
+ addendum=TRUNCATE_COMMENT.format(
85
+ type_='alert', url=PORTAL_URL.format(playbook_alert.playbook_alert_id)
86
+ ),
87
+ character_limit=character_limit,
88
+ defang_iocs=defang_iocs,
89
+ iocs_to_defang=iocs_to_defang or [],
90
+ )
91
+ _generic_pba_summary(playbook_alert, md_maker)
92
+
93
+ try:
94
+ return MARKDOWN_BY_PBA_TYPE.get(playbook_alert.category, _unmapped_pba)(
95
+ playbook_alert, md_maker, html_tags
96
+ )
97
+ except AttributeError:
98
+ return _unmapped_pba(playbook_alert, md_maker, html_tags)
@@ -0,0 +1,64 @@
1
+ ##################################### TERMS OF USE ###########################################
2
+ # The following code is provided for demonstration purpose only, and should not be used #
3
+ # without independent verification. Recorded Future makes no representations or warranties, #
4
+ # express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
5
+ # information it may retrieve, and provides it both strictly “as-is” and without assuming #
6
+ # responsibility for any information it may retrieve. Recorded Future shall not be liable #
7
+ # for, and you assume all risk of using, the foregoing. By using this code, Customer #
8
+ # represents that it is solely responsible for having all necessary licenses, permissions, #
9
+ # rights, and/or consents to connect to third party APIs, and that it is solely responsible #
10
+ # for having all necessary licenses, permissions, rights, and/or consents to any data #
11
+ # accessed from any third party API. #
12
+ ##############################################################################################
13
+
14
+ from collections import defaultdict
15
+
16
+ from markdown_strings import blockquote, bold
17
+
18
+ from ...constants import TIMESTAMP_STR
19
+ from ...markdown import MarkdownMaker, clean_text, divider, html_textarea
20
+
21
+
22
+ def _add_repository(pba, md_maker: MarkdownMaker):
23
+ repos = [
24
+ f'{bold("Owner:" )} {pba.panel_evidence_summary.repository.owner.name}',
25
+ f'{bold("URL:" )} {pba.panel_evidence_summary.repository.name}',
26
+ ]
27
+
28
+ md_maker.add_section('Repository', repos)
29
+
30
+
31
+ def _add_assessment(pba, md_maker: MarkdownMaker, html_tags: bool):
32
+ assessments = []
33
+
34
+ for assess in pba.panel_evidence_summary.evidence:
35
+ details = defaultdict(list)
36
+ for a in assess.assessments:
37
+ details[a.title].append(clean_text(a.value))
38
+
39
+ details_list = []
40
+ for k, v in details.items():
41
+ details_list.append(f'{bold(k + ":")} {", ".join(v)}')
42
+
43
+ targets = ''
44
+ if targets := ', '.join(t.name for t in assess.targets):
45
+ targets = f'{bold("Assessment targets:")} {targets}'
46
+
47
+ commit = f'{bold("Commit:")} {assess.url}'
48
+ published = f'{bold("Published:")} {assess.published.strftime(TIMESTAMP_STR)}'
49
+ content = blockquote(
50
+ html_textarea(clean_text(assess.content)) if html_tags else clean_text(assess.content)
51
+ )
52
+ content = f'{bold("Content:")}\n\n{content}'
53
+
54
+ assessments.extend([published, targets, *details_list, commit, content, divider()])
55
+ md_maker.add_section('Assessments', assessments)
56
+
57
+
58
+ def _code_repo_markdown(pba, md_maker: MarkdownMaker, html_tags: bool) -> str:
59
+ if targets := pba.panel_status.targets:
60
+ md_maker.add_section('Targets', [t.name for t in targets])
61
+
62
+ _add_repository(pba, md_maker)
63
+ _add_assessment(pba, md_maker, html_tags)
64
+ return md_maker.format_output()