Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DO NOT MERGE UNTIL READY TO SUPPORT NEW OSCAL DRAFT] support draft OSCAL proposal and mapping #1322

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "nist-source"]
path = nist-source
url = https://github.com/usnistgov/OSCAL.git
branch = release-1.0
branch = develop
[submodule "nist-content"]
path = nist-content
url = https://github.com/usnistgov/oscal-content.git
2 changes: 2 additions & 0 deletions docs/api_reference/trestle.oscal.mapping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
::: trestle.oscal.mapping
handler: python
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ nav:
- catalog: api_reference/trestle.oscal.catalog.md
- common: api_reference/trestle.oscal.common.md
- component: api_reference/trestle.oscal.component.md
- mapping: api_reference/trestle.oscal.mapping.md
- poam: api_reference/trestle.oscal.poam.md
- profile: api_reference/trestle.oscal.profile.md
- ssp: api_reference/trestle.oscal.ssp.md
Expand Down
2 changes: 1 addition & 1 deletion nist-source
Submodule nist-source updated 223 files
3 changes: 2 additions & 1 deletion scripts/oscal_normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Parameter(OscalBaseModel):
if at all in the trestle code - but they could also be removed by extensions to this script.

NOTE: The routine is fully automatic except the new Mapping class requires a slight manual edit to MappingCollection.
TODO: change the name at the bottom of the mapping.py file.

"""

Expand All @@ -86,7 +87,7 @@ class Parameter(OscalBaseModel):
# List of filestems not including 'complete' or 'common'
# 'common' is generated by this script. 'complete.py' comes from NIST and is ignored
# mapping does not exist in the schema until new oscal is released at which point it should be added here
fstems = ['assessment_plan', 'assessment_results', 'catalog', 'component', 'poam', 'profile', 'ssp']
fstems = ['assessment_plan', 'assessment_results', 'catalog', 'component', 'mapping', 'poam', 'profile', 'ssp']

alias_map = {
'assessment_plan': 'assessment-plan',
Expand Down
4 changes: 2 additions & 2 deletions tests/trestle/core/commands/author/profile_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ def test_profile_alter_props(tmp_trestle_dir: pathlib.Path) -> None:
)
adds = profile.modify.alters[0].adds
assert len(adds) == 5
assert adds[0].position.value == 'ending'
assert adds[0].position == const.ENDING
assert adds[0].by_id is None
assert len(adds[0].parts) == 2
assert len(adds[0].props) == 2
Expand Down Expand Up @@ -699,7 +699,7 @@ def test_profile_alter_props(tmp_trestle_dir: pathlib.Path) -> None:
)
adds = profile.modify.alters[0].adds
assert len(adds) == 6
assert adds[0].position.value == 'ending'
assert adds[0].position == const.ENDING
assert adds[1].by_id == 'ac-1_expevid'
assert adds[2].by_id == 'ac-1_smt.a'
assert adds[3].by_id == 'ac-1_smt.b'
Expand Down
4 changes: 2 additions & 2 deletions tests/trestle/core/profile_resolver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from tests import test_utils

from trestle.common.const import RESOLUTION_SOURCE
from trestle.common.const import RESOLUTION_SOURCE, USE_FIRST
from trestle.common.err import TrestleError
from trestle.common.model_utils import ModelUtils
from trestle.core import generators as gens
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_profile_resolver_merge(sample_catalog_rich_controls: cat.Catalog) -> No
assert catalog_interface.get_control(control_id).parts[-1].name == 'foo'

# add part to first control and merge but with use-first. The part should not be there at end.
method = prof.Method.use_first
method = USE_FIRST
combine = prof.Combine(method=method)
profile.merge = prof.Merge(combine=combine)
merge = Merge(profile)
Expand Down
25 changes: 25 additions & 0 deletions trestle/common/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
MODEL_TYPE_A_RESULT = 'assessment-results'
MODEL_TYPE_CATALOG = 'catalog'
MODEL_TYPE_COMPDEF = 'component-definition'
MODEL_TYPE_MAPPING = 'mapping-collection'
MODEL_TYPE_POAM = 'plan-of-action-and-milestones'
MODEL_TYPE_PROFILE = 'profile'
MODEL_TYPE_SSP = 'system-security-plan'
Expand All @@ -46,6 +47,7 @@
MODEL_DIR_A_RESULT = 'assessment-results'
MODEL_DIR_CATALOG = 'catalogs'
MODEL_DIR_COMPDEF = 'component-definitions'
MODEL_DIR_MAPPING = 'mapping-collections'
MODEL_DIR_POAM = 'plan-of-action-and-milestones'
MODEL_DIR_PROFILE = 'profiles'
MODEL_DIR_SSP = 'system-security-plans'
Expand All @@ -56,6 +58,7 @@
MODULE_NAME_A_RESULT = 'assessment_results'
MODULE_NAME_CATALOG = 'catalog'
MODULE_NAME_COMPDEF = 'component'
MODULE_NAME_MAPPING = 'mapping'
MODULE_NAME_POAM = 'poam'
MODULE_NAME_PROFILE = 'profile'
MODULE_NAME_SSP = 'ssp'
Expand All @@ -64,6 +67,7 @@
MODEL_MODULE_A_RESULT = f'{PACKAGE_OSCAL}.{MODULE_NAME_A_RESULT}'
MODEL_MODULE_CATALOG = f'{PACKAGE_OSCAL}.{MODULE_NAME_CATALOG}'
MODEL_MODULE_COMPDEF = f'{PACKAGE_OSCAL}.{MODULE_NAME_COMPDEF}'
MODEL_MODULE_MAPPING = f'{PACKAGE_OSCAL}.{MODULE_NAME_MAPPING}'
MODEL_MODULE_POAM = f'{PACKAGE_OSCAL}.{MODULE_NAME_POAM}'
MODEL_MODULE_PROFILE = f'{PACKAGE_OSCAL}.{MODULE_NAME_PROFILE}'
MODEL_MODULE_SSP = f'{PACKAGE_OSCAL}.{MODULE_NAME_SSP}'
Expand All @@ -73,6 +77,7 @@
MODEL_TYPE_A_RESULT,
MODEL_TYPE_CATALOG,
MODEL_TYPE_COMPDEF,
MODEL_TYPE_MAPPING,
MODEL_TYPE_POAM,
MODEL_TYPE_PROFILE,
MODEL_TYPE_SSP
Expand All @@ -83,6 +88,7 @@
MODEL_DIR_A_RESULT,
MODEL_DIR_CATALOG,
MODEL_DIR_COMPDEF,
MODEL_DIR_MAPPING,
MODEL_DIR_POAM,
MODEL_DIR_PROFILE,
MODEL_DIR_SSP
Expand All @@ -93,6 +99,7 @@
MODEL_MODULE_A_RESULT,
MODEL_MODULE_CATALOG,
MODEL_MODULE_COMPDEF,
MODEL_MODULE_MAPPING,
MODEL_MODULE_POAM,
MODEL_MODULE_PROFILE,
MODEL_MODULE_SSP
Expand All @@ -103,6 +110,7 @@
MODEL_DIR_A_RESULT: MODEL_MODULE_A_RESULT,
MODEL_DIR_CATALOG: MODEL_MODULE_CATALOG,
MODEL_DIR_COMPDEF: MODEL_MODULE_COMPDEF,
MODEL_DIR_MAPPING: MODEL_MODULE_MAPPING,
MODEL_DIR_POAM: MODEL_MODULE_POAM,
MODEL_DIR_PROFILE: MODEL_MODULE_PROFILE,
MODEL_DIR_SSP: MODEL_MODULE_SSP
Expand All @@ -113,6 +121,7 @@
MODEL_TYPE_A_RESULT: MODEL_MODULE_A_RESULT,
MODEL_TYPE_CATALOG: MODEL_MODULE_CATALOG,
MODEL_TYPE_COMPDEF: MODEL_MODULE_COMPDEF,
MODEL_TYPE_MAPPING: MODEL_MODULE_MAPPING,
MODEL_TYPE_POAM: MODEL_MODULE_POAM,
MODEL_TYPE_PROFILE: MODEL_MODULE_PROFILE,
MODEL_TYPE_SSP: MODEL_MODULE_SSP
Expand All @@ -123,6 +132,7 @@
MODEL_MODULE_A_RESULT: MODEL_TYPE_A_RESULT,
MODEL_MODULE_CATALOG: MODEL_TYPE_CATALOG,
MODEL_MODULE_COMPDEF: MODEL_TYPE_COMPDEF,
MODEL_MODULE_MAPPING: MODEL_TYPE_MAPPING,
MODEL_MODULE_POAM: MODEL_TYPE_POAM,
MODEL_MODULE_PROFILE: MODEL_TYPE_PROFILE,
MODEL_MODULE_SSP: MODEL_TYPE_SSP
Expand All @@ -133,6 +143,7 @@
MODEL_TYPE_A_RESULT: MODEL_DIR_A_RESULT,
MODEL_TYPE_CATALOG: MODEL_DIR_CATALOG,
MODEL_TYPE_COMPDEF: MODEL_DIR_COMPDEF,
MODEL_TYPE_MAPPING: MODEL_DIR_MAPPING,
MODEL_TYPE_POAM: MODEL_DIR_POAM,
MODEL_TYPE_PROFILE: MODEL_DIR_PROFILE,
MODEL_TYPE_SSP: MODEL_DIR_SSP
Expand Down Expand Up @@ -553,6 +564,20 @@

ONE_OR_MORE_SPACED = 'one or more'

KEEP = 'keep'

MERGE = 'merge'

BEFORE = 'before'

AFTER = 'after'

STARTING = 'starting'

ENDING = 'ending'

USE_FIRST = 'use-first'

VALUE_ASSIGNED_PREFIX = 'value-assigned-prefix'

VALUE_NOT_ASSIGNED_PREFIX = 'value-not-assigned-prefix'
Expand Down
2 changes: 0 additions & 2 deletions trestle/common/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,6 @@ def _parameter_to_dict_recurse(obj: Union[OscalBaseModel, str], partial: bool) -
main_fields = ['id', 'label', 'values', 'select', 'choice', 'how_many', 'guidelines', 'prose']
if isinstance(obj, common.Remarks):
return obj.__root__
if isinstance(obj, common.HowMany):
return obj.value
# it is either a string already or we cast it to string
if not hasattr(obj, const.FIELDS_SET):
return str(obj)
Expand Down
2 changes: 1 addition & 1 deletion trestle/core/control_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def read_editable_content(
for by_id in sorted(by_ids):
parts = by_id_parts.get(by_id, None)
props = props_by_id.get(by_id, None)
adds.append(prof.Add(parts=parts, props=props, position=prof.Position.ending, by_id=by_id))
adds.append(prof.Add(parts=parts, props=props, position=const.ENDING, by_id=by_id))

new_alters = []
if adds:
Expand Down
2 changes: 2 additions & 0 deletions trestle/core/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def generate_sample_value_by_type(
return const.SAMPLE_UUID_STR
if field_name == 'date_authorized':
return str(date.today().isoformat())
if field_name == 'date_datatype':
return '2023-01-01'
if field_name == 'oscal_version':
return OSCAL_VERSION
if 'uuid' in field_name:
Expand Down
23 changes: 12 additions & 11 deletions trestle/core/resolver/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import trestle.oscal.catalog as cat
import trestle.oscal.common as com
import trestle.oscal.profile as prof
from trestle.common import const
from trestle.common.common_types import OBT
from trestle.common.err import TrestleError
from trestle.common.list_utils import as_list, none_if_empty
Expand Down Expand Up @@ -68,9 +69,9 @@ def _get_id(self, item: OBT) -> Optional[str]:
id_ = getattr(item, NAME, None)
return id_

def _merge_lists(self, dest: List[OBT], src: List[OBT], merge_method: Optional[prof.Method]) -> None:
def _merge_lists(self, dest: List[OBT], src: List[OBT], merge_method: Optional[str]) -> None:
added_items = []
if merge_method == prof.Method.keep:
if merge_method == const.KEEP:
dest.extend(src)
return
for item in src:
Expand All @@ -82,7 +83,7 @@ def _merge_lists(self, dest: List[OBT], src: List[OBT], merge_method: Optional[p
for other in dest:
other_id = self._get_id(other)
if other_id == item_id:
if merge_method == prof.Method.merge:
if merge_method == const.MERGE:
self._merge_items(other, item, merge_method)
merged = True
break
Expand All @@ -92,7 +93,7 @@ def _merge_lists(self, dest: List[OBT], src: List[OBT], merge_method: Optional[p
dest.extend(added_items)

def _merge_attrs(
self, dest: Union[OBT, List[OBT]], src: Union[OBT, List[OBT]], attr: str, merge_method: Optional[prof.Method]
self, dest: Union[OBT, List[OBT]], src: Union[OBT, List[OBT]], attr: str, merge_method: Optional[str]
) -> None:
"""Merge this attr of src into the attr of dest."""
src_attr = getattr(src, attr, None)
Expand All @@ -106,13 +107,13 @@ def _merge_attrs(
self._merge_lists(dest_attr, src_attr, merge_method)
setattr(dest, attr, dest_attr)
return
if dest_attr and merge_method == prof.Method.use_first:
if dest_attr and merge_method == const.USE_FIRST:
return
if dest_attr == src_attr and merge_method not in [None, prof.Method.keep]:
if dest_attr == src_attr and merge_method not in [None, const.KEEP]:
return
setattr(dest, attr, src_attr)

def _merge_items(self, dest: OBT, src: OBT, merge_method: Optional[prof.Method]) -> None:
def _merge_items(self, dest: OBT, src: OBT, merge_method: Optional[str]) -> None:
"""Merge two items recursively."""
for field in src.__fields_set__:
self._merge_attrs(dest, src, field, merge_method)
Expand Down Expand Up @@ -148,7 +149,7 @@ def _flatten_catalog(self, catalog: cat.Catalog, as_is: bool) -> cat.Catalog:
return catalog

def _merge_two_catalogs(
self, dest: cat.Catalog, src: cat.Catalog, merge_method: Optional[prof.Method], as_is: bool
self, dest: cat.Catalog, src: cat.Catalog, merge_method: Optional[str], as_is: bool
) -> cat.Catalog:
# merge_method is use_first, merge, keep
# no combine or merge_method equates to merge_method=keep
Expand Down Expand Up @@ -176,7 +177,7 @@ def _merge_catalog(self, merged: Optional[cat.Catalog], catalog: cat.Catalog) ->
local_cat = catalog.copy(deep=True)
local_merged = merged.copy(deep=True) if merged else None

merge_method = prof.Method.keep
merge_method = const.KEEP
as_is = False
if self._profile.merge is not None:
if self._profile.merge.custom is not None:
Expand All @@ -185,12 +186,12 @@ def _merge_catalog(self, merged: Optional[cat.Catalog], catalog: cat.Catalog) ->
as_is = self._profile.merge.as_is
if self._profile.merge.combine is None:
logger.debug('Profile has merge but no combine so defaulting to combine/merge.')
merge_method = prof.Method.merge
merge_method = const.MERGE
else:
merge_combine = self._profile.merge.combine
if merge_combine.method is None:
logger.debug('Profile has merge combine but no method. Defaulting to merge.')
merge_method = prof.Method.merge
merge_method = const.MERGE
else:
merge_method = merge_combine.method

Expand Down
16 changes: 7 additions & 9 deletions trestle/core/resolver/modify.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import trestle.oscal.catalog as cat
import trestle.oscal.profile as prof
from trestle.common.common_types import OBT
from trestle.common.const import RESOLUTION_SOURCE, TRESTLE_INHERITED_PROPS_TRACKER
from trestle.common.const import AFTER, BEFORE, RESOLUTION_SOURCE, STARTING, TRESTLE_INHERITED_PROPS_TRACKER
from trestle.common.err import TrestleNotFoundError
from trestle.common.list_utils import as_list, get_item_from_list, none_if_empty
from trestle.core.catalog.catalog_interface import CatalogInterface
Expand Down Expand Up @@ -85,11 +85,11 @@ def _add_to_list(parts_list: List[common.Part], add: prof.Add) -> bool:
for index in range(len(parts_list)):
# find the matching part
if parts_list[index].id == add.by_id:
if add.position == prof.Position.after:
if add.position == AFTER:
for offset, new_item in enumerate(as_list(add.parts)):
parts_list.insert(index + 1 + offset, new_item)
added_parts = True
elif add.position == prof.Position.before:
elif add.position == BEFORE:
for offset, new_item in enumerate(as_list(add.parts)):
parts_list.insert(index + offset, new_item)
added_parts = True
Expand All @@ -113,23 +113,21 @@ def _add_to_parts(parts: List[common.Part], add: prof.Add) -> bool:
return False

@staticmethod
def _add_attr_to_part(part: common.Part, items: List[OBT], attr: str, position: Optional[prof.Position]) -> None:
def _add_attr_to_part(part: common.Part, items: List[OBT], attr: str, position: Optional[str]) -> None:
attr_list = as_list(getattr(part, attr, None))
# position may be None and if so will go at end
if position in [prof.Position.starting, prof.Position.before]:
if position in [STARTING, BEFORE]:
items.extend(attr_list)
attr_list = items
else:
attr_list.extend(items)
setattr(part, attr, attr_list)

@staticmethod
def _add_attr_to_control(
control: cat.Control, items: List[OBT], attr: str, position: Optional[prof.Position]
) -> None:
def _add_attr_to_control(control: cat.Control, items: List[OBT], attr: str, position: Optional[str]) -> None:
attr_list = as_list(getattr(control, attr, None))
# if position is None it will add to end
if position in [prof.Position.starting, prof.Position.before]:
if position in [STARTING, BEFORE]:
items.extend(attr_list)
attr_list = items
else:
Expand Down
2 changes: 1 addition & 1 deletion trestle/core/resolver/prune.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _controls_selected(self, select_list: Optional[List[prof.SelectControlById]]
control_ids.append(id_)
if include_children:
control_ids.extend(self._catalog_interface.get_dependent_control_ids(id_))
return [control_id.__root__ for control_id in control_ids]
return control_ids

def _find_needed_control_ids(self) -> List[str]:
"""Get list of control_ids needed by profile and corresponding groups."""
Expand Down
Loading