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

rename Table to SummaryTable #38

Merged
merged 24 commits into from
Aug 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e5a5c46
rename control.Table to .SummaryTable
afeld Jul 26, 2016
af067bd
refactor SSP loading for document_test
afeld Jul 26, 2016
a87ea77
add a test for SumaryTables()
afeld Jul 28, 2016
8ccbcee
refactor SummaryTable tests
afeld Jul 29, 2016
cc537b7
change `ct` to `st` for referencing SummaryTables
afeld Aug 28, 2016
a92ad09
share some logic for finding summary tables with tests
afeld Aug 28, 2016
353014d
refactor helper methods out of templater test
afeld Jul 23, 2016
17ce563
remove redundant disclaimer about gokogiri version
afeld Jul 29, 2016
a75cd3b
share fixture helper functions between tests
afeld Aug 27, 2016
7666929
fix component fixture to match actual SSP narrative structure
afeld Jul 28, 2016
c7a2dfa
add a method to retrieve the narrative tables
afeld Jul 29, 2016
3d012ac
add a GetNarrative() method for opencontrols.Data
afeld Jul 29, 2016
fe77edd
fix narrative tests
afeld Aug 28, 2016
e961533
refactor table struct out from SummaryTable
afeld Aug 28, 2016
56c1473
create a NarrativeTable struct (broken)
afeld Aug 28, 2016
397aa02
fill narrative tables
afeld Aug 28, 2016
dcca032
refactor narrative row filling
afeld Aug 28, 2016
21a79d2
fix narrative row filling
afeld Aug 28, 2016
72139b5
refactor NarrativeTable
afeld Aug 28, 2016
55db87a
fix Code Climate errors
afeld Aug 28, 2016
3234e7a
respect newlines in narrative content
afeld Aug 28, 2016
06655b7
embed tables directly in the Narrative/SummaryTable structs
afeld Aug 28, 2016
ccae626
create a narrativeSection struct
afeld Aug 28, 2016
3965568
create an XML helper package
afeld Aug 28, 2016
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
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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI that this is go-plus making these kinds of changes automatically. I'm not quite that OCD 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol!! No worries. go-plus is running go fmt after every save. side note: we probably need to activate this for add this to our codeclimate rules: https://github.com/codeclimate-community/codeclimate-gofmt

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ (not for this PR) #41

"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) {
Copy link
Member

@jcscottiii jcscottiii Aug 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we aren't using Table to represent Control Table (because there are multiple tables) anymore, could we rename ct to st (here and all the other places)?

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTable -> getSummaryTable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that's implied by this file being for summary table tests, no?

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