diff --git a/database/models.go b/database/models.go index f51ff09..153dedd 100644 --- a/database/models.go +++ b/database/models.go @@ -60,8 +60,8 @@ type Scenario struct { Model // Name of scenario Name string `json:"name" gorm:"not null"` - // Running state of scenario - Running bool `json:"running" gorm:"default:false" ` + // IsLocked state of scenario (true if scenario is locked by administrator) + IsLocked bool `json:"isLocked" gorm:"default:false" ` // Start parameters of scenario as JSON StartParameters postgres.Jsonb `json:"startParameters"` // Users that have access to the scenario diff --git a/doc/api/docs.go b/doc/api/docs.go index fa1f409..17cf0eb 100644 --- a/doc/api/docs.go +++ b/doc/api/docs.go @@ -3727,9 +3727,6 @@ var doc = `{ "Name": { "type": "string" }, - "Running": { - "type": "boolean" - }, "StartParameters": { "$ref": "#/definitions/postgres.Jsonb" } @@ -3738,12 +3735,12 @@ var doc = `{ "scenario.validUpdatedScenario": { "type": "object", "properties": { + "IsLocked": { + "type": "boolean" + }, "Name": { "type": "string" }, - "Running": { - "type": "boolean" - }, "StartParameters": { "$ref": "#/definitions/postgres.Jsonb" } diff --git a/doc/api/swagger.json b/doc/api/swagger.json index 32b1bd9..32b02bb 100644 --- a/doc/api/swagger.json +++ b/doc/api/swagger.json @@ -3711,9 +3711,6 @@ "Name": { "type": "string" }, - "Running": { - "type": "boolean" - }, "StartParameters": { "$ref": "#/definitions/postgres.Jsonb" } @@ -3722,12 +3719,12 @@ "scenario.validUpdatedScenario": { "type": "object", "properties": { + "IsLocked": { + "type": "boolean" + }, "Name": { "type": "string" }, - "Running": { - "type": "boolean" - }, "StartParameters": { "$ref": "#/definitions/postgres.Jsonb" } diff --git a/doc/api/swagger.yaml b/doc/api/swagger.yaml index 85dca29..5dcdc9f 100644 --- a/doc/api/swagger.yaml +++ b/doc/api/swagger.yaml @@ -286,8 +286,6 @@ definitions: properties: Name: type: string - Running: - type: boolean StartParameters: $ref: '#/definitions/postgres.Jsonb' required: @@ -296,10 +294,10 @@ definitions: type: object scenario.validUpdatedScenario: properties: + IsLocked: + type: boolean Name: type: string - Running: - type: boolean StartParameters: $ref: '#/definitions/postgres.Jsonb' type: object diff --git a/routes/component-configuration/config_test.go b/routes/component-configuration/config_test.go index 43bf804..6f81cc6 100644 --- a/routes/component-configuration/config_test.go +++ b/routes/component-configuration/config_test.go @@ -64,7 +64,6 @@ type ICRequest struct { type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -120,7 +119,6 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } code, resp, err = helper.TestEndpoint(router, token, diff --git a/routes/dashboard/dashboard_test.go b/routes/dashboard/dashboard_test.go index a73cc0f..0f592fa 100644 --- a/routes/dashboard/dashboard_test.go +++ b/routes/dashboard/dashboard_test.go @@ -49,7 +49,6 @@ type DashboardRequest struct { type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -63,7 +62,6 @@ func addScenario(token string) (scenarioID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } _, resp, err := helper.TestEndpoint(router, token, diff --git a/routes/file/file_test.go b/routes/file/file_test.go index 28d05ad..48c5c30 100644 --- a/routes/file/file_test.go +++ b/routes/file/file_test.go @@ -47,7 +47,6 @@ var router *gin.Engine type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -62,7 +61,6 @@ func addScenario() (scenarioID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } _, resp, _ := helper.TestEndpoint(router, token, diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 000df25..194ea1e 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -65,7 +65,6 @@ type ICRequest struct { type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -829,7 +828,6 @@ func TestDeleteICViaAMQPRecv(t *testing.T) { // add scenario newScenario := ScenarioRequest{ Name: "ScenarioA", - Running: true, StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`)}, } diff --git a/routes/result/result_test.go b/routes/result/result_test.go index 76af32c..521dfd6 100644 --- a/routes/result/result_test.go +++ b/routes/result/result_test.go @@ -50,7 +50,6 @@ var baseAPIResults = "/api/v2/results" type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -79,7 +78,6 @@ func addScenario() (scenarioID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } _, resp, _ := helper.TestEndpoint(router, token, diff --git a/routes/scenario/scenario_endpoints.go b/routes/scenario/scenario_endpoints.go index 24e0789..c597ccc 100644 --- a/routes/scenario/scenario_endpoints.go +++ b/routes/scenario/scenario_endpoints.go @@ -182,7 +182,8 @@ func updateScenario(c *gin.Context) { } // Create the updatedScenario from oldScenario - updatedScenario := req.updatedScenario(oldScenario) + userRole, _ := c.Get(database.UserRoleCtx) + updatedScenario := req.updatedScenario(oldScenario, userRole.(string)) // Finally update the scenario err := oldScenario.update(updatedScenario) diff --git a/routes/scenario/scenario_methods.go b/routes/scenario/scenario_methods.go index 1a60e52..9f76bab 100644 --- a/routes/scenario/scenario_methods.go +++ b/routes/scenario/scenario_methods.go @@ -58,7 +58,7 @@ func (s *Scenario) update(updatedScenario Scenario) error { // TODO: if the field is empty member shouldn't be updated s.Name = updatedScenario.Name - s.Running = updatedScenario.Running + s.IsLocked = updatedScenario.IsLocked s.StartParameters = updatedScenario.StartParameters db := database.GetDB() @@ -146,7 +146,7 @@ func (s *Scenario) delete() error { return nil } -func (s *Scenario) checkAccess(userID uint, userRole string) bool { +func (s *Scenario) checkAccess(userID uint, userRole string, operation database.CRUD) bool { if userRole == "Admin" { return true @@ -155,7 +155,7 @@ func (s *Scenario) checkAccess(userID uint, userRole string) bool { u := database.User{} u.Username = "" err := db.Order("ID asc").Model(s).Where("ID = ?", userID).Related(&u, "Users").Error - if err != nil || !u.Active { + if err != nil || !u.Active || (s.IsLocked && operation != database.Read) { return false } else { return true diff --git a/routes/scenario/scenario_middleware.go b/routes/scenario/scenario_middleware.go index 2a9740a..c8f87e8 100644 --- a/routes/scenario/scenario_middleware.go +++ b/routes/scenario/scenario_middleware.go @@ -56,8 +56,8 @@ func CheckPermissions(c *gin.Context, operation database.CRUD, scenarioIDsource return false, so } - if so.checkAccess(userID.(uint), userRole.(string)) == false { - helper.UnprocessableEntityError(c, "Access denied (for scenario ID).") + if so.checkAccess(userID.(uint), userRole.(string), operation) == false { + helper.UnprocessableEntityError(c, "Access denied (user has no access or scenario is locked).") return false, so } diff --git a/routes/scenario/scenario_test.go b/routes/scenario/scenario_test.go index 65c39a2..51740ae 100644 --- a/routes/scenario/scenario_test.go +++ b/routes/scenario/scenario_test.go @@ -40,7 +40,7 @@ var router *gin.Engine type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` + IsLocked bool `json:"isLocked,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -54,13 +54,11 @@ type UserRequest struct { var newScenario1 = ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } var newScenario2 = ScenarioRequest{ Name: "Scenario2", - Running: false, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`)}, } @@ -133,7 +131,7 @@ func TestAddScenario(t *testing.T) { // try to POST a malformed scenario // Required fields are missing malformedNewScenario := ScenarioRequest{ - Running: false, + IsLocked: false, } // this should NOT work and return a unprocessable entity 442 status code code, resp, err = helper.TestEndpoint(router, token, @@ -205,12 +203,6 @@ func TestUpdateScenario(t *testing.T) { newScenarioID, err := helper.GetResponseID(resp) assert.NoError(t, err) - updatedScenario := ScenarioRequest{ - Name: "Updated name", - Running: false, - StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, - } - // try to update with non JSON body // should return a bad request error code, resp, err = helper.TestEndpoint(router, token, @@ -218,6 +210,24 @@ func TestUpdateScenario(t *testing.T) { assert.NoError(t, err) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) + updatedScenario := ScenarioRequest{ + Name: "Updated name", + IsLocked: true, + StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, + } + + // try to change locked state as non admin user + // should return 200 but locked state not updated + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/v2/scenarios/%v", newScenarioID), "PUT", helper.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updatedScenario (should result in error) + err = helper.CompareResponse(resp, helper.KeyModels{"scenario": updatedScenario}) + assert.Error(t, err) + + updatedScenario.IsLocked = false code, resp, err = helper.TestEndpoint(router, token, fmt.Sprintf("/api/v2/scenarios/%v", newScenarioID), "PUT", helper.KeyModels{"scenario": updatedScenario}) assert.NoError(t, err) @@ -243,6 +253,21 @@ func TestUpdateScenario(t *testing.T) { assert.NoError(t, err) assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + // authenticate as admin user who has no access to everything + token, err = helper.AuthenticateForTest(router, helper.AdminCredentials) + assert.NoError(t, err) + + // changed locked state of scenario as admin user (should work) + updatedScenario.IsLocked = true + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/v2/scenarios/%v", newScenarioID), "PUT", helper.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare PUT's response with the updatedScenario + err = helper.CompareResponse(resp, helper.KeyModels{"scenario": updatedScenario}) + assert.NoError(t, err) + } func TestGetAllScenariosAsAdmin(t *testing.T) { diff --git a/routes/scenario/scenario_validators.go b/routes/scenario/scenario_validators.go index b42cf1b..f80d4ac 100644 --- a/routes/scenario/scenario_validators.go +++ b/routes/scenario/scenario_validators.go @@ -32,13 +32,12 @@ var validate *validator.Validate type validNewScenario struct { Name string `form:"Name" validate:"required"` - Running bool `form:"Running" validate:"omitempty"` StartParameters postgres.Jsonb `form:"StartParameters" validate:"required"` } type validUpdatedScenario struct { Name string `form:"Name" validate:"omitempty"` - Running bool `form:"Running" validate:"omitempty"` + IsLocked bool `form:"IsLocked" validate:"omitempty"` StartParameters postgres.Jsonb `form:"StartParameters" validate:"omitempty"` } @@ -66,22 +65,24 @@ func (r *addScenarioRequest) createScenario() Scenario { var s Scenario s.Name = r.Scenario.Name - s.Running = r.Scenario.Running + s.IsLocked = false // new scenarios are not locked s.StartParameters = r.Scenario.StartParameters return s } -func (r *updateScenarioRequest) updatedScenario(oldScenario Scenario) Scenario { +func (r *updateScenarioRequest) updatedScenario(oldScenario Scenario, userRole string) Scenario { // Use the old Scenario as a basis for the updated Scenario `s` s := oldScenario + if userRole == "Admin" { // only admin users can change isLocked status + s.IsLocked = r.Scenario.IsLocked + } + if r.Scenario.Name != "" { s.Name = r.Scenario.Name } - s.Running = r.Scenario.Running - // only update Params if not empty var emptyJson postgres.Jsonb // Serialize empty json and params diff --git a/routes/signal/signal_test.go b/routes/signal/signal_test.go index 9444b53..b6a072b 100644 --- a/routes/signal/signal_test.go +++ b/routes/signal/signal_test.go @@ -72,7 +72,6 @@ type ICRequest struct { type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -118,7 +117,6 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } _, resp, _ = helper.TestEndpoint(router, token, diff --git a/routes/widget/widget_test.go b/routes/widget/widget_test.go index 8d47b94..ba6aace 100644 --- a/routes/widget/widget_test.go +++ b/routes/widget/widget_test.go @@ -64,7 +64,6 @@ type DashboardRequest struct { type ScenarioRequest struct { Name string `json:"name,omitempty"` - Running bool `json:"running,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"` } @@ -88,7 +87,6 @@ func addScenarioAndDashboard(token string) (scenarioID uint, dashboardID uint) { // POST $newScenario newScenario := ScenarioRequest{ Name: "Scenario1", - Running: true, StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, } _, resp, _ := helper.TestEndpoint(router, token,