Skip to content

Commit

Permalink
Merge pull request #38 from opencontrol/summary-tables-C
Browse files Browse the repository at this point in the history
rename Table to SummaryTable
  • Loading branch information
jcscottiii authored Aug 29, 2016
2 parents 2c30eb7 + 3965568 commit 7468e7e
Show file tree
Hide file tree
Showing 19 changed files with 548 additions and 171 deletions.
65 changes: 65 additions & 0 deletions control/narrative_section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package control

import (
"errors"
"regexp"

"github.com/jbowtie/gokogiri/xml"
docxHelper "github.com/opencontrol/fedramp-templater/docx/helper"
"github.com/opencontrol/fedramp-templater/opencontrols"
xmlHelper "github.com/opencontrol/fedramp-templater/xml/helper"
)

type narrativeSection struct {
row xml.Node
}

func (n narrativeSection) parsePart() (key string, err error) {
re := regexp.MustCompile(`Part ([a-z])`)
content := []byte(n.row.Content())
subMatches := re.FindSubmatch(content)
if len(subMatches) != 2 {
err = errors.New("No Parts found.")
return
}
key = string(subMatches[1])
return
}

// GetKey returns the narrative section "part"/key. `key` will be an empty string if there is no "Part".
func (n narrativeSection) GetKey() (key string, err error) {
cells, err := xmlHelper.SearchSubtree(n.row, `./w:tc`)
numCells := len(cells)
if numCells == 1 {
// there is only a single narrative section
key = ""
} else if numCells == 2 {
key, err = n.parsePart()
if err != nil {
return
}
} else {
err = errors.New("Don't know how to parse row.")
}

return
}

// Fill populates the section/part with the narrative for this control part from the provided data.
func (n narrativeSection) Fill(data opencontrols.Data, control string) (err error) {
// the row should have one or two cells; either way, the last one is what should be filled
cellNode, err := xmlHelper.SearchOne(n.row, `./w:tc[last()]`)
if err != nil {
return
}

key, err := n.GetKey()
if err != nil {
return
}

narrative := data.GetNarrative(control, key)
docxHelper.FillCell(cellNode, narrative)

return
}
50 changes: 50 additions & 0 deletions control/narrative_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package control

import (
"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/opencontrols"
)

func fillRows(rows []xml.Node, data opencontrols.Data, control string) error {
for _, row := range rows {
section := narrativeSection{row}
err := section.Fill(data, control)
if err != nil {
return err
}
}
return nil
}

// NarrativeTable represents the node in the Word docx XML tree that corresponds to the justification fields for a security control.
type NarrativeTable struct {
table
}

// NewNarrativeTable creates a NarrativeTable instance.
func NewNarrativeTable(root xml.Node) NarrativeTable {
tbl := table{Root: root}
return NarrativeTable{tbl}
}

// SectionRows returns the list of rows which correspond to each "part" of the narrative. Will return a single row when the narrative isn't split into parts.
func (t *NarrativeTable) SectionRows() ([]xml.Node, error) {
// skip the header row
return t.table.searchSubtree(`.//w:tr[position() > 1]`)
}

// Fill inserts the OpenControl data into the table.
func (t *NarrativeTable) Fill(openControlData opencontrols.Data) (err error) {
control, err := t.table.controlName()
if err != nil {
return
}

rows, err := t.SectionRows()
if err != nil {
return
}

fillRows(rows, openControlData, control)
return
}
37 changes: 37 additions & 0 deletions control/narrative_table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package control_test

