Skip to content

Commit a65a1ce

Browse files
authored
Issue UI: redact private data like (user, locations, ...) (#25039)
1 parent f8dcd4d commit a65a1ce

23 files changed

+277
-109
lines changed

assets/js/views/Issue.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,8 @@ export default defineComponent({
475475
476476
for (const endpoint of endpoints) {
477477
try {
478-
const response = await api.get(endpoint);
478+
// Add private=false for device endpoints to hide private data in bug reports
479+
const response = await api.get(endpoint, { params: { private: false } });
479480
if (response.data && Object.keys(response.data).length > 0) {
480481
const key = endpoint.replace("config/", "").replace("devices/", "");
481482
let data = response.data;

cmd/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
"github.com/evcc-io/evcc/util/config"
8+
"github.com/evcc-io/evcc/util/redact"
89
"github.com/evcc-io/evcc/util/templates"
910
"github.com/spf13/cobra"
1011
)
@@ -54,7 +55,7 @@ func runConfig(cmd *cobra.Command, args []string) {
5455
}
5556

5657
for _, c := range configurable {
57-
fmt.Println(config.NameForID(c.ID), fmt.Sprintf("%+v", c.Properties), redactMap(c.Data))
58+
fmt.Println(config.NameForID(c.ID), fmt.Sprintf("%+v", c.Properties), redact.Map(c.Data))
5859
}
5960

6061
fmt.Println("")

cmd/discuss.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/cli/browser"
1212
"github.com/evcc-io/evcc/util"
13+
"github.com/evcc-io/evcc/util/redact"
1314
"github.com/spf13/cobra"
1415
)
1516

@@ -44,7 +45,7 @@ func runDiscuss(cmd *cobra.Command, args []string) {
4445

4546
var redacted string
4647
if src, err := os.ReadFile(cfgFile); err == nil {
47-
redacted = redact(string(src))
48+
redacted = redact.String(string(src))
4849
}
4950

5051
tmpl := template.Must(template.New("discuss").Parse(discussTmpl))

cmd/dump.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/evcc-io/evcc/core"
1414
"github.com/evcc-io/evcc/util"
1515
"github.com/evcc-io/evcc/util/config"
16+
"github.com/evcc-io/evcc/util/redact"
1617
"github.com/spf13/cobra"
1718
)
1819

@@ -67,7 +68,7 @@ func runDump(cmd *cobra.Command, args []string) {
6768

6869
var redacted string
6970
if src, err := os.ReadFile(cfgFile); err == nil {
70-
redacted = redact(string(src))
71+
redacted = redact.String(string(src))
7172
}
7273

7374
tmpl := template.Must(

cmd/helper.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,6 @@ func unwrap(err error) (res []string) {
4949
return
5050
}
5151

52-
func redact(src string) string {
53-
return util.RedactConfigString(src)
54-
}
55-
56-
func redactMap(src map[string]any) map[string]any {
57-
return util.RedactConfigMap(src)
58-
}
59-
6052
// fatal logs a fatal error and runs shutdown functions before terminating
6153
func fatal(err error) {
6254
log.FATAL.Println(err)

cmd/root_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"reflect"
77
"strings"
88
"testing"
9+
10+
"github.com/evcc-io/evcc/util/redact"
911
)
1012

1113
func TestUnwrap(t *testing.T) {
@@ -23,17 +25,17 @@ func TestRedact(t *testing.T) {
2325
sponsortoken: geheim
2426
user: geheim
2527
password: geheim
26-
secret: geheim
28+
clientsecret: geheim
2729
token:
28-
access: geheim
29-
refresh: geheim
30+
accesstoken: geheim
31+
refreshtoken: geheim
3032
pin: geheim
3133
mac: geheim
32-
secret: geheim # comment
33-
secret : geheim
34+
clientsecret: geheim # comment
35+
clientsecret : geheim
3436
`
3537

36-
if res := redact(secret); strings.Contains(res, "geheim") || !strings.Contains(res, "public") {
38+
if res := redact.String(secret); strings.Contains(res, "geheim") || !strings.Contains(res, "public") {
3739
t.Errorf("secret exposed: %v", res)
3840
}
3941
}

playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default defineConfig({
1515
video: "on-first-retry",
1616
screenshot: "only-on-failure",
1717
permissions: ["clipboard-write"],
18+
actionTimeout: 20000, // 20s for individual actions
1819
},
1920
projects: [
2021
{

server/http_config_device_handler.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import (
2323
"go.yaml.in/yaml/v4"
2424
)
2525

26-
func devicesConfig[T any](class templates.Class, h config.Handler[T]) ([]map[string]any, error) {
26+
func devicesConfig[T any](class templates.Class, h config.Handler[T], hidePrivate bool) ([]map[string]any, error) {
2727
var res []map[string]any
2828

2929
for _, dev := range h.Devices() {
30-
dc, err := deviceConfigMap(class, dev)
30+
dc, err := deviceConfigMap(class, dev, hidePrivate)
3131
if err != nil {
3232
return nil, err
3333
}
@@ -54,20 +54,23 @@ func devicesConfigHandler(w http.ResponseWriter, r *http.Request) {
5454
return
5555
}
5656

57+
// Check if private data should be hidden (default: true, showing private data)
58+
hidePrivate := r.URL.Query().Get("private") == "false"
59+
5760
var res []map[string]any
5861

5962
switch class {
6063
case templates.Meter:
61-
res, err = devicesConfig(class, config.Meters())
64+
res, err = devicesConfig(class, config.Meters(), hidePrivate)
6265

6366
case templates.Charger:
64-
res, err = devicesConfig(class, config.Chargers())
67+
res, err = devicesConfig(class, config.Chargers(), hidePrivate)
6568

6669
case templates.Vehicle:
67-
res, err = devicesConfig(class, config.Vehicles())
70+
res, err = devicesConfig(class, config.Vehicles(), hidePrivate)
6871

6972
case templates.Circuit:
70-
res, err = devicesConfig(class, config.Circuits())
73+
res, err = devicesConfig(class, config.Circuits(), hidePrivate)
7174
}
7275

7376
if err != nil {
@@ -78,7 +81,7 @@ func devicesConfigHandler(w http.ResponseWriter, r *http.Request) {
7881
jsonWrite(w, res)
7982
}
8083

81-
func deviceConfigMap[T any](class templates.Class, dev config.Device[T]) (map[string]any, error) {
84+
func deviceConfigMap[T any](class templates.Class, dev config.Device[T], hidePrivate bool) (map[string]any, error) {
8285
conf := dev.Config()
8386

8487
dc := map[string]any{
@@ -102,7 +105,7 @@ func deviceConfigMap[T any](class templates.Class, dev config.Device[T]) (map[st
102105
}
103106

104107
if conf.Type == typeTemplate {
105-
params, err := sanitizeMasked(class, conf.Other)
108+
params, err := sanitizeMasked(class, conf.Other, hidePrivate)
106109
if err != nil {
107110
return nil, err
108111
}
@@ -146,13 +149,13 @@ func deviceConfigMap[T any](class templates.Class, dev config.Device[T]) (map[st
146149
return dc, nil
147150
}
148151

149-
func deviceConfig[T any](class templates.Class, id int, h config.Handler[T]) (map[string]any, error) {
152+
func deviceConfig[T any](class templates.Class, id int, h config.Handler[T], hidePrivate bool) (map[string]any, error) {
150153
dev, err := h.ByName(config.NameForID(id))
151154
if err != nil {
152155
return nil, err
153156
}
154157

155-
return deviceConfigMap(class, dev)
158+
return deviceConfigMap(class, dev, hidePrivate)
156159
}
157160

158161
// deviceConfigHandler returns a device configuration by class
@@ -171,20 +174,23 @@ func deviceConfigHandler(w http.ResponseWriter, r *http.Request) {
171174
return
172175
}
173176

177+
// Check if private data should be hidden (default: true, showing private data)
178+
hidePrivate := r.URL.Query().Get("private") == "false"
179+
174180
var res map[string]any
175181

176182
switch class {
177183
case templates.Meter:
178-
res, err = deviceConfig(class, id, config.Meters())
184+
res, err = deviceConfig(class, id, config.Meters(), hidePrivate)
179185

180186
case templates.Charger:
181-
res, err = deviceConfig(class, id, config.Chargers())
187+
res, err = deviceConfig(class, id, config.Chargers(), hidePrivate)
182188

183189
case templates.Vehicle:
184-
res, err = deviceConfig(class, id, config.Vehicles())
190+
res, err = deviceConfig(class, id, config.Vehicles(), hidePrivate)
185191

186192
case templates.Circuit:
187-
res, err = deviceConfig(class, id, config.Circuits())
193+
res, err = deviceConfig(class, id, config.Circuits(), hidePrivate)
188194
}
189195

190196
if err != nil {

server/http_config_helper.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func filterValidTemplateParams(tmpl *templates.Template, conf map[string]any) ma
133133
return res
134134
}
135135

136-
func sanitizeMasked(class templates.Class, conf map[string]any) (map[string]any, error) {
136+
func sanitizeMasked(class templates.Class, conf map[string]any, hidePrivate bool) (map[string]any, error) {
137137
tmpl, err := templateForConfig(class, conf)
138138
if err != nil {
139139
return nil, err
@@ -142,8 +142,12 @@ func sanitizeMasked(class templates.Class, conf map[string]any) (map[string]any,
142142
res := make(map[string]any, len(conf))
143143

144144
for k, v := range conf {
145-
if i, p := tmpl.ParamByName(k); i >= 0 && p.IsMasked() {
146-
v = masked
145+
if i, p := tmpl.ParamByName(k); i >= 0 {
146+
if p.IsMasked() {
147+
v = masked
148+
} else if hidePrivate && p.IsPrivate() {
149+
v = masked
150+
}
147151
}
148152

149153
res[k] = v

server/http_config_yaml_handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"net/http"
66
"os"
77

8-
"github.com/evcc-io/evcc/util"
8+
"github.com/evcc-io/evcc/util/redact"
99
)
1010

1111
// configYamlHandler returns the redacted evcc.yaml configuration file
@@ -25,7 +25,7 @@ func configYamlHandler(configFilePath string) http.HandlerFunc {
2525
}
2626

2727
// Redact sensitive information
28-
redacted := util.RedactConfigString(string(src))
28+
redacted := redact.String(string(src))
2929

3030
// Return the redacted content as plain text
3131
w.Header().Set("Content-Type", "text/plain; charset=utf-8")

0 commit comments

Comments
 (0)