def test_profile_resolver(tmp_trestle_dir: pathlib.Path) -> None:
    """Test the resolver."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)

    prof_a_path = ModelUtils.path_for_top_level_model(
        tmp_trestle_dir, 'test_profile_a', prof.Profile, FileContentType.JSON
    )
    cat = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_a_path)
    interface = CatalogInterface(cat)
    # added part ac-1_expevid from prof a
    list1 = find_string_in_all_controls_prose(interface, 'Detailed evidence logs')
    # modify param ac-3.3_prm_2 in prof b
    list2 = find_string_in_all_controls_prose(interface, 'full and complete compliance')

    assert len(list1) == 1
    assert len(list2) == 1

    assert interface.get_count_of_controls_in_catalog(False) == 6

    assert interface.get_count_of_controls_in_catalog(True) == 7

    assert len(cat.controls) == 4

    assert interface.get_dependent_control_ids('ac-3') == ['ac-3.3']

    control = interface.get_control('a-1')
    assert control.parts[0].parts[0].id == 'a-1_deep'
    assert control.parts[0].parts[0].prose == 'Extra added part in subpart'
def test_get_control_and_group_info_from_catalog(tmp_trestle_dir: pathlib.Path) -> None:
    """Test get all groups from the catalog."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)

    prof_a_path = ModelUtils.path_for_top_level_model(
        tmp_trestle_dir, 'test_profile_a', prof.Profile, FileContentType.JSON
    )
    catalog = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_a_path)
    cat_interface = CatalogInterface(catalog)

    all_groups_top = cat_interface.get_all_controls_from_catalog(recurse=False)
    assert len(list(all_groups_top)) == 6

    all_groups_rec = cat_interface.get_all_controls_from_catalog(recurse=True)
    assert len(list(all_groups_rec)) == 7

    all_group_ids = cat_interface.get_group_ids()
    assert len(all_group_ids) == 1

    statement_label, part = cat_interface.get_statement_label_if_exists('ac-1', 'ac-1_smt.c.2')
    assert statement_label == '2.'
    assert part.id == 'ac-1_smt.c.2'

    cat_path = cat_interface._get_control_path('ac-2')
    assert cat_path[0] == 'ac'
    assert len(cat_path) == 1
예제 #3
0
def test_ssp_generate_resolved_catalog(tmp_trestle_dir: pathlib.Path) -> None:
    """Test the ssp generator to create a resolved profile catalog."""
    _, _, _ = setup_for_ssp(False, True, tmp_trestle_dir, prof_name, ssp_name)
    profile_path = tmp_trestle_dir / f'profiles/{prof_name}/profile.json'
    new_catalog_dir = tmp_trestle_dir / f'catalogs/{prof_name}_resolved_catalog'
    new_catalog_dir.mkdir(parents=True, exist_ok=True)
    new_catalog_path = new_catalog_dir / 'catalog.json'

    profile_resolver = ProfileResolver()
    resolved_catalog = profile_resolver.get_resolved_profile_catalog(
        tmp_trestle_dir, profile_path)
    assert resolved_catalog
    # FIXME this should test with a more complex catalog
    assert len(resolved_catalog.groups) == 1

    resolved_catalog.oscal_write(new_catalog_path)
