From 53e04b5346f35bffec1db34da61b5b34e1338356 Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Sun, 23 Oct 2022 11:12:04 -0400 Subject: [PATCH] Swagger securityRequirement added to what-changed This one was perhaps one of the more frustrating models. It's a simple design, checking maps of string arrays for accurate changes - but the combinations make it a bit of a challenge. I hope I've caught the variations correctly. Bloody swagger, the sooner you go away, the easier all of our lies will be. --- datamodel/low/v2/security_requirement.go | 16 +- datamodel/low/v3/constants.go | 1 + what-changed/parameter.go | 2 +- what-changed/security_requirement.go | 134 +++++++++ what-changed/security_requirement_test.go | 348 ++++++++++++++++++++++ 5 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 what-changed/security_requirement.go create mode 100644 what-changed/security_requirement_test.go diff --git a/datamodel/low/v2/security_requirement.go b/datamodel/low/v2/security_requirement.go index bdd97c5..4aad965 100644 --- a/datamodel/low/v2/security_requirement.go +++ b/datamodel/low/v2/security_requirement.go @@ -4,9 +4,11 @@ package v2 import ( + "crypto/sha256" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" + "strings" ) // SecurityRequirement is a low-level representation of a Swagger / OpenAPI 2 SecurityRequirement object. @@ -23,11 +25,12 @@ type SecurityRequirement struct { // Build will extract security requirements from the node (the structure is odd, to be honest) func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error { var labelNode *yaml.Node - var arr []low.ValueReference[string] valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]) + var arr []low.ValueReference[string] for i := range root.Content { if i%2 == 0 { labelNode = root.Content[i] + arr = []low.ValueReference[string]{} // reset roles. continue } for j := range root.Content[i].Content { @@ -50,3 +53,14 @@ func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error { } return nil } + +func (s *SecurityRequirement) Hash() [32]byte { + var f []string + for k := range s.Values.Value { + for y := range s.Values.Value[k].Value { + f = append(f, s.Values.Value[k].Value[y].Value) + // lol, I know. -------^^^^^ <- this is the actual value. + } + } + return sha256.Sum256([]byte(strings.Join(f, "|"))) +} diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index 2981a4e..f4a1bed 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -100,4 +100,5 @@ const ( AllowReservedLabel = "allowReserved" ExplodeLabel = "explode" ContentTypeLabel = "contentType" + SecurityDefinitionLabel = "securityDefinition" ) diff --git a/what-changed/parameter.go b/what-changed/parameter.go index a2b2b7d..e9f5037 100644 --- a/what-changed/parameter.go +++ b/what-changed/parameter.go @@ -17,7 +17,7 @@ type ParameterChanges struct { SchemaChanges *SchemaChanges ExtensionChanges *ExtensionChanges - // V2 change types + // v2 change types ItemsChanges *ItemsChanges // v3 change types diff --git a/what-changed/security_requirement.go b/what-changed/security_requirement.go new file mode 100644 index 0000000..6bfc36e --- /dev/null +++ b/what-changed/security_requirement.go @@ -0,0 +1,134 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v2" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "gopkg.in/yaml.v3" +) + +type SecurityRequirementChanges struct { + PropertyChanges +} + +func (s *SecurityRequirementChanges) TotalChanges() int { + return s.PropertyChanges.TotalChanges() +} + +func (s *SecurityRequirementChanges) TotalBreakingChanges() int { + return s.PropertyChanges.TotalBreakingChanges() +} + +func CompareSecurityRequirement(l, r *v2.SecurityRequirement) *SecurityRequirementChanges { + if low.AreEqual(l, r) { + return nil + } + var changes []*Change + lKeys := make([]string, len(l.Values.Value)) + rKeys := make([]string, len(r.Values.Value)) + lValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) + rValues := make(map[string]low.ValueReference[[]low.ValueReference[string]]) + var n, z int + for i := range l.Values.Value { + lKeys[n] = i.Value + lValues[i.Value] = l.Values.Value[i] + n++ + } + for i := range r.Values.Value { + rKeys[z] = i.Value + rValues[i.Value] = r.Values.Value[i] + z++ + } + removed := func(z int, vn *yaml.Node, name string) { + CreateChange(&changes, ObjectRemoved, v3.SecurityDefinitionLabel, + vn, nil, true, name, nil) + } + + added := func(z int, vn *yaml.Node, name string) { + CreateChange(&changes, ObjectAdded, v3.SecurityDefinitionLabel, + nil, vn, false, nil, name) + } + + for z = range lKeys { + if z < len(rKeys) { + if _, ok := rValues[lKeys[z]]; !ok { + removed(z, lValues[lKeys[z]].ValueNode, lKeys[z]) + continue + } + + lValue := lValues[lKeys[z]].Value + rValue := rValues[lKeys[z]].Value + + // check if actual values match up + lRoleKeys := make([]string, len(lValue)) + rRoleKeys := make([]string, len(rValue)) + lRoleValues := make(map[string]low.ValueReference[string]) + rRoleValues := make(map[string]low.ValueReference[string]) + var t, k int + for i := range lValue { + lRoleKeys[t] = lValue[i].Value + lRoleValues[lValue[i].Value] = lValue[i] + t++ + } + for i := range rValue { + rRoleKeys[k] = rValue[i].Value + rRoleValues[rValue[i].Value] = rValue[i] + k++ + } + + for t = range lRoleKeys { + if t < len(rRoleKeys) { + if _, ok := rRoleValues[lRoleKeys[t]]; !ok { + removed(t, lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t]) + continue + } + } + if t >= len(rRoleKeys) { + if _, ok := rRoleValues[lRoleKeys[t]]; !ok { + removed(t, lRoleValues[lRoleKeys[t]].ValueNode, lRoleKeys[t]) + } + } + } + for t = range rRoleKeys { + if t < len(lRoleKeys) { + if _, ok := lRoleValues[rRoleKeys[t]]; !ok { + added(t, rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t]) + continue + } + } + if t >= len(lRoleKeys) { + added(t, rRoleValues[rRoleKeys[t]].ValueNode, rRoleKeys[t]) + } + } + + } + if z >= len(rKeys) { + if _, ok := rValues[lKeys[z]]; !ok { + removed(z, lValues[lKeys[z]].ValueNode, lKeys[z]) + } + } + } + for z = range rKeys { + if z < len(lKeys) { + if _, ok := lValues[rKeys[z]]; !ok { + added(z, rValues[rKeys[z]].ValueNode, rKeys[z]) + continue + } + } + if z >= len(lKeys) { + if _, ok := lValues[rKeys[z]]; !ok { + added(z, rValues[rKeys[z]].ValueNode, rKeys[z]) + } + } + } + + sc := new(SecurityRequirementChanges) + sc.Changes = changes + if sc.TotalChanges() <= 0 { + return nil + } + return sc +} diff --git a/what-changed/security_requirement_test.go b/what-changed/security_requirement_test.go new file mode 100644 index 0000000..8185457 --- /dev/null +++ b/what-changed/security_requirement_test.go @@ -0,0 +1,348 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package what_changed + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareSecurityRequirement(t *testing.T) { + + left := `auth: + - pizza + - pie` + + right := `auth: + - pie + - pizza` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Nil(t, extChanges) + +} +func TestCompareSecurityRequirement_NewReq(t *testing.T) { + + left := `tip: + - tap +auth: + - pizza + - pie` + + right := `auth: + - pie + - pizza +tip: + - tap +biscuit: + - digestive` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) + assert.Equal(t, "biscuit", extChanges.Changes[0].NewObject) +} + +func TestCompareSecurityRequirement_RemoveReq(t *testing.T) { + + left := `auth: + - pizza + - pie` + + right := `auth: + - pie + - pizza +biscuit: + - digestive` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) +} + +func TestCompareSecurityRequirement_SwapOut(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `bread: + - pie + - pizza +milk: + - digestive` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + assert.Equal(t, ObjectRemoved, extChanges.Changes[1].ChangeType) + assert.Equal(t, ObjectAdded, extChanges.Changes[2].ChangeType) + assert.Equal(t, ObjectAdded, extChanges.Changes[3].ChangeType) +} + +func TestCompareSecurityRequirement_SwapLeft(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `cheese: + - pie + - pizza +milk: + - digestive` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + assert.Equal(t, ObjectAdded, extChanges.Changes[1].ChangeType) +} + +func TestCompareSecurityRequirement_AddedRole(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `cheese: + - pizza + - pie +biscuit: + - digestive + - rich tea` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) + assert.Equal(t, "rich tea", extChanges.Changes[0].New) +} + +func TestCompareSecurityRequirement_AddedMultiple(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `cheese: + - pizza + - pie +cake: + - vanilla + - choccy +biscuit: + - digestive + - rich tea` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) +} + +func TestCompareSecurityRequirement_ReplaceRole(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `cheese: + - pizza + - pie +biscuit: + - biscotti + - rich tea` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) +} + +func TestCompareSecurityRequirement_Identical(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - biscotti + - rich tea` + + right := `cheese: + - pizza + - pie +biscuit: + - biscotti + - rich tea` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareSecurityRequirement_RemovedRole(t *testing.T) { + + left := `cheese: + - pizza + - pie +biscuit: + - digestive` + + right := `cheese: + - pizza + - pie +biscuit: + - digestive + - rich tea` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityRequirement + var rDoc v2.SecurityRequirement + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecurityRequirement(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + assert.Equal(t, "rich tea", extChanges.Changes[0].Original) +}