Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39150,6 +39150,13 @@ const docTemplate = `{
"controlId": {
"type": "string"
},
"driftRiskId": {
"description": "DriftRiskID is the open drift risk for this link (BCH-1341's applyDriftToLink /\ncomputeDedupeKeyForLeverageDrift convention), set only when Status is Drifted and a\nmatching risk is still open — nil otherwise (including for Revoked, which has no\nre-attest path and thus no risk to link).",
"type": "string"
},
"id": {
"type": "string"
},
"inheritedFrom": {
"$ref": "#/definitions/oscal.leveragedControlInheritedFrom"
},
Expand All @@ -39171,6 +39178,9 @@ const docTemplate = `{
},
"statementId": {
"type": "string"
},
"status": {
"$ref": "#/definitions/relational.SSPLeverageStatus"
}
}
},
Expand Down
10 changes: 10 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -39144,6 +39144,13 @@
"controlId": {
"type": "string"
},
"driftRiskId": {
"description": "DriftRiskID is the open drift risk for this link (BCH-1341's applyDriftToLink /\ncomputeDedupeKeyForLeverageDrift convention), set only when Status is Drifted and a\nmatching risk is still open — nil otherwise (including for Revoked, which has no\nre-attest path and thus no risk to link).",
"type": "string"
},
"id": {
"type": "string"
},
"inheritedFrom": {
"$ref": "#/definitions/oscal.leveragedControlInheritedFrom"
},
Expand All @@ -39165,6 +39172,9 @@
},
"statementId": {
"type": "string"
},
"status": {
"$ref": "#/definitions/relational.SSPLeverageStatus"
}
}
},
Expand Down
11 changes: 11 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4395,6 +4395,15 @@ definitions:
properties:
controlId:
type: string
driftRiskId:
description: |-
DriftRiskID is the open drift risk for this link (BCH-1341's applyDriftToLink /
computeDedupeKeyForLeverageDrift convention), set only when Status is Drifted and a
matching risk is still open — nil otherwise (including for Revoked, which has no
re-attest path and thus no risk to link).
type: string
id:
type: string
inheritedFrom:
$ref: '#/definitions/oscal.leveragedControlInheritedFrom'
outstandingResponsibilities:
Expand All @@ -4415,6 +4424,8 @@ definitions:
$ref: '#/definitions/relational.SSPLeverageSatisfaction'
statementId:
type: string
status:
$ref: '#/definitions/relational.SSPLeverageStatus'
type: object
oscal.profileSummary:
properties:
Expand Down
44 changes: 44 additions & 0 deletions internal/api/handler/oscal/ssp_leverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,17 +677,24 @@ type leveragedControlInheritedFrom struct {
}

type leveragedControlResponse struct {
ID uuid.UUID `json:"id"`
ControlID string `json:"controlId"`
StatementID *string `json:"statementId,omitempty"`
InheritedFrom leveragedControlInheritedFrom `json:"inheritedFrom"`
Satisfaction relational.SSPLeverageSatisfaction `json:"satisfaction"`
Status relational.SSPLeverageStatus `json:"status"`
OutstandingResponsibilities []upstreamResponsibility `json:"outstandingResponsibilities"`
// ResponsibilityPosture is the live, evidence-backed posture (satisfied /
// not-satisfied / unknown) per upstream responsibility uuid under this link's
// provided-uuid — computed via filter_responsibilities (BCH-1339), independent of
// Satisfaction/OutstandingResponsibilities above (which reflect what was attested at
// subscribe time, not current evidence).
ResponsibilityPosture map[uuid.UUID]string `json:"responsibilityPosture"`
// DriftRiskID is the open drift risk for this link (BCH-1341's applyDriftToLink /
// computeDedupeKeyForLeverageDrift convention), set only when Status is Drifted and a
// matching risk is still open — nil otherwise (including for Revoked, which has no
// re-attest path and thus no risk to link).
DriftRiskID *uuid.UUID `json:"driftRiskId,omitempty"`
}

// LeveragedControls godoc
Expand Down Expand Up @@ -797,6 +804,35 @@ func (h *SSPLeverageHandler) LeveragedControls(ctx echo.Context) error {
return ctx.JSON(http.StatusInternalServerError, api.NewError(err))
}