예제 #4
0
def test_get_control_param_dict(tmp_trestle_dir: pathlib.Path) -> None:
    """Test getting the param dict of a control."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)
    prof_a_path = ModelUtils.path_for_top_level_model(tmp_trestle_dir,
                                                      'test_profile_a',
                                                      prof.Profile,
                                                      FileContentType.JSON)
    catalog = ProfileResolver.get_resolved_profile_catalog(
        tmp_trestle_dir, prof_a_path)
    catalog_interface = CatalogInterface(catalog)
    control = catalog_interface.get_control('ac-1')
    param_dict = ControlIOReader.get_control_param_dict(control, False)
    # confirm profile value is used
    assert ControlIOReader.param_values_as_str(
        param_dict['ac-1_prm_1']) == 'all alert personnel'
    # confirm original param label is used since no value was assigned
    assert ControlIOReader.param_to_str(param_dict['ac-1_prm_7'],
                                        ParameterRep.VALUE_OR_LABEL_OR_CHOICES
                                        ) == 'organization-defined events'
    param = control.params[0]
    param.values = None
    param.select = common.ParameterSelection(
        how_many=common.HowMany.one_or_more, choice=['choice 1', 'choice 2'])
    param_dict = ControlIOReader.get_control_param_dict(control, False)
    assert ControlIOReader.param_to_str(
        param_dict['ac-1_prm_1'],
        ParameterRep.VALUE_OR_LABEL_OR_CHOICES) == 'choice 1, choice 2'
def test_profile_missing_position(tmp_trestle_dir: pathlib.Path) -> None:
    """Test when alter adds parts is missing position it defaults to after."""
    cat_path = test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME
    repo = Repository(tmp_trestle_dir)
    repo.load_and_import_model(cat_path, 'nist_cat')
    prof_path = test_utils.JSON_TEST_DATA_PATH / 'profile_missing_position.json'
    repo.load_and_import_model(prof_path, 'profile_missing_position')

    catalog = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_path)
    assert catalog
def test_ok_when_props_added(tmp_trestle_dir: pathlib.Path) -> None:
    """Test when by_id is not given and position is set to after or before it defaults to after."""
    cat_path = test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME
    repo = Repository(tmp_trestle_dir)
    repo.load_and_import_model(cat_path, 'nist_cat')
    prof_path = test_utils.JSON_TEST_DATA_PATH / 'profile_with_alter_props.json'
    repo.load_and_import_model(prof_path, 'profile_with_alter_props')

    catalog = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_path)
    assert catalog
def test_ok_when_reference_id_is_not_given_after_or_before(tmp_trestle_dir: pathlib.Path) -> None:
    """Test when by_id is not given and position is set to after or before it fails."""
    cat_path = test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME
    repo = Repository(tmp_trestle_dir)
    repo.load_and_import_model(cat_path, 'nist_cat')
    prof_path = test_utils.JSON_TEST_DATA_PATH / 'profile_with_incorrect_alter.json'
    repo.load_and_import_model(prof_path, 'incorrect_profile')

    # this originally failed but now it is OK based on OSCAL saying to default to starting or ending if no by_id
    catalog = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_path)
    assert catalog
def test_add_props_before_after_ok(tmp_trestle_dir: pathlib.Path) -> None:
    """
    Test for property addition behavior with before or after.

    Properties added with before or after will default to starting or ending.
    """
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)
    prof_g_path = ModelUtils.path_for_top_level_model(
        tmp_trestle_dir, 'test_profile_g', prof.Profile, FileContentType.JSON
    )
    _ = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_g_path)
예제 #9
0
def test_get_profile_param_dict(tmp_trestle_dir: pathlib.Path) -> None:
    """Test get profile param dict for control."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)
    profile, profile_path = ModelUtils.load_top_level_model(
        tmp_trestle_dir, 'test_profile_a', prof.Profile, FileContentType.JSON)
    profile_resolver = ProfileResolver()
    catalog = profile_resolver.get_resolved_profile_catalog(
        tmp_trestle_dir, profile_path)
    catalog_interface = CatalogInterface(catalog)
    control = catalog_interface.get_control('ac-1')

    full_param_dict = CatalogInterface._get_full_profile_param_dict(profile)
    control_param_dict = CatalogInterface._get_profile_param_dict(
        control, full_param_dict, False)
    assert ControlIOReader.param_to_str(
        control_param_dict['ac-1_prm_1'],
        ParameterRep.VALUE_OR_LABEL_OR_CHOICES) == 'all alert personnel'
    assert ControlIOReader.param_to_str(
        control_param_dict['ac-1_prm_6'],
        ParameterRep.VALUE_OR_LABEL_OR_CHOICES) == 'monthly'
    # param 7 has no value so its label will be used
    assert ControlIOReader.param_to_str(control_param_dict['ac-1_prm_7'],
                                        ParameterRep.VALUE_OR_LABEL_OR_CHOICES
                                        ) == 'organization-defined events'
