From 2e5f1808d79a456c2a86538e2d74acd4b086ec8e Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 4 Apr 2025 12:43:11 +0200 Subject: [PATCH] Config UI: keep mqtt and influx secrets (part 2) (#20340) --- api/globalconfig/types.go | 34 ++++------------ plugin/mqtt/client.go | 33 +++++++++++++--- server/http_global_settings_handler.go | 26 +++++++++---- server/http_global_settings_handler_test.go | 43 +++++++++++++++++++-- 4 files changed, 91 insertions(+), 45 deletions(-) diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go index 0cc6d5ae5..05cb68d83 100644 --- a/api/globalconfig/types.go +++ b/api/globalconfig/types.go @@ -7,6 +7,7 @@ import ( "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/plugin/mqtt" "github.com/evcc-io/evcc/push" "github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/util/config" @@ -79,39 +80,18 @@ func masked(s any) string { } type Mqtt struct { - Broker string `json:"broker"` - Topic string `json:"topic"` - User string `json:"user"` - ClientID string `json:"clientID"` - Insecure bool `json:"insecure"` - Password string `json:"password"` - CaCert string `json:"caCert"` - ClientCert string `json:"clientCert"` - ClientKey string `json:"clientKey"` + mqtt.Config + Topic string `json:"topic"` } // Redacted implements the redactor interface used by the tee publisher func (m Mqtt) Redacted() any { return struct { - Broker string `json:"broker"` - Topic string `json:"topic"` - User string `json:"user,omitempty"` - ClientID string `json:"clientID,omitempty"` - Insecure bool `json:"insecure,omitempty"` - Password string `json:"password,omitempty"` - CaCert string `json:"caCert,omitempty"` - ClientCert string `json:"clientCert,omitempty"` - ClientKey string `json:"clientKey,omitempty"` + mqtt.Config + Topic string `json:"topic"` }{ - Broker: m.Broker, - Topic: m.Topic, - User: m.User, - ClientID: m.ClientID, - Insecure: m.Insecure, - Password: masked(m.Password), - CaCert: masked(m.CaCert), - ClientCert: masked(m.ClientCert), - ClientKey: masked(m.ClientKey), + m.Config.Redacted(), + m.Topic, } } diff --git a/plugin/mqtt/client.go b/plugin/mqtt/client.go index d0b847921..9e8d22e71 100644 --- a/plugin/mqtt/client.go +++ b/plugin/mqtt/client.go @@ -28,12 +28,33 @@ func ClientID() string { type Config struct { Broker string `json:"broker"` User string `json:"user"` - Password string `json:"password"` - ClientID string `json:"clientID"` - Insecure bool `json:"insecure"` - CaCert string `json:"caCert"` - ClientCert string `json:"clientCert"` - ClientKey string `json:"clientKey"` + Password string `json:"password,omitempty"` + ClientID string `json:"clientID,omitempty"` + Insecure bool `json:"insecure,omitempty"` + CaCert string `json:"caCert,omitempty"` + ClientCert string `json:"clientCert,omitempty"` + ClientKey string `json:"clientKey,omitempty"` +} + +func masked(s any) string { + if s != "" { + return "***" + } + return "" +} + +// Redacted implements the redactor interface +func (c Config) Redacted() Config { + return Config{ + Broker: c.Broker, + User: c.User, + Password: masked(c.Password), + ClientID: c.ClientID, + Insecure: c.Insecure, + CaCert: masked(c.CaCert), + ClientCert: masked(c.ClientCert), + ClientKey: masked(c.ClientKey), + } } // Client encapsulates mqtt publish/subscribe functions diff --git a/server/http_global_settings_handler.go b/server/http_global_settings_handler.go index 9a1788739..788ac4146 100644 --- a/server/http_global_settings_handler.go +++ b/server/http_global_settings_handler.go @@ -123,13 +123,23 @@ func mergeSettings(old any, new any) error { oldMap := structs.Map(old) redactedMap := structs.Map(redactable.Redacted()) - for k, v := range newMap { - if rv, ok := redactedMap[k]; ok && v == rv { - if ov, ok := oldMap[k]; ok { - newMap[k] = ov - } - } - } - + newMap = mergeRedacted(oldMap, newMap, redactedMap) return mapstructure.Decode(newMap, &new) } + +func mergeRedacted(old, new, redacted map[string]any) map[string]any { + for k, v := range new { + rv := redacted[k] + ov := old[k] + if nv, ok := v.(map[string]any); ok { + rv, ok := rv.(map[string]any) + ov, ok2 := ov.(map[string]any) + if ok && ok2 { + new[k] = mergeRedacted(ov, nv, rv) + } + } else if rv == v { + new[k] = ov + } + } + return new +} diff --git a/server/http_global_settings_handler_test.go b/server/http_global_settings_handler_test.go index 84f3ccd38..84a99a37d 100644 --- a/server/http_global_settings_handler_test.go +++ b/server/http_global_settings_handler_test.go @@ -9,8 +9,8 @@ import ( func TestMergeSettings(t *testing.T) { tests := []struct { old any - new *RedactedStruct - expected *RedactedStruct + new any + expected any }{ { old: nil, @@ -32,12 +32,29 @@ func TestMergeSettings(t *testing.T) { new: &RedactedStruct{"newValue1", 42}, expected: &RedactedStruct{"newValue1", 42}, }, + { + old: &NestedStruct{RedactedStruct{"oldValue1", 35}, 24}, + new: &NestedStruct{RedactedStruct{"newValue1", 45}, 42}, + expected: &NestedStruct{RedactedStruct{"newValue1", 45}, 42}, + }, + { + old: &NestedStruct{RedactedStruct{"oldValue1", 35}, 24}, + new: &NestedStruct{RedactedStruct{"redacted", 45}, 0}, + expected: &NestedStruct{RedactedStruct{"oldValue1", 45}, 24}, + }, } for _, tc := range tests { mergeSettings(tc.old, tc.new) - assert.Equal(t, tc.expected.Field1, tc.new.Field1) - assert.Equal(t, tc.expected.Field2, tc.new.Field2) + switch expected := tc.expected.(type) { + case *RedactedStruct: + assert.Equal(t, expected.Field1, tc.new.(*RedactedStruct).Field1) + assert.Equal(t, expected.Field2, tc.new.(*RedactedStruct).Field2) + case *NestedStruct: + assert.Equal(t, expected.Field1.Field1, tc.new.(*NestedStruct).Field1.Field1) + assert.Equal(t, expected.Field1.Field2, tc.new.(*NestedStruct).Field1.Field2) + assert.Equal(t, expected.Field2, tc.new.(*NestedStruct).Field2) + } } } @@ -51,6 +68,11 @@ type RedactedStruct struct { Field2 int } +type NestedStruct struct { + Field1 RedactedStruct + Field2 int +} + func (t *RedactedStruct) Redacted() any { return struct { Field1 string @@ -60,3 +82,16 @@ func (t *RedactedStruct) Redacted() any { Field2: t.Field2, } } + +func (t *NestedStruct) Redacted() any { + return struct { + Field1 RedactedStruct + Field2 int + }{ + Field1: t.Field1.Redacted().(struct { + Field1 string + Field2 int + }), + Field2: 0, + } +}