From bed0f9b99d1e3637c2b32063f4903e960cc2d407 Mon Sep 17 00:00:00 2001 From: DevipriyaS17 Date: Fri, 31 Jul 2026 19:49:10 +0530 Subject: [PATCH 1/4] fix(security): default useTLS to true on device creation --- internal/controller/httpapi/v1/devices.go | 15 ++++- .../controller/httpapi/v1/devices_test.go | 59 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/internal/controller/httpapi/v1/devices.go b/internal/controller/httpapi/v1/devices.go index a60381e0e..7cf6c3517 100644 --- a/internal/controller/httpapi/v1/devices.go +++ b/internal/controller/httpapi/v1/devices.go @@ -190,13 +190,26 @@ func (dr *deviceRoutes) getByID(c *gin.Context) { func (dr *deviceRoutes) insert(c *gin.Context) { var device dto.Device - if err := c.ShouldBindJSON(&device); err != nil { + if err := c.ShouldBindBodyWithJSON(&device); err != nil { validationErr := ErrValidationDevices.Wrap("insert", "ShouldBindJSON", err) ErrorResponse(c, validationErr) return } + fields, err := providedJSONFields(c) + if err != nil { + validationErr := ErrValidationDevices.Wrap("insert", "providedJSONFields", err) + ErrorResponse(c, validationErr) + + return + } + + // Security default: if useTLS is omitted on create, default to TLS enabled. + if !fields["usetls"] { + device.UseTLS = true + } + newDevice, err := dr.t.Insert(c.Request.Context(), &device) if err != nil { dr.l.Error(err, "http - devices - v1 - insert") diff --git a/internal/controller/httpapi/v1/devices_test.go b/internal/controller/httpapi/v1/devices_test.go index f1f243589..8d75526a2 100644 --- a/internal/controller/httpapi/v1/devices_test.go +++ b/internal/controller/httpapi/v1/devices_test.go @@ -411,6 +411,64 @@ func TestDevicesUpdatePartialPatch(t *testing.T) { require.Equal(t, string(expected), w.Body.String()) } +func TestDevicesInsertDefaultsUseTLSToTrueWhenOmitted(t *testing.T) { + t.Parallel() + + devicesFeature, engine := devicesTest(t) + + expected := &dto.Device{ + ConnectionStatus: false, + Hostname: "host-no-tls-field", + GUID: "123e4567-e89b-12d3-a456-426614174000", + Username: "admin1", + Password: "password1", + UseTLS: true, + } + + devicesFeature.EXPECT().Insert(context.Background(), expected).Return(expected, nil) + + body := []byte(`{"connectionStatus":false,"hostname":"host-no-tls-field","guid":"123e4567-e89b-12d3-a456-426614174000","username":"admin1","password":"password1"}`) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) + require.NoError(t, err) + + w := httptest.NewRecorder() + engine.ServeHTTP(w, req) + + require.Equal(t, http.StatusCreated, w.Code) + + jsonBytes, _ := json.Marshal(expected) + require.Equal(t, string(jsonBytes), w.Body.String()) +} + +func TestDevicesInsertHonorsExplicitUseTLSFalse(t *testing.T) { + t.Parallel() + + devicesFeature, engine := devicesTest(t) + + expected := &dto.Device{ + ConnectionStatus: false, + Hostname: "host-explicit-false", + GUID: "123e4567-e89b-12d3-a456-426614174001", + Username: "admin1", + Password: "password1", + UseTLS: false, + } + + devicesFeature.EXPECT().Insert(context.Background(), expected).Return(expected, nil) + + body := []byte(`{"connectionStatus":false,"hostname":"host-explicit-false","guid":"123e4567-e89b-12d3-a456-426614174001","username":"admin1","password":"password1","useTLS":false}`) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) + require.NoError(t, err) + + w := httptest.NewRecorder() + engine.ServeHTTP(w, req) + + require.Equal(t, http.StatusCreated, w.Code) + + jsonBytes, _ := json.Marshal(expected) + require.Equal(t, string(jsonBytes), w.Body.String()) +} + // encoding/json unmarshals case-insensitively; the merge must see the field as // provided regardless of the casing the client used. func TestDevicesUpdatePartialPatchMixedCaseKeys(t *testing.T) { @@ -527,6 +585,7 @@ func TestDevicesInsertAcceptsFullDeviceInfo(t *testing.T) { incoming := &dto.Device{ GUID: testDeviceGUID, Hostname: "test-device", + UseTLS: true, DeviceInfo: &dto.DeviceInfo{ FWVersion: "16.1.30", FWBuild: "3400", From b8789e01e646a4294aee58d9566d51d15ba22fef Mon Sep 17 00:00:00 2001 From: DevipriyaS17 Date: Fri, 31 Jul 2026 20:18:53 +0530 Subject: [PATCH 2/4] fix: add code coverage --- .../console_mps_apis.postman_collection.json | 1 + internal/controller/httpapi/v1/devices.go | 24 +++++++++++++++---- .../controller/httpapi/v1/devices_test.go | 16 +++++++++++++ internal/controller/openapi/devices.go | 2 +- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/integration-test/collections/console_mps_apis.postman_collection.json b/integration-test/collections/console_mps_apis.postman_collection.json index 5de19d307..792257986 100644 --- a/integration-test/collections/console_mps_apis.postman_collection.json +++ b/integration-test/collections/console_mps_apis.postman_collection.json @@ -1716,6 +1716,7 @@ " pm.expect(jsonData.tags.length).to.be.equal(0);\r", " pm.expect(jsonData.mpsInstance).to.be.equal(\"\");\r", " pm.expect(jsonData.connectionStatus).to.be.equal(false);\r", + " pm.expect(jsonData.useTLS).to.be.equal(true);\r", "})" ], "type": "text/javascript", diff --git a/internal/controller/httpapi/v1/devices.go b/internal/controller/httpapi/v1/devices.go index 7cf6c3517..a68c12c93 100644 --- a/internal/controller/httpapi/v1/devices.go +++ b/internal/controller/httpapi/v1/devices.go @@ -191,22 +191,22 @@ func (dr *deviceRoutes) getByID(c *gin.Context) { func (dr *deviceRoutes) insert(c *gin.Context) { var device dto.Device if err := c.ShouldBindBodyWithJSON(&device); err != nil { - validationErr := ErrValidationDevices.Wrap("insert", "ShouldBindJSON", err) + validationErr := ErrValidationDevices.Wrap("insert", "ShouldBindBodyWithJSON", err) ErrorResponse(c, validationErr) return } - fields, err := providedJSONFields(c) + hasUseTLS, err := hasTopLevelJSONField(c, "usetls") if err != nil { - validationErr := ErrValidationDevices.Wrap("insert", "providedJSONFields", err) + validationErr := ErrValidationDevices.Wrap("insert", "hasTopLevelJSONField", err) ErrorResponse(c, validationErr) return } // Security default: if useTLS is omitted on create, default to TLS enabled. - if !fields["usetls"] { + if !hasUseTLS { device.UseTLS = true } @@ -221,6 +221,22 @@ func (dr *deviceRoutes) insert(c *gin.Context) { c.JSON(http.StatusCreated, newDevice) } +func hasTopLevelJSONField(c *gin.Context, field string) (bool, error) { + var raw map[string]json.RawMessage + if err := c.ShouldBindBodyWithJSON(&raw); err != nil { + return false, err + } + + needle := strings.ToLower(field) + for k := range raw { + if strings.EqualFold(k, needle) { + return true, nil + } + } + + return false, nil +} + // Keys are lowercased so callers can match against setter maps regardless of // client casing (encoding/json unmarshals case-insensitively). // Nested objects are flattened with dot notation (for example, diff --git a/internal/controller/httpapi/v1/devices_test.go b/internal/controller/httpapi/v1/devices_test.go index 8d75526a2..9ffc6d447 100644 --- a/internal/controller/httpapi/v1/devices_test.go +++ b/internal/controller/httpapi/v1/devices_test.go @@ -469,6 +469,22 @@ func TestDevicesInsertHonorsExplicitUseTLSFalse(t *testing.T) { require.Equal(t, string(jsonBytes), w.Body.String()) } +func TestDevicesInsertRejectsInvalidJSON(t *testing.T) { + t.Parallel() + + _, engine := devicesTest(t) + + // Invalid JSON should fail during request binding. + body := []byte(`{invalid json}`) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) + require.NoError(t, err) + + w := httptest.NewRecorder() + engine.ServeHTTP(w, req) + + require.Equal(t, http.StatusBadRequest, w.Code) +} + // encoding/json unmarshals case-insensitively; the merge must see the field as // provided regardless of the casing the client used. func TestDevicesUpdatePartialPatchMixedCaseKeys(t *testing.T) { diff --git a/internal/controller/openapi/devices.go b/internal/controller/openapi/devices.go index 1cdc67fbf..f10d5f2b9 100644 --- a/internal/controller/openapi/devices.go +++ b/internal/controller/openapi/devices.go @@ -124,7 +124,7 @@ func (f *FuegoAdapter) registerDeviceMutationRoutes() { fuego.Post(f.server, "/api/v1/devices", f.createDevice, fuego.OptionTags("Devices"), fuego.OptionSummary("Create Device"), - fuego.OptionDescription("Create a new device"), + fuego.OptionDescription("Create a new device. If useTLS is omitted, it defaults to true."), fuego.OptionDefaultStatusCode(http.StatusCreated), protectedRouteOptions(), ) From 0ffc9843d4a73dc192629b9651e261838edf09be Mon Sep 17 00:00:00 2001 From: DevipriyaS17 Date: Fri, 14 Aug 2026 22:33:47 +0530 Subject: [PATCH 3/4] refactor(security): address review comments --- .../console_mps_apis.postman_collection.json | 16 ++++ .../console_rps_apis.postman_collection.json | 15 ++++ internal/controller/httpapi/v1/devices.go | 62 ++++++++++++---- .../controller/httpapi/v1/devices_test.go | 73 ++++++++++++++++++- internal/controller/httpapi/v1/login.go | 3 + internal/controller/httpapi/v1/login_test.go | 3 + internal/controller/httpapi/v1/profiles.go | 17 ++++- pkg/logger/logger.go | 6 +- 8 files changed, 169 insertions(+), 26 deletions(-) diff --git a/integration-test/collections/console_mps_apis.postman_collection.json b/integration-test/collections/console_mps_apis.postman_collection.json index 792257986..5943679a0 100644 --- a/integration-test/collections/console_mps_apis.postman_collection.json +++ b/integration-test/collections/console_mps_apis.postman_collection.json @@ -1717,6 +1717,7 @@ " pm.expect(jsonData.mpsInstance).to.be.equal(\"\");\r", " pm.expect(jsonData.connectionStatus).to.be.equal(false);\r", " pm.expect(jsonData.useTLS).to.be.equal(true);\r", + " pm.expect(jsonData.allowSelfSigned).to.be.equal(true);\r", "})" ], "type": "text/javascript", @@ -2689,6 +2690,21 @@ "exec": [ "pm.test(\"Response includes X-Content-Type-Options: nosniff\", function () {", " pm.expect(pm.response.headers.get(\"X-Content-Type-Options\")).to.eql(\"nosniff\");", + "});", + "", + "pm.test(\"Explicit false TLS settings remain false when present\", function () {", + " var jsonData = {};", + " try {", + " jsonData = pm.response.json();", + " } catch (e) {", + " return;", + " }", + " if (jsonData && Object.prototype.hasOwnProperty.call(jsonData, 'useTLS') && jsonData.useTLS === false) {", + " pm.expect(jsonData.useTLS).to.be.equal(false);", + " }", + " if (jsonData && Object.prototype.hasOwnProperty.call(jsonData, 'allowSelfSigned') && jsonData.allowSelfSigned === false) {", + " pm.expect(jsonData.allowSelfSigned).to.be.equal(false);", + " }", "});" ] } diff --git a/integration-test/collections/console_rps_apis.postman_collection.json b/integration-test/collections/console_rps_apis.postman_collection.json index 4309cf9b1..9c8db15a7 100644 --- a/integration-test/collections/console_rps_apis.postman_collection.json +++ b/integration-test/collections/console_rps_apis.postman_collection.json @@ -8421,6 +8421,21 @@ "exec": [ "pm.test(\"Response includes X-Content-Type-Options: nosniff\", function () {", " pm.expect(pm.response.headers.get(\"X-Content-Type-Options\")).to.eql(\"nosniff\");", + "});", + "", + "pm.test(\"Explicit false TLS settings remain false when present\", function () {", + " var jsonData = {};", + " try {", + " jsonData = pm.response.json();", + " } catch (e) {", + " return;", + " }", + " if (jsonData && Object.prototype.hasOwnProperty.call(jsonData, 'useTLS') && jsonData.useTLS === false) {", + " pm.expect(jsonData.useTLS).to.be.equal(false);", + " }", + " if (jsonData && Object.prototype.hasOwnProperty.call(jsonData, 'allowSelfSigned') && jsonData.allowSelfSigned === false) {", + " pm.expect(jsonData.allowSelfSigned).to.be.equal(false);", + " }", "});" ] } diff --git a/internal/controller/httpapi/v1/devices.go b/internal/controller/httpapi/v1/devices.go index a68c12c93..6f7608ef6 100644 --- a/internal/controller/httpapi/v1/devices.go +++ b/internal/controller/httpapi/v1/devices.go @@ -1,7 +1,9 @@ package v1 import ( + "bytes" "encoding/json" + "io" "net/http" "strings" "time" @@ -189,27 +191,42 @@ func (dr *deviceRoutes) getByID(c *gin.Context) { } func (dr *deviceRoutes) insert(c *gin.Context) { + body, err := readJSONBody(c) + if err != nil { + validationErr := ErrValidationDevices.Wrap("insert", "readJSONBody", err) + ErrorResponse(c, validationErr) + + return + } + var device dto.Device - if err := c.ShouldBindBodyWithJSON(&device); err != nil { - validationErr := ErrValidationDevices.Wrap("insert", "ShouldBindBodyWithJSON", err) + if err := json.Unmarshal(body, &device); err != nil { + validationErr := ErrValidationDevices.Wrap("insert", "json.Unmarshal", err) ErrorResponse(c, validationErr) return } - hasUseTLS, err := hasTopLevelJSONField(c, "usetls") - if err != nil { - validationErr := ErrValidationDevices.Wrap("insert", "hasTopLevelJSONField", err) + var raw map[string]json.RawMessage + if err := json.Unmarshal(body, &raw); err != nil { + validationErr := ErrValidationDevices.Wrap("insert", "json.Unmarshal", err) ErrorResponse(c, validationErr) return } - // Security default: if useTLS is omitted on create, default to TLS enabled. + hasUseTLS := hasJSONKey(raw, "usetls") + hasAllowSelfSigned := hasJSONKey(raw, "allowselfsigned") + + // Security defaults: if these flags are omitted on create, default to secure values. if !hasUseTLS { device.UseTLS = true } + if !hasAllowSelfSigned { + device.AllowSelfSigned = true + } + newDevice, err := dr.t.Insert(c.Request.Context(), &device) if err != nil { dr.l.Error(err, "http - devices - v1 - insert") @@ -221,29 +238,35 @@ func (dr *deviceRoutes) insert(c *gin.Context) { c.JSON(http.StatusCreated, newDevice) } -func hasTopLevelJSONField(c *gin.Context, field string) (bool, error) { - var raw map[string]json.RawMessage - if err := c.ShouldBindBodyWithJSON(&raw); err != nil { - return false, err +func readJSONBody(c *gin.Context) ([]byte, error) { + body, err := io.ReadAll(c.Request.Body) + if err != nil { + return nil, err } + c.Request.Body = io.NopCloser(bytes.NewReader(body)) + + return body, nil +} + +func hasJSONKey(raw map[string]json.RawMessage, field string) bool { needle := strings.ToLower(field) for k := range raw { if strings.EqualFold(k, needle) { - return true, nil + return true } } - return false, nil + return false } // Keys are lowercased so callers can match against setter maps regardless of // client casing (encoding/json unmarshals case-insensitively). // Nested objects are flattened with dot notation (for example, // "deviceinfo.fwversion") so PATCH handlers can deep-merge object fields. -func providedJSONFields(c *gin.Context) (map[string]bool, error) { +func providedJSONFieldsFromBody(body []byte) (map[string]bool, error) { var raw map[string]json.RawMessage - if err := c.ShouldBindBodyWithJSON(&raw); err != nil { + if err := json.Unmarshal(body, &raw); err != nil { return nil, err } @@ -277,14 +300,21 @@ func collectNestedJSONFields(prefix string, raw json.RawMessage, fields map[stri } func (dr *deviceRoutes) update(c *gin.Context) { + body, err := readJSONBody(c) + if err != nil { + ErrorResponse(c, err) + + return + } + var device dto.Device - if err := c.ShouldBindBodyWithJSON(&device); err != nil { + if err := json.Unmarshal(body, &device); err != nil { ErrorResponse(c, err) return } - fields, err := providedJSONFields(c) + fields, err := providedJSONFieldsFromBody(body) if err != nil { ErrorResponse(c, err) diff --git a/internal/controller/httpapi/v1/devices_test.go b/internal/controller/httpapi/v1/devices_test.go index 9ffc6d447..a3319fc7e 100644 --- a/internal/controller/httpapi/v1/devices_test.go +++ b/internal/controller/httpapi/v1/devices_test.go @@ -423,6 +423,7 @@ func TestDevicesInsertDefaultsUseTLSToTrueWhenOmitted(t *testing.T) { Username: "admin1", Password: "password1", UseTLS: true, + AllowSelfSigned: true, } devicesFeature.EXPECT().Insert(context.Background(), expected).Return(expected, nil) @@ -440,6 +441,36 @@ func TestDevicesInsertDefaultsUseTLSToTrueWhenOmitted(t *testing.T) { require.Equal(t, string(jsonBytes), w.Body.String()) } +func TestDevicesInsertDefaultsAllowSelfSignedToTrueWhenOmitted(t *testing.T) { + t.Parallel() + + devicesFeature, engine := devicesTest(t) + + expected := &dto.Device{ + ConnectionStatus: false, + Hostname: "host-no-self-signed-field", + GUID: "123e4567-e89b-12d3-a456-426614174001", + Username: "admin1", + Password: "password1", + UseTLS: true, + AllowSelfSigned: true, + } + + devicesFeature.EXPECT().Insert(context.Background(), expected).Return(expected, nil) + + body := []byte(`{"connectionStatus":false,"hostname":"host-no-self-signed-field","guid":"123e4567-e89b-12d3-a456-426614174001","username":"admin1","password":"password1"}`) + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) + require.NoError(t, err) + + w := httptest.NewRecorder() + engine.ServeHTTP(w, req) + + require.Equal(t, http.StatusCreated, w.Code) + + jsonBytes, _ := json.Marshal(expected) + require.Equal(t, string(jsonBytes), w.Body.String()) +} + func TestDevicesInsertHonorsExplicitUseTLSFalse(t *testing.T) { t.Parallel() @@ -452,11 +483,12 @@ func TestDevicesInsertHonorsExplicitUseTLSFalse(t *testing.T) { Username: "admin1", Password: "password1", UseTLS: false, + AllowSelfSigned: false, } devicesFeature.EXPECT().Insert(context.Background(), expected).Return(expected, nil) - body := []byte(`{"connectionStatus":false,"hostname":"host-explicit-false","guid":"123e4567-e89b-12d3-a456-426614174001","username":"admin1","password":"password1","useTLS":false}`) + body := []byte(`{"connectionStatus":false,"hostname":"host-explicit-false","guid":"123e4567-e89b-12d3-a456-426614174001","username":"admin1","password":"password1","useTLS":false,"allowSelfSigned":false}`) req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) require.NoError(t, err) @@ -587,6 +619,38 @@ func TestCollectNestedJSONFields(t *testing.T) { }) } +func TestDevicesInsertDefaultsTLSAndSelfSignedWhenOmitted(t *testing.T) { + t.Parallel() + + incoming := &dto.Device{ + AllowSelfSigned: true, + GUID: testDeviceGUID, + Hostname: "test-device", + UseTLS: true, + } + + devicesFeature, engine := devicesTest(t) + devicesFeature.EXPECT(). + Insert(context.Background(), incoming). + Return(incoming, nil) + + body := []byte(`{ + "guid":"` + testDeviceGUID + `", + "hostname":"test-device" + }`) + + req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/api/v1/devices", bytes.NewBuffer(body)) + require.NoError(t, err) + + w := httptest.NewRecorder() + engine.ServeHTTP(w, req) + + require.Equal(t, http.StatusCreated, w.Code) + + expected, _ := json.Marshal(incoming) + require.Equal(t, string(expected), w.Body.String()) +} + func TestDevicesInsertAcceptsFullDeviceInfo(t *testing.T) { t.Parallel() @@ -599,9 +663,10 @@ func TestDevicesInsertAcceptsFullDeviceInfo(t *testing.T) { ieee8021xEnabled := false incoming := &dto.Device{ - GUID: testDeviceGUID, - Hostname: "test-device", - UseTLS: true, + GUID: testDeviceGUID, + Hostname: "test-device", + UseTLS: true, + AllowSelfSigned: true, DeviceInfo: &dto.DeviceInfo{ FWVersion: "16.1.30", FWBuild: "3400", diff --git a/internal/controller/httpapi/v1/login.go b/internal/controller/httpapi/v1/login.go index c246d9299..b727b2dd3 100644 --- a/internal/controller/httpapi/v1/login.go +++ b/internal/controller/httpapi/v1/login.go @@ -119,6 +119,9 @@ func (lr LoginRoute) handleBasicAuth(creds dto.Credentials, c *gin.Context) { // still clear its own. Not revocation: the JWT stays valid until it expires. func (lr LoginRoute) Logout(c *gin.Context) { clearSessionCookies(c) + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + c.Header("Pragma", "no-cache") + c.Header("Expires", "0") c.JSON(http.StatusOK, gin.H{messageKey: "logged out"}) } diff --git a/internal/controller/httpapi/v1/login_test.go b/internal/controller/httpapi/v1/login_test.go index 38e3a2af6..1d25d7710 100644 --- a/internal/controller/httpapi/v1/login_test.go +++ b/internal/controller/httpapi/v1/login_test.go @@ -237,6 +237,9 @@ func TestLogoutExpiresSessionCookie(t *testing.T) { engine.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, "logout must work without a valid session") + require.Equal(t, "no-cache, no-store, must-revalidate", w.Header().Get("Cache-Control")) + require.Equal(t, "no-cache", w.Header().Get("Pragma")) + require.Equal(t, "0", w.Header().Get("Expires")) cleared := make(map[string]*http.Cookie) for _, cookie := range w.Result().Cookies() { diff --git a/internal/controller/httpapi/v1/profiles.go b/internal/controller/httpapi/v1/profiles.go index 7c03c81bc..5b784c18e 100644 --- a/internal/controller/httpapi/v1/profiles.go +++ b/internal/controller/httpapi/v1/profiles.go @@ -1,6 +1,7 @@ package v1 import ( + "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -136,17 +137,25 @@ func (r *profileRoutes) insert(c *gin.Context) { } func (r *profileRoutes) update(c *gin.Context) { + body, err := readJSONBody(c) + if err != nil { + validationErr := ErrValidationProfile.Wrap("update", "readJSONBody", err) + ErrorResponse(c, validationErr) + + return + } + var profile dto.Profile - if err := c.ShouldBindBodyWithJSON(&profile); err != nil { - validationErr := ErrValidationProfile.Wrap("update", "ShouldBindBodyWithJSON", err) + if err := json.Unmarshal(body, &profile); err != nil { + validationErr := ErrValidationProfile.Wrap("update", "json.Unmarshal", err) ErrorResponse(c, validationErr) return } - fields, err := providedJSONFields(c) + fields, err := providedJSONFieldsFromBody(body) if err != nil { - validationErr := ErrValidationProfile.Wrap("update", "providedJSONFields", err) + validationErr := ErrValidationProfile.Wrap("update", "providedJSONFieldsFromBody", err) ErrorResponse(c, validationErr) return diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 227b5fb06..fea99c027 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -113,7 +113,9 @@ func (l *logger) Fatal(message interface{}, args ...any) { func (l *logger) log(e *zerolog.Event, m string, args ...any) { if len(args) == 0 { e.Msg(m) - } else { - e.Msgf(m, args...) + + return } + + e.Msgf("%s %s", m, fmt.Sprint(args...)) } From 4c6e2c9ca8f0cee457de3ed176f0e95d9cbbcfbc Mon Sep 17 00:00:00 2001 From: DevipriyaS17 Date: Mon, 17 Aug 2026 12:21:09 +0530 Subject: [PATCH 4/4] refactor(security): fix the codeql error --- internal/usecase/nosqldb/mongo/device.go | 67 +++++++++++++++++------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/internal/usecase/nosqldb/mongo/device.go b/internal/usecase/nosqldb/mongo/device.go index 0636faceb..c1e5a45f3 100644 --- a/internal/usecase/nosqldb/mongo/device.go +++ b/internal/usecase/nosqldb/mongo/device.go @@ -19,6 +19,35 @@ type DeviceRepo struct { col *mongo.Collection } +type deviceFilter struct { + GUID string `bson:"guid"` + TenantID string `bson:"tenantid"` +} + +type deviceUpdateFields struct { + GUID string `bson:"guid"` + Hostname string `bson:"hostname"` + Tags string `bson:"tags"` + MPSInstance string `bson:"mpsinstance"` + ConnectionStatus bool `bson:"connectionstatus"` + MPSUsername string `bson:"mpsusername"` + TenantID string `bson:"tenantid"` + FriendlyName string `bson:"friendlyname"` + DNSSuffix string `bson:"dnssuffix"` + DeviceInfo string `bson:"deviceinfo"` + Username string `bson:"username"` + Password string `bson:"password"` + MPSPassword *string `bson:"mpspassword"` + MEBXPassword *string `bson:"mebxpassword"` + UseTLS bool `bson:"usetls"` + AllowSelfSigned bool `bson:"allowselfsigned"` + CertHash *string `bson:"certhash"` +} + +type deviceUpdateDocument struct { + Set deviceUpdateFields `bson:"$set"` +} + var _ devices.Repository = (*DeviceRepo)(nil) func NewDeviceRepo(db *mongo.Database) *DeviceRepo { @@ -199,25 +228,25 @@ func (r *DeviceRepo) Update(ctx context.Context, d *entity.Device) (bool, error) // Explicit field list mirrors sqldb/device.go:Update so a new field must be wired in intentionally. res, err := r.col.UpdateOne(ctx, - bson.M{fieldGUID: d.GUID, fieldTenantID: d.TenantID}, - bson.M{opSet: bson.M{ - fieldGUID: d.GUID, - "hostname": d.Hostname, - fieldTags: d.Tags, - "mpsinstance": d.MPSInstance, - "connectionstatus": d.ConnectionStatus, - "mpsusername": d.MPSUsername, - fieldTenantID: d.TenantID, - "friendlyname": d.FriendlyName, - "dnssuffix": d.DNSSuffix, - "deviceinfo": d.DeviceInfo, - "username": d.Username, - "password": d.Password, - "mpspassword": d.MPSPassword, - "mebxpassword": d.MEBXPassword, - "usetls": d.UseTLS, - "allowselfsigned": d.AllowSelfSigned, - "certhash": d.CertHash, + deviceFilter{GUID: d.GUID, TenantID: d.TenantID}, + deviceUpdateDocument{Set: deviceUpdateFields{ + GUID: d.GUID, + Hostname: d.Hostname, + Tags: d.Tags, + MPSInstance: d.MPSInstance, + ConnectionStatus: d.ConnectionStatus, + MPSUsername: d.MPSUsername, + TenantID: d.TenantID, + FriendlyName: d.FriendlyName, + DNSSuffix: d.DNSSuffix, + DeviceInfo: d.DeviceInfo, + Username: d.Username, + Password: d.Password, + MPSPassword: d.MPSPassword, + MEBXPassword: d.MEBXPassword, + UseTLS: d.UseTLS, + AllowSelfSigned: d.AllowSelfSigned, + CertHash: d.CertHash, }}, ) if err != nil {