Skip to content
Merged
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
21 changes: 12 additions & 9 deletions src/LaunchDarkly/Impl/Events/EventSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,36 @@ public function serializeEvents(array $events): string

private function filterEvent(array $e): array
{
$isFeatureEvent = ($e['kind'] ?? '') == 'feature';

$ret = [];
foreach ($e as $key => $value) {
if ($key == 'context') {
$ret[$key] = $this->serializeContext($value);
$ret[$key] = $this->serializeContext($value, $isFeatureEvent);
} else {
$ret[$key] = $value;
}
}
return $ret;
}

private function serializeContext(LDContext $context): array
private function serializeContext(LDContext $context, bool $redactAnonymousAttributes): array
{
if ($context->isMultiple()) {
$ret = ['kind' => 'multi'];
for ($i = 0; $i < $context->getIndividualContextCount(); $i++) {
$c = $context->getIndividualContext($i);
if ($c !== null) {
$ret[$c->getKind()] = $this->serializeContextSingleKind($c, false);
$ret[$c->getKind()] = $this->serializeContextSingleKind($c, false, $redactAnonymousAttributes);
}
}
return $ret;
} else {
return $this->serializeContextSingleKind($context, true);
return $this->serializeContextSingleKind($context, true, $redactAnonymousAttributes);
}
}

private function serializeContextSingleKind(LDContext $c, bool $includeKind): array
private function serializeContextSingleKind(LDContext $c, bool $includeKind, bool $redactAnonymousAttributes): array
{
$ret = ['key' => $c->getKey()];
if ($includeKind) {
Expand All @@ -86,11 +88,12 @@ private function serializeContextSingleKind(LDContext $c, bool $includeKind): ar
}
$redacted = [];
$allPrivate = array_merge($this->_privateAttributes, $c->getPrivateAttributes() ?? []);
if ($c->getName() !== null && !$this->checkWholeAttributePrivate('name', $allPrivate, $redacted)) {
$redactAllAttributes = $this->_allAttributesPrivate || ($redactAnonymousAttributes && $c->isAnonymous());
if ($c->getName() !== null && !$this->checkWholeAttributePrivate('name', $allPrivate, $redacted, $redactAllAttributes)) {
$ret['name'] = $c->getName();
}
foreach ($c->getCustomAttributeNames() as $attr) {
if (!$this->checkWholeAttributePrivate($attr, $allPrivate, $redacted)) {
if (!$this->checkWholeAttributePrivate($attr, $allPrivate, $redacted, $redactAllAttributes)) {
$value = $c->get($attr);
$ret[$attr] = self::redactJsonValue(null, $attr, $value, $allPrivate, $redacted);
}
Expand All @@ -101,9 +104,9 @@ private function serializeContextSingleKind(LDContext $c, bool $includeKind): ar
return $ret;
}

private function checkWholeAttributePrivate(string $attr, array $allPrivate, array &$redactedOut): bool
private function checkWholeAttributePrivate(string $attr, array $allPrivate, array &$redactedOut, bool $redactAllAttributes): bool
{
if ($this->_allAttributesPrivate) {
if ($redactAllAttributes) {
$redactedOut[] = $attr;
return true;
}
Expand Down
4 changes: 3 additions & 1 deletion test-service/TestService.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ public function getStatus(): array
'context-type',
'secure-mode-hash',
'migrations',
'event-sampling'
'event-sampling',
'inline-context',
'anonymous-redaction'
],
'clientVersion' => \LaunchDarkly\LDClient::VERSION
];
Expand Down
107 changes: 96 additions & 11 deletions tests/Impl/Events/EventSerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ private function getContext(): LDContext
->set('firstName', 'Sue')
->build();
}

private function getContextSpecifyingOwnPrivateAttr()
{
return LDContext::builder('abc')
Expand All @@ -26,7 +26,7 @@ private function getContextSpecifyingOwnPrivateAttr()
->private('dizzle')
->build();
}

private function getFullContextResult()
{
return [
Expand All @@ -37,7 +37,7 @@ private function getFullContextResult()
'dizzle' => 'ghi'
];
}

private function getContextResultWithAllAttrsHidden()
{
return [
Expand All @@ -48,7 +48,7 @@ private function getContextResultWithAllAttrsHidden()
]
];
}

private function getContextResultWithSomeAttrsHidden()
{
return [
Expand All @@ -60,7 +60,7 @@ private function getContextResultWithSomeAttrsHidden()
]
];
}

