Skip to content

Commit b1a8f32

Browse files
authored
fix: Escape attribute names reported in redactedAttributes (#228)
1 parent 70f2c97 commit b1a8f32

2 files changed

Lines changed: 89 additions & 5 deletions

File tree

‎lib/shared/internal/src/main/java/com/launchdarkly/sdk/internal/events/EventContextFormatter.java‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,9 @@ private List<String> writeOrRedactAttribute(
103103
List<String> redacted,
104104
boolean redactAnonymous
105105
) throws IOException {
106-
if (allAttributesPrivate) {
107-
return addOrCreate(redacted, attrName);
108-
} else if (redactAnonymous && c.isAnonymous()) {
109-
return addOrCreate(redacted, attrName);
106+
if (allAttributesPrivate || (redactAnonymous && c.isAnonymous())) {
107+
// An escaped reference keeps a leading slash from reading as a path.
108+
return addOrCreate(redacted, AttributeRef.fromLiteral(attrName).toString());
110109
}
111110
return writeRedactedValue(w, c, 0, attrName, value, null, redacted);
112111
}

‎lib/shared/internal/src/test/java/com/launchdarkly/sdk/internal/events/EventContextFormatterTest.java‎

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,21 @@
2424
public class EventContextFormatterTest extends BaseTest {
2525
private final LDContext context;
2626
private final boolean allAttributesPrivate;
27+
private final boolean redactAnonymous;
2728
private final AttributeRef[] globalPrivateAttributes;
2829
private final String expectedJson;
2930

3031
public EventContextFormatterTest(
3132
String name,
3233
LDContext context,
3334
boolean allAttributesPrivate,
35+
boolean redactAnonymous,
3436
AttributeRef[] globalPrivateAttributes,
3537
String expectedJson
3638
) {
3739
this.context = context;
3840
this.allAttributesPrivate = allAttributesPrivate;
41+
this.redactAnonymous = redactAnonymous;
3942
this.globalPrivateAttributes = globalPrivateAttributes;
4043
this.expectedJson = expectedJson;
4144
}
@@ -53,6 +56,7 @@ public static Iterable<Object[]> data() {
5356
.set("attr1", "value1")
5457
.build(),
5558
false,
59+
false,
5660
new AttributeRef[0],
5761
"{\"kind\": \"org\", \"key\": \"my-key\", \"name\": \"my-name\", \"attr1\": \"value1\"}"
5862
},
@@ -68,6 +72,7 @@ public static Iterable<Object[]> data() {
6872
.build()
6973
),
7074
false,
75+
false,
7176
new AttributeRef[0],
7277
"{" +
7378
"\"kind\": \"multi\"," +
@@ -79,6 +84,7 @@ public static Iterable<Object[]> data() {
7984
"anonymous",
8085
LDContext.builder("my-key").kind("org").anonymous(true).build(),
8186
false,
87+
false,
8288
new AttributeRef[0],
8389
"{\"kind\": \"org\", \"key\": \"my-key\", \"anonymous\": true}"
8490
},
@@ -89,6 +95,7 @@ public static Iterable<Object[]> data() {
8995
.set("attr1", "value1")
9096
.build(),
9197
true,
98+
false,
9299
new AttributeRef[0],
93100
"{" +
94101
"\"kind\": \"org\"," +
@@ -107,6 +114,7 @@ public static Iterable<Object[]> data() {
107114
.privateAttributes("attr2")
108115
.build(),
109116
false,
117+
false,
110118
new AttributeRef[] { AttributeRef.fromLiteral("name") },
111119
"{" +
112120
"\"kind\": \"org\"," +
@@ -125,6 +133,7 @@ public static Iterable<Object[]> data() {
125133
.privateAttributes("/complex/a/b/d", "/complex/a/b/nonexistent-prop", "/complex/f", "/complex/g/g-is-not-an-object")
126134
.build(),
127135
false,
136+
false,
128137
new AttributeRef[] { AttributeRef.fromPath("/address/street") },
129138
"{" +
130139
"\"kind\": \"user\"," +
@@ -135,6 +144,82 @@ public static Iterable<Object[]> data() {
135144
"\"redactedAttributes\": [\"/address/street\", \"/complex/a/b/d\", \"/complex/f\"]" +
136145
"}" +
137146
"}"
147+
},
148+
// A name that starts with a slash is reported as an escaped reference. Any other name
149+
// is already a valid reference and is reported unchanged.
150+
new Object[] {
151+
"all attributes private globally - names needing escaping",
152+
LDContext.builder("my-key").kind("org")
153+
.set("/ssn", "123-45-6789")
154+
.set("/a~b", "secret")
155+
.set("c/d~e", "value")
156+
.build(),
157+
true,
158+
false,
159+
new AttributeRef[0],
160+
"{" +
161+
"\"kind\": \"org\"," +
162+
"\"key\": \"my-key\"," +
163+
"\"_meta\": {" +
164+
"\"redactedAttributes\": [\"/~1a~0b\", \"/~1ssn\", \"c/d~e\"]" +
165+
"}" +
166+
"}"
167+
},
168+
new Object[] {
169+
"redacting anonymous context - names needing escaping",
170+
LDContext.builder("my-key").kind("org")
171+
.anonymous(true)
172+
.name("my-name")
173+
.set("/ssn", "123-45-6789")
174+
.build(),
175+
false,
176+
true,
177+
new AttributeRef[0],
178+
"{" +
179+
"\"kind\": \"org\"," +
180+
"\"key\": \"my-key\"," +
181+
"\"anonymous\": true," +
182+
"\"_meta\": {" +
183+
"\"redactedAttributes\": [\"/~1ssn\", \"name\"]" +
184+
"}" +
185+
"}"
186+
},
187+
new Object[] {
188+
"slash-prefixed attribute name configured as private",
189+
LDContext.builder("my-key").kind("org")
190+
.name("my-name")
191+
.set("/ssn", "123-45-6789")
192+
.build(),
193+
false,
194+
false,
195+
new AttributeRef[] { AttributeRef.fromLiteral("/ssn") },
196+
"{" +
197+
"\"kind\": \"org\"," +
198+
"\"key\": \"my-key\"," +
199+
"\"name\": \"my-name\"," +
200+
"\"_meta\": {" +
201+
"\"redactedAttributes\": [\"/~1ssn\"]" +
202+
"}" +
203+
"}"
204+
},
205+
new Object[] {
206+
"slash-prefixed attribute name private for this context",
207+
LDContext.builder("my-key").kind("org")
208+
.name("my-name")
209+
.set("/ssn", "123-45-6789")
210+
.privateAttributes("/~1ssn")
211+
.build(),
212+
false,
213+
false,
214+
new AttributeRef[0],
215+
"{" +
216+
"\"kind\": \"org\"," +
217+
"\"key\": \"my-key\"," +
218+
"\"name\": \"my-name\"," +
219+
"\"_meta\": {" +
220+
"\"redactedAttributes\": [\"/~1ssn\"]" +
221+
"}" +
222+
"}"
138223
}
139224
);
140225
}
@@ -145,7 +230,7 @@ public void testOutput() throws Exception {
145230
StringWriter sw = new StringWriter();
146231
JsonWriter jw = new JsonWriter(sw);
147232

148-
f.write(context, jw, false);
233+
f.write(context, jw, redactAnonymous);
149234
jw.flush();
150235

151236
String canonicalizedOutput = canonicalizeOutputJson(sw.toString());

0 commit comments

Comments
 (0)