Skip to content

Commit d73de43

Browse files
authored
fix: handle setting protected/type-enforced attributes correctly (#433)
This addresses a bug where you couldn't set the `name` attribute as private using the `AttributesBuilder::SetPrivate(key, value)` API. The issue was that this API forwards to an internal `AttributesBuilder::Set(key, value, is_private)` method, which disallowed setting of "protected" attributes. In this PR, I've re-designated attributes as: 1. "protected": they cannot be set using the Set APIs at all - This includes `_meta`, `kind`, and `key`. 3. "typed": they can be set using Set, but must have the correct type - This includes `anonymous` and `name` 5. All other attributes, which don't have any type checking Within (2), the current behavior is that both of these can be marked private, which isn't really correct. In reality only `name` can be redacted. So currently, you could say `SetPrivate("anonymous, true)`, and it'd set it and add it to the private attributes list. Come redaction time, it wouldn't be redacted. To make this totally consistent, we'd need to disallow `SetPrivate("anonymous", ..)` as well as modify the `AddPrivateAttribute` and `AddPrivateAttributes` APIs. Perhaps the behavior in this PR is sufficient, given some documentation that mentions that setting `anonymous` private isn't meaningful.
1 parent bb11ce2 commit d73de43

File tree

4 files changed

+214
-76
lines changed

4 files changed

+214
-76
lines changed

libs/common/include/launchdarkly/attributes_builder.hpp

+26-24
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#pragma once
22

3-
#include <string>
4-
53
#include <launchdarkly/attribute_reference.hpp>
64
#include <launchdarkly/attributes.hpp>
75
#include <launchdarkly/value.hpp>
86

9-
namespace launchdarkly {
7+
#include <map>
8+
#include <string>
109

10+
namespace launchdarkly {
1111
class ContextBuilder;
1212

1313
/**
@@ -22,7 +22,7 @@ template <class BuilderReturn, class BuildType>
2222
class AttributesBuilder final {
2323
friend class ContextBuilder;
2424

25-
public:
25+
public:
2626
/**
2727
* Create an attributes builder with the given kind and key.
2828
* @param builder The context builder associated with this attributes
@@ -31,7 +31,8 @@ class AttributesBuilder final {
3131
* @param key The key for the kind.
3232
*/
3333
AttributesBuilder(BuilderReturn& builder, std::string kind, std::string key)
34-
: key_(std::move(key)), kind_(std::move(kind)), builder_(builder) {}
34+
: builder_(builder), kind_(std::move(kind)), key_(std::move(key)) {
35+
}
3536

3637
/**
3738
* Crate an attributes builder with the specified kind, and pre-populated
@@ -44,9 +45,9 @@ class AttributesBuilder final {
4445
AttributesBuilder(BuilderReturn& builder,
4546
std::string kind,
4647
Attributes const& attributes)
47-
: key_(attributes.Key()),
48+
: builder_(builder),
4849
kind_(std::move(kind)),
49-
builder_(builder),
50+
key_(attributes.Key()),
5051
name_(attributes.Name()),
5152
anonymous_(attributes.Anonymous()),
5253
private_attributes_(attributes.PrivateAttributes()) {
@@ -90,36 +91,36 @@ class AttributesBuilder final {
9091
/**
9192
* Add or update an attribute in the context.
9293
*
93-
* This method cannot be used to set the key, kind, name, anonymous, or
94-
* _meta property of a context. The specific methods on the context builder,
95-
* or attributes builder, should be used.
94+
* This method cannot be used to set the key, kind, or _meta property
95+
* of a context. The anonymous and name properties can be set, but must
96+
* be of the correct types (boolean and string respectively). The specific
97+
* methods on the context builder or attributes builder can also be used
98+
* for these properties.
9699
*
97100
* @param name The name of the attribute.
98101
* @param value The value for the attribute.
99-
* @param private_attribute If the attribute should be considered private:
100-
* that is, the value will not be sent to LaunchDarkly in analytics events.
101102
* @return A reference to the current builder.
102103
*/
103-
AttributesBuilder& Set(std::string name, launchdarkly::Value value);
104+
AttributesBuilder& Set(std::string name, Value value);
104105

105106
/**
106107
* Add or update a private attribute in the context.
107108
*
108-
* This method cannot be used to set the key, kind, name, or anonymous
109-
* property of a context. The specific methods on the context builder, or
110-
* attributes builder, should be used.
109+
* This method cannot be used to set the key, kind, or _meta property
110+
* of a context. The anonymous and name properties can be set, but must
111+
* be of the correct types (boolean and string respectively). The specific
112+
* methods on the context builder or attributes builder can also be used
113+
* for these properties.
111114
*
112115
* Once you have set an attribute private it will remain in the private
113116
* list even if you call `set` afterward. This method is just a convenience
114117
* which also adds the attribute to the `PrivateAttributes`.
115118
*
116119
* @param name The name of the attribute.
117120
* @param value The value for the attribute.
118-
* @param private_attribute If the attribute should be considered private:
119-
* that is, the value will not be sent to LaunchDarkly in analytics events.
120121
* @return A reference to the current builder.
121122
*/
122-
AttributesBuilder& SetPrivate(std::string name, launchdarkly::Value value);
123+
AttributesBuilder& SetPrivate(std::string name, Value value);
123124

124125
/**
125126
* Designate a context attribute, or properties within them, as private:
@@ -150,7 +151,7 @@ class AttributesBuilder final {
150151
* launchdarkly::config::shared::builders::EventsBuilder<SDK>::PrivateAttribute.
151152
*
152153
* The attributes "kind" and "key", and the "_meta" attributes cannot be
153-
* made private.
154+
* made private. The "anonymous" attribute can be marked private, but will not be redacted.
154155
*
155156
* In this example, firstName is marked as private, but lastName is not:
156157
*
@@ -216,7 +217,7 @@ class AttributesBuilder final {
216217
*/
217218
[[nodiscard]] BuildType Build() const { return builder_.Build(); }
218219

219-
private:
220+
private:
220221
BuilderReturn& builder_;
221222

222223
/**
@@ -229,15 +230,16 @@ class AttributesBuilder final {
229230
Attributes BuildAttributes() const;
230231

231232
AttributesBuilder& Set(std::string name,
232-
launchdarkly::Value value,
233+
Value value,
233234
bool private_attribute);
234235

236+
235237
std::string kind_;
236238
std::string key_;
237239
std::string name_;
238240
bool anonymous_ = false;
239241

240-
std::map<std::string, launchdarkly::Value> values_;
242+
std::map<std::string, Value> values_;
241243
AttributeReference::SetType private_attributes_;
242244
};
243-
} // namespace launchdarkly
245+
} // namespace launchdarkly

libs/common/include/launchdarkly/bindings/c/context_builder.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ LDContextBuilder_Attributes_Set(LDContextBuilder builder,
9494
* A subsequent call to @ref LDContextBuilder_Attributes_Set, for the same
9595
* attribute, will not remove the private status.
9696
*
97-
* This method cannot be used to set the `key`, `kind`, `name`, or `anonymous`
98-
* property of a context.
97+
* This method cannot be used to set the `key`, `kind`, or `_meta`
98+
* property of a context. The `anonymous` and `name` properties can be set, but must be of the correct types.
9999
*
100100
* Adding a @ref LDValue to the builder will consume that value.
101101
*
@@ -179,7 +179,7 @@ LDContextBuilder_Attributes_SetAnonymous(LDContextBuilder builder,
179179
* - @ref LDClientConfigBuilder_Events_PrivateAttribute
180180
*
181181
* The attributes "kind" and "key", and the "_meta" attributes cannot be
182-
* made private.
182+
* made private. The "anonymous" attribute can be marked private, but will not be redacted.
183183
*
184184
* @param builder The builder. Must not be NULL.
185185
* @param kind The kind to set the attribute as private for. Must not be NULL.

libs/common/src/attributes_builder.cpp

+84-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#include <launchdarkly/attributes_builder.hpp>
22
#include <launchdarkly/context_builder.hpp>
33

4-
namespace launchdarkly {
4+
#include <set>
5+
#include <unordered_map>
6+
#include <functional>
57

8+
namespace launchdarkly {
69
template <>
710
AttributesBuilder<ContextBuilder, Context>&
811
AttributesBuilder<ContextBuilder, Context>::Name(std::string name) {
@@ -17,33 +20,104 @@ AttributesBuilder<ContextBuilder, Context>::Anonymous(bool anonymous) {
1720
return *this;
1821
}
1922

23+
// These attributes values cannot be set via the public Set/SetPrivate methods.
24+
// 'key' and 'kind' are defined in the AttributesBuilder constructor, whereas
25+
// '_meta' is set internally by the SDK.
26+
std::set<std::string> const& ProtectedAttributes() {
27+
static std::set<std::string> protectedAttrs = {
28+
"key",
29+
"kind",
30+
"_meta"
31+
};
32+
return protectedAttrs;
33+
}
34+
35+
// Utility struct to enforce the type-safe setting of a particular attribute
36+
// via a method on AttributesBuilder.
37+
struct TypedAttribute {
38+
enum Value::Type type;
39+
std::function<void(AttributesBuilder<ContextBuilder, Context>& builder,
40+
Value)> setter;
41+
42+
TypedAttribute(enum Value::Type type,
43+
std::function<void(
44+
AttributesBuilder<ContextBuilder, Context>& builder,
45+
Value const&)> setter)
46+
: type(type), setter(std::move(setter)) {
47+
}
48+
};
49+
50+
// These attribute values, while able to be set via public Set/SetPrivate methods,
51+
// are regarded as special and are type-enforced. Instead of being stored in the
52+
// internal AttributesBuilder::values map, they are stored as individual fields.
53+
// This is because they have special meaning/usage within the LaunchDarkly Platform.
54+
std::unordered_map<std::string, TypedAttribute> const&
55+
TypedAttributes() {
56+
static std::unordered_map<std::string, TypedAttribute> typedAttrs = {
57+
{
58+
"anonymous", {Value::Type::kBool,
59+
[](AttributesBuilder<
60+
ContextBuilder, Context>
61+
& builder,
62+
Value const& value) {
63+
builder.Anonymous(
64+
value.AsBool());
65+
}}
66+
},
67+
{"name", {Value::Type::kString,
68+
[](AttributesBuilder<
69+
ContextBuilder, Context>&
70+
builder,
71+
Value const& value) {
72+
builder.Name(value.AsString());
73+
}}}
74+
};
75+
return typedAttrs;
76+
}
77+
78+
2079
template <>
2180
AttributesBuilder<ContextBuilder, Context>&
2281
AttributesBuilder<ContextBuilder, Context>::Set(std::string name,
2382
Value value,
2483
bool private_attribute) {
25-
if (name == "key" || name == "kind" || name == "anonymous" ||
26-
name == "name" || name == "_meta") {
84+
// Protected attributes cannot be set at all.
85+
if (ProtectedAttributes().count(name) > 0) {
86+
return *this;
87+
}
88+
89+
// Typed attributes can be set only if the value is of the correct type;
90+
// the setting is forwarded to one of AttributeBuilder's dedicated methods.
91+
auto const typed_attr = TypedAttributes().find(name);
92+
const bool is_typed = typed_attr != TypedAttributes().end();
93+
if (is_typed && value.Type() != typed_attr->second.type) {
2794
return *this;
2895
}
2996

97+
if (is_typed) {
98+
typed_attr->second.setter(*this, value);
99+
} else {
100+
values_[name] = std::move(value);
101+
}
102+
30103
if (private_attribute) {
31-
this->private_attributes_.insert(name);
104+
this->private_attributes_.insert(std::move(name));
32105
}
33-
values_[std::move(name)] = std::move(value);
34106
return *this;
35107
}
36108

37109
template <>
38110
AttributesBuilder<ContextBuilder, Context>&
39-
AttributesBuilder<ContextBuilder, Context>::Set(std::string name, Value value) {
111+
AttributesBuilder<ContextBuilder, Context>::Set(
112+
std::string name,
113+
Value value) {
40114
return Set(std::move(name), std::move(value), false);
41115
}
42116

43117
template <>
44118
AttributesBuilder<ContextBuilder, Context>&
45119
AttributesBuilder<ContextBuilder, Context>::SetPrivate(std::string name,
46-
Value value) {
120+
Value value) {
47121
return Set(std::move(name), std::move(value), true);
48122
}
49123

@@ -56,8 +130,8 @@ AttributesBuilder<ContextBuilder, Context>::AddPrivateAttribute(
56130
}
57131

58132
template <>
59-
Attributes AttributesBuilder<ContextBuilder, Context>::BuildAttributes() const {
133+
Attributes AttributesBuilder<
134+
ContextBuilder, Context>::BuildAttributes() const {
60135
return {key_, name_, anonymous_, values_, private_attributes_};
61136
}
62-
63-
} // namespace launchdarkly
137+
} // namespace launchdarkly

0 commit comments

Comments
 (0)