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

fix(generate): component generation fixed for many catalogs #573

Merged
merged 5 commits into from
Aug 6, 2024
Merged
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 src/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ var generateComponentCmd = &cobra.Command{
// Create a component definition from the catalog given required context
comp, err := oscal.ComponentFromCatalog(source, catalog, title, componentOpts.Requirements, remarks)
if err != nil {
message.Fatalf(fmt.Errorf("error creating component"), "error creating component")
message.Fatalf(err, fmt.Sprintf("error creating component - %s\n", err.Error()))
}

// Add the command to the remarks - this will always overwrite the existing remarks content
Expand Down
155 changes: 102 additions & 53 deletions src/pkg/common/oscal/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func mergeLinks(orig []oscalTypes_1_1_2.Link, latest []oscalTypes_1_1_2.Link) *[
// Creates a component-definition from a catalog and identified (or all) controls. Allows for specification of what the content of the remarks section should contain.
func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, componentTitle string, targetControls []string, targetRemarks []string) (*oscalTypes_1_1_2.ComponentDefinition, error) {
// store all of the implemented requirements
implmentedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0)
implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0)
var componentDefinition = &oscalTypes_1_1_2.ComponentDefinition{}

if len(targetControls) == 0 {
Expand All @@ -255,40 +255,32 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp
controlMap[control] = false
}

if catalog.Groups == nil {
return componentDefinition, fmt.Errorf("catalog Groups is nil - no catalog provided")
}
// A catalog has groups and controls
// A group has controls and groups (note the nesting of groups)
// A control has controls (note the nesting)
// Given the nesting of groups/controls we will need to recursively search groups/controls

// Iterate through all possible controls -> improve the efficiency of this in the future
for _, group := range *catalog.Groups {
if group.Controls == nil {
message.Debugf("group %s has no controls", group.ID)
continue
// Begin Recursive group search
if catalog.Groups != nil {
newReqs, err := searchGroups(catalog.Groups, controlMap, targetRemarks)
if err != nil {
return componentDefinition, err
}
for _, control := range *group.Controls {
if _, ok := controlMap[control.ID]; ok {
newRequirement, err := ControlToImplementedRequirement(&control, targetRemarks)
if err != nil {
return componentDefinition, err
}
implmentedRequirements = append(implmentedRequirements, newRequirement)
controlMap[control.ID] = true
}
implementedRequirements = append(implementedRequirements, newReqs...)
}

if control.Controls != nil {
for _, subControl := range *control.Controls {
if _, ok := controlMap[subControl.ID]; ok {
newRequirement, err := ControlToImplementedRequirement(&subControl, targetRemarks)
if err != nil {
return componentDefinition, err
}
implmentedRequirements = append(implmentedRequirements, newRequirement)
controlMap[subControl.ID] = true
continue
}
}
}
// Begin recursive control search
if catalog.Controls != nil {
newReqs, err := searchControls(catalog.Controls, controlMap, targetRemarks)
if err != nil {
return componentDefinition, err
}
implementedRequirements = append(implementedRequirements, newReqs...)
}

// TODO: rework this - a catalog does not require groups
if catalog.Groups == nil {
return componentDefinition, fmt.Errorf("catalog Groups is nil - no catalog provided")
}

for id, found := range controlMap {
Expand All @@ -297,6 +289,10 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp
}
}

if len(implementedRequirements) == 0 {
return componentDefinition, fmt.Errorf("no controls were identified in the catalog from the requirements list: %v\n", targetControls)
}

componentDefinition.Components = &[]oscalTypes_1_1_2.DefinedComponent{
{
UUID: uuid.NewUUID(),
Expand All @@ -307,7 +303,7 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp
{
UUID: uuid.NewUUIDWithSource(source),
Source: source,
ImplementedRequirements: implmentedRequirements,
ImplementedRequirements: implementedRequirements,
Description: "Control Implementation Description",
},
},
Expand All @@ -330,6 +326,54 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp

}

func searchGroups(groups *[]oscalTypes_1_1_2.Group, controlMap map[string]bool, remarks []string) ([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, error) {

implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0)

for _, group := range *groups {
if group.Groups != nil {
newReqs, err := searchGroups(group.Groups, controlMap, remarks)
if err != nil {
return implementedRequirements, err
}
implementedRequirements = append(implementedRequirements, newReqs...)
}
if group.Controls != nil {
newReqs, err := searchControls(group.Controls, controlMap, remarks)
if err != nil {
return implementedRequirements, err
}
implementedRequirements = append(implementedRequirements, newReqs...)
}
}
return implementedRequirements, nil
}

func searchControls(controls *[]oscalTypes_1_1_2.Control, controlMap map[string]bool, remarks []string) ([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, error) {

implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0)

for _, control := range *controls {
if _, ok := controlMap[control.ID]; ok {
newRequirement, err := ControlToImplementedRequirement(&control, remarks)
if err != nil {
return implementedRequirements, err
}
implementedRequirements = append(implementedRequirements, newRequirement)
controlMap[control.ID] = true
}

if control.Controls != nil {
newReqs, err := searchControls(control.Controls, controlMap, remarks)
if err != nil {
return implementedRequirements, err
}
implementedRequirements = append(implementedRequirements, newReqs...)
}
}
return implementedRequirements, nil
}

// Consume a control - Identify statements - iterate through parts in order to create a description
func ControlToImplementedRequirement(control *oscalTypes_1_1_2.Control, targetRemarks []string) (implementedRequirement oscalTypes_1_1_2.ImplementedRequirementControlImplementation, err error) {
var controlDescription string
Expand Down Expand Up @@ -531,29 +575,34 @@ func addPart(part *[]oscalTypes_1_1_2.Part, paramMap map[string]parameter, level

var result, label string

for _, part := range *part {
// need to get the label first - unsure if there will ever be more than one?
for _, prop := range *part.Props {
if prop.Name == "label" {
label = prop.Value
if part != nil {
for _, part := range *part {
// need to get the label first - unsure if there will ever be more than one?
if part.Props != nil {
for _, prop := range *part.Props {
if prop.Name == "label" {
label = prop.Value
}
}
}
}
var tabs string
for range level {
tabs += "\t"
}
prose := part.Prose
if prose == "" {
result += fmt.Sprintf("%s%s\n", tabs, label)
} else if strings.Contains(prose, "{{ insert: param,") {
result += fmt.Sprintf("%s%s %s\n", tabs, label, replaceParams(prose, paramMap))
} else {
result += fmt.Sprintf("%s%s %s\n", tabs, label, prose)
}
if part.Parts != nil {
result += addPart(part.Parts, paramMap, level+1)
}

var tabs string
for range level {
tabs += "\t"
}
prose := part.Prose
if prose == "" {
result += fmt.Sprintf("%s%s\n", tabs, label)
} else if strings.Contains(prose, "{{ insert: param,") {
result += fmt.Sprintf("%s%s %s\n", tabs, label, replaceParams(prose, paramMap))
} else {
result += fmt.Sprintf("%s%s %s\n", tabs, label, prose)
}
if part.Parts != nil {
result += addPart(part.Parts, paramMap, level+1)
}

}
}

return result
Expand Down