diff --git a/docs/docs.go b/docs/docs.go index 413dc5bf..8441a51a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -16631,7 +16631,7 @@ const docTemplate = `{ }, "/oscal/system-security-plans/{id}/system-characteristics/authorization-boundary/diagrams": { "post": { - "description": "Creates a new Diagram under the Authorization Boundary of a System Security Plan.", + "description": "Creates a new Diagram under the Authorization Boundary of a System Security Plan. Creates the Authorization Boundary grouping if it does not exist yet.", "consumes": [ "application/json" ], @@ -16896,7 +16896,7 @@ const docTemplate = `{ }, "/oscal/system-security-plans/{id}/system-characteristics/data-flow/diagrams": { "post": { - "description": "Creates a new Diagram under the Data Flow of a System Security Plan.", + "description": "Creates a new Diagram under the Data Flow of a System Security Plan. Creates the Data Flow grouping if it does not exist yet.", "consumes": [ "application/json" ], @@ -17161,7 +17161,7 @@ const docTemplate = `{ }, "/oscal/system-security-plans/{id}/system-characteristics/network-architecture/diagrams": { "post": { - "description": "Creates a new Diagram under the Network Architecture of a System Security Plan.", + "description": "Creates a new Diagram under the Network Architecture of a System Security Plan. Creates the Network Architecture grouping if it does not exist yet.", "consumes": [ "application/json" ], diff --git a/docs/swagger.json b/docs/swagger.json index 8814dc9e..294fcf7c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -16625,7 +16625,7 @@ }, "/oscal/system-security-plans/{id}/system-characteristics/authorization-boundary/diagrams": { "post": { - "description": "Creates a new Diagram under the Authorization Boundary of a System Security Plan.", + "description": "Creates a new Diagram under the Authorization Boundary of a System Security Plan. Creates the Authorization Boundary grouping if it does not exist yet.", "consumes": [ "application/json" ], @@ -16890,7 +16890,7 @@ }, "/oscal/system-security-plans/{id}/system-characteristics/data-flow/diagrams": { "post": { - "description": "Creates a new Diagram under the Data Flow of a System Security Plan.", + "description": "Creates a new Diagram under the Data Flow of a System Security Plan. Creates the Data Flow grouping if it does not exist yet.", "consumes": [ "application/json" ], @@ -17155,7 +17155,7 @@ }, "/oscal/system-security-plans/{id}/system-characteristics/network-architecture/diagrams": { "post": { - "description": "Creates a new Diagram under the Network Architecture of a System Security Plan.", + "description": "Creates a new Diagram under the Network Architecture of a System Security Plan. Creates the Network Architecture grouping if it does not exist yet.", "consumes": [ "application/json" ], diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2dc37c1d..7598e459 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -20433,7 +20433,8 @@ paths: consumes: - application/json description: Creates a new Diagram under the Authorization Boundary of a System - Security Plan. + Security Plan. Creates the Authorization Boundary grouping if it does not + exist yet. parameters: - description: System Security Plan ID in: path @@ -20607,7 +20608,7 @@ paths: consumes: - application/json description: Creates a new Diagram under the Data Flow of a System Security - Plan. + Plan. Creates the Data Flow grouping if it does not exist yet. parameters: - description: System Security Plan ID in: path @@ -20782,7 +20783,8 @@ paths: consumes: - application/json description: Creates a new Diagram under the Network Architecture of a System - Security Plan. + Security Plan. Creates the Network Architecture grouping if it does not exist + yet. parameters: - description: System Security Plan ID in: path diff --git a/internal/api/handler/oscal/system_security_plans.go b/internal/api/handler/oscal/system_security_plans.go index 3136b114..f3eddb3d 100644 --- a/internal/api/handler/oscal/system_security_plans.go +++ b/internal/api/handler/oscal/system_security_plans.go @@ -565,7 +565,7 @@ func (h *SystemSecurityPlanHandler) GetCharacteristicsNetworkArchitecture(ctx ec // CreateCharacteristicsNetworkArchitectureDiagram godoc // // @Summary Create a Network Architecture Diagram -// @Description Creates a new Diagram under the Network Architecture of a System Security Plan. +// @Description Creates a new Diagram under the Network Architecture of a System Security Plan. Creates the Network Architecture grouping if it does not exist yet. // @Tags System Security Plans // @Accept json // @Produce json @@ -600,7 +600,14 @@ func (h *SystemSecurityPlanHandler) CreateCharacteristicsNetworkArchitectureDiag na := ssp.SystemCharacteristics.NetworkArchitecture if na == nil || na.ID == nil { - return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no network architecture for system security plan %s", idParam))) + if ssp.SystemCharacteristics.ID == nil { + return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no system characteristics for system security plan %s", idParam))) + } + na = &relational.NetworkArchitecture{SystemCharacteristicsId: *ssp.SystemCharacteristics.ID} + if err := h.db.Create(na).Error; err != nil { + h.sugar.Errorf("Failed to create network architecture: %v", err) + return ctx.JSON(http.StatusInternalServerError, api.NewError(err)) + } } // Bind incoming diagram @@ -803,7 +810,7 @@ func (h *SystemSecurityPlanHandler) GetCharacteristicsDataFlow(ctx echo.Context) // CreateCharacteristicsDataFlowDiagram godoc // // @Summary Create a Data Flow Diagram -// @Description Creates a new Diagram under the Data Flow of a System Security Plan. +// @Description Creates a new Diagram under the Data Flow of a System Security Plan. Creates the Data Flow grouping if it does not exist yet. // @Tags System Security Plans // @Accept json // @Produce json @@ -838,7 +845,14 @@ func (h *SystemSecurityPlanHandler) CreateCharacteristicsDataFlowDiagram(ctx ech df := ssp.SystemCharacteristics.DataFlow if df == nil || df.ID == nil { - return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no data flow for system security plan %s", idParam))) + if ssp.SystemCharacteristics.ID == nil { + return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no system characteristics for system security plan %s", idParam))) + } + df = &relational.DataFlow{SystemCharacteristicsId: *ssp.SystemCharacteristics.ID} + if err := h.db.Create(df).Error; err != nil { + h.sugar.Errorf("Failed to create data flow: %v", err) + return ctx.JSON(http.StatusInternalServerError, api.NewError(err)) + } } var oscalDiag oscalTypes_1_1_3.Diagram @@ -1037,7 +1051,7 @@ func (h *SystemSecurityPlanHandler) GetCharacteristicsAuthorizationBoundary(ctx // CreateCharacteristicsAuthorizationBoundaryDiagram godoc // // @Summary Create an Authorization Boundary Diagram -// @Description Creates a new Diagram under the Authorization Boundary of a System Security Plan. +// @Description Creates a new Diagram under the Authorization Boundary of a System Security Plan. Creates the Authorization Boundary grouping if it does not exist yet. // @Tags System Security Plans // @Accept json // @Produce json @@ -1072,7 +1086,14 @@ func (h *SystemSecurityPlanHandler) CreateCharacteristicsAuthorizationBoundaryDi ab := ssp.SystemCharacteristics.AuthorizationBoundary if ab == nil || ab.ID == nil { - return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no authorization boundary for system security plan %s", idParam))) + if ssp.SystemCharacteristics.ID == nil { + return ctx.JSON(http.StatusNotFound, api.NewError(fmt.Errorf("no system characteristics for system security plan %s", idParam))) + } + ab = &relational.AuthorizationBoundary{SystemCharacteristicsId: *ssp.SystemCharacteristics.ID} + if err := h.db.Create(ab).Error; err != nil { + h.sugar.Errorf("Failed to create authorization boundary: %v", err) + return ctx.JSON(http.StatusInternalServerError, api.NewError(err)) + } } var oscalDiag oscalTypes_1_1_3.Diagram diff --git a/internal/api/handler/oscal/system_security_plans_test.go b/internal/api/handler/oscal/system_security_plans_test.go index 3b1217a3..3f250de1 100644 --- a/internal/api/handler/oscal/system_security_plans_test.go +++ b/internal/api/handler/oscal/system_security_plans_test.go @@ -2319,7 +2319,7 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateAuthorizationBound suite.True(found, "created diagram should be present in authorization boundary") } -// Negative: Network Architecture missing, invalid IDs and invalid body +// Network Architecture diagram creation: invalid IDs, missing grouping auto-create, invalid body func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateNetworkArchitectureDiagram_Negative() { logConf := zap.NewDevelopmentConfig() logConf.Level = zap.NewAtomicLevelAt(zap.ErrorLevel) @@ -2340,7 +2340,7 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateNetworkArchitectur server.E().ServeHTTP(resp, req) suite.Equal(http.StatusBadRequest, resp.Code) - // 2) Missing Network Architecture -> 404 + // 2) Missing Network Architecture -> grouping is auto-created on first diagram POST ssp := suite.createBasicSSP() ssp.SystemCharacteristics.NetworkArchitecture = nil req = suite.createRequest(http.MethodPost, "/api/oscal/system-security-plans", ssp) @@ -2348,13 +2348,28 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateNetworkArchitectur server.E().ServeHTTP(resp, req) suite.Equal(http.StatusCreated, resp.Code) - // Attempt to create diagram when NA is missing + // Create diagram when NA is missing diagram := oscalTypes_1_1_3.Diagram{UUID: uuid.New().String(), Description: "NA diag"} req = suite.createRequest(http.MethodPost, fmt.Sprintf("/api/oscal/system-security-plans/%s/system-characteristics/network-architecture/diagrams", ssp.UUID), diagram) resp = httptest.NewRecorder() server.E().ServeHTTP(resp, req) - suite.Equal(http.StatusNotFound, resp.Code) + suite.Equal(http.StatusCreated, resp.Code) + + // The grouping should now exist and contain the diagram + req = suite.createRequest(http.MethodGet, + fmt.Sprintf("/api/oscal/system-security-plans/%s/system-characteristics/network-architecture", ssp.UUID), nil) + resp = httptest.NewRecorder() + server.E().ServeHTTP(resp, req) + suite.Equal(http.StatusOK, resp.Code) + + var naResponse handler.GenericDataResponse[*oscalTypes_1_1_3.NetworkArchitecture] + err = json.Unmarshal(resp.Body.Bytes(), &naResponse) + suite.Require().NoError(err) + suite.Require().NotNil(naResponse.Data) + suite.Require().NotNil(naResponse.Data.Diagrams) + suite.Require().Len(*naResponse.Data.Diagrams, 1) + suite.Equal(diagram.UUID, (*naResponse.Data.Diagrams)[0].UUID) // 3) Invalid/Missing diagram UUID -> 400 // Recreate SSP with NA present @@ -2385,7 +2400,7 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateNetworkArchitectur suite.Equal(http.StatusBadRequest, resp.Code) } -// Negative: Data Flow missing, invalid IDs and invalid body +// Data Flow diagram creation: invalid IDs, missing grouping auto-create, invalid body func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateDataFlowDiagram_Negative() { logConf := zap.NewDevelopmentConfig() logConf.Level = zap.NewAtomicLevelAt(zap.ErrorLevel) @@ -2406,7 +2421,7 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateDataFlowDiagram_Ne server.E().ServeHTTP(resp, req) suite.Equal(http.StatusBadRequest, resp.Code) - // 2) Missing Data Flow -> 404 + // 2) Missing Data Flow -> grouping is auto-created on first diagram POST ssp := suite.createBasicSSP() ssp.SystemCharacteristics.DataFlow = nil req = suite.createRequest(http.MethodPost, "/api/oscal/system-security-plans", ssp) @@ -2419,7 +2434,22 @@ func (suite *SystemSecurityPlanApiIntegrationSuite) TestCreateDataFlowDiagram_Ne fmt.Sprintf("/api/oscal/system-security-plans/%s/system-characteristics/data-flow/diagrams", ssp.UUID), diagram) resp = httptest.NewRecorder() server.E().ServeHTTP(resp, req) - suite.Equal(http.StatusNotFound, resp.Code) + suite.Equal(http.StatusCreated, resp.Code) + + // The grouping should now exist and contain the diagram + req = suite.createRequest(http.MethodGet, + fmt.Sprintf("/api/oscal/system-security-plans/%s/system-characteristics/data-flow", ssp.UUID), nil) + resp = httptest.NewRecorder() + server.E().ServeHTTP(resp, req) + suite.Equal(http.StatusOK, resp.Code) + + var dfResponse handler.GenericDataResponse[*oscalTypes_1_1_3.DataFlow] + err = json.Unmarshal(resp.Body.Bytes(), &dfResponse) + suite.Require().NoError(err) + suite.Require().NotNil(dfResponse.Data) + suite.Require().NotNil(dfResponse.Data.Diagrams) + suite.Require().Len(*dfResponse.Data.Diagrams, 1) + suite.Equal(diagram.UUID, (*dfResponse.Data.Diagrams)[0].UUID) // 3) Invalid/Missing diagram UUID -> 400 // Recreate SSP with DF present