Skip to content
This repository has been archived by the owner on Aug 20, 2021. It is now read-only.

Commit

Permalink
Add code generator command (#17)
Browse files Browse the repository at this point in the history
add code generator command
  • Loading branch information
asadullah-yousuf-10p authored and anweiss committed Dec 11, 2018
1 parent 7cffc08 commit 4967fab
Show file tree
Hide file tree
Showing 8 changed files with 576 additions and 4 deletions.
1 change: 1 addition & 0 deletions cli/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func Execute() error {
convert.Convert,
Validate,
Sign,
Generate,
}

return app.Run(os.Args)
Expand Down
94 changes: 94 additions & 0 deletions cli/cmd/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package cmd

import (
"fmt"
"go/format"
"io/ioutil"
"os"

"github.com/Sirupsen/logrus"
"github.com/opencontrol/oscalkit/generator"
"github.com/opencontrol/oscalkit/templates"
"github.com/opencontrol/oscalkit/types/oscal/catalog"

"github.com/urfave/cli"
)

var profilePath string

//Generate Cli command to generate go code for controls
var Generate = cli.Command{
Name: "generate",
Usage: "generates go code against provided profile",
Flags: []cli.Flag{
cli.StringFlag{
Name: "profile, p",
Usage: "profile to intersect against",
Destination: &profilePath,
},
},
Before: func(c *cli.Context) error {
if profilePath == "" {
return cli.NewExitError("oscalkit generate is missing the --profile flag", 1)
}

return nil
},
Action: func(c *cli.Context) error {

outputFileName := "catalogs.go"
profilePath, err := generator.GetAbsolutePath(profilePath)
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot get absolute path, err: %v", err), 1)
}

_, err = os.Stat(profilePath)
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot fetch file, err %v", err), 1)
}
f, err := os.Open(profilePath)
if err != nil {
return cli.NewExitError(err, 1)
}
defer f.Close()

profile, err := generator.ReadProfile(f)
if err != nil {
return cli.NewExitError(err, 1)
}
newFile, err := os.Create(outputFileName)
if err != nil {
return cli.NewExitError("cannot create file for catalogs", 1)
}
defer newFile.Close()

catalogs := generator.CreateCatalogsFromProfile(profile)
t, err := templates.GetCatalogTemplate()
if err != nil {
return cli.NewExitError("cannot fetch template", 1)
}
err = t.Execute(newFile, struct {
Catalogs []*catalog.Catalog
}{catalogs})

//TODO: discuss better approach for formatting generate code file.
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot write file for catalogs, err: %v", err), 1)
}
b, err := ioutil.ReadFile(outputFileName)
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot open %s file", outputFileName), 1)
}
b, err = format.Source(b)
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot format %s file", outputFileName), 1)
}
newFile.WriteAt(b, 0)
if err != nil {
return cli.NewExitError(fmt.Sprintf("cannot write formmated "), 1)
}
logrus.Info("catalogs.go file created.")
return nil

},
}
172 changes: 172 additions & 0 deletions generator/generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package generator

import (
"bytes"
"fmt"
"net/url"
"testing"

"github.com/opencontrol/oscalkit/types/oscal/profile"

"github.com/opencontrol/oscalkit/types/oscal/catalog"
)

const (
temporaryFilePathForCatalogJSON = "/tmp/catalog.json"
temporaryFilePathForProfileJSON = "/tmp/profile.json"
temporaryFilePathForCatalogsGoFile = "/tmp/catalogs.go"
)

func TestIsHttp(t *testing.T) {

httpRoute := "http://localhost:3000"
expectedOutputForHTTP := true

nonHTTPRoute := "NIST.GOV.JSON"
expectedOutputForNonHTTP := false

r, err := url.Parse(httpRoute)
if err != nil {
t.Error(err)
}
if isHTTPResource(r) != expectedOutputForHTTP {
t.Error("Invalid output for http routes")
}

r, err = url.Parse(nonHTTPRoute)
if err != nil {
t.Error(err)
}
if isHTTPResource(r) != expectedOutputForNonHTTP {
t.Error("Invalid output for non http routes")
}

}

func TestReadCatalog(t *testing.T) {

catalogTitle := "NIST SP800-53"
r := bytes.NewReader([]byte(string(
fmt.Sprintf(`
{
"catalog": {
"title": "%s",
"declarations": {
"href": "NIST_SP-800-53_rev4_declarations.xml"
},
"groups": [
{
"controls": [
{
"id": "at-1",
"class": "SP800-53",
"title": "Security Awareness and Training Policy and Procedures",
"params": [
{
"id": "at-1_prm_1",
"label": "organization-defined personnel or roles"
},
{
"id": "at-1_prm_2",
"label": "organization-defined frequency"
},
{
"id": "at-1_prm_3",
"label": "organization-defined frequency"
}
]
}
]
}
]
}
}`, catalogTitle))))

c, err := ReadCatalog(r)
if err != nil {
t.Error(err)
}

if c.Title != catalog.Title(catalogTitle) {
t.Error("title not equal")
}

}

