Skip to content

Commit

Permalink
feat: Redact anonymous attributes within feature events
Browse files Browse the repository at this point in the history
  • Loading branch information
keelerm84 committed Jan 17, 2024
1 parent 1e14e78 commit 8abc04a
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 18 deletions.
3 changes: 1 addition & 2 deletions rawsrc/LaunchDarklyClient.brs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ function LaunchDarklyClient(launchDarklyParamConfig as Object, context as Object
launchDarklyLocalThis = {
private: {
context: context,
encodedContext: LaunchDarklyContextEncode(context, true, launchDarklyParamConfig),

config: launchDarklyParamConfig,
messagePort: launchDarklyParamMessagePort,
Expand Down Expand Up @@ -324,7 +323,7 @@ function LaunchDarklyClient(launchDarklyParamConfig as Object, context as Object

preparePolling: function() as Void
launchDarklyLocalBuffer = createObject("roByteArray")
launchDarklyLocalBuffer.fromAsciiString(FormatJSON(LaunchDarklyContextEncode(m.context, false)))
launchDarklyLocalBuffer.fromAsciiString(FormatJSON(NewLaunchDarklyContextFilter().filter(m.context, true)))
launchDarklyLocalContextBase64JSON = launchDarklyLocalBuffer.toBase64String()
launchDarklyLocalUrl = m.config.private.appURI + "/msdk/evalx/contexts/" + launchDarklyLocalContextBase64JSON

Expand Down
29 changes: 17 additions & 12 deletions rawsrc/LaunchDarklyContext.brs
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,11 @@ function LaunchDarklyContextUtilities(createContext as Function) as Object
}
end function

function LaunchDarklyContextEncode(context as Object, redact as Boolean, config = invalid as Object) as Object
if redact then
return LaunchDarklyContextFilter(config.private.allAttributesPrivate, config.private.privateAttributeNames.Keys()).filter(context)
function NewLaunchDarklyContextFilter(config = invalid as Object) as Object
if config <> invalid then
return LaunchDarklyContextFilter(config.private.allAttributesPrivate, config.private.privateAttributeNames.Keys())
else
return LaunchDarklyContextFilter(false, []).filter(context, true)
return LaunchDarklyContextFilter(false, [])
end if
end function

Expand All @@ -590,16 +590,21 @@ function LaunchDarklyContextFilter(allAttributesPrivate as Boolean, privateAttri
allAttributesPrivate: allAttributesPrivate
privateAttributes: privateAttributes,

filterSingleContext: function(context as Object, includeKind as Boolean, includePrivateAttributes as Boolean) as Object
filterSingleContext: function(context as Object, includeKind as Boolean, includePrivateAttributes as Boolean, redactAnonymous as Boolean) as Object
filtered = {key: context.key()}

if includeKind then
filtered["kind"] = context.kind()
end if

redactAll = m.allAttributesPrivate
anonymous = context.getValue("anonymous")
if anonymous = true then
filtered["anonymous"] = true

if redactAnonymous = true then
redactAll = true
end if
end if

privateAttributes = []
Expand All @@ -617,12 +622,12 @@ function LaunchDarklyContextFilter(allAttributesPrivate as Boolean, privateAttri

redacted = []
name = context.getValue("name")
if name <> invalid and m.checkWholeAttributePrivate("name", privateAttributes, redacted) = false
if name <> invalid and m.checkWholeAttributePrivate("name", privateAttributes, redacted, redactAll) = false
filtered["name"] = name
end if

for each attribute in context.getCustomAttributeNames()
if m.checkWholeAttributePrivate(attribute, privateAttributes, redacted) = false
if m.checkWholeAttributePrivate(attribute, privateAttributes, redacted, redactAll) = false
value = context.getValue(attribute)
redactedValue = m.redactJsonValue(invalid, attribute, value, privateAttributes, redacted)

Expand All @@ -645,8 +650,8 @@ function LaunchDarklyContextFilter(allAttributesPrivate as Boolean, privateAttri
return filtered
end function,

checkWholeAttributePrivate: function(attribute as String, privateAttributes as Object, redacted as Object) as Object
if m.allAttributesPrivate then
checkWholeAttributePrivate: function(attribute as String, privateAttributes as Object, redacted as Object, redactAll as Boolean) as Object
if redactAll then
redacted.Push(attribute)
return true
end if
Expand Down Expand Up @@ -714,9 +719,9 @@ function LaunchDarklyContextFilter(allAttributesPrivate as Boolean, privateAttri
end function
},

filter: function(context as Object, includePrivateAttributes = false As Boolean) as Object
filter: function(context as Object, includePrivateAttributes = false As Boolean, redactAnonymous = false As Boolean) as Object
if context.isMulti() = false then
return m.private.filterSingleContext(context, true, includePrivateAttributes)
return m.private.filterSingleContext(context, true, includePrivateAttributes, redactAnonymous)
end if

filtered = {kind: "multi"}
Expand All @@ -727,7 +732,7 @@ function LaunchDarklyContextFilter(allAttributesPrivate as Boolean, privateAttri
continue for
end if

filtered[c.kind()] = m.private.filterSingleContext(c, false, includePrivateAttributes)
filtered[c.kind()] = m.private.filterSingleContext(c, false, includePrivateAttributes, redactAnonymous)
end for

return filtered
Expand Down
8 changes: 6 additions & 2 deletions rawsrc/LaunchDarklyEventProcessor.brs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ function LaunchDarklyEventProcessor(launchDarklyParamConfig as Object, context a
util: LaunchDarklyUtility(),

context: context,
encodedContext: LaunchDarklyContextEncode(context, true, launchDarklyParamConfig),
encodedContext: NewLaunchDarklyContextFilter(launchDarklyParamConfig).filter(context),
anonymousEncodedContext: NewLaunchDarklyContextFilter(launchDarklyParamConfig).filter(context, false, true),

events: createObject("roArray", 0, true),
summary: {},
Expand Down Expand Up @@ -43,6 +44,8 @@ function LaunchDarklyEventProcessor(launchDarklyParamConfig as Object, context a

if isDebugEvent then
launchDarklyLocalEvent.kind = "debug"
else
launchDarklyLocalEvent.context = m.anonymousEncodedContext
end if

launchDarklyLocalEvent.key = launchDarklyParamBundle.flagKey
Expand Down Expand Up @@ -196,7 +199,8 @@ function LaunchDarklyEventProcessor(launchDarklyParamConfig as Object, context a

identify: function(context as Object) as Void
m.private.context = context
m.private.encodedContext = LaunchDarklyContextEncode(m.private.context, true, m.private.config)
m.private.encodedContext = NewLaunchDarklyContextFilter(m.private.config).filter(m.private.context)
m.private.anonymousEncodedContext = NewLaunchDarklyContextFilter(m.private.config).filter(m.private.context, false, true)

m.private.enqueueEvent(m.private.makeIdentifyEvent(context))
end function,
Expand Down
2 changes: 1 addition & 1 deletion rawsrc/LaunchDarklyStreamClient.brs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function LaunchDarklyStreamClient(launchDarklyParamConfig as Object, launchDarkl
if m.config.private.offline = false then
m.config.private.logger.debug("stream client starting handshake transfer")

encodedContext = FormatJSON(LaunchDarklyContextEncode(m.context, false))
encodedContext = FormatJSON(NewLaunchDarklyContextFilter().filter(m.context, false))
m.handshakeTransfer.asyncPostFromString(encodedContext)
m.stage = m.stageMap.handshake

Expand Down
1 change: 1 addition & 0 deletions src/contract-tests/components/HttpServerTask.brs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ function Handler(clients as Object, launchDarklyNode as Object) as Object
"tags",
"user-type",
"inline-context",
"anonymous-redaction",
]

return m.makeResponse(200, "OK", status)
Expand Down
2 changes: 1 addition & 1 deletion src/test/source/tests/Test__Context.brs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ function TestCase__Context_SingleKind_RedactsNestedAttributes() as String
context = LaunchDarklyCreateContext({"key": "my-key", "kind": "org", "nested": {"prop1": "remove me", "prop2": "keep me"}, "_meta": {"privateAttributes": ["/nested/prop1"]}})
config = LaunchDarklyConfig("mob")

encoded = LaunchDarklyContextEncode(context, true, config)
encoded = NewLaunchDarklyContextFilter(config).filter(context)
r = m.assertTrue(encoded.DoesExist("nested"))
if r <> "" then
return r
Expand Down

0 comments on commit 8abc04a

Please sign in to comment.