Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/ldclient-rb/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Config
# @option opts [Float] :poll_interval (30) See {#poll_interval}.
# @option opts [Boolean] :stream (true) See {#stream?}.
# @option opts [Boolean] all_attributes_private (false) See {#all_attributes_private}.
# @option opts [Array] :private_attributes See {#private_attributes}.
# @option opts [Enumerable<String>] :private_attributes See {#private_attributes}.
# @option opts [Boolean] :send_events (true) See {#send_events}.
# @option opts [Integer] :context_keys_capacity (1000) See {#context_keys_capacity}.
# @option opts [Float] :context_keys_flush_interval (300) See {#context_keys_flush_interval}.
Expand Down Expand Up @@ -69,7 +69,7 @@ def initialize(opts = {})
@offline = opts.has_key?(:offline) ? opts[:offline] : Config.default_offline
@poll_interval = opts.has_key?(:poll_interval) && opts[:poll_interval] > Config.default_poll_interval ? opts[:poll_interval] : Config.default_poll_interval
@all_attributes_private = opts[:all_attributes_private] || false
@private_attributes = opts[:private_attributes] || []
@private_attributes = Set.new(opts[:private_attributes] || [])
@send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
@compress_events = opts.has_key?(:compress_events) ? opts[:compress_events] : Config.default_compress_events
@context_keys_capacity = opts[:context_keys_capacity] || Config.default_context_keys_capacity
Expand Down Expand Up @@ -255,7 +255,7 @@ def offline?
attr_reader :all_attributes_private

#
# A list of context attribute names that should always be considered private. This means that the
# A set of context attribute names that should always be considered private. This means that the
# attribute values will not be sent to LaunchDarkly in analytics events and will not appear on
# the LaunchDarkly dashboard.
#
Expand All @@ -268,7 +268,7 @@ def offline?
#
# @see https://docs.launchdarkly.com/sdk/features/user-context-config#using-private-attributes
#
# @return [Array<String>]
# @return [Set<String>]
# @see #all_attributes_private
#
attr_reader :private_attributes
Expand Down
13 changes: 5 additions & 8 deletions lib/ldclient-rb/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attr
reference = Reference.create(attribute)
@private_attributes.add(reference) if reference.error.nil?
end
@private_attributes.freeze
@error = error
@contexts = contexts
@is_multi = !contexts.nil?
Expand All @@ -91,12 +92,9 @@ def initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attr
protected attr_reader :name, :anonymous, :attributes

#
# @return [Array<Reference>] Returns the private attributes associated with this LDContext
# @return [Set<Reference>] Returns the private attributes associated with this LDContext. The set is frozen.
#
def private_attributes
# TODO(sc-227265): Return a set instead of an array.
@private_attributes.to_a
end
attr_reader :private_attributes

#
# @return [Boolean] Is this LDContext a multi-kind context?
Expand Down Expand Up @@ -353,8 +351,7 @@ def ==(other)
return false unless self.anonymous == other.anonymous
return false unless self.attributes == other.attributes

# TODO(sc-227265): Calling .to_set is unnecessary once private_attributes are sets.
return false unless self.private_attributes.to_set == other.private_attributes.to_set
return false unless self.private_attributes == other.private_attributes

true
end
Expand Down Expand Up @@ -413,7 +410,7 @@ def to_h

hash[:name] = name unless name.nil?
hash[:anonymous] = anonymous if anonymous
hash[:_meta] = {privateAttributes: private_attributes} unless private_attributes.empty?
hash[:_meta] = {privateAttributes: private_attributes.to_a} unless private_attributes.empty?

hash
end
Expand Down
10 changes: 5 additions & 5 deletions lib/ldclient-rb/impl/context_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ module Impl
class ContextFilter
#
# @param all_attributes_private [Boolean]
# @param private_attributes [Array<String>]
# @param private_attributes [Enumerable<String>]
# @param logger [Logger, nil]
#
def initialize(all_attributes_private, private_attributes, logger = nil)
@all_attributes_private = all_attributes_private
@logger = logger
@non_symbol_name_logged = Concurrent::AtomicBoolean.new(false)

@private_attributes = []
@private_attributes = Set.new
private_attributes.each do |attribute|
reference = LaunchDarkly::Reference.create(attribute)
@private_attributes << reference if reference.error.nil?
Expand Down Expand Up @@ -75,7 +75,7 @@ def filter_redact_anonymous(context)
filtered[:anonymous] = true if anonymous

redacted = []
private_attributes = @private_attributes + context.private_attributes
private_attributes = @private_attributes | context.private_attributes

name = context.get_value(:name)
if !name.nil? && !check_whole_attribute_private(Reference.create_literal(:name), private_attributes, redacted, anonymous && redact_anonymous)
Expand Down Expand Up @@ -103,7 +103,7 @@ def filter_redact_anonymous(context)
# Check if an entire attribute should be redacted.
#
# @param attribute [Reference]
# @param private_attributes [Array<Reference>]
# @param private_attributes [Set<Reference>]
# @param redacted [Array<Symbol>]
# @param redact_all [Boolean]
# @return [Boolean]
Expand All @@ -130,7 +130,7 @@ def filter_redact_anonymous(context)
# @param parent_path [Array<String>, nil]
# @param name [String]
# @param value [any]
# @param private_attributes [Array<Reference>]
# @param private_attributes [Set<Reference>]
# @param redacted [Array<Symbol>]
# @param visited [Array<Hash>]
# @return [any]
Expand Down
8 changes: 8 additions & 0 deletions spec/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,13 @@ module LaunchDarkly
expect(subject.new(omit_anonymous_contexts: true).omit_anonymous_contexts).to eq true
end
end
describe ".private_attributes" do
it "defaults to an empty set" do
expect(subject.new.private_attributes).to eq Set.new
end
it "converts an array to a set" do
expect(subject.new(private_attributes: ["name", "email", "name"]).private_attributes).to eq Set["name", "email"]
end
end
end
end
13 changes: 13 additions & 0 deletions spec/context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,19 @@ module LaunchDarkly
end
end

describe "private attributes" do
it "returns a frozen set of references" do
context = subject.create({ key: 'context-key', kind: 'user', _meta: { privateAttributes: ['name', 'address/city', 'name'] } })
expect(context.private_attributes).to be_a(Set)
expect(context.private_attributes).to be_frozen
expect(context.private_attributes).to eq(Set[LaunchDarkly::Reference.create('name'), LaunchDarkly::Reference.create('address/city')])
end

it "returns an empty set when none are given" do
expect(subject.create({ key: 'context-key', kind: 'user' }).private_attributes).to eq(Set.new)
end
end

describe "equality comparisons" do
it "wrong types are not equal" do
context = subject.create({ key: 'context-key', kind: 'user' })
Expand Down
Loading