-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanon.go
177 lines (149 loc) · 3.49 KB
/
anon.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package anon
import (
"crypto/sha512"
"errors"
"fmt"
"reflect"
"strings"
"unicode/utf8"
"github.com/mitchellh/copystructure"
)
const tagName = "anon"
const (
tagStars = "stars"
tagEmpty = "empty"
tagLen = "stars_with_len"
tagInfo = "with_info"
tagSHA512 = "sha512"
emptyErr = "empty"
)
// Marshal anonymises fields that have anon tags, and then runs the given
// function (e.g. json.Marshal()) on the resulting struct.
// Marshal takes a pointer to a struct.
func Marshal(v any, m func(any) ([]byte, error)) ([]byte, error) {
res, err := Anonymise(v)
if err != nil {
return nil, err
}
return m(res)
}
// Anonymise anonymises data according to the "anon" tags it has in it.
// It returns a copy of the given value. Use AnonymiseByRef if you'd
// prefer to pass by reference.
func Anonymise(v any) (res any, err error) {
defer func() {
// I don't trust reflection.
if r := recover(); r != nil {
err = fmt.Errorf("anon: %v", r)
}
}()
// Deep copy, otherwise we risk changing maps/slices inside the struct.
cp, err := copystructure.Copy(v)
if err != nil {
return nil, err
}
val := reflect.ValueOf(cp)
tmp := reflect.New(val.Type())
tmp.Elem().Set(val)
err = anonymise("", tmp)
return tmp.Interface(), err
}
// AnonymiseByRef anonymises the struct that the given pointer points
// to, according to the "anon" tags it has in it.
func AnonymiseByRef(v any) (err error) {
defer func() error {
// I don't trust reflection.
if r := recover(); r != nil {
err = fmt.Errorf("anon: %v", r)
}
return nil
}()
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return errors.New("value must be pointer")
}
return anonymise("", val)
}
func anonymise(tag string, v reflect.Value) error {
switch v.Kind() {
case reflect.Ptr, reflect.Interface:
return anonymise(tag, v.Elem())
case reflect.Struct:
for i := 0; i < v.Type().NumField(); i++ {
tag := v.Type().Field(i).Tag.Get(tagName)
err := anonymise(tag, v.Field(i))
if err != nil {
return err
}
}
case reflect.String:
err := obfuscate(tag, v)
if err != nil {
return err
}
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
err := anonymise(tag, v.Index(i))
if err != nil {
return err
}
}
}
return nil
}
func obfuscate(tag string, v reflect.Value) error {
if tag == "-" || tag == "" {
return nil
}
var a anonymiser
switch tag {
case tagStars:
a = Stars
case tagEmpty:
a = Empty
case tagLen:
a = StarsWithLen
case tagInfo:
a = WithInfo
case tagSHA512:
a = SHA512
default:
return fmt.Errorf("no tag for %s", tag)
}
v.SetString(a(v.String()))
return nil
}
type anonymiser func(string) string
func Stars(string) string {
return "****"
}
// Returns a number of asterisks equal to string length.
func StarsWithLen(s string) string {
return strings.Repeat("*", len(s))
}
func Empty(string) string {
return ""
}
// Returns length and existence of non-ASCII characters in string.
// Do keep in mind that a non-ASCII character will have a length greater than 1.
//
// len("á") == 2
//
// len("a") == 1
func WithInfo(s string) string {
return fmt.Sprintf("len:%d,is_ascii:%t", len(s), isASCII(s))
}
func SHA512(s string) string {
sum := sha512.Sum512([]byte(s))
return string(sum[:])
}
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
// if a byte is greater than RuneSelf, then the following byte is still
// part of the same rune character, meaning it's not ASCII.
if s[i] > utf8.RuneSelf {
return false
}
}
return true
}