1 package ldevents
2
3 import (
4 "github.com/launchdarkly/go-sdk-common/v3/ldattr"
5 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
6 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
7
8 "github.com/launchdarkly/go-jsonstream/v3/jwriter"
9 )
10
11
12
13
14 type eventContextFormatter struct {
15 allAttributesPrivate bool
16 privateAttributes map[string]*privateAttrLookupNode
17 }
18
19 type privateAttrLookupNode struct {
20 attribute *ldattr.Ref
21 children map[string]*privateAttrLookupNode
22 }
23
24
25
26
27
28 func newEventContextFormatter(config EventsConfiguration) eventContextFormatter {
29 ret := eventContextFormatter{allAttributesPrivate: config.AllAttributesPrivate}
30 if len(config.PrivateAttributes) != 0 {
31
32
33 ret.privateAttributes = makePrivateAttrLookupData(config.PrivateAttributes)
34 }
35 return ret
36 }
37
38 func makePrivateAttrLookupData(attrRefList []ldattr.Ref) map[string]*privateAttrLookupNode {
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 ret := make(map[string]*privateAttrLookupNode)
59 for _, a := range attrRefList {
60 parentMap := &ret
61 for i := 0; i < a.Depth(); i++ {
62 name := a.Component(i)
63 if *parentMap == nil {
64 *parentMap = make(map[string]*privateAttrLookupNode)
65 }
66 nextNode := (*parentMap)[name]
67 if nextNode == nil {
68 nextNode = &privateAttrLookupNode{}
69 if i == a.Depth()-1 {
70 aa := a
71 nextNode.attribute = &aa
72 }
73 (*parentMap)[name] = nextNode
74 }
75 parentMap = &nextNode.children
76 }
77 }
78 return ret
79 }
80
81
82
83 func (f *eventContextFormatter) WriteContext(w *jwriter.Writer, ec *EventInputContext) {
84 if ec.preserialized != nil {
85 w.Raw(ec.preserialized)
86 return
87 }
88 if ec.context.Err() != nil {
89 w.AddError(ec.context.Err())
90 return
91 }
92 if ec.context.Multiple() {
93 f.writeContextInternalMulti(w, ec)
94 } else {
95 f.writeContextInternalSingle(w, &ec.context, true)
96 }
97 }
98
99 func (f *eventContextFormatter) writeContextInternalSingle(
100 w *jwriter.Writer,
101 c *ldcontext.Context,
102 includeKind bool,
103 ) {
104 obj := w.Object()
105 if includeKind {
106 obj.Name(ldattr.KindAttr).String(string(c.Kind()))
107 }
108
109 obj.Name(ldattr.KeyAttr).String(c.Key())
110
111 optionalAttrNames := make([]string, 0, 50)
112 redactedAttrs := make([]string, 0, 20)
113
114 optionalAttrNames = c.GetOptionalAttributeNames(optionalAttrNames)
115
116 for _, key := range optionalAttrNames {
117 if value := c.GetValue(key); value.IsDefined() {
118 if f.allAttributesPrivate {
119
120
121
122
123
124 escapedAttrName := ldattr.NewLiteralRef(key).String()
125 redactedAttrs = append(redactedAttrs, escapedAttrName)
126 continue
127 }
128 path := make([]string, 0, 10)
129 f.writeFilteredAttribute(w, c, &obj, path, key, value, &redactedAttrs)
130 }
131 }
132
133 if c.Anonymous() {
134 obj.Name(ldattr.AnonymousAttr).Bool(true)
135 }
136
137 anyRedacted := len(redactedAttrs) != 0
138 if anyRedacted {
139 metaJSON := obj.Name("_meta").Object()
140 privateAttrsJSON := metaJSON.Name("redactedAttributes").Array()
141 for _, a := range redactedAttrs {
142 privateAttrsJSON.String(a)
143 }
144 privateAttrsJSON.End()
145 metaJSON.End()
146 }
147
148 obj.End()
149 }
150
151 func (f *eventContextFormatter) writeContextInternalMulti(w *jwriter.Writer, ec *EventInputContext) {
152 obj := w.Object()
153 obj.Name(ldattr.KindAttr).String(string(ldcontext.MultiKind))
154
155 for i := 0; i < ec.context.IndividualContextCount(); i++ {
156 if ic := ec.context.IndividualContextByIndex(i); ic.IsDefined() {
157 obj.Name(string(ic.Kind()))
158 f.writeContextInternalSingle(w, &ic, false)
159 }
160 }
161
162 obj.End()
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 func (f *eventContextFormatter) writeFilteredAttribute(
178 w *jwriter.Writer,
179 c *ldcontext.Context,
180 parentObj *jwriter.ObjectState,
181 parentPath []string,
182 key string,
183 value ldvalue.Value,
184 redactedAttrs *[]string,
185 ) {
186 path := append(parentPath, key)
187
188 isRedacted, nestedPropertiesAreRedacted := f.maybeRedact(c, path, value.Type(), redactedAttrs)
189
190 if value.Type() != ldvalue.ObjectType {
191
192
193 if !isRedacted {
194 parentObj.Name(key)
195 value.WriteToJSONWriter(w)
196 }
197 return
198 }
199
200
201
202
203
204
205 if isRedacted {
206 return
207 }
208 parentObj.Name(key)
209 if !nestedPropertiesAreRedacted {
210 value.WriteToJSONWriter(w)
211 return
212 }
213 subObj := w.Object()
214
215 objectKeys := make([]string, 0, 50)
216 for _, subKey := range value.Keys(objectKeys) {
217 subValue := value.GetByKey(subKey)
218
219 f.writeFilteredAttribute(w, c, &subObj, path, subKey, subValue, redactedAttrs)
220 }
221 subObj.End()
222 }
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244 func (f *eventContextFormatter) maybeRedact(
245 c *ldcontext.Context,
246 attrPath []string,
247 valueType ldvalue.ValueType,
248 redactedAttrs *[]string,
249 ) (bool, bool) {
250
251 redactedAttrRef, nestedPropertiesAreRedacted := f.checkGlobalPrivateAttrRefs(attrPath)
252 if redactedAttrRef != nil {
253 *redactedAttrs = append(*redactedAttrs, redactedAttrRef.String())
254 return true, false
255
256 }
257
258 shouldCheckForNestedProperties := valueType == ldvalue.ObjectType
259
260
261
262 for i := 0; i < c.PrivateAttributeCount(); i++ {
263 a, _ := c.PrivateAttributeByIndex(i)
264 depth := a.Depth()
265 if depth < len(attrPath) {
266
267
268 continue
269 }
270 if !shouldCheckForNestedProperties && depth > len(attrPath) {
271 continue
272 }
273 match := true
274 for j := 0; j < len(attrPath); j++ {
275 name := a.Component(j)
276 if name != attrPath[j] {
277 match = false
278 break
279 }
280 }
281 if match {
282 if depth == len(attrPath) {
283 *redactedAttrs = append(*redactedAttrs, a.String())
284 return true, false
285
286 }
287 nestedPropertiesAreRedacted = true
288 }
289 }
290 return false, nestedPropertiesAreRedacted
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 func (f eventContextFormatter) checkGlobalPrivateAttrRefs(attrPath []string) (
308 redactedAttrRef *ldattr.Ref, nestedPropertiesAreRedacted bool,
309 ) {
310 redactedAttrRef = nil
311 nestedPropertiesAreRedacted = false
312 lookup := f.privateAttributes
313 if lookup == nil {
314 return
315 }
316 for i, pathComponent := range attrPath {
317 nextNode := lookup[pathComponent]
318 if nextNode == nil {
319 break
320 }
321 if i == len(attrPath)-1 {
322 if nextNode.attribute != nil {
323 redactedAttrRef = nextNode.attribute
324 return
325 }
326 nestedPropertiesAreRedacted = true
327 return
328 } else if nextNode.children != nil {
329 lookup = nextNode.children
330 continue
331 }
332 }
333 return
334 }
335
View as plain text