diff --git a/pkg/assembler/clients/generated/operations.go b/pkg/assembler/clients/generated/operations.go index 704a0281ec..c793ff29b4 100644 --- a/pkg/assembler/clients/generated/operations.go +++ b/pkg/assembler/clients/generated/operations.go @@ -13433,10 +13433,14 @@ func __marshalNeighborsNeighborsNode(v *NeighborsNeighborsNode) ([]byte, error) case *NeighborsNeighborsPointOfContact: typename = "PointOfContact" + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } result := struct { TypeName string `json:"__typename"` - *NeighborsNeighborsPointOfContact - }{typename, v} + *__premarshalNeighborsNeighborsPointOfContact + }{typename, premarshaled} return json.Marshal(result) case *NeighborsNeighborsSource: typename = "Source" @@ -13735,12 +13739,122 @@ func (v *NeighborsNeighborsPkgEqual) __premarshalJSON() (*__premarshalNeighborsN // hierarchy. However, until the use case arises, PointOfContact will be a flat // reference to the contact details. type NeighborsNeighborsPointOfContact struct { - Typename *string `json:"__typename"` + Typename *string `json:"__typename"` + AllPointOfContact `json:"-"` } // GetTypename returns NeighborsNeighborsPointOfContact.Typename, and is useful for accessing the field via an interface. func (v *NeighborsNeighborsPointOfContact) GetTypename() *string { return v.Typename } +// GetId returns NeighborsNeighborsPointOfContact.Id, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetId() string { return v.AllPointOfContact.Id } + +// GetSubject returns NeighborsNeighborsPointOfContact.Subject, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetSubject() AllPointOfContactSubjectPackageSourceOrArtifact { + return v.AllPointOfContact.Subject +} + +// GetEmail returns NeighborsNeighborsPointOfContact.Email, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetEmail() string { return v.AllPointOfContact.Email } + +// GetInfo returns NeighborsNeighborsPointOfContact.Info, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetInfo() string { return v.AllPointOfContact.Info } + +// GetSince returns NeighborsNeighborsPointOfContact.Since, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetSince() time.Time { return v.AllPointOfContact.Since } + +// GetJustification returns NeighborsNeighborsPointOfContact.Justification, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetJustification() string { + return v.AllPointOfContact.Justification +} + +// GetOrigin returns NeighborsNeighborsPointOfContact.Origin, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetOrigin() string { return v.AllPointOfContact.Origin } + +// GetCollector returns NeighborsNeighborsPointOfContact.Collector, and is useful for accessing the field via an interface. +func (v *NeighborsNeighborsPointOfContact) GetCollector() string { + return v.AllPointOfContact.Collector +} + +func (v *NeighborsNeighborsPointOfContact) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *NeighborsNeighborsPointOfContact + graphql.NoUnmarshalJSON + } + firstPass.NeighborsNeighborsPointOfContact = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllPointOfContact) + if err != nil { + return err + } + return nil +} + +type __premarshalNeighborsNeighborsPointOfContact struct { + Typename *string `json:"__typename"` + + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Email string `json:"email"` + + Info string `json:"info"` + + Since time.Time `json:"since"` + + Justification string `json:"justification"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *NeighborsNeighborsPointOfContact) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *NeighborsNeighborsPointOfContact) __premarshalJSON() (*__premarshalNeighborsNeighborsPointOfContact, error) { + var retval __premarshalNeighborsNeighborsPointOfContact + + retval.Typename = v.Typename + retval.Id = v.AllPointOfContact.Id + { + + dst := &retval.Subject + src := v.AllPointOfContact.Subject + var err error + *dst, err = __marshalAllPointOfContactSubjectPackageSourceOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NeighborsNeighborsPointOfContact.AllPointOfContact.Subject: %w", err) + } + } + retval.Email = v.AllPointOfContact.Email + retval.Info = v.AllPointOfContact.Info + retval.Since = v.AllPointOfContact.Since + retval.Justification = v.AllPointOfContact.Justification + retval.Origin = v.AllPointOfContact.Origin + retval.Collector = v.AllPointOfContact.Collector + return &retval, nil +} + // NeighborsNeighborsSource includes the requested fields of the GraphQL type Source. // The GraphQL type's documentation follows. // @@ -14322,10 +14436,14 @@ func __marshalNodeNode(v *NodeNode) ([]byte, error) { case *NodeNodePointOfContact: typename = "PointOfContact" + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } result := struct { TypeName string `json:"__typename"` - *NodeNodePointOfContact - }{typename, v} + *__premarshalNodeNodePointOfContact + }{typename, premarshaled} return json.Marshal(result) case *NodeNodeSource: typename = "Source" @@ -16217,12 +16335,118 @@ func (v *NodeNodePkgEqual) __premarshalJSON() (*__premarshalNodeNodePkgEqual, er // hierarchy. However, until the use case arises, PointOfContact will be a flat // reference to the contact details. type NodeNodePointOfContact struct { - Typename *string `json:"__typename"` + Typename *string `json:"__typename"` + AllPointOfContact `json:"-"` } // GetTypename returns NodeNodePointOfContact.Typename, and is useful for accessing the field via an interface. func (v *NodeNodePointOfContact) GetTypename() *string { return v.Typename } +// GetId returns NodeNodePointOfContact.Id, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetId() string { return v.AllPointOfContact.Id } + +// GetSubject returns NodeNodePointOfContact.Subject, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetSubject() AllPointOfContactSubjectPackageSourceOrArtifact { + return v.AllPointOfContact.Subject +} + +// GetEmail returns NodeNodePointOfContact.Email, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetEmail() string { return v.AllPointOfContact.Email } + +// GetInfo returns NodeNodePointOfContact.Info, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetInfo() string { return v.AllPointOfContact.Info } + +// GetSince returns NodeNodePointOfContact.Since, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetSince() time.Time { return v.AllPointOfContact.Since } + +// GetJustification returns NodeNodePointOfContact.Justification, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetJustification() string { return v.AllPointOfContact.Justification } + +// GetOrigin returns NodeNodePointOfContact.Origin, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetOrigin() string { return v.AllPointOfContact.Origin } + +// GetCollector returns NodeNodePointOfContact.Collector, and is useful for accessing the field via an interface. +func (v *NodeNodePointOfContact) GetCollector() string { return v.AllPointOfContact.Collector } + +func (v *NodeNodePointOfContact) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *NodeNodePointOfContact + graphql.NoUnmarshalJSON + } + firstPass.NodeNodePointOfContact = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllPointOfContact) + if err != nil { + return err + } + return nil +} + +type __premarshalNodeNodePointOfContact struct { + Typename *string `json:"__typename"` + + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Email string `json:"email"` + + Info string `json:"info"` + + Since time.Time `json:"since"` + + Justification string `json:"justification"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *NodeNodePointOfContact) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *NodeNodePointOfContact) __premarshalJSON() (*__premarshalNodeNodePointOfContact, error) { + var retval __premarshalNodeNodePointOfContact + + retval.Typename = v.Typename + retval.Id = v.AllPointOfContact.Id + { + + dst := &retval.Subject + src := v.AllPointOfContact.Subject + var err error + *dst, err = __marshalAllPointOfContactSubjectPackageSourceOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NodeNodePointOfContact.AllPointOfContact.Subject: %w", err) + } + } + retval.Email = v.AllPointOfContact.Email + retval.Info = v.AllPointOfContact.Info + retval.Since = v.AllPointOfContact.Since + retval.Justification = v.AllPointOfContact.Justification + retval.Origin = v.AllPointOfContact.Origin + retval.Collector = v.AllPointOfContact.Collector + return &retval, nil +} + // NodeNodeSource includes the requested fields of the GraphQL type Source. // The GraphQL type's documentation follows. // @@ -18390,10 +18614,14 @@ func __marshalNodesNodesNode(v *NodesNodesNode) ([]byte, error) { case *NodesNodesPointOfContact: typename = "PointOfContact" + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } result := struct { TypeName string `json:"__typename"` - *NodesNodesPointOfContact - }{typename, v} + *__premarshalNodesNodesPointOfContact + }{typename, premarshaled} return json.Marshal(result) case *NodesNodesSource: typename = "Source" @@ -18692,12 +18920,120 @@ func (v *NodesNodesPkgEqual) __premarshalJSON() (*__premarshalNodesNodesPkgEqual // hierarchy. However, until the use case arises, PointOfContact will be a flat // reference to the contact details. type NodesNodesPointOfContact struct { - Typename *string `json:"__typename"` + Typename *string `json:"__typename"` + AllPointOfContact `json:"-"` } // GetTypename returns NodesNodesPointOfContact.Typename, and is useful for accessing the field via an interface. func (v *NodesNodesPointOfContact) GetTypename() *string { return v.Typename } +// GetId returns NodesNodesPointOfContact.Id, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetId() string { return v.AllPointOfContact.Id } + +// GetSubject returns NodesNodesPointOfContact.Subject, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetSubject() AllPointOfContactSubjectPackageSourceOrArtifact { + return v.AllPointOfContact.Subject +} + +// GetEmail returns NodesNodesPointOfContact.Email, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetEmail() string { return v.AllPointOfContact.Email } + +// GetInfo returns NodesNodesPointOfContact.Info, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetInfo() string { return v.AllPointOfContact.Info } + +// GetSince returns NodesNodesPointOfContact.Since, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetSince() time.Time { return v.AllPointOfContact.Since } + +// GetJustification returns NodesNodesPointOfContact.Justification, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetJustification() string { + return v.AllPointOfContact.Justification +} + +// GetOrigin returns NodesNodesPointOfContact.Origin, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetOrigin() string { return v.AllPointOfContact.Origin } + +// GetCollector returns NodesNodesPointOfContact.Collector, and is useful for accessing the field via an interface. +func (v *NodesNodesPointOfContact) GetCollector() string { return v.AllPointOfContact.Collector } + +func (v *NodesNodesPointOfContact) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *NodesNodesPointOfContact + graphql.NoUnmarshalJSON + } + firstPass.NodesNodesPointOfContact = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllPointOfContact) + if err != nil { + return err + } + return nil +} + +type __premarshalNodesNodesPointOfContact struct { + Typename *string `json:"__typename"` + + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Email string `json:"email"` + + Info string `json:"info"` + + Since time.Time `json:"since"` + + Justification string `json:"justification"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *NodesNodesPointOfContact) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *NodesNodesPointOfContact) __premarshalJSON() (*__premarshalNodesNodesPointOfContact, error) { + var retval __premarshalNodesNodesPointOfContact + + retval.Typename = v.Typename + retval.Id = v.AllPointOfContact.Id + { + + dst := &retval.Subject + src := v.AllPointOfContact.Subject + var err error + *dst, err = __marshalAllPointOfContactSubjectPackageSourceOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal NodesNodesPointOfContact.AllPointOfContact.Subject: %w", err) + } + } + retval.Email = v.AllPointOfContact.Email + retval.Info = v.AllPointOfContact.Info + retval.Since = v.AllPointOfContact.Since + retval.Justification = v.AllPointOfContact.Justification + retval.Origin = v.AllPointOfContact.Origin + retval.Collector = v.AllPointOfContact.Collector + return &retval, nil +} + // NodesNodesSource includes the requested fields of the GraphQL type Source. // The GraphQL type's documentation follows. // @@ -21108,10 +21444,14 @@ func __marshalPathPathNode(v *PathPathNode) ([]byte, error) { case *PathPathPointOfContact: typename = "PointOfContact" + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } result := struct { TypeName string `json:"__typename"` - *PathPathPointOfContact - }{typename, v} + *__premarshalPathPathPointOfContact + }{typename, premarshaled} return json.Marshal(result) case *PathPathSource: typename = "Source" @@ -21408,12 +21748,118 @@ func (v *PathPathPkgEqual) __premarshalJSON() (*__premarshalPathPathPkgEqual, er // hierarchy. However, until the use case arises, PointOfContact will be a flat // reference to the contact details. type PathPathPointOfContact struct { - Typename *string `json:"__typename"` + Typename *string `json:"__typename"` + AllPointOfContact `json:"-"` } // GetTypename returns PathPathPointOfContact.Typename, and is useful for accessing the field via an interface. func (v *PathPathPointOfContact) GetTypename() *string { return v.Typename } +// GetId returns PathPathPointOfContact.Id, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetId() string { return v.AllPointOfContact.Id } + +// GetSubject returns PathPathPointOfContact.Subject, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetSubject() AllPointOfContactSubjectPackageSourceOrArtifact { + return v.AllPointOfContact.Subject +} + +// GetEmail returns PathPathPointOfContact.Email, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetEmail() string { return v.AllPointOfContact.Email } + +// GetInfo returns PathPathPointOfContact.Info, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetInfo() string { return v.AllPointOfContact.Info } + +// GetSince returns PathPathPointOfContact.Since, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetSince() time.Time { return v.AllPointOfContact.Since } + +// GetJustification returns PathPathPointOfContact.Justification, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetJustification() string { return v.AllPointOfContact.Justification } + +// GetOrigin returns PathPathPointOfContact.Origin, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetOrigin() string { return v.AllPointOfContact.Origin } + +// GetCollector returns PathPathPointOfContact.Collector, and is useful for accessing the field via an interface. +func (v *PathPathPointOfContact) GetCollector() string { return v.AllPointOfContact.Collector } + +func (v *PathPathPointOfContact) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *PathPathPointOfContact + graphql.NoUnmarshalJSON + } + firstPass.PathPathPointOfContact = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.AllPointOfContact) + if err != nil { + return err + } + return nil +} + +type __premarshalPathPathPointOfContact struct { + Typename *string `json:"__typename"` + + Id string `json:"id"` + + Subject json.RawMessage `json:"subject"` + + Email string `json:"email"` + + Info string `json:"info"` + + Since time.Time `json:"since"` + + Justification string `json:"justification"` + + Origin string `json:"origin"` + + Collector string `json:"collector"` +} + +func (v *PathPathPointOfContact) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *PathPathPointOfContact) __premarshalJSON() (*__premarshalPathPathPointOfContact, error) { + var retval __premarshalPathPathPointOfContact + + retval.Typename = v.Typename + retval.Id = v.AllPointOfContact.Id + { + + dst := &retval.Subject + src := v.AllPointOfContact.Subject + var err error + *dst, err = __marshalAllPointOfContactSubjectPackageSourceOrArtifact( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal PathPathPointOfContact.AllPointOfContact.Subject: %w", err) + } + } + retval.Email = v.AllPointOfContact.Email + retval.Info = v.AllPointOfContact.Info + retval.Since = v.AllPointOfContact.Since + retval.Justification = v.AllPointOfContact.Justification + retval.Origin = v.AllPointOfContact.Origin + retval.Collector = v.AllPointOfContact.Collector + return &retval, nil +} + // PathPathSource includes the requested fields of the GraphQL type Source. // The GraphQL type's documentation follows. // @@ -28066,6 +28512,9 @@ query Neighbors ($node: ID!, $usingOnly: [Edge!]!) { ... on HasSourceAt { ... AllHasSourceAt } + ... on PointOfContact { + ... AllPointOfContact + } ... on CertifyVuln { ... AllCertifyVuln } @@ -28297,6 +28746,27 @@ fragment AllHasSourceAt on HasSourceAt { origin collector } +fragment AllPointOfContact on PointOfContact { + id + subject { + __typename + ... on Package { + ... AllPkgTree + } + ... on Source { + ... AllSourceTree + } + ... on Artifact { + ... AllArtifactTree + } + } + email + info + since + justification + origin + collector +} fragment AllCertifyVuln on CertifyVuln { id package { @@ -28465,6 +28935,9 @@ query Node ($node: ID!) { ... on HasSourceAt { ... AllHasSourceAt } + ... on PointOfContact { + ... AllPointOfContact + } ... on CertifyVuln { ... AllCertifyVuln } @@ -28696,6 +29169,27 @@ fragment AllHasSourceAt on HasSourceAt { origin collector } +fragment AllPointOfContact on PointOfContact { + id + subject { + __typename + ... on Package { + ... AllPkgTree + } + ... on Source { + ... AllSourceTree + } + ... on Artifact { + ... AllArtifactTree + } + } + email + info + since + justification + origin + collector +} fragment AllCertifyVuln on CertifyVuln { id package { @@ -28862,6 +29356,9 @@ query Nodes ($nodes: [ID!]!) { ... on HasSourceAt { ... AllHasSourceAt } + ... on PointOfContact { + ... AllPointOfContact + } ... on CertifyVuln { ... AllCertifyVuln } @@ -29093,6 +29590,27 @@ fragment AllHasSourceAt on HasSourceAt { origin collector } +fragment AllPointOfContact on PointOfContact { + id + subject { + __typename + ... on Package { + ... AllPkgTree + } + ... on Source { + ... AllSourceTree + } + ... on Artifact { + ... AllArtifactTree + } + } + email + info + since + justification + origin + collector +} fragment AllCertifyVuln on CertifyVuln { id package { @@ -29354,6 +29872,9 @@ query Path ($subject: ID!, $target: ID!, $maxPathLength: Int!, $usingOnly: [Edge ... on HasSourceAt { ... AllHasSourceAt } + ... on PointOfContact { + ... AllPointOfContact + } ... on CertifyVuln { ... AllCertifyVuln } @@ -29585,6 +30106,27 @@ fragment AllHasSourceAt on HasSourceAt { origin collector } +fragment AllPointOfContact on PointOfContact { + id + subject { + __typename + ... on Package { + ... AllPkgTree + } + ... on Source { + ... AllSourceTree + } + ... on Artifact { + ... AllArtifactTree + } + } + email + info + since + justification + origin + collector +} fragment AllCertifyVuln on CertifyVuln { id package { diff --git a/pkg/assembler/clients/operations/path.graphql b/pkg/assembler/clients/operations/path.graphql index 03562802ca..bc9eedf64e 100644 --- a/pkg/assembler/clients/operations/path.graphql +++ b/pkg/assembler/clients/operations/path.graphql @@ -74,6 +74,9 @@ query Path($subject: ID!, $target: ID!, $maxPathLength: Int!, $usingOnly: [Edge! ... on HasSourceAt { ...AllHasSourceAt } + ... on PointOfContact{ + ...AllPointOfContact + } ... on CertifyVuln { ...AllCertifyVuln } @@ -146,6 +149,9 @@ query Neighbors($node: ID!, $usingOnly: [Edge!]!) { ... on HasSourceAt { ...AllHasSourceAt } + ... on PointOfContact { + ...AllPointOfContact + } ... on CertifyVuln { ...AllCertifyVuln } @@ -218,6 +224,9 @@ query Node($node: ID!) { ... on HasSourceAt { ...AllHasSourceAt } + ... on PointOfContact { + ...AllPointOfContact + } ... on CertifyVuln { ...AllCertifyVuln } @@ -290,6 +299,9 @@ query Nodes($nodes: [ID!]!) { ... on HasSourceAt { ...AllHasSourceAt } + ... on PointOfContact{ + ...AllPointOfContact + } ... on CertifyVuln { ...AllCertifyVuln } diff --git a/pkg/guacanalytics/patchPlanning.go b/pkg/guacanalytics/patchPlanning.go index 60a42a4c89..78e9c4ff32 100644 --- a/pkg/guacanalytics/patchPlanning.go +++ b/pkg/guacanalytics/patchPlanning.go @@ -33,23 +33,24 @@ const ( Artifact ) -type DfsNode struct { - Expanded bool // true once all node neighbors are added to queue - Parent string // TODO: turn parent into a list in cause discovered twice from two different nodes - Depth int - Type NodeType - nodeVersions []string // for a packageName, what was the packageVersion associated with this version. For a packageVersion, what is the version. +type BfsNode struct { + Expanded bool // true once all node neighbors are added to queue + Parent string // TODO: turn parent into a list in cause discovered twice from two different nodes + Depth int + Type NodeType + nodeVersions []string // for a packageName, what was the packageVersion associated with this version. For a packageVersion, what is the version. + PointOfContact model.AllPointOfContact + NotInBlastRadius bool // true if it is solely an informational node, not to be included in the blast radius subgraph } type queueValues struct { - nodeMap map[string]DfsNode + nodeMap map[string]BfsNode now string - nowNode DfsNode + nowNode BfsNode queue []string } -// TODO: make more robust using predicates -func SearchDependenciesFromStartNode(ctx context.Context, gqlClient graphql.Client, startID string, stopID *string, maxDepth int) (map[string]DfsNode, error) { +func SearchDependenciesFromStartNode(ctx context.Context, gqlClient graphql.Client, startID string, stopID *string, maxDepth int) (map[string]BfsNode, error) { startNode, err := model.Node(ctx, gqlClient, startID) if err != nil { @@ -64,7 +65,7 @@ func SearchDependenciesFromStartNode(ctx context.Context, gqlClient graphql.Clie q := queueValues{ queue: make([]string, 0), // the queue of nodes in bfs - nodeMap: map[string]DfsNode{}, + nodeMap: map[string]BfsNode{}, } // TODO: add functionality to start with other nodes? @@ -88,11 +89,11 @@ func SearchDependenciesFromStartNode(ctx context.Context, gqlClient graphql.Clie var versionsList []string versionsList = append(versionsList, nodePkg.AllPkgTree.Namespaces[0].Names[0].Versions[0].Version) - q.nodeMap[startID] = DfsNode{ + q.nodeMap[startID] = BfsNode{ Type: PackageVersion, } - q.nodeMap[nodePkg.AllPkgTree.Namespaces[0].Names[0].Id] = DfsNode{ + q.nodeMap[nodePkg.AllPkgTree.Namespaces[0].Names[0].Id] = BfsNode{ Type: PackageName, nodeVersions: versionsList, } @@ -146,16 +147,36 @@ func caseOnPredicates(ctx context.Context, gqlClient graphql.Client, q *queueVal return err } case *model.NeighborsNeighborsHasSourceAt: - exploreHasSourceAtFromPackage(ctx, gqlClient, q, *neighbor) + err := exploreHasSourceAtFromPackage(ctx, gqlClient, q, *neighbor) + + if err != nil { + return err + } + case *model.NeighborsNeighborsPointOfContact: + err := explorePointOfContact(ctx, gqlClient, q, *neighbor) + + if err != nil { + return err + } } case PackageVersion: switch neighbor := neighbor.(type) { case *model.NeighborsNeighborsIsOccurrence: exploreIsOccurrenceFromSubject(ctx, gqlClient, q, *neighbor) case *model.NeighborsNeighborsHasSourceAt: - exploreHasSourceAtFromPackage(ctx, gqlClient, q, *neighbor) + err := exploreHasSourceAtFromPackage(ctx, gqlClient, q, *neighbor) + + if err != nil { + return err + } case *model.NeighborsNeighborsPkgEqual: explorePkgEqual(ctx, gqlClient, q, *neighbor) + case *model.NeighborsNeighborsPointOfContact: + err := explorePointOfContact(ctx, gqlClient, q, *neighbor) + + if err != nil { + return err + } } case SourceName: switch neighbor := neighbor.(type) { @@ -164,6 +185,12 @@ func caseOnPredicates(ctx context.Context, gqlClient graphql.Client, q *queueVal case *model.NeighborsNeighborsHasSourceAt: err := exploreHasSourceAtFromSource(ctx, gqlClient, q, *neighbor) + if err != nil { + return err + } + case *model.NeighborsNeighborsPointOfContact: + err := explorePointOfContact(ctx, gqlClient, q, *neighbor) + if err != nil { return err } @@ -176,8 +203,13 @@ func caseOnPredicates(ctx context.Context, gqlClient graphql.Client, q *queueVal exploreIsOccurrenceFromArtifact(ctx, gqlClient, q, *neighbor) case *model.NeighborsNeighborsHashEqual: exploreHashEqual(ctx, gqlClient, q, *neighbor) - } + case *model.NeighborsNeighborsPointOfContact: + err := explorePointOfContact(ctx, gqlClient, q, *neighbor) + if err != nil { + return err + } + } } return nil } @@ -213,7 +245,7 @@ func exploreHasSLSAFromArtifact(ctx context.Context, gqlClient graphql.Client, q func exploreIsOccurrenceFromArtifact(ctx context.Context, gqlClient graphql.Client, q *queueValues, isOccurrence model.NeighborsNeighborsIsOccurrence) { switch subject := isOccurrence.Subject.(type) { case *model.AllIsOccurrencesTreeSubjectPackage: - q.addNodeToQueue(PackageVersion, []string{subject.Namespaces[0].Names[0].Versions[0].Version}, subject.Namespaces[0].Names[0].Versions[0].Id) + q.addNodeToQueue(PackageVersion, nil, subject.Namespaces[0].Names[0].Versions[0].Id) q.addNodeToQueue(PackageName, []string{subject.Namespaces[0].Names[0].Versions[0].Version}, subject.Namespaces[0].Names[0].Id) case *model.AllIsOccurrencesTreeSubjectSource: q.addNodeToQueue(SourceName, nil, subject.Namespaces[0].Names[0].Id) @@ -228,7 +260,7 @@ func exploreHasSourceAtFromSource(ctx context.Context, gqlClient graphql.Client, return err } } else { - q.addNodeToQueue(PackageVersion, []string{hasSourceAt.Package.Namespaces[0].Names[0].Versions[0].Version}, hasSourceAt.Package.Namespaces[0].Names[0].Versions[0].Id) + q.addNodeToQueue(PackageVersion, nil, hasSourceAt.Package.Namespaces[0].Names[0].Versions[0].Id) q.addNodeToQueue(PackageName, []string{hasSourceAt.Package.Namespaces[0].Names[0].Versions[0].Version}, hasSourceAt.Package.Namespaces[0].Names[0].Id) } return nil @@ -239,7 +271,7 @@ func explorePkgEqual(ctx context.Context, gqlClient graphql.Client, q *queueValu for _, pkg := range pkgEqual.Packages { if pkg.Namespaces[0].Names[0].Versions[0].Id != q.now { q.addNodeToQueue(PackageVersion, nil, pkg.Namespaces[0].Names[0].Versions[0].Id) - q.addNodeToQueue(PackageName, []string{pkg.Namespaces[0].Names[0].Versions[0].Id}, pkg.Namespaces[0].Names[0].Id) + q.addNodeToQueue(PackageName, []string{pkg.Namespaces[0].Names[0].Versions[0].Version}, pkg.Namespaces[0].Names[0].Id) } } } @@ -252,21 +284,108 @@ func exploreHashEqual(ctx context.Context, gqlClient graphql.Client, q *queueVal } } -func exploreHasSourceAtFromPackage(ctx context.Context, gqlClient graphql.Client, q *queueValues, hasSourceAt model.NeighborsNeighborsHasSourceAt) { +func exploreHasSourceAtFromPackage(ctx context.Context, gqlClient graphql.Client, q *queueValues, hasSourceAt model.NeighborsNeighborsHasSourceAt) error { node, seen := q.nodeMap[hasSourceAt.Source.Namespaces[0].Names[0].Id] - if !seen { - node = DfsNode{ + node = BfsNode{ Parent: q.now, Depth: q.nowNode.Depth + 1, Type: SourceName, } + + // check if the Src has any POCs + neighborsResponse, err := model.Neighbors(ctx, gqlClient, hasSourceAt.Source.Namespaces[0].Names[0].Id, []model.Edge{}) + + if err != nil { + return err + } + + for _, neighbor := range neighborsResponse.Neighbors { + switch neighbor := neighbor.(type) { + case *model.NeighborsNeighborsPointOfContact: + node = BfsNode{ + Parent: q.now, + Depth: q.nowNode.Depth + 1, + Type: SourceName, + PointOfContact: neighbor.AllPointOfContact, + } + } + } } q.nodeMap[hasSourceAt.Source.Namespaces[0].Names[0].Id] = node + return nil +} + +func explorePointOfContact(ctx context.Context, gqlClient graphql.Client, q *queueValues, pointOfContact model.NeighborsNeighborsPointOfContact) error { + node := BfsNode{ + Parent: q.nowNode.Parent, + Depth: q.nowNode.Depth, + Type: q.nowNode.Type, + nodeVersions: q.nowNode.nodeVersions, + PointOfContact: pointOfContact.AllPointOfContact, + NotInBlastRadius: q.nowNode.NotInBlastRadius, + Expanded: q.nowNode.Expanded, + } + q.nodeMap[q.now] = node + q.nowNode = node + + // If it is a packageName, add the POC to applicable versions (versions in the nodeVersions) but not the reverse + if q.nowNode.Type != PackageName { + return nil + } + + switch poc := pointOfContact.Subject.(type) { + case *model.AllPointOfContactSubjectPackage: + pkgFilter := model.PkgSpec{ + Type: &poc.Type, + Namespace: &poc.Namespaces[0].Namespace, + Name: &poc.Namespaces[0].Names[0].Name, + } + + pkgResponse, err := model.Packages(ctx, gqlClient, &pkgFilter) + + if err != nil { + return fmt.Errorf("error finding inputted node %s", err) + } + + for _, versionEntry := range pkgResponse.Packages[0].Namespaces[0].Names[0].Versions { + if versionEntry.Version == "" { + break // this version entry is unpopulated, do not add to map + } + if node, seen := q.nodeMap[versionEntry.Id]; seen { + node = BfsNode{ + Parent: node.Parent, + Depth: node.Depth, + Type: node.Type, + PointOfContact: pointOfContact.AllPointOfContact, + NotInBlastRadius: node.NotInBlastRadius, + Expanded: node.Expanded, + } + } else { + node = BfsNode{ + Parent: q.now, + Depth: q.nowNode.Depth + 1, + Type: PackageVersion, + PointOfContact: pointOfContact.AllPointOfContact, + NotInBlastRadius: true, + } + } + q.nodeMap[versionEntry.Id] = node + } + } + + return nil } func (q *queueValues) addNodesToQueueFromPackageName(ctx context.Context, gqlClient graphql.Client, pkgType string, pkgNamespace string, pkgName string, id string) error { + if _, seen := q.nodeMap[id]; seen { + if !q.nodeMap[id].Expanded { + q.queue = append(q.queue, id) + } + return nil + } + pkgFilter := model.PkgSpec{ Type: &pkgType, Namespace: &pkgNamespace, @@ -282,21 +401,30 @@ func (q *queueValues) addNodesToQueueFromPackageName(ctx context.Context, gqlCli var versionsList []string for _, versionEntry := range pkgResponse.Packages[0].Namespaces[0].Names[0].Versions { versionsList = append(versionsList, versionEntry.Version) - q.nodeMap[versionEntry.Id] = DfsNode{ - Parent: q.now, - Depth: q.nowNode.Depth + 1, - Type: PackageVersion, + if _, seen := q.nodeMap[versionEntry.Id]; seen { + if !q.nodeMap[versionEntry.Id].Expanded { + q.queue = append(q.queue, versionEntry.Id) + } + break + } + q.nodeMap[versionEntry.Id] = BfsNode{ + Parent: q.now, + Depth: q.nowNode.Depth + 1, + Type: PackageVersion, + PointOfContact: q.nowNode.PointOfContact, + NotInBlastRadius: false, } - q.queue = append(q.queue, versionEntry.Id) } - q.nodeMap[id] = DfsNode{ - Parent: q.now, - Depth: q.nowNode.Depth + 1, - Type: PackageName, - nodeVersions: versionsList, + q.nodeMap[id] = BfsNode{ + Parent: q.now, + Depth: q.nowNode.Depth + 1, + Type: PackageName, + nodeVersions: versionsList, + PointOfContact: q.nowNode.PointOfContact, } + q.queue = append(q.queue, id) return nil @@ -306,16 +434,26 @@ func (q *queueValues) addNodeToQueue(nodeType NodeType, versions []string, id st node, seen := q.nodeMap[id] if !seen { - node = DfsNode{ + node = BfsNode{ Parent: q.now, Depth: q.nowNode.Depth + 1, Type: nodeType, nodeVersions: versions, } - q.nodeMap[id] = node + } else if nodeType == PackageVersion { + node = BfsNode{ + Parent: q.now, + Depth: q.nowNode.Depth + 1, + Type: nodeType, + Expanded: node.Expanded, + PointOfContact: node.PointOfContact, + NotInBlastRadius: false, + } } - if !node.Expanded { + q.nodeMap[id] = node + + if !node.Expanded && !node.NotInBlastRadius { q.queue = append(q.queue, id) } } diff --git a/pkg/guacanalytics/patchPlanning_test.go b/pkg/guacanalytics/patchPlanning_test.go index b24cead1b4..58adb95a0d 100644 --- a/pkg/guacanalytics/patchPlanning_test.go +++ b/pkg/guacanalytics/patchPlanning_test.go @@ -796,6 +796,138 @@ var ( }, }, } + pointOfContactGraph = assembler.IngestPredicates{ + IsOccurrence: []assembler.IsOccurrenceIngest{ + { + Pkg: &model.PkgInputSpec{ + Type: "pkgTypeL", + Namespace: ptrfrom.String("pkgNamespaceL"), + Name: "pkgNameL", + Version: ptrfrom.String("1.19.0"), + }, + Artifact: &model.ArtifactInputSpec{ + Algorithm: "testArtifactAlgorithmK", + Digest: "testArtifactDigestK", + }, + IsOccurrence: &model.IsOccurrenceInputSpec{ + Justification: "connect pkgL and artifactK", + }, + }, + }, + HasSourceAt: []assembler.HasSourceAtIngest{ + { + Src: &model.SourceInputSpec{ + Type: "srcTypeK", + Namespace: "srcNamespaceK", + Name: "srcNameK", + }, + Pkg: &model.PkgInputSpec{ + Type: "pkgTypeL", + Namespace: ptrfrom.String("pkgNamespaceL"), + Name: "pkgNameL", + Version: ptrfrom.String("1.19.0"), + }, + HasSourceAt: &model.HasSourceAtInputSpec{ + Justification: "test justification", + KnownSince: tm, + }, + PkgMatchFlag: model.MatchFlags{ + Pkg: model.PkgMatchTypeSpecificVersion, + }, + }, + }, + IsDependency: []assembler.IsDependencyIngest{ + { + Pkg: &model.PkgInputSpec{ + Type: "pkgTypeL", + Namespace: ptrfrom.String("pkgNamespaceL"), + Name: "pkgNameL", + Version: ptrfrom.String("1.19.0"), + }, + DepPkg: &model.PkgInputSpec{ + Type: "pkgTypeM", + Namespace: ptrfrom.String("pkgNamespaceM"), + Name: "pkgNameM", + Version: ptrfrom.String("3.0.3"), + }, + IsDependency: &model.IsDependencyInputSpec{ + VersionRange: "=>1.0.0", + DependencyType: model.DependencyTypeDirect, + Justification: "test justification one", + Origin: "Demo ingestion", + Collector: "Demo ingestion", + }, + }, + }, + PointOfContact: []assembler.PointOfContactIngest{ + { + Pkg: &model.PkgInputSpec{ + Type: "pkgTypeM", + Namespace: ptrfrom.String("pkgNamespaceM"), + Name: "pkgNameM", + Version: ptrfrom.String("3.0.3"), + }, + PkgMatchFlag: model.MatchFlags{ + Pkg: model.PkgMatchTypeSpecificVersion, + }, + PointOfContact: &model.PointOfContactInputSpec{ + Email: "testEmailM", + Info: "testInfo", + Since: tm, + Justification: "testJustification", + Origin: "testOrigin", + Collector: "testCollector", + }, + }, + { + Pkg: &model.PkgInputSpec{ + Type: "pkgTypeL", + Namespace: ptrfrom.String("pkgNamespaceL"), + Name: "pkgNameL", + }, + PkgMatchFlag: model.MatchFlags{ + Pkg: model.PkgMatchTypeAllVersions, + }, + PointOfContact: &model.PointOfContactInputSpec{ + Email: "testEmailL", + Info: "testInfo", + Since: tm, + Justification: "testJustification", + Origin: "testOrigin", + Collector: "testCollector", + }, + }, + { + Artifact: &model.ArtifactInputSpec{ + Algorithm: "testArtifactAlgorithmK", + Digest: "testArtifactDigestK", + }, + PointOfContact: &model.PointOfContactInputSpec{ + Email: "testEmailK", + Info: "testInfo", + Since: tm, + Justification: "testJustification", + Origin: "testOrigin", + Collector: "testCollector", + }, + }, + { + Src: &model.SourceInputSpec{ + Type: "srcTypeK", + Namespace: "srcNamespaceK", + Name: "srcNameK", + }, + PointOfContact: &model.PointOfContactInputSpec{ + Email: "testEmailK", + Info: "testInfo", + Since: tm, + Justification: "testJustification", + Origin: "testOrigin", + Collector: "testCollector", + }, + }, + }, + } ) func ingestIsDependency(ctx context.Context, client graphql.Client, graph assembler.IngestPredicates) error { @@ -827,13 +959,6 @@ func ingestHasSLSA(ctx context.Context, client graphql.Client, graph assembler.I if err != nil { return fmt.Errorf("error in ingesting Builder for HasSlsa: %v\n", err) } - - _, err = model.IngestArtifacts(ctx, client, ingest.Materials) - - if err != nil { - return fmt.Errorf("error in ingesting Material for HasSlsa: %v\n", err) - } - _, err = model.SLSAForArtifact(ctx, client, *ingest.Artifact, ingest.Materials, *ingest.Builder, *ingest.HasSlsa) if err != nil { @@ -963,6 +1088,38 @@ func ingestHashEqual(ctx context.Context, client graphql.Client, graph assembler return nil } +func ingestPointOfContact(ctx context.Context, client graphql.Client, graph assembler.IngestPredicates) error { + for _, ingest := range graph.PointOfContact { + var err error + + if ingest.Src != nil { + _, err = model.IngestSource(ctx, client, *ingest.Src) + } else if ingest.Pkg != nil { + _, err = model.IngestPackage(ctx, client, *ingest.Pkg) + + } else { + _, err = model.IngestArtifact(ctx, client, *ingest.Artifact) + } + + if err != nil { + return fmt.Errorf("error in ingesting pkg/src/artifact PointOfContact: %v\n", err) + } + + if ingest.Src != nil { + _, err = model.PointOfContactSrc(ctx, client, *ingest.Src, *ingest.PointOfContact) + } else if ingest.Pkg != nil { + _, err = model.PointOfContactPkg(ctx, client, *ingest.Pkg, &ingest.PkgMatchFlag, *ingest.PointOfContact) + } else { + _, err = model.PointOfContactArtifact(ctx, client, *ingest.Artifact, *ingest.PointOfContact) + } + + if err != nil { + return fmt.Errorf("error in ingesting PointOfContact: %v\n", err) + } + } + return nil +} + func ingestTestData(ctx context.Context, client graphql.Client, graph assembler.IngestPredicates) error { if len(graph.IsDependency) > 0 { err := ingestIsDependency(ctx, client, graph) @@ -1013,6 +1170,13 @@ func ingestTestData(ctx context.Context, client graphql.Client, graph assembler. } } + if len(graph.PointOfContact) > 0 { + err := ingestPointOfContact(ctx, client, graph) + if err != nil { + return err + } + } + return nil } @@ -1044,6 +1208,8 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { expectedPkgs []string expectedArtifacts []string expectedSrcs []string + expectedPOCLen int + expectedPOCs []string graphInputs []assembler.IngestPredicates }{ { @@ -1255,8 +1421,8 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { startName: "aName", startVersion: ptrfrom.String("1.19.0"), maxDepth: 10, - expectedLen: 8, // change to 8 once implemented - expectedPkgs: []string{"aType", "bType", "cType", "dType"}, // add dType once implemented + expectedLen: 8, + expectedPkgs: []string{"aType", "bType", "cType", "dType"}, graphInputs: []assembler.IngestPredicates{pkgEqualGraph}, }, { @@ -1266,11 +1432,26 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { startName: "abcPkgName1", startVersion: ptrfrom.String("3.0.3"), maxDepth: 10, - expectedLen: 7, // change to 8 once implemented - expectedPkgs: []string{"abcPkg1", "abcPkg2"}, // add abcPkg2 once implemented and expectedArtifacts abcTestArtifactAlgorithm + expectedLen: 7, + expectedPkgs: []string{"abcPkg1", "abcPkg2"}, expectedArtifacts: []string{"abcTestArtifactAlgorithm"}, graphInputs: []assembler.IngestPredicates{hashEqualGraph}, }, + { + name: "20: test pointOfContact", + startType: "pkgTypeM", + startNamespace: "pkgNamespaceM", + startName: "pkgNameM", + startVersion: ptrfrom.String("3.0.3"), + maxDepth: 9, + expectedLen: 6, + expectedPkgs: []string{"pkgTypeL", "pkgTypeM"}, + expectedSrcs: []string{"srcTypeK"}, + expectedArtifacts: []string{"testArtifactAlgorithmK"}, + expectedPOCs: []string{"testEmailK", "testEmailL", "testEmailM"}, + expectedPOCLen: 5, + graphInputs: []assembler.IngestPredicates{pointOfContactGraph}, + }, } for _, tt := range testCases { @@ -1367,6 +1548,24 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { expectedSrcIDs = append(expectedSrcIDs, srcID) } + returnedPOCs := getReturnedPOCs(ctx, gqlClient, gotMap) + if len(returnedPOCs) != tt.expectedPOCLen { + t.Errorf("Found %d POCs recorded but expected %d \n", len(getReturnedPOCs(ctx, gqlClient, gotMap)), tt.expectedPOCLen) + } + + inExpectedPOCs := false + for _, gotPOC := range returnedPOCs { + for _, expectedPOC := range tt.expectedPOCs { + if gotPOC == expectedPOC { + inExpectedPOCs = true + break + } + } + if !inExpectedPOCs { + t.Errorf("this ID appears in the returned POCs but is not expected: %s \n", gotPOC) + } + } + for gotID, node := range gotMap { if stopID == nil && tt.maxDepth == 10 { if !node.Expanded { @@ -1390,7 +1589,6 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { break } } - inExpectedSrcs := false for _, expectedID := range expectedSrcIDs { if expectedID == gotID { @@ -1404,6 +1602,8 @@ func Test_SearchSubgraphFromVuln(t *testing.T) { t.Errorf("this ID appears in the returned map but is not expected: %s \n", gotID) return } + + // TODO: add check for POC } }) @@ -1542,3 +1742,14 @@ func getSrcID(ctx context.Context, gqlClient graphql.Client, srcType string) (st return srcResponse.Sources[0].Namespaces[0].Names[0].Id, nil } + +func getReturnedPOCs(ctx context.Context, gqlClient graphql.Client, nodeMap map[string]BfsNode) []string { + var found []string + for _, node := range nodeMap { + if node.PointOfContact.Email != "" { + found = append(found, node.PointOfContact.Email) + } + } + + return found +}