-
Notifications
You must be signed in to change notification settings - Fork 14
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
Changes from all commits
e5a5c46
af067bd
a87ea77
8ccbcee
cc537b7
a92ad09
353014d
17ce563
a75cd3b
7666929
c7a2dfa
3d012ac
fe77edd
e961533
56c1473
397aa02
dcca032
21a79d2
72139b5
55db87a
3234e7a
06655b7
ccae626
3965568
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} |
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 | ||
} |
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)) | ||
}) | ||
}) | ||
}) |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since we aren't using |
||
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.") | ||
} | ||
|
@@ -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. | ||
|
@@ -73,4 +75,4 @@ func (r *responsibleRole) getValue() string { | |
} | ||
} | ||
return result | ||
} | ||
} |
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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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()) | ||
|
||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
|
There was a problem hiding this comment.
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 😉
There was a problem hiding this comment.
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 runninggo 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-gofmtThere was a problem hiding this comment.
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