예제 #10
0
    def generate_markdown(self, trestle_root: pathlib.Path,
                          profile_path: pathlib.Path,
                          markdown_path: pathlib.Path, yaml_header: dict,
                          overwrite_header_values: bool,
                          sections_dict: Optional[Dict[str, str]],
                          required_sections: Optional[str]) -> int:
        """Generate markdown for the controls in the profile.

        Args:
            trestle_root: Root directory of the trestle workspace
            profile_path: Path of the profile json file
            markdown_path: Path to the directory into which the markdown will be written
            yaml_header: Dict to merge into the yaml header of the control markdown
            overwrite_header_values: Overwrite values in the markdown header but allow new items to be added
            sections_dict: Optional dict mapping section short names to long
            required_sections: Optional comma-sep list of sections that get prompted for prose if not in the profile

        Returns:
            0 on success, 1 on error
        """
        try:
            if sections_dict and 'statement' in sections_dict:
                logger.warning('statement is not allowed as a section name.')
                return CmdReturnCodes.COMMAND_ERROR.value
            _, _, profile = ModelUtils.load_distributed(
                profile_path, trestle_root)
            catalog = ProfileResolver().get_resolved_profile_catalog(
                trestle_root, profile_path, True, True, None,
                ParameterRep.LEAVE_MOUSTACHE)
            catalog_interface = CatalogInterface(catalog)
            catalog_interface.write_catalog_as_markdown(
                md_path=markdown_path,
                yaml_header=yaml_header,
                sections_dict=sections_dict,
                prompt_responses=False,
                additional_content=True,
                profile=profile,
                overwrite_header_values=overwrite_header_values,
                set_parameters=True,
                required_sections=required_sections,
                allowed_sections=None)
        except TrestleNotFoundError as e:
            raise TrestleError(f'Profile {profile_path} not found, error {e}')
        except TrestleError as e:
            raise TrestleError(
                f'Error generating the catalog as markdown: {e}')
        return CmdReturnCodes.SUCCESS.value