// Batch-resolve every drifted link's open drift risk in one query, keyed by the
// dedupe_key convention computeDedupeKeyForLeverageDrift/applyDriftToLink already use —
// rather than a lookup per drifted link.
dedupeKeyToLinkID := make(map[string]uuid.UUID)
dedupeKeys := make([]string, 0, len(links))
for _, link := range links {
if link.Status != relational.SSPLeverageStatusDrifted {
continue
}
key := computeDedupeKeyForLeverageDrift(*link.ID)
dedupeKeyToLinkID[key] = *link.ID
dedupeKeys = append(dedupeKeys, key)
}
driftRiskIDByLinkID := make(map[uuid.UUID]uuid.UUID, len(dedupeKeys))
if len(dedupeKeys) > 0 {
var driftRisks []risks.Risk
if err := h.db.Select("id, dedupe_key").
Where("ssp_id = ? AND dedupe_key IN ? AND status != ?", sspID, dedupeKeys, risks.RiskStatusClosed).
Find(&driftRisks).Error; err != nil {
h.sugar.Errorf("Failed to load drift risks for leverage links: %v", err)
return ctx.JSON(http.StatusInternalServerError, api.NewError(err))
}
for _, r := range driftRisks {
if linkID, ok := dedupeKeyToLinkID[r.DedupeKey]; ok {
driftRiskIDByLinkID[linkID] = *r.ID
}
}
}

result := make([]leveragedControlResponse, 0, len(links))
for _, link := range links {
byComponentID := byComponentIDByInherited[link.InheritedUUID]
Expand All @@ -808,7 +844,13 @@ func (h *SSPLeverageHandler) LeveragedControls(ctx echo.Context) error {
linkPosture[r.ResponsibilityUUID] = posture[r.ResponsibilityUUID]
}

var driftRiskID *uuid.UUID
if id, ok := driftRiskIDByLinkID[*link.ID]; ok {
driftRiskID = &id
}

result = append(result, leveragedControlResponse{
ID: *link.ID,
ControlID: link.ControlID,
StatementID: link.StatementID,
InheritedFrom: leveragedControlInheritedFrom{
Expand All @@ -818,8 +860,10 @@ func (h *SSPLeverageHandler) LeveragedControls(ctx echo.Context) error {
OfferingVersion: link.OfferingVersion,
},
Satisfaction: satisfaction,
Status: link.Status,
OutstandingResponsibilities: outstanding,
ResponsibilityPosture: linkPosture,
DriftRiskID: driftRiskID,
})
}

Expand Down
113 changes: 113 additions & 0 deletions internal/api/handler/oscal/ssp_leverage_reattest_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package oscal

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -75,6 +76,118 @@ func TestReAttestClearsDriftAndRemediatesRisk(t *testing.T) {
require.Equal(t, string(risks.RiskStatusRemediated), risk.Status)
}

// TestLeveragedControlsIncludesIdStatusAndDriftRiskId: BCH-1346 needs the link's own id
// (to call ReAttest) and status (to know a link is drifted at all) on the projection
// response — neither existed before. An active link has both but no driftRiskId; a
// drifted link's driftRiskId points at its still-open drift risk; a revoked link (no
// current code path sets this status, but the type allows it) has status but no
// driftRiskId, matching drift risk's own reattest-only-from-drifted invariant.
func TestLeveragedControlsIncludesIdStatusAndDriftRiskId(t *testing.T) {
db := newSSPLeverageTestDB(t)
fx := newLeverageFixture(t, db)
link, riskID := subscribeAndDrift(t, db, fx)
require.Equal(t, relational.SSPLeverageStatusDrifted, link.Status)

h := NewSSPLeverageHandler(zap.NewNop().Sugar(), db, &stubPDP{allow: true}, authz.FailClosed)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.SetParamNames("id")
ctx.SetParamValues(fx.downstreamSSPID.String())

require.NoError(t, h.LeveragedControls(ctx))
require.Equal(t, http.StatusOK, rec.Code)

var parsed struct {
Data []leveragedControlResponse `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &parsed))
require.Len(t, parsed.Data, 1)
require.Equal(t, *link.ID, parsed.Data[0].ID)
require.Equal(t, relational.SSPLeverageStatusDrifted, parsed.Data[0].Status)
require.NotNil(t, parsed.Data[0].DriftRiskID)
require.Equal(t, riskID, *parsed.Data[0].DriftRiskID)
}

// TestLeveragedControlsActiveLinkHasNoDriftRiskId: an active (non-drifted) link's
// projection entry carries its own id/status but no driftRiskId — there's no drift risk
// to link because nothing drifted.
func TestLeveragedControlsActiveLinkHasNoDriftRiskId(t *testing.T) {
db := newSSPLeverageTestDB(t)
fx := newLeverageFixture(t, db)

pdp := &stubPDP{allow: true}
subscribeHandler := NewSSPLeverageHandler(zap.NewNop().Sugar(), db, pdp, authz.FailClosed)
body := subscribeBody(fx.downstreamSSPID, fx.itemID, fx.respAID)
subCtx, _, subRec := newSubscribeRequestContext(fx.offeringID, body)
require.NoError(t, subscribeHandler.Subscribe(subCtx))
require.Equal(t, http.StatusCreated, subRec.Code)

var link relational.SSPLeverageLink
require.NoError(t, db.Where("downstream_ssp_id = ?", fx.downstreamSSPID).First(&link).Error)

h := NewSSPLeverageHandler(zap.NewNop().Sugar(), db, &stubPDP{allow: true}, authz.FailClosed)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.SetParamNames("id")
ctx.SetParamValues(fx.downstreamSSPID.String())

require.NoError(t, h.LeveragedControls(ctx))
require.Equal(t, http.StatusOK, rec.Code)

var parsed struct {
Data []leveragedControlResponse `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &parsed))
require.Len(t, parsed.Data, 1)
require.Equal(t, *link.ID, parsed.Data[0].ID)
require.Equal(t, relational.SSPLeverageStatusActive, parsed.Data[0].Status)
require.Nil(t, parsed.Data[0].DriftRiskID)
}