private function getContextResultWithOwnSpecifiedAttrHidden()
{
return [
Expand All @@ -73,7 +73,7 @@ private function getContextResultWithOwnSpecifiedAttrHidden()
]
];
}

private function makeEvent($context)
{
return [
Expand All @@ -83,14 +83,14 @@ private function makeEvent($context)
'context' => $context
];
}

private function getJsonForContextBySerializingEvent($user)
{
$es = new EventSerializer([]);
$event = $this->makeEvent($user);
return json_decode($es->serializeEvents([$event]), true)[0]['context'];
}

public function testAllContextAttrsSerialized()
{
$es = new EventSerializer([]);
Expand All @@ -108,7 +108,92 @@ public function testAllContextAttrsPrivate()
$expected = $this->makeEvent($this->getContextResultWithAllAttrsHidden());
$this->assertEquals([$expected], json_decode($json, true));
}


public function testRedactsAllAttributesFromAnonymousContextWithFeatureEvent()
{
$anonymousContext = LDContext::builder('abc')
->anonymous(true)
->set('bizzle', 'def')
->set('dizzle', 'ghi')
->set('firstName', 'Sue')
->build();

$es = new EventSerializer([]);
$event = $this->makeEvent($anonymousContext);
$event['kind'] = 'feature';
$json = $es->serializeEvents([$event]);

// But we redact all attributes when the context is anonymous
$expectedContextOutput = $this->getContextResultWithAllAttrsHidden();
$expectedContextOutput['anonymous'] = true;

$expected = $this->makeEvent($expectedContextOutput);
$expected['kind'] = 'feature';

$this->assertEquals([$expected], json_decode($json, true));
}

public function testDoesNotRedactAttributesFromAnonymousContextWithNonFeatureEvent()
{
$anonymousContext = LDContext::builder('abc')
->anonymous(true)
->set('bizzle', 'def')
->set('dizzle', 'ghi')
->set('firstName', 'Sue')
->build();

$es = new EventSerializer([]);
$event = $this->makeEvent($anonymousContext);
$json = $es->serializeEvents([$event]);

// But we redact all attributes when the context is anonymous
$expectedContextOutput = $this->getFullContextResult();
$expectedContextOutput['anonymous'] = true;

$expected = $this->makeEvent($expectedContextOutput);

$this->assertEquals([$expected], json_decode($json, true));
}

public function testRedactsAllAttributesOnlyIfContextIsAnonymous()
{
$userContext = LDContext::builder('user-key')
->kind('user')
->anonymous(true)
->name('Example user')
->build();

$orgContext = LDContext::builder('org-key')
->kind('org')
->anonymous(false)
->name('Example org')
->build();

$multiContext = LDContext::createMulti($userContext, $orgContext);

$es = new EventSerializer([]);
$event = $this->makeEvent($multiContext);
$event['kind'] = 'feature';
$json = $es->serializeEvents([$event]);

$expectedContextOutput = [
'kind' => 'multi',
'user' => [
'key' => 'user-key',
'anonymous' => true,
'_meta' => ['redactedAttributes' => ['name']]
],
'org' => [
'key' => 'org-key',
'name' => 'Example org',
],
];
$expected = $this->makeEvent($expectedContextOutput);
$expected['kind'] = 'feature';

$this->assertEquals([$expected], json_decode($json, true));
}

public function testSomeContextAttrsPrivate()
{
$es = new EventSerializer(['private_attribute_names' => ['firstName', 'bizzle']]);
Expand All @@ -117,7 +202,7 @@ public function testSomeContextAttrsPrivate()
$expected = $this->makeEvent($this->getContextResultWithSomeAttrsHidden());
$this->assertEquals([$expected], json_decode($json, true));
}

public function testPerContextPrivateAttr()
{
$es = new EventSerializer([]);
Expand All @@ -135,7 +220,7 @@ public function testPerContextPrivateAttrPlusGlobalPrivateAttrs()
$expected = $this->makeEvent($this->getContextResultWithAllAttrsHidden());
$this->assertEquals([$expected], json_decode($json, true));
}

public function testObjectPropertyRedaction()
{
$es = new EventSerializer(['private_attribute_names' => ['/b/prop1', '/c/prop2/sub1']]);
Expand Down