def test_parameter_resolution(tmp_trestle_dir: pathlib.Path) -> None:
    """Test whether expected order of operations is preserved for parameter substution."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)

    prof_e_path = ModelUtils.path_for_top_level_model(
        tmp_trestle_dir, 'test_profile_e', prof.Profile, FileContentType.JSON
    )
    profile_e_parameter_string = '## Override value ##'
    profile_a_value = 'all alert personnel'

    # based on 800-53 rev 5
    cat = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_e_path)
    interface = CatalogInterface(cat)
    control = interface.get_control('ac-1')
    locations = interface.find_string_in_control(control, profile_e_parameter_string)
    locations_a = interface.find_string_in_control(control, profile_a_value)
    assert len(locations) == 1
    assert len(locations_a) == 0
    assert len(control.params[1].constraints) == 1
def test_add_props(tmp_trestle_dir: pathlib.Path) -> None:
    """Test all types of property additions."""
    test_utils.setup_for_multi_profile(tmp_trestle_dir, False, True)
    prof_f_path = ModelUtils.path_for_top_level_model(
        tmp_trestle_dir, 'test_profile_f', prof.Profile, FileContentType.JSON
    )
    cat = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_f_path)
    interface = CatalogInterface(cat)
    ac_3 = interface.get_control('ac-3')

    assert len(ac_3.props) == 6
    assert ac_3.props[-1].value == 'four'

    for part in ac_3.parts:
        if part.id == 'ac-3_stmt':
            assert len(part.props) == 4

    ac_5 = interface.get_control('ac-5')
    for part in ac_5.parts:
        if part.id == 'ac-5_stmt':
            for sub_part in part.parts:
                if sub_part.id == 'ac-5_smt.a':
                    assert len(sub_part.props) == 4
def test_all_positions_for_alter_can_be_resolved(tmp_trestle_dir: pathlib.Path) -> None:
    """Test that all alter adds positions can be resolved."""
    cat_path = test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME
    repo = Repository(tmp_trestle_dir)
    repo.load_and_import_model(cat_path, 'nist_cat')

    prof_d_path = test_utils.JSON_TEST_DATA_PATH / 'test_profile_d.json'
    repo.load_and_import_model(prof_d_path, 'test_profile_d')

    cat = ProfileResolver.get_resolved_profile_catalog(tmp_trestle_dir, prof_d_path)

    interface = CatalogInterface(cat)
    control_a1 = interface.get_control('ac-1')
    control_a2 = interface.get_control('ac-2')

    assert control_a1.parts[0].id == 'ac-1_first_lev1'
    assert control_a1.parts[1].parts[3].id == 'ac-1_last_lev2'
    assert control_a1.parts[2].id == 'ac-1_after1_ac-1_smt_lev1'
    assert control_a1.parts[3].id == 'ac-1_after2_ac-1_smt_lev1'
    assert control_a1.parts[1].parts[0].parts[1].id == 'ac-1_smt_before1_a.2_lev3'
    assert control_a1.parts[1].parts[0].parts[2].id == 'ac-1_smt_before2_a.2_lev3'
    assert control_a1.parts[1].parts[0].parts[3].parts[0].id == 'ac-1_smt_inside1_at_the_end_a.2_lev4'
    assert control_a1.parts[1].parts[0].parts[3].parts[1].id == 'ac-1_smt_inside2_at_the_end_a.2_lev4'
    assert control_a2.parts[0].id == 'ac-2_implgdn_lev1'
예제 #14
0
def test_profile_ohv(required_sections: Optional[str], success: bool,
                     ohv: bool, tmp_trestle_dir: pathlib.Path) -> None:
    """Test profile generate assemble with overwrite-header-values."""
    ac1_path, assembled_prof_dir, profile_path, markdown_path = setup_profile_generate(
        tmp_trestle_dir)
    yaml_header_path = test_utils.YAML_TEST_DATA_PATH / 'good_simple.yaml'
    new_version = '1.2.3'

    # convert resolved profile catalog to markdown then assemble it after adding an item to a control
    # if set_parameters is true, the yaml header will contain all the parameters
    profile_generate = ProfileGenerate()
    yaml = YAML()
    yaml_header = yaml.load(yaml_header_path.open('r'))
    profile_generate.generate_markdown(tmp_trestle_dir, profile_path,
                                       markdown_path, yaml_header, ohv, None,
                                       None)

    edit_files(ac1_path, True, multi_guidance_dict)
    markdown_path = tmp_trestle_dir / md_name
    # change guidance in the other two controls but don't change header
    ac2_path = markdown_path / 'ac/ac-2.md'
    ac21_path = markdown_path / 'ac/ac-2.1.md'
    edit_files(ac2_path, False, multi_guidance_dict)
    edit_files(ac21_path, False, multi_guidance_dict)

    if success:
        assert ProfileAssemble.assemble_profile(tmp_trestle_dir, prof_name,
                                                md_name, assembled_prof_name,
                                                True, False, new_version,
                                                required_sections, None) == 0

        # check the assembled profile is as expected
        profile: prof.Profile
        profile, _ = ModelUtils.load_top_level_model(tmp_trestle_dir,
                                                     assembled_prof_name,
                                                     prof.Profile,
                                                     FileContentType.JSON)
        set_params = profile.modify.set_parameters

        assert len(set_params) == 14
        assert set_params[0].values[0].__root__ == 'all personnel'
        assert set_params[1].param_id == 'ac-1_prm_2'
        assert set_params[1].values[0].__root__ == 'Organization-level'
        assert set_params[1].values[1].__root__ == 'System-level'
        assert set_params[2].values[0].__root__ == 'new value'
        assert profile.metadata.version.__root__ == new_version
        if ohv:
            assert set_params[3].values[0].__root__ == 'no meetings'
            assert set_params[3].label == 'meetings cancelled'
        else:
            assert set_params[3].values[0].__root__ == 'all meetings'
            assert set_params[3].label == 'organization-defined events'

        catalog = ProfileResolver.get_resolved_profile_catalog(
            tmp_trestle_dir, assembled_prof_dir / 'profile.json')
        catalog_interface = CatalogInterface(catalog)
        # confirm presence of all expected strings in the control named parts
        for name, exp_str in multi_guidance_dict['name_exp']:
            prose = catalog_interface.get_control_part_prose('ac-1', name)
            assert prose.find(exp_str) >= 0
    else:
        with pytest.raises(TrestleError):
            ProfileAssemble.assemble_profile(tmp_trestle_dir, prof_name,
                                             md_name, assembled_prof_name,
                                             True, False, new_version,
                                             required_sections, None)
예제 #15
0
def test_profile_generate_assemble(add_header: bool, guid_dict: Dict,
                                   use_cli: bool, dir_exists: bool,
                                   set_parameters: bool,
                                   tmp_trestle_dir: pathlib.Path,
                                   monkeypatch: MonkeyPatch) -> None:
    """Test the profile markdown generator."""
    ac1_path, assembled_prof_dir, profile_path, markdown_path = setup_profile_generate(
        tmp_trestle_dir)
    yaml_header_path = test_utils.YAML_TEST_DATA_PATH / 'good_simple.yaml'

    # convert resolved profile catalog to markdown then assemble it after adding an item to a control
    if use_cli:
        test_args = f'trestle author profile-generate -n {prof_name} -o {md_name} -rs NeededExtra'.split(
        )
        if add_header:
            test_args.extend(['-y', str(yaml_header_path)])
        test_args.extend(['-s', all_sections_str])
        monkeypatch.setattr(sys, 'argv', test_args)
        assert Trestle().run() == 0

        edit_files(ac1_path, set_parameters, guid_dict)

        test_args = f'trestle author profile-assemble -n {prof_name} -m {md_name} -o {assembled_prof_name}'.split(
        )
        if set_parameters:
            test_args.append('-sp')
        if dir_exists:
            assembled_prof_dir.mkdir()
        monkeypatch.setattr(sys, 'argv', test_args)
        assert Trestle().run() == 0
    else:
        profile_generate = ProfileGenerate()
        yaml_header = {}
        if add_header:
            yaml = YAML()
            yaml_header = yaml.load(yaml_header_path.open('r'))
        sections_dict = sections_to_dict(all_sections_str)
        profile_generate.generate_markdown(tmp_trestle_dir, profile_path,
                                           markdown_path, yaml_header, False,
                                           sections_dict, 'NeededExtra')

        edit_files(ac1_path, set_parameters, guid_dict)

        if dir_exists:
            assembled_prof_dir.mkdir()
        assert ProfileAssemble.assemble_profile(tmp_trestle_dir, prof_name,
                                                md_name, assembled_prof_name,
                                                set_parameters, False, None,
                                                None, None) == 0

    # check the assembled profile is as expected
    profile: prof.Profile
    profile, _ = ModelUtils.load_top_level_model(tmp_trestle_dir,
                                                 assembled_prof_name,
                                                 prof.Profile,
                                                 FileContentType.JSON)
    assert ModelUtils.model_age(profile) < test_utils.NEW_MODEL_AGE_SECONDS
    # get the set_params in the assembled profile
    set_params = profile.modify.set_parameters
    if set_parameters:
        assert set_params[0].param_id == 'ac-1_prm_1'
        assert set_params[0].values[0].__root__ == 'all personnel'
        assert set_params[1].param_id == 'ac-1_prm_2'
        assert set_params[1].values[0].__root__ == 'Organization-level'
        assert set_params[1].values[1].__root__ == 'System-level'
        assert set_params[2].param_id == 'ac-1_prm_3'
        assert set_params[2].values[0].__root__ == 'new value'
    else:
        assert len(set_params) == 15

    # now create the resolved profile catalog from the assembled json profile and confirm the addition is there

    catalog = ProfileResolver.get_resolved_profile_catalog(
        tmp_trestle_dir, assembled_prof_dir / 'profile.json')
    catalog_interface = CatalogInterface(catalog)
    # confirm presence of all expected strings in the control named parts
    for name, exp_str in guid_dict['name_exp']:
        prose = catalog_interface.get_control_part_prose('ac-1', name)
        assert prose.find(exp_str) >= 0
예제 #16
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            trestle_root = args.trestle_root
            if not file_utils.is_directory_name_allowed(args.output):
                raise TrestleError(
                    f'{args.output} is not an allowed directory name')

            profile_path = trestle_root / f'profiles/{args.profile}/profile.json'

            yaml_header: dict = {}
            if args.yaml_header:
                try:
                    logging.debug(
                        f'Loading yaml header file {args.yaml_header}')
                    yaml = YAML()
                    yaml_header = yaml.load(
                        pathlib.Path(args.yaml_header).open('r'))
                except YAMLError as e:
                    raise TrestleError(
                        f'YAML error loading yaml header for ssp generation: {e}'
                    )

            markdown_path = trestle_root / args.output

            profile_resolver = ProfileResolver()

            resolved_catalog = profile_resolver.get_resolved_profile_catalog(
                trestle_root, profile_path)
            catalog_interface = CatalogInterface(resolved_catalog)

            sections_dict: Dict[str, str] = {}
            if args.sections:
                sections_dict = sections_to_dict(args.sections)
                if 'statement' in sections_dict:
                    raise TrestleError(
                        'Statement is not allowed as a section name.')
                # add any existing sections from the controls but only have short names
                control_section_short_names = catalog_interface.get_sections()
                for short_name in control_section_short_names:
                    if short_name not in sections_dict:
                        sections_dict[short_name] = short_name
                logger.debug(f'ssp sections dict: {sections_dict}')

            catalog_interface.write_catalog_as_markdown(
                md_path=markdown_path,
                yaml_header=yaml_header,
                sections_dict=sections_dict,
                prompt_responses=True,
                additional_content=False,
                profile=None,
                overwrite_header_values=args.overwrite_header_values,
                set_parameters=False,
                required_sections=None,
                allowed_sections=args.allowed_sections)

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while writing markdown from catalog')
예제 #17
0
    def filter_ssp(self, trestle_root: pathlib.Path, ssp_name: str,
                   profile_name: str, out_name: str, regenerate: bool,
                   version: Optional[str]) -> int:
        """
        Filter the ssp based on controls included by the profile and output new ssp.

        Args:
            trestle_root: root directory of the trestle project
            ssp_name: name of the ssp model
            profile_name: name of the profile model used for filtering
            out_name: name of the output ssp model with filtered controls
            regenerate: whether to regenerate the uuid's in the ssp
            version: new version for the model

        Returns:
            0 on success, 1 otherwise
        """
        ssp: ossp.SystemSecurityPlan

        ssp, _ = ModelUtils.load_top_level_model(trestle_root, ssp_name,
                                                 ossp.SystemSecurityPlan,
                                                 FileContentType.JSON)
        profile_path = ModelUtils.path_for_top_level_model(
            trestle_root, profile_name, prof.Profile, FileContentType.JSON)

        prof_resolver = ProfileResolver()
        catalog = prof_resolver.get_resolved_profile_catalog(
            trestle_root, profile_path)
        catalog_interface = CatalogInterface(catalog)

        # The input ssp should reference a superset of the controls referenced by the profile
        # Need to cull references in the ssp to controls not in the profile
        # Also make sure the output ssp contains imp reqs for all controls in the profile
        control_imp = ssp.control_implementation
        ssp_control_ids: Set[str] = set()

        new_set_params: List[ossp.SetParameter] = []
        for set_param in as_list(control_imp.set_parameters):
            control = catalog_interface.get_control_by_param_id(
                set_param.param_id)
            if control is not None:
                new_set_params.append(set_param)
                ssp_control_ids.add(control.id)
        control_imp.set_parameters = new_set_params if new_set_params else None

        new_imp_requirements: List[ossp.ImplementedRequirement] = []
        for imp_requirement in as_list(control_imp.implemented_requirements):
            control = catalog_interface.get_control(imp_requirement.control_id)
            if control is not None:
                new_imp_requirements.append(imp_requirement)
                ssp_control_ids.add(control.id)
        control_imp.implemented_requirements = new_imp_requirements

        # make sure all controls in the profile have implemented reqs in the final ssp
        if not ssp_control_ids.issuperset(catalog_interface.get_control_ids()):
            raise TrestleError(
                'Unable to filter the ssp because the profile references controls not in it.'
            )

        ssp.control_implementation = control_imp
        if regenerate:
            ssp, _, _ = ModelUtils.regenerate_uuids(ssp)
        if version:
            ssp.metadata.version = com.Version(__root__=version)
        ModelUtils.update_last_modified(ssp)

        ModelUtils.save_top_level_model(ssp, trestle_root, out_name,
                                        FileContentType.JSON)

        return CmdReturnCodes.SUCCESS.value
예제 #18
0
    def jinja_ify(
        trestle_root: pathlib.Path,
        r_input_file: pathlib.Path,
        r_output_file: pathlib.Path,
        ssp: Optional[str],
        profile: Optional[str],
        lut: Optional[Dict[str, Any]] = None,
        number_captions: Optional[bool] = False,
        parameters_formatting: Optional[str] = None
    ) -> int:
        """Run jinja over an input file with additional booleans."""
        if lut is None:
            lut = {}
        template_folder = pathlib.Path.cwd()
        jinja_env = Environment(
            loader=FileSystemLoader(template_folder),
            extensions=[MDSectionInclude, MDCleanInclude, MDDatestamp],
            trim_blocks=True,
            autoescape=True
        )
        template = jinja_env.get_template(str(r_input_file))
        # create boolean dict
        if operator.xor(bool(ssp), bool(profile)):
            raise TrestleIncorrectArgsError('Both SSP and profile should be provided or not at all')
        if ssp:
            # name lookup
            ssp_data, _ = ModelUtils.load_top_level_model(trestle_root, ssp, SystemSecurityPlan)
            lut['ssp'] = ssp_data
            _, profile_path = ModelUtils.load_top_level_model(trestle_root, profile, Profile)
            profile_resolver = ProfileResolver()
            resolved_catalog = profile_resolver.get_resolved_profile_catalog(
                trestle_root, profile_path, False, False, parameters_formatting
            )

            ssp_writer = SSPMarkdownWriter(trestle_root)
            ssp_writer.set_ssp(ssp_data)
            ssp_writer.set_catalog(resolved_catalog)
            lut['catalog'] = resolved_catalog
            lut['catalog_interface'] = CatalogInterface(resolved_catalog)
            lut['control_io_writer'] = ControlIOWriter()
            lut['ssp_md_writer'] = ssp_writer

        new_output = template.render(**lut)
        output = ''
        # This recursion allows nesting within expressions (e.g. an expression can contain jinja templates).
        error_countdown = JinjaCmd.max_recursion_depth
        while new_output != output and error_countdown > 0:
            error_countdown = error_countdown - 1
            output = new_output
            random_name = uuid.uuid4()  # Should be random and not used.
            dict_loader = DictLoader({str(random_name): new_output})
            jinja_env = Environment(
                loader=ChoiceLoader([dict_loader, FileSystemLoader(template_folder)]),
                extensions=[MDCleanInclude, MDSectionInclude, MDDatestamp],
                autoescape=True,
                trim_blocks=True
            )
            template = jinja_env.get_template(str(random_name))
            new_output = template.render(**lut)

        output_file = trestle_root / r_output_file
        if number_captions:
            output_file.open('w', encoding=const.FILE_ENCODING).write(_number_captions(output))
        else:
            output_file.open('w', encoding=const.FILE_ENCODING).write(output)

        return CmdReturnCodes.SUCCESS.value