Skip to content

export_reader

trestle.core.crm.export_reader ¤

Provided interface to read inheritance statements from Markdown.

Attributes¤

ByComponentDict = Dict[str, Tuple[List[ossp.Inherited], List[ossp.Satisfied]]] module-attribute ¤

InheritanceViewDict = Dict[str, ByComponentDict] module-attribute ¤

logger = logging.getLogger(__name__) module-attribute ¤

Classes¤

ExportReader ¤

By-Component Assembly Exports Markdown reader.

Export reader handles all operations related to reading authored inherited and satisfied statements from exports in Markdown. The reader will read all the markdown files in the exports directory and update the SSP with the inheritance.

Source code in trestle/core/crm/export_reader.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
class ExportReader:
    """
    By-Component Assembly Exports Markdown reader.

    Export reader handles all operations related to reading authored inherited and satisfied statements from exports
    in Markdown. The reader will read all the markdown files in the exports directory and update the SSP with the
    inheritance.
    """

    def __init__(self, root_path: pathlib.Path, ssp: ossp.SystemSecurityPlan):
        """
        Initialize export reader.

        Arguments:
            root_path: A root path object where an SSP's inheritance markdown is located.
            ssp: A system security plan object that will be updated with the inheritance information.

        Notes:
            The mapped components list is used to track which components have been mapped to controls in the markdown.
            It can be retrieved with the get_leveraged_components method. This will be empty until the
            read_exports_from_markdown method is called.
        """
        self._ssp: ossp.SystemSecurityPlan = ssp

        # Create a dictionary of implemented requirements keyed by control id for merging operations
        self._implemented_requirements: Dict[str, ossp.ImplementedRequirement] = self._create_impl_req_dict()

        # List of component titles that have been mapped to controls in the Markdown
        self._mapped_components: List[str] = []

        self._root_path: pathlib.Path = root_path

    def _create_impl_req_dict(self) -> Dict[str, ossp.ImplementedRequirement]:
        """Create a dictionary of implemented requirements keyed by control id."""
        impl_req_dict: Dict[str, ossp.ImplementedRequirement] = {}
        for impl_req in as_list(self._ssp.control_implementation.implemented_requirements):
            impl_req_dict[impl_req.control_id] = impl_req
        return impl_req_dict

    def read_exports_from_markdown(self) -> ossp.SystemSecurityPlan:
        """Read inheritance markdown and update the SSP with the inheritance information."""
        # Read the information from the markdown files into a dictionary for quick lookup
        markdown_dict: InheritanceViewDict = self._read_inheritance_markdown_directory()

        # Merge the markdown information into existing the implemented requirements
        self._merge_exports_implemented_requirements(markdown_dict)

        # Process remaining markdown information that was not in the implemented requirements
        for control_id, by_comp_dict in markdown_dict.items():
            if by_comp_dict:
                logging.debug(f'Adding control mapping {control_id} to implemented requirements')
                self._add_control_mappings_to_implemented_requirements(control_id, by_comp_dict)

        self._ssp.control_implementation.implemented_requirements = list(self._implemented_requirements.values())
        return self._ssp

    def get_leveraged_ssp_href(self) -> str:
        """Get the href of the leveraged SSP from a markdown file."""
        comp_dirs = os.listdir(self._root_path)
        if len(comp_dirs) == 0:
            raise TrestleError('No components were found in the markdown.')

        control_dirs = os.listdir(self._root_path.joinpath(comp_dirs[0]))
        if len(control_dirs) == 0:
            raise TrestleError('No controls were found in the markdown for component {comp_dirs[0]}.')

        control_dir = self._root_path.joinpath(comp_dirs[0], control_dirs[0])

        files = [f for f in os.listdir(control_dir) if os.path.isfile(os.path.join(control_dir, f))]
        if len(files) == 0:
            raise TrestleError(f'No files were found in the markdown for control {control_dirs[0]}.')

        markdown_file_path = control_dir.joinpath(files[0])
        reader = InheritanceMarkdownReader(markdown_file_path)
        return reader.get_leveraged_ssp_reference()

    def get_leveraged_components(self) -> List[str]:
        """Get a list of component titles that have been mapped to controls in the Markdown."""
        return self._mapped_components

    def _merge_exports_implemented_requirements(self, markdown_dict: InheritanceViewDict) -> None:
        """Merge all exported inheritance info from the markdown into the implemented requirement dict."""
        for implemented_requirement in self._implemented_requirements.values():

            # If the control id existing in the markdown, then update the by_components
            if implemented_requirement.control_id in markdown_dict:

                # Delete the entry from the markdown_dict once processed to avoid duplicates
                by_comp_dict: ByComponentDict = markdown_dict.pop(implemented_requirement.control_id)

                self._update_type_with_by_comp(implemented_requirement, by_comp_dict)

            # Update any implemented requirements statements assemblies
            new_statements: List[ossp.Statement] = []

            for stm in as_list(implemented_requirement.statements):
                statement_id = getattr(stm, 'statement_id', f'{implemented_requirement.control_id}_smt')

                # If the statement id existing in the markdown, then update the by_components
                if statement_id in markdown_dict:

                    # Delete the entry from the markdown_dict once processed to avoid duplicates
                    by_comp_dict: ByComponentDict = markdown_dict.pop(statement_id)

                    self._update_type_with_by_comp(stm, by_comp_dict)

                new_statements.append(stm)

            implemented_requirement.statements = none_if_empty(new_statements)

    def _update_type_with_by_comp(self, with_bycomp: TypeWithByComps, by_comp_dict: ByComponentDict) -> None:
        """Update the by_components for a type with by_components."""
        new_by_comp: List[ossp.ByComponent] = []

        by_comp: ossp.ByComponent
        comp_inheritance_info: Tuple[List[ossp.Inherited], List[ossp.Satisfied]]
        for by_comp in as_list(with_bycomp.by_components):

            # If the by_component uuid exists in the by_comp_dict, then update it
            # If not, clear the by_component inheritance information
            comp_inheritance_info = by_comp_dict.pop(by_comp.component_uuid, ([], []))

            bycomp_interface = ByComponentInterface(by_comp)
            by_comp = bycomp_interface.reconcile_inheritance_by_component(
                comp_inheritance_info[0], comp_inheritance_info[1]
            )

            new_by_comp.append(by_comp)

        # Add any new by_components that were not in the original statement
        new_by_comp.extend(ExportReader._add_new_by_comps(by_comp_dict))
        with_bycomp.by_components = none_if_empty(new_by_comp)

    def _add_control_mappings_to_implemented_requirements(
        self, control_mapping: str, by_comps: ByComponentDict
    ) -> None:
        """Add control mappings to implemented requirements."""
        # Determine if the control id is actually a statement id
        if '_smt.' in control_mapping:
            control_id = control_mapping.split('_smt')[0]
            implemented_req = self._add_or_get_implemented_requirement(control_id)
            statement = gens.generate_sample_model(ossp.Statement)
            statement.statement_id = control_mapping
            statement.by_components = ExportReader._add_new_by_comps(by_comps)
            implemented_req.statements = as_list(implemented_req.statements)
            implemented_req.statements.append(statement)
            implemented_req.statements = sorted(implemented_req.statements, key=lambda x: x.statement_id)
        else:
            implemented_req = self._add_or_get_implemented_requirement(control_mapping)
            implemented_req.by_components = as_list(implemented_req.by_components)
            implemented_req.by_components.extend(ExportReader._add_new_by_comps(by_comps))

    def _add_or_get_implemented_requirement(self, control_id: str) -> ossp.ImplementedRequirement:
        """Add or get implemented requirement from implemented requirements dictionary."""
        if control_id in self._implemented_requirements:
            return self._implemented_requirements[control_id]

        implemented_requirement = gens.generate_sample_model(ossp.ImplementedRequirement)
        implemented_requirement.control_id = control_id
        self._implemented_requirements[control_id] = implemented_requirement
        return implemented_requirement

    @staticmethod
    def _add_new_by_comps(by_comp_dict: ByComponentDict) -> List[ossp.ByComponent]:
        """Add new by_components to the implemented requirement."""
        new_by_comp: List[ossp.ByComponent] = []
        for comp_uuid, inheritance_info in by_comp_dict.items():
            by_comp: ossp.ByComponent = gens.generate_sample_model(ossp.ByComponent)
            by_comp.component_uuid = comp_uuid
            by_comp.inherited = none_if_empty(inheritance_info[0])
            by_comp.satisfied = none_if_empty(inheritance_info[1])
            new_by_comp.append(by_comp)
        return new_by_comp

    def _read_inheritance_markdown_directory(self) -> InheritanceViewDict:
        """Read all inheritance markdown files and return a dictionary of all the information."""
        markdown_dict: InheritanceViewDict = {}

        # Creating a dictionary to find the component uuid by title for faster lookup
        uuid_by_title: Dict[str, str] = {}
        for component in as_list(self._ssp.system_implementation.components):
            uuid_by_title[component.title] = component.uuid

        for comp_dir in os.listdir(self._root_path):
            is_comp_leveraged = False
            for control_dir in os.listdir(os.path.join(self._root_path, comp_dir)):
                control_dir_path = os.path.join(self._root_path, comp_dir, control_dir)

                # Initialize the by_component dictionary for the control directory
                # If it exists in the markdown dictionary, then update it with the new information
                by_comp_dict = markdown_dict.get(control_dir, {})

                for file in os.listdir(control_dir_path):
                    file_path = pathlib.Path(control_dir_path).joinpath(file)
                    reader = InheritanceMarkdownReader(file_path)
                    leveraged_info = reader.process_leveraged_statement_markdown()

                    # If there is no leveraged information, then skip this file
                    if leveraged_info is None:
                        continue

                    # If a file has leveraged information, then set the flag to indicate the component is leveraged
                    is_comp_leveraged = True

                    for comp in leveraged_info.leveraging_comp_titles:
                        if comp not in uuid_by_title:
                            keys_as_string = ', '.join(uuid_by_title.keys())
                            raise TrestleError(
                                f'Component "{comp}" does not exist in the {self._ssp.metadata.title} SSP. '
                                f'Please use options: {keys_as_string}.'
                            )

                        comp_uuid = uuid_by_title[comp]
                        inherited, satisfied = by_comp_dict.get(comp_uuid, ([], []))

                        if leveraged_info.inherited is not None:
                            inherited.append(leveraged_info.inherited)
                        if leveraged_info.satisfied is not None:
                            satisfied.append(leveraged_info.satisfied)

                        by_comp_dict[comp_uuid] = (inherited, satisfied)

                markdown_dict[control_dir] = by_comp_dict

            if is_comp_leveraged:
                self._mapped_components.append(comp_dir)

        return markdown_dict