// TestLeveragedControlsRevokedLinkHasNoDriftRiskId: no current code path sets a leverage
// link to Revoked, but the status exists on the type and BCH-1346's UI must handle it —
// manually flipping status confirms the projection still omits driftRiskId (matching
// ReAttest's own drifted-only precondition: a revoked link was never drifted, so it has
// no drift risk to find via the dedupe-key lookup).
func TestLeveragedControlsRevokedLinkHasNoDriftRiskId(t *testing.T) {
db := newSSPLeverageTestDB(t)
fx := newLeverageFixture(t, db)

pdp := &stubPDP{allow: true}
subscribeHandler := NewSSPLeverageHandler(zap.NewNop().Sugar(), db, pdp, authz.FailClosed)
body := subscribeBody(fx.downstreamSSPID, fx.itemID, fx.respAID)
subCtx, _, subRec := newSubscribeRequestContext(fx.offeringID, body)
require.NoError(t, subscribeHandler.Subscribe(subCtx))
require.Equal(t, http.StatusCreated, subRec.Code)

require.NoError(t, db.Model(&relational.SSPLeverageLink{}).
Where("downstream_ssp_id = ?", fx.downstreamSSPID).
Update("status", relational.SSPLeverageStatusRevoked).Error)

h := NewSSPLeverageHandler(zap.NewNop().Sugar(), db, &stubPDP{allow: true}, authz.FailClosed)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.SetParamNames("id")
ctx.SetParamValues(fx.downstreamSSPID.String())

require.NoError(t, h.LeveragedControls(ctx))
require.Equal(t, http.StatusOK, rec.Code)

var parsed struct {
Data []leveragedControlResponse `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &parsed))
require.Len(t, parsed.Data, 1)
require.Equal(t, relational.SSPLeverageStatusRevoked, parsed.Data[0].Status)
require.Nil(t, parsed.Data[0].DriftRiskID)
}

func TestReAttestRejectsNonDriftedLink(t *testing.T) {
db := newSSPLeverageTestDB(t)
fx := newLeverageFixture(t, db)
Expand Down
Loading