func TestReadInvalidCatalog(t *testing.T) {

r := bytes.NewReader([]byte(string(`{ "catalog": "some dummy bad json"}`)))
_, err := ReadCatalog(r)
if err == nil {
t.Error("successfully parsed invalid catalog file")
}
}

func TestCreateCatalogsFromProfile(t *testing.T) {

href, _ := url.Parse("https://raw.githubusercontent.com/usnistgov/OSCAL/master/content/nist.gov/SP800-53/rev4/NIST_SP-800-53_rev4_catalog.json")
p := profile.Profile{
Imports: []profile.Import{
profile.Import{
Href: &catalog.Href{
URL: href,
},
Include: &profile.Include{
IdSelectors: []profile.Call{
profile.Call{
ControlId: "ac-1",
},
},
},
},
},
}
x := CreateCatalogsFromProfile(&p)
if len(x) != 1 {
t.Error("there must be one catalog")
}
if x[0].Groups[0].Controls[0].Id != "ac-1" {
t.Error("Invalid control Id")
}

}

func TestCreateCatalogsFromProfileWithBadHref(t *testing.T) {

href, _ := url.Parse("this is a bad url")
p := profile.Profile{
Imports: []profile.Import{
profile.Import{
Href: &catalog.Href{
URL: href,
},
Include: &profile.Include{
IdSelectors: []profile.Call{
profile.Call{
ControlId: "ac-1",
},
},
},
},
},
}
catalogs := CreateCatalogsFromProfile(&p)
if len(catalogs) > 0 {
t.Error("nothing should be parsed due to bad url")
}
}

func TestGetCatalogInvalidFilePath(t *testing.T) {

url := "http://[::1]a"
_, err := GetCatalogFilePath(url)
if err == nil {
t.Error("should fail")
}
}

func failTest(err error, t *testing.T) {
if err != nil {
t.Error(t)
}
}
70 changes: 70 additions & 0 deletions generator/intersection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package generator

import (
"os"

"github.com/Sirupsen/logrus"

"github.com/opencontrol/oscalkit/types/oscal/catalog"
"github.com/opencontrol/oscalkit/types/oscal/profile"
)

//CreateCatalogsFromProfile maps profile controls to multiple catalogs
func CreateCatalogsFromProfile(profile *profile.Profile) []*catalog.Catalog {

var outputCatalogs []*catalog.Catalog
//Get first import of the profile (which is a catalog)
for _, profileImport := range profile.Imports {

//ForEach Import's Href, Fetch the Catalog JSON file
catalogReference, err := GetCatalogFilePath(profileImport.Href.String())
if err != nil {
logrus.Errorf("invalid file path: %v", err)
continue
}

f, err := os.Open(catalogReference)
if err != nil {
logrus.Errorf("cannot read file: %v", err)
continue
}

//Once fetched, Read the catalog JSON and Marshall it to Go struct.
importedCatalog, err := ReadCatalog(f)
if err != nil {
logrus.Errorf("cannot parse catalog listed in import.href %v", err)
}
//Prepare a new catalog object to merge into the final List of OutputCatalogs
newCatalog := getMappedCatalogControlsFromImport(importedCatalog, profileImport)
outputCatalogs = append(outputCatalogs, &newCatalog)
}
return outputCatalogs
}

func getMappedCatalogControlsFromImport(importedCatalog *catalog.Catalog, profileImport profile.Import) catalog.Catalog {
newCatalog := catalog.Catalog{Groups: []catalog.Group{}}
for _, group := range importedCatalog.Groups {
//Prepare a new group to append matching controls into.
newGroup := catalog.Group{
Title: group.Title,
Controls: []catalog.Control{},
}
//Append controls to the new group if matches
for _, catalogControl := range group.Controls {
for _, z := range profileImport.Include.IdSelectors {
if catalogControl.Id == z.ControlId {
newGroup.Controls = append(newGroup.Controls, catalog.Control{
Id: catalogControl.Id,
Class: catalogControl.Class,
Title: catalogControl.Title,
Subcontrols: catalogControl.Subcontrols,
})
}
}
}
if len(newGroup.Controls) > 0 {
newCatalog.Groups = append(newCatalog.Groups, newGroup)
}
}
return newCatalog
}
Loading

0 comments on commit 4967fab

Please sign in to comment.