forked from Azure/redact
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredact.go
134 lines (118 loc) · 2.99 KB
/
redact.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package redact
import (
"errors"
"reflect"
)
const (
tagName = "redact"
RedactStrConst = "REDACTED"
)
type redactor func(string) string
var redactors = map[string]redactor{}
// AddRedactor allows for adding custom functionality based on tag values
func AddRedactor(key string, r redactor) {
redactors[key] = r
}
// Redact redacts all strings without the nonsecret tag
func Redact(iface interface{}) error {
ifv := reflect.ValueOf(iface)
if ifv.Kind() != reflect.Ptr {
return errors.New("Not a pointer")
}
ift := reflect.Indirect(ifv).Type()
if ift.Kind() != reflect.Struct {
return nil
}
for i := 0; i < ift.NumField(); i++ {
v := ift.Field(i)
el := reflect.Indirect(ifv.Elem().FieldByName(v.Name))
switch el.Kind() {
case reflect.Slice:
if el.CanInterface() {
elType := getSliceElemType(v.Type)
// allow strings and string pointers
str := ""
if (elType.ConvertibleTo(reflect.TypeOf(str)) && reflect.TypeOf(str).ConvertibleTo(elType)) ||
(elType.ConvertibleTo(reflect.TypeOf(&str)) && reflect.TypeOf(&str).ConvertibleTo(elType)) {
tagVal := v.Tag.Get(tagName)
for i := 0; i < el.Len(); i++ {
el.Index(i).Set(transformValue(tagVal, el.Index(i)))
}
} else {
val := reflect.ValueOf(el.Interface())
for i := 0; i < val.Len(); i++ {
elVal := val.Index(i)
if elVal.Kind() != reflect.Ptr {
elVal = elVal.Addr()
}
Redact(elVal.Interface())
}
}
}
case reflect.Map:
if el.CanInterface() {
val := reflect.ValueOf(el.Interface())
for _, key := range val.MapKeys() {
mapValue := val.MapIndex(key)
mapValuePtr := reflect.New(mapValue.Type())
mapValuePtr.Elem().Set(mapValue)
if mapValuePtr.Elem().CanAddr() {
Redact(mapValuePtr.Elem().Addr().Interface())
}
val.SetMapIndex(key, reflect.Indirect(mapValuePtr))
}
}
case reflect.Struct:
if el.CanAddr() && el.Addr().CanInterface() {
Redact(el.Addr().Interface())
}
case reflect.String:
if el.CanSet() {
tagVal := v.Tag.Get(tagName)
input := el.String()
el.SetString(transformString(input, tagVal))
}
}
}
return nil
}
func getSliceElemType(t reflect.Type) reflect.Type {
var elType reflect.Type
if t.Kind() == reflect.Ptr {
elType = t.Elem().Elem()
} else {
elType = t.Elem()
}
return elType
}
func transformValue(tags string, val reflect.Value) reflect.Value {
if val.Kind() == reflect.Ptr && val.IsNil() {
return val
}
var oldStr string
if val.Kind() == reflect.Ptr {
oldStr = val.Elem().String()
} else {
oldStr = val.String()
}
newStr := transformString(oldStr, tags)
var newVal reflect.Value
if val.Kind() == reflect.Ptr {
newVal = reflect.ValueOf(&newStr)
} else {
newVal = reflect.ValueOf(newStr)
}
return newVal.Convert(val.Type())
}
func transformString(input, tagVal string) string {
switch tagVal {
case "nonsecret":
return input
default:
redactor, ok := redactors[tagVal]
if !ok {
return RedactStrConst
}
return redactor(input)
}
}