Functions¤
__init__(root_path, ssp) ¤

Initialize export reader.

Parameters:

Name Type Description Default
root_path Path

A root path object where an SSP's inheritance markdown is located.

required
ssp SystemSecurityPlan

A system security plan object that will be updated with the inheritance information.

required
Notes

The mapped components list is used to track which components have been mapped to controls in the markdown. It can be retrieved with the get_leveraged_components method. This will be empty until the read_exports_from_markdown method is called.

Source code in trestle/core/crm/export_reader.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __init__(self, root_path: pathlib.Path, ssp: ossp.SystemSecurityPlan):
    """
    Initialize export reader.

    Arguments:
        root_path: A root path object where an SSP's inheritance markdown is located.
        ssp: A system security plan object that will be updated with the inheritance information.

    Notes:
        The mapped components list is used to track which components have been mapped to controls in the markdown.
        It can be retrieved with the get_leveraged_components method. This will be empty until the
        read_exports_from_markdown method is called.
    """
    self._ssp: ossp.SystemSecurityPlan = ssp

    # Create a dictionary of implemented requirements keyed by control id for merging operations
    self._implemented_requirements: Dict[str, ossp.ImplementedRequirement] = self._create_impl_req_dict()

    # List of component titles that have been mapped to controls in the Markdown
    self._mapped_components: List[str] = []

    self._root_path: pathlib.Path = root_path
get_leveraged_components() ¤

Get a list of component titles that have been mapped to controls in the Markdown.

Source code in trestle/core/crm/export_reader.py
116
117
118
def get_leveraged_components(self) -> List[str]:
    """Get a list of component titles that have been mapped to controls in the Markdown."""
    return self._mapped_components
get_leveraged_ssp_href() ¤

Get the href of the leveraged SSP from a markdown file.

Source code in trestle/core/crm/export_reader.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def get_leveraged_ssp_href(self) -> str:
    """Get the href of the leveraged SSP from a markdown file."""
    comp_dirs = os.listdir(self._root_path)
    if len(comp_dirs) == 0:
        raise TrestleError('No components were found in the markdown.')

    control_dirs = os.listdir(self._root_path.joinpath(comp_dirs[0]))
    if len(control_dirs) == 0:
        raise TrestleError('No controls were found in the markdown for component {comp_dirs[0]}.')

    control_dir = self._root_path.joinpath(comp_dirs[0], control_dirs[0])

    files = [f for f in os.listdir(control_dir) if os.path.isfile(os.path.join(control_dir, f))]
    if len(files) == 0:
        raise TrestleError(f'No files were found in the markdown for control {control_dirs[0]}.')

    markdown_file_path = control_dir.joinpath(files[0])
    reader = InheritanceMarkdownReader(markdown_file_path)
    return reader.get_leveraged_ssp_reference()
read_exports_from_markdown() ¤

Read inheritance markdown and update the SSP with the inheritance information.

Source code in trestle/core/crm/export_reader.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def read_exports_from_markdown(self) -> ossp.SystemSecurityPlan:
    """Read inheritance markdown and update the SSP with the inheritance information."""
    # Read the information from the markdown files into a dictionary for quick lookup
    markdown_dict: InheritanceViewDict = self._read_inheritance_markdown_directory()

    # Merge the markdown information into existing the implemented requirements
    self._merge_exports_implemented_requirements(markdown_dict)

    # Process remaining markdown information that was not in the implemented requirements
    for control_id, by_comp_dict in markdown_dict.items():
        if by_comp_dict:
            logging.debug(f'Adding control mapping {control_id} to implemented requirements')
            self._add_control_mappings_to_implemented_requirements(control_id, by_comp_dict)

    self._ssp.control_implementation.implemented_requirements = list(self._implemented_requirements.values())
    return self._ssp

Functions¤

handler: python