Skip to content

Commit 640ac39

Browse files
committed
feat: Redact anonymous attributes within feature events (#246)
1 parent bd30c3b commit 640ac39

File tree

4 files changed

+36
-10
lines changed

4 files changed

+36
-10
lines changed

contract-tests/service.rb

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
'context-comparison',
3939
'polling-gzip',
4040
'inline-context',
41+
'anonymous-redaction',
4142
],
4243
}.to_json
4344
end

lib/ldclient-rb/events.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -481,12 +481,14 @@ def make_output_events(events, summary)
481481
key: event.key,
482482
value: event.value,
483483
}
484+
484485
out[:default] = event.default unless event.default.nil?
485486
out[:variation] = event.variation unless event.variation.nil?
486487
out[:version] = event.version unless event.version.nil?
487488
out[:prereqOf] = event.prereq_of unless event.prereq_of.nil?
488-
out[:context] = @context_filter.filter(event.context)
489+
out[:context] = @context_filter.filter_redact_anonymous(event.context)
489490
out[:reason] = event.reason unless event.reason.nil?
491+
490492
out
491493

492494
when LaunchDarkly::Impl::MigrationOpEvent

lib/ldclient-rb/impl/context_filter.rb

+29-8
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,32 @@ def initialize(all_attributes_private, private_attributes)
2323
# @return [Hash]
2424
#
2525
def filter(context)
26-
return filter_single_context(context, true) unless context.multi_kind?
26+
internal_filter(context, false)
27+
end
28+
29+
#
30+
# Return a hash representation of the provided context with attribute
31+
# redaction applied.
32+
#
33+
# If a context is anonyomous, all attributes will be redacted except
34+
# for key, kind, and anonymous.
35+
#
36+
# @param context [LaunchDarkly::LDContext]
37+
# @return [Hash]
38+
#
39+
def filter_redact_anonymous(context)
40+
internal_filter(context, true)
41+
end
42+
43+
private def internal_filter(context, redact_anonymous)
44+
return filter_single_context(context, true, redact_anonymous) unless context.multi_kind?
2745

2846
filtered = {kind: 'multi'}
2947
(0...context.individual_context_count).each do |i|
3048
c = context.individual_context(i)
3149
next if c.nil?
3250

33-
filtered[c.kind] = filter_single_context(c, false)
51+
filtered[c.kind] = filter_single_context(c, false, redact_anonymous)
3452
end
3553

3654
filtered
@@ -43,22 +61,24 @@ def filter(context)
4361
# @param include_kind [Boolean]
4462
# @return [Hash]
4563
#
46-
private def filter_single_context(context, include_kind)
64+
private def filter_single_context(context, include_kind, redact_anonymous)
4765
filtered = {key: context.key}
4866

4967
filtered[:kind] = context.kind if include_kind
50-
filtered[:anonymous] = true if context.get_value(:anonymous)
68+
69+
anonymous = context.get_value(:anonymous)
70+
filtered[:anonymous] = true if anonymous
5171

5272
redacted = []
5373
private_attributes = @private_attributes.concat(context.private_attributes)
5474

5575
name = context.get_value(:name)
56-
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted)
76+
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted, anonymous && redact_anonymous)
5777
filtered[:name] = name
5878
end
5979

6080
context.get_custom_attribute_names.each do |attribute|
61-
unless check_whole_attribute_private(attribute, private_attributes, redacted)
81+
unless check_whole_attribute_private(attribute, private_attributes, redacted, anonymous && redact_anonymous)
6282
value = context.get_value(attribute)
6383
filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted)
6484
end
@@ -75,10 +95,11 @@ def filter(context)
7595
# @param attribute [Symbol]
7696
# @param private_attributes [Array<Reference>]
7797
# @param redacted [Array<Symbol>]
98+
# @param redact_all [Boolean]
7899
# @return [Boolean]
79100
#
80-
private def check_whole_attribute_private(attribute, private_attributes, redacted)
81-
if @all_attributes_private
101+
private def check_whole_attribute_private(attribute, private_attributes, redacted, redact_all)
102+
if @all_attributes_private || redact_all
82103
redacted << attribute
83104
return true
84105
end

spec/events_spec.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -634,10 +634,12 @@ def identify_event(config, context, timestamp = starting_timestamp)
634634
#
635635
def feature_event(config, flag, context, variation, value, timestamp = starting_timestamp)
636636
context_filter = Impl::ContextFilter.new(config.all_attributes_private, config.private_attributes)
637+
redacted_context = context_filter.filter_redact_anonymous(context)
638+
637639
out = {
638640
kind: 'feature',
639641
creationDate: timestamp,
640-
context: context_filter.filter(context),
642+
context: redacted_context,
641643
key: flag[:key],
642644
variation: variation,
643645
version: flag[:version],

0 commit comments

Comments
 (0)