import (
. "github.com/opencontrol/fedramp-templater/control"
"github.com/opencontrol/fedramp-templater/fixtures"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("NarrativeTable", func() {
Describe("SectionRows", func() {
It("returns the correct number for a singular narrative", func() {
doc := fixtures.LoadSSP("FedRAMP_ac-2-1_v2.1.docx")
defer doc.Close()
root, err := doc.NarrativeTable("AC-2 (1)")
Expect(err).NotTo(HaveOccurred())

table := NewNarrativeTable(root)
sections, err := table.SectionRows()
Expect(err).NotTo(HaveOccurred())
Expect(len(sections)).To(Equal(1))
})

It("returns the correct number for multiple narrative sections", func() {
doc := fixtures.LoadSSP("FedRAMP_ac-2_v2.1.docx")
defer doc.Close()
root, err := doc.NarrativeTable("AC-2")
Expect(err).NotTo(HaveOccurred())

table := NewNarrativeTable(root)
sections, err := table.SectionRows()
Expect(err).NotTo(HaveOccurred())
Expect(len(sections)).To(Equal(11))
})
})
})
16 changes: 9 additions & 7 deletions control/responsible_role.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package control

import (
"github.com/jbowtie/gokogiri/xml"
"errors"
"fmt"
"regexp"
"strings"
"fmt"

"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/xml/helper"
)

// findResponsibleRole looks for the Responsible Role cell in the control table.
func findResponsibleRole(ct *Table) (*responsibleRole, error) {
nodes, err := ct.searchSubtree(".//w:tc[starts-with(normalize-space(.), 'Responsible Role')]")
func findResponsibleRole(ct *SummaryTable) (*responsibleRole, error) {
nodes, err := ct.table.searchSubtree(".//w:tc[starts-with(normalize-space(.), 'Responsible Role')]")
if err != nil {
return nil, err
}
if len(nodes) != 1 {
return nil, errors.New("could not find Responsible Role cell")
}
parentNode := nodes[0]
childNodes, err := parentNode.Search(".//w:t")
childNodes, err := helper.SearchSubtree(parentNode, `.//w:t`)
if err != nil || len(childNodes) < 1 {
return nil, errors.New("Should not happen, cannot find text nodes.")
}
Expand All @@ -28,7 +30,7 @@ func findResponsibleRole(ct *Table) (*responsibleRole, error) {
// responsibleRole is the container for the responsible role cell.
type responsibleRole struct {
parentNode xml.Node
textNodes *[]xml.Node
textNodes *[]xml.Node
}

// getContent returns the full string representation of the content of the cell itself.
Expand Down Expand Up @@ -73,4 +75,4 @@ func (r *responsibleRole) getValue() string {
}
}
return result
}
}
69 changes: 69 additions & 0 deletions control/summary_table.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package control

import (
"github.com/jbowtie/gokogiri/xml"
"github.com/opencontrol/fedramp-templater/opencontrols"
"github.com/opencontrol/fedramp-templater/reporter"
)

const (
responsibleRoleField = "Responsible Role"
)

// SummaryTable represents the node in the Word docx XML tree that corresponds to the summary information for a security control.
type SummaryTable struct {
table
}

// NewSummaryTable creates a SummaryTable instance.
func NewSummaryTable(root xml.Node) SummaryTable {
tbl := table{Root: root}
return SummaryTable{tbl}
}

func (st *SummaryTable) controlName() (name string, err error) {
return st.table.controlName()
}

// Fill inserts the OpenControl justifications into the table. Note this modifies the `table`.
func (st *SummaryTable) Fill(openControlData opencontrols.Data) (err error) {
roleCell, err := findResponsibleRole(st)
if err != nil {
return
}

control, err := st.controlName()
if err != nil {
return
}

roles := openControlData.GetResponsibleRoles(control)
roleCell.setValue(roles)

return
}

// diffResponsibleRole computes the diff of the responsible role cell.
func (st *SummaryTable) diffResponsibleRole(control string, openControlData opencontrols.Data) ([]reporter.Reporter, error) {
roleCell, err := findResponsibleRole(st)
if err != nil {
return []reporter.Reporter{}, err
}
yamlRoles := openControlData.GetResponsibleRoles(control)
sspRoles := roleCell.getValue()
if roleCell.isDefaultValue(sspRoles) || yamlRoles == sspRoles {
return []reporter.Reporter{}, nil
}
return []reporter.Reporter{
NewDiff(control, responsibleRoleField, sspRoles, yamlRoles),
}, nil
}

// Diff returns the list of diffs in the control table.
func (st *SummaryTable) Diff(openControlData opencontrols.Data) ([]reporter.Reporter, error) {
control, err := st.controlName()
if err != nil {
return []reporter.Reporter{}, err
}
return st.diffResponsibleRole(control, openControlData)
}
66 changes: 23 additions & 43 deletions control/table_test.go → control/summary_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ package control_test

import (
"bytes"
"os"
"path/filepath"
"text/template"
// using fork because of https://github.com/moovweb/gokogiri/pull/93#issuecomment-215582446
"github.com/jbowtie/gokogiri/xml"

"github.com/jbowtie/gokogiri/xml"
. "github.com/opencontrol/fedramp-templater/control"
"github.com/opencontrol/fedramp-templater/docx/helper"
"github.com/opencontrol/fedramp-templater/opencontrols"
"github.com/opencontrol/fedramp-templater/ssp"
"github.com/opencontrol/fedramp-templater/fixtures"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -22,7 +20,7 @@ type tableData struct {
}

func docFixture(control string) *xml.XmlDocument {
path := filepath.Join("..", "fixtures", "simplified_table.xml")
path := fixtures.FixturePath("simplified_table.xml")
tpl, err := template.ParseFiles(path)
Expect(err).ToNot(HaveOccurred())

Expand All @@ -36,61 +34,43 @@ func docFixture(control string) *xml.XmlDocument {
return doc
}

func openControlFixturePath() string {
path := filepath.Join("..", "fixtures", "opencontrols")
path, err := filepath.Abs(path)
Expect(err).NotTo(HaveOccurred())
_, err = os.Stat(path)
func getTable(control string) xml.Node {
doc := docFixture(control)
// replicate what ssp.Document's SummaryTables() method is doing, except that this source isn't a full Word doc
tables, err := doc.Search(ssp.SummaryTablesXPath)
Expect(err).NotTo(HaveOccurred())

return path
return tables[0]
}

func openControlFixture() opencontrols.Data {
path := openControlFixturePath()
data, errors := opencontrols.LoadFrom(path)
for _, err := range errors {
Expect(err).NotTo(HaveOccurred())
}

return data
}

var _ = Describe("Table", func() {
var _ = Describe("SummaryTable", func() {
Describe("Fill", func() {
It("fills in the Responsible Role for controls", func() {
doc := docFixture("AC-2")
tables, _ := doc.Search("//w:tbl")
table := tables[0]
table := getTable("AC-2")
st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()

ct := Table{Root: table}
openControlData := openControlFixture()
ct.Fill(openControlData)
st.Fill(openControlData)

Expect(table.Content()).To(ContainSubstring(`Responsible Role: Amazon Elastic Compute Cloud: AWS Staff`))
})

It("fills in the Responsible Role for control enhancements", func() {
doc := docFixture("AC-2 (1)")
tables, _ := doc.Search("//w:tbl")
table := tables[0]
table := getTable("AC-2 (1)")
st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()

ct := Table{Root: table}
openControlData := openControlFixture()
ct.Fill(openControlData)
st.Fill(openControlData)

Expect(table.Content()).To(ContainSubstring(`Responsible Role: Amazon Elastic Compute Cloud: AWS Staff`))
})
})

Describe("Diff", func() {
It("detects no diff when the value of responsible role is empty", func() {
doc := docFixture("AC-2")
tables, _ := doc.Search("//w:tbl")
table := tables[0]

ct := Table{Root: table}
openControlData := openControlFixture()
diff, err := ct.Diff(openControlData)
table := getTable("AC-2")
st := NewSummaryTable(table)
openControlData := fixtures.LoadOpenControlFixture()
diff, err := st.Diff(openControlData)

Expect(diff).To(Equal([]reporter.Reporter{}))
Expect(err).To(BeNil())
Expand Down
Loading

0 comments on commit 7468e7e

Please sign in to comment.