Skip to content

Commit d334861

Browse files
committed
feat: Redact anonymous attributes within feature events (#120)
1 parent e5c6cc4 commit d334861

File tree

4 files changed

+96
-12
lines changed

4 files changed

+96
-12
lines changed

src/ldclient_context_filter.erl

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
%% API
1010
-export([
11-
format_context_for_event/2
11+
format_context_for_event/2,
12+
format_context_for_event_with_anonyous_redaction/2
1213
]).
1314

1415
-ifdef(TEST).
@@ -25,11 +26,35 @@
2526
-spec format_context_for_event(PrivateAttributes :: [ldclient_attribute_reference:attribute_reference()] | all,
2627
Context :: ldclient_context:context()
2728
) -> Formatted :: map().
28-
format_context_for_event(PrivateAttributes, #{kind := <<"multi">>} = Context) ->
29+
format_context_for_event(PrivateAttributes, Context) ->
30+
internal_format_context_for_event(PrivateAttributes, Context, false).
31+
32+
%% @doc Format a context for events.
33+
%%
34+
%% Should produce an event schema v4 formatted context.
35+
%%
36+
%% The private attributes specified in the context, and those included in the private attributes parameter, will be
37+
%% removed from the context. Additionally they will be added to the redacted attributes list in _meta.
38+
%%
39+
%% If a provided context is anonymous, all attributes will be redacted except for key, kind, and anonymous.
40+
%% @end
41+
-spec format_context_for_event_with_anonyous_redaction(PrivateAttributes :: [ldclient_attribute_reference:attribute_reference()] | all,
42+
Context :: ldclient_context:context()
43+
) -> Formatted :: map().
44+
format_context_for_event_with_anonyous_redaction(PrivateAttributes, Context) ->
45+
internal_format_context_for_event(PrivateAttributes, Context, true).
46+
47+
-spec internal_format_context_for_event(PrivateAttributes :: [ldclient_attribute_reference:attribute_reference()] | all,
48+
Context :: ldclient_context:context(),
49+
RedactAnonymous :: boolean()
50+
) -> Formatted :: map().
51+
internal_format_context_for_event(PrivateAttributes, #{kind := <<"multi">>} = Context, RedactAnonymous) ->
2952
maps:fold(fun(Key, Value, Acc) ->
30-
Acc#{ensure_binary(Key) => format_context_part(PrivateAttributes, Key, Value)}
53+
Acc#{ensure_binary(Key) => format_context_part(PrivateAttributes, Key, Value, RedactAnonymous)}
3154
end, #{}, Context);
32-
format_context_for_event(PrivateAttributes, Context) ->
55+
internal_format_context_for_event(_PrivateAttributes, #{anonymous := true} = Context, true) ->
56+
internal_format_context_for_event(all, Context, false);
57+
internal_format_context_for_event(PrivateAttributes, Context, _RedactAnonymous) ->
3358
Attributes = maps:get(attributes, Context, null),
3459
ContextPrivateAttributes = lists:map(fun(Value) ->
3560
ldclient_attribute_reference:new(Value)
@@ -122,11 +147,12 @@ can_redact(_Components) -> true.
122147

123148
-spec format_context_part(PrivateAttributes :: [ldclient_attribute_reference:attribute_reference()],
124149
Key :: string() | atom(),
125-
Value :: map() | binary()
150+
Value :: map() | binary(),
151+
RedactAnonymous :: boolean()
126152
) -> map() | binary().
127-
format_context_part(_PrivateAttributes, kind, Value) -> Value;
128-
format_context_part(PrivateAttributes, _Key, Value) ->
129-
format_context_for_event(PrivateAttributes, Value).
153+
format_context_part(_PrivateAttributes, kind, Value, _RedactAnonymous) -> Value;
154+
format_context_part(PrivateAttributes, _Key, Value, RedactAnonymous) ->
155+
internal_format_context_for_event(PrivateAttributes, Value, RedactAnonymous).
130156

131157
-spec ensure_binary(Value :: binary() | atom()) -> binary().
132158
ensure_binary(Value) when is_atom(Value) -> atom_to_binary(Value, utf8);

src/ldclient_event_process_server.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ maybe_set_reason(_Event, OutputEvent) ->
230230
-spec format_event_set_context(binary(), ldclient_context:context(), map(), ldclient_config:private_attributes()) -> map().
231231
format_event_set_context(<<"feature">>, Context, OutputEvent, GlobalPrivateAttributes) ->
232232
OutputEvent#{
233-
<<"context">> => ldclient_context_filter:format_context_for_event(GlobalPrivateAttributes, Context)
233+
<<"context">> => ldclient_context_filter:format_context_for_event_with_anonyous_redaction(GlobalPrivateAttributes, Context)
234234
};
235235
format_event_set_context(<<"debug">>, Context, OutputEvent, GlobalPrivateAttributes) ->
236236
OutputEvent#{

test-service/src/ts_service_request_handler.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ get_service_detail(Req, State) ->
7474
<<"tags">>,
7575
<<"server-side-polling">>,
7676
<<"user-type">>,
77-
<<"inline-context">>
77+
<<"inline-context">>,
78+
<<"anonymous-redaction">>
7879
],
7980
<<"clientVersion">> => ldclient_config:get_version()
8081
}),

test/ldclient_context_filter_SUITE.erl

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
redacts_from_meta_single_context/1,
1818
handles_missing_attributes/1,
1919
can_redact_all_attributes_single_context/1,
20-
can_redact_all_attributes_multi_context/1
20+
can_redact_all_attributes_multi_context/1,
21+
can_redact_single_context_anonymous_attributes/1,
22+
can_redact_multi_context_anonymous_attributes/1
2123
]).
2224

2325
%%====================================================================
@@ -33,7 +35,9 @@ all() ->
3335
redacts_from_meta_single_context,
3436
handles_missing_attributes,
3537
can_redact_all_attributes_single_context,
36-
can_redact_all_attributes_multi_context
38+
can_redact_all_attributes_multi_context,
39+
can_redact_single_context_anonymous_attributes,
40+
can_redact_multi_context_anonymous_attributes
3741
].
3842

3943
init_per_suite(Config) ->
@@ -262,3 +266,56 @@ can_redact_all_attributes_multi_context(_) ->
262266
<<"_meta">> := #{<<"redactedAttributes">> := [<<"anAttribute">>, <<"nested">>]}
263267
}
264268
} = ldclient_context_filter:format_context_for_event(all, TestContext).
269+
270+
can_redact_single_context_anonymous_attributes(_) ->
271+
TestContext =
272+
ldclient_context:set(name, <<"the-name">>,
273+
ldclient_context:set(anonymous, true,
274+
ldclient_context:set(<<"org">>, <<"anAttribute">>, <<"aValue">>,
275+
ldclient_context:set(<<"org">>, <<"nested">>, #{
276+
<<"key1">> => <<"value1">>,
277+
<<"key2">> => <<"value2">>
278+
},
279+
ldclient_context:new(<<"org-key">>, <<"org">>))))),
280+
#{
281+
<<"kind">> := <<"org">>,
282+
<<"key">> := <<"org-key">>,
283+
<<"anonymous">> := true,
284+
<<"_meta">> := #{
285+
<<"redactedAttributes">> := [
286+
<<"name">>,
287+
<<"anAttribute">>,
288+
<<"nested">>
289+
]
290+
}
291+
} = ldclient_context_filter:format_context_for_event_with_anonyous_redaction([], TestContext).
292+
293+
can_redact_multi_context_anonymous_attributes(_) ->
294+
TestContext = ldclient_context:new_multi_from([
295+
ldclient_context:set(<<"org">>, <<"anAttribute">>, <<"aValue">>,
296+
ldclient_context:set(anonymous, true,
297+
ldclient_context:set(<<"org">>, <<"nested">>, #{
298+
<<"key1">> => <<"value1">>,
299+
<<"key2">> => <<"value2">>
300+
},
301+
ldclient_context:new(<<"org-key">>, <<"org">>)))),
302+
ldclient_context:set(<<"user">>, <<"anAttribute">>, <<"aValue">>,
303+
ldclient_context:set(<<"user">>, <<"nested">>, #{
304+
<<"key1">> => <<"value1">>,
305+
<<"key2">> => <<"value2">>
306+
},
307+
ldclient_context:new(<<"user-key">>, <<"user">>)))
308+
]),
309+
#{
310+
<<"kind">> := <<"multi">>,
311+
<<"org">> := #{
312+
<<"key">> := <<"org-key">>,
313+
<<"anonymous">> := true,
314+
<<"_meta">> := #{<<"redactedAttributes">> := [<<"anAttribute">>, <<"nested">>]}
315+
},
316+
<<"user">> := #{
317+
<<"key">> := <<"user-key">>,
318+
<<"anAttribute">> := <<"aValue">>,
319+
<<"nested">> := #{<<"key1">> := <<"value1">>, <<"key2">> := <<"value2">>}
320+
}
321+
} = ldclient_context_filter:format_context_for_event_with_anonyous_redaction([], TestContext).

0 commit comments

Comments
 (0)