From 7109a7341068955c732622b530fdbf1d4e3922bb Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Wed, 26 Aug 2026 14:33:21 +0200 Subject: [PATCH] feat(pii): collect request information --- src/DataCollection/KeyValueDataFilter.php | 9 +- src/DataCollection/RequestDataCollector.php | 177 +++++++++++++ src/Integration/RequestIntegration.php | 95 ++++--- src/Options.php | 14 +- src/functions.php | 2 +- .../DataCollection/KeyValueDataFilterTest.php | 19 ++ .../RequestDataCollectorTest.php | 242 ++++++++++++++++++ tests/Integration/RequestIntegrationTest.php | 109 ++++++++ tests/OptionsTest.php | 10 +- 9 files changed, 611 insertions(+), 66 deletions(-) create mode 100644 src/DataCollection/RequestDataCollector.php create mode 100644 tests/DataCollection/RequestDataCollectorTest.php diff --git a/src/DataCollection/KeyValueDataFilter.php b/src/DataCollection/KeyValueDataFilter.php index e29efe9d0..0af97c5af 100644 --- a/src/DataCollection/KeyValueDataFilter.php +++ b/src/DataCollection/KeyValueDataFilter.php @@ -96,7 +96,14 @@ public static function filterKeyValueData(array $data, array $behavior): ?array /** @mago-ignore analysis:mixed-assignment */ foreach ($data as $key => $value) { $key = (string) $key; - $filtered[$key] = self::shouldFilterValue($key, $behavior) ? '[Filtered]' : $value; + + if (self::shouldFilterValue($key, $behavior)) { + $filtered[$key] = '[Filtered]'; + } elseif (\is_array($value)) { + $filtered[$key] = self::filterKeyValueData($value, $behavior); + } else { + $filtered[$key] = $value; + } } return $filtered; diff --git a/src/DataCollection/RequestDataCollector.php b/src/DataCollection/RequestDataCollector.php new file mode 100644 index 000000000..1a49e24a4 --- /dev/null +++ b/src/DataCollection/RequestDataCollector.php @@ -0,0 +1,177 @@ +dataCollection = $dataCollection; + $this->sendDefaultPii = $sendDefaultPii; + $this->piiSanitizeHeaders = $piiSanitizeHeaders; + } + + public function usesDataCollection(): bool + { + return $this->dataCollection !== null; + } + + public function shouldCollectUserInfo(): bool + { + if ($this->dataCollection === null) { + return $this->sendDefaultPii; + } + + return $this->dataCollection->shouldCollectUserInfo(); + } + + public function collectQueryString(string $queryString): ?string + { + if ($this->dataCollection === null) { + return $queryString !== '' ? $queryString : null; + } + + if ($queryString === '') { + return null; + } + + return KeyValueDataFilter::filterQueryString( + $queryString, + $this->dataCollection->getUrlQueryParams() + ); + } + + /** + * @param array $cookies + * + * @return array|null + */ + public function collectCookies(array $cookies): ?array + { + if ($this->dataCollection === null) { + return $this->sendDefaultPii ? $cookies : null; + } + + return KeyValueDataFilter::filterKeyValueData( + $cookies, + $this->dataCollection->getCookies() + ); + } + + /** + * @param array $headers + * + * @return array|null + */ + public function collectHeaders(array $headers): ?array + { + if ($this->dataCollection === null) { + return $this->sendDefaultPii ? $headers : $this->sanitizeLegacyHeaders($headers); + } + + return KeyValueDataFilter::filterHeaders( + $headers, + $this->dataCollection->getHttpHeaders()['request'] + ); + } + + public function shouldCollectRequestBody(): bool + { + if ($this->dataCollection === null) { + // Legacy request body collection is controlled by max_request_body_size. + return true; + } + + return \in_array('incomingRequest', $this->dataCollection->getHttpBodies(), true); + } + + /** + * @param mixed $body + * + * @return mixed + */ + public function collectRequestBody($body) + { + if (empty($body) || !$this->shouldCollectRequestBody()) { + return null; + } + + if ($this->dataCollection === null) { + return $body; + } + + if (!\is_array($body)) { + return '[Filtered]'; + } + + return KeyValueDataFilter::filterKeyValueData($body, [ + 'mode' => 'denyList', + 'terms' => [], + ]); + } + + /** + * @param array $headers + * + * @return array + */ + private function sanitizeLegacyHeaders(array $headers): array + { + $sanitized = []; + + foreach ($headers as $name => $values) { + $name = (string) $name; + + if (\in_array(strtolower($name), $this->piiSanitizeHeaders, true)) { + foreach ($values as $headerLine => $headerValue) { + $values[$headerLine] = '[Filtered]'; + } + } + + $sanitized[$name] = $values; + } + + return $sanitized; + } +} diff --git a/src/Integration/RequestIntegration.php b/src/Integration/RequestIntegration.php index 8f4949bcd..befd68e64 100644 --- a/src/Integration/RequestIntegration.php +++ b/src/Integration/RequestIntegration.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; +use Sentry\DataCollection\RequestDataCollector; use Sentry\Event; use Sentry\Exception\JsonException; use Sentry\Options; @@ -48,19 +49,6 @@ final class RequestIntegration implements IntegrationInterface 'always' => \PHP_INT_MAX, ]; - /** - * This constant defines the default list of headers that may contain - * sensitive data and that will be sanitized if sending PII is disabled. - */ - private const DEFAULT_SENSITIVE_HEADERS = [ - 'Authorization', - 'Proxy-Authorization', - 'Cookie', - 'Set-Cookie', - 'X-Forwarded-For', - 'X-Real-IP', - ]; - /** * @var RequestFetcherInterface PSR-7 request fetcher */ @@ -128,69 +116,72 @@ private function processEvent(Event $event, Options $options): void return; } + $collector = new RequestDataCollector( + $options->getDataCollection(), + $options->shouldSendDefaultPii(), + $this->options['pii_sanitize_headers'] + ); + $queryString = $collector->collectQueryString($request->getUri()->getQuery()); + $requestData = [ - 'url' => (string) $request->getUri(), + 'url' => $collector->usesDataCollection() + ? (string) $request->getUri()->withQuery($queryString ?? '') + : (string) $request->getUri(), 'method' => $request->getMethod(), ]; - if ($request->getUri()->getQuery()) { - $requestData['query_string'] = $request->getUri()->getQuery(); + if ($queryString !== null) { + $requestData['query_string'] = $queryString; } - if ($options->shouldSendDefaultPii()) { - $serverParams = $request->getServerParams(); + if ($collector->shouldCollectUserInfo()) { + $this->addRequestUserInfo($event, $request, $requestData); + } - if (!empty($serverParams['REMOTE_ADDR'])) { - $user = $event->getUser(); - $requestData['env']['REMOTE_ADDR'] = $serverParams['REMOTE_ADDR']; + $cookies = $collector->collectCookies($request->getCookieParams()); - if ($user === null) { - $user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']); - } elseif ($user->getIpAddress() === null) { - $user->setIpAddress($serverParams['REMOTE_ADDR']); - } + if ($cookies !== null) { + $requestData['cookies'] = $cookies; + } - $event->setUser($user); - } + $headers = $collector->collectHeaders($request->getHeaders()); - $requestData['cookies'] = $request->getCookieParams(); - $requestData['headers'] = $request->getHeaders(); - } else { - $requestData['headers'] = $this->sanitizeHeaders($request->getHeaders()); + if ($headers !== null) { + $requestData['headers'] = $headers; } - $requestBody = $this->captureRequestBody($options, $request); + if ($collector->shouldCollectRequestBody()) { + $requestBody = $collector->collectRequestBody($this->captureRequestBody($options, $request)); - if (!empty($requestBody)) { - $requestData['data'] = $requestBody; + if ($requestBody !== null) { + $requestData['data'] = $requestBody; + } } $event->setRequest($requestData); } /** - * Removes headers containing potential PII. - * - * @param array $headers Array containing request headers - * - * @return array + * @param array $requestData */ - private function sanitizeHeaders(array $headers): array + private function addRequestUserInfo(Event $event, ServerRequestInterface $request, array &$requestData): void { - foreach ($headers as $name => $values) { - // Cast the header name into a string, to avoid errors on numeric headers - $name = (string) $name; + $serverParams = $request->getServerParams(); - if (!\in_array(strtolower($name), $this->options['pii_sanitize_headers'], true)) { - continue; - } + if (empty($serverParams['REMOTE_ADDR'])) { + return; + } - foreach ($values as $headerLine => $headerValue) { - $headers[$name][$headerLine] = '[Filtered]'; - } + $user = $event->getUser(); + $requestData['env'] = ['REMOTE_ADDR' => $serverParams['REMOTE_ADDR']]; + + if ($user === null) { + $user = UserDataBag::createFromUserIpAddress($serverParams['REMOTE_ADDR']); + } elseif ($user->getIpAddress() === null) { + $user->setIpAddress($serverParams['REMOTE_ADDR']); } - return $headers; + $event->setUser($user); } /** @@ -309,6 +300,6 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setNormalizer('pii_sanitize_headers', static function (array $value): array { return array_map('strtolower', $value); }); - $resolver->setDefault('pii_sanitize_headers', self::DEFAULT_SENSITIVE_HEADERS); + $resolver->setDefault('pii_sanitize_headers', RequestDataCollector::DEFAULT_PII_SANITIZE_HEADERS); } } diff --git a/src/Options.php b/src/Options.php index 03e770271..34e843c1d 100644 --- a/src/Options.php +++ b/src/Options.php @@ -352,9 +352,9 @@ public function setContextLines(?int $contextLines): self return $this->updateOptions(['context_lines' => $contextLines]); } - public function getDataCollection(): DataCollectionOptions + public function getDataCollection(): ?DataCollectionOptions { - /** @var DataCollectionOptions $dataCollection */ + /** @var DataCollectionOptions|null $dataCollection */ $dataCollection = $this->options['data_collection']; return $dataCollection; @@ -1267,7 +1267,7 @@ private function configureOptions(OptionsResolver $resolver): void $resolver->setAllowedTypes('capture_silenced_errors', 'bool'); $resolver->setAllowedTypes('max_request_body_size', 'string'); $resolver->setAllowedTypes('class_serializers', 'array'); - $resolver->setAllowedTypes('data_collection', ['array', DataCollectionOptions::class]); + $resolver->setAllowedTypes('data_collection', ['null', 'array', DataCollectionOptions::class]); $resolver->setAllowedValues('max_request_body_size', ['none', 'never', 'small', 'medium', 'always']); $resolver->setAllowedValues('dsn', \Closure::fromCallable([$this, 'validateDsnOption'])); @@ -1376,7 +1376,7 @@ private function configureOptions(OptionsResolver $resolver): void 'capture_silenced_errors' => false, 'max_request_body_size' => 'medium', 'class_serializers' => [], - 'data_collection' => new DataCollectionOptions(), + 'data_collection' => null, ]); } @@ -1427,11 +1427,11 @@ private function normalizeSpotlightUrl(string $url): string } /** - * @param array|DataCollectionOptions $value + * @param array|DataCollectionOptions|null $value */ - private function normalizeDataCollectionOption($value): DataCollectionOptions + private function normalizeDataCollectionOption($value): ?DataCollectionOptions { - if ($value instanceof DataCollectionOptions) { + if ($value === null || $value instanceof DataCollectionOptions) { return $value; } diff --git a/src/functions.php b/src/functions.php index 66e76cffb..38d7f451d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -45,7 +45,7 @@ * queues?: bool, * stack_frame_variables?: bool|array{mode?: "off"|"denyList"|"allowList", terms?: array}, * frame_context_lines?: int, - * }, + * }|null, * default_integrations?: bool, * dsn?: string|bool|Dsn|null, * enable_logs?: bool, diff --git a/tests/DataCollection/KeyValueDataFilterTest.php b/tests/DataCollection/KeyValueDataFilterTest.php index bf2752873..4352e28fa 100644 --- a/tests/DataCollection/KeyValueDataFilterTest.php +++ b/tests/DataCollection/KeyValueDataFilterTest.php @@ -77,6 +77,25 @@ public function testFilterKeyValueDataAllowListCannotOverrideMandatoryDenyList() $this->assertSame(['authorization' => '[Filtered]'], $filtered); } + public function testFilterKeyValueDataFiltersNestedData(): void + { + $behavior = ['mode' => 'denyList', 'terms' => []]; + + $filtered = KeyValueDataFilter::filterKeyValueData([ + 'user' => [ + 'password' => 'secret', + 'name' => 'alice', + ], + ], $behavior); + + $this->assertSame([ + 'user' => [ + 'password' => '[Filtered]', + 'name' => 'alice', + ], + ], $filtered); + } + public function testFilterHeadersReturnsNullWhenCollectionIsOff(): void { $behavior = ['mode' => 'off', 'terms' => ['x-request-id']]; diff --git a/tests/DataCollection/RequestDataCollectorTest.php b/tests/DataCollection/RequestDataCollectorTest.php new file mode 100644 index 000000000..d17ec4bc2 --- /dev/null +++ b/tests/DataCollection/RequestDataCollectorTest.php @@ -0,0 +1,242 @@ +assertFalse($this->legacyCollector(false)->usesDataCollection()); + $this->assertFalse($this->legacyCollector(true)->usesDataCollection()); + $this->assertTrue($this->collector([])->usesDataCollection()); + } + + public function testShouldCollectUserInfoFollowsLegacySendDefaultPii(): void + { + $this->assertFalse($this->legacyCollector(false)->shouldCollectUserInfo()); + $this->assertTrue($this->legacyCollector(true)->shouldCollectUserInfo()); + } + + public function testShouldCollectUserInfoUsesDataCollectionWhenConfigured(): void + { + $enabled = new RequestDataCollector(new DataCollectionOptions(['user_info' => true]), false); + $disabled = new RequestDataCollector(new DataCollectionOptions(['user_info' => false]), true); + + $this->assertTrue($enabled->shouldCollectUserInfo()); + $this->assertFalse($disabled->shouldCollectUserInfo()); + } + + public function testCollectQueryStringPreservesLegacyBehavior(): void + { + $queryString = 'api%5Ftoken=secret&q=a%20b%26c'; + + $this->assertSame($queryString, $this->legacyCollector(false)->collectQueryString($queryString)); + $this->assertSame($queryString, $this->legacyCollector(true)->collectQueryString($queryString)); + $this->assertNull($this->legacyCollector(false)->collectQueryString('')); + } + + public function testCollectQueryStringUsesUrlQueryParamsBehavior(): void + { + $collector = $this->collector([ + 'url_query_params' => [ + 'mode' => 'denyList', + 'terms' => ['page'], + ], + ]); + + $this->assertSame( + 'api%5Ftoken=[Filtered]&page=[Filtered]&q=a%20b%26c', + $collector->collectQueryString('api%5Ftoken=secret&page=5&q=a%20b%26c') + ); + } + + public function testCollectQueryStringReturnsNullWhenDisabledOrEmpty(): void + { + $disabled = $this->collector(['url_query_params' => ['mode' => 'off']]); + + $this->assertNull($disabled->collectQueryString('page=5')); + $this->assertNull($this->collector([])->collectQueryString('')); + } + + public function testCollectCookiesPreservesLegacyBehavior(): void + { + $cookies = ['session_id' => 'secret', 'theme' => 'dark']; + + $this->assertSame($cookies, $this->legacyCollector(true)->collectCookies($cookies)); + $this->assertNull($this->legacyCollector(false)->collectCookies($cookies)); + } + + public function testCollectCookiesUsesConfiguredBehavior(): void + { + $collector = $this->collector([ + 'cookies' => [ + 'mode' => 'allowList', + 'terms' => ['theme'], + ], + ]); + + $this->assertSame([ + 'session_id' => '[Filtered]', + 'theme' => 'dark', + 'tracking_id' => '[Filtered]', + ], $collector->collectCookies([ + 'session_id' => 'secret', + 'theme' => 'dark', + 'tracking_id' => '12345', + ])); + } + + public function testCollectCookiesReturnsNullWhenDisabled(): void + { + $collector = $this->collector(['cookies' => ['mode' => 'off']]); + + $this->assertNull($collector->collectCookies(['theme' => 'dark'])); + } + + public function testCollectHeadersPreservesLegacyBehaviorWhenPiiIsEnabled(): void + { + $headers = ['Authorization' => ['secret']]; + + $this->assertSame($headers, $this->legacyCollector(true)->collectHeaders($headers)); + } + + public function testCollectHeadersSanitizesConfiguredLegacyHeadersWhenPiiIsDisabled(): void + { + $collector = $this->legacyCollector(false, ['authorization']); + + $this->assertSame([ + 'Authorization' => ['[Filtered]'], + 'X-Authorization-Token' => ['untouched'], + 'X-Request-Id' => ['request-id'], + ], $collector->collectHeaders([ + 'Authorization' => ['secret'], + 'X-Authorization-Token' => ['untouched'], + 'X-Request-Id' => ['request-id'], + ])); + } + + public function testCollectHeadersSupportsNumericNamesInLegacyMode(): void + { + $this->assertSame( + ['123' => ['test']], + $this->legacyCollector(false)->collectHeaders([123 => ['test']]) + ); + } + + public function testCollectHeadersUsesRequestHeaderBehavior(): void + { + $collector = $this->collector([ + 'http_headers' => [ + 'request' => [ + 'mode' => 'allowList', + 'terms' => ['x-request-id'], + ], + 'response' => ['mode' => 'off'], + ], + ]); + + $this->assertSame([ + 'Authorization' => ['[Filtered]'], + 'X-Request-Id' => ['request-id'], + 'Host' => ['[Filtered]'], + ], $collector->collectHeaders([ + 'Authorization' => ['secret'], + 'X-Request-Id' => ['request-id'], + 'Host' => ['example.com'], + ])); + } + + public function testCollectHeadersReturnsNullWhenRequestHeadersAreDisabled(): void + { + $collector = $this->collector([ + 'http_headers' => [ + 'request' => ['mode' => 'off'], + 'response' => ['mode' => 'denyList'], + ], + ]); + + $this->assertNull($collector->collectHeaders(['X-Request-Id' => ['request-id']])); + } + + public function testShouldCollectRequestBodyPreservesLegacyBehavior(): void + { + $this->assertTrue($this->legacyCollector(false)->shouldCollectRequestBody()); + $this->assertTrue($this->legacyCollector(true)->shouldCollectRequestBody()); + } + + public function testShouldCollectRequestBodyUsesIncomingRequestBodyType(): void + { + $this->assertTrue($this->collector(['http_bodies' => ['incomingRequest']])->shouldCollectRequestBody()); + $this->assertFalse($this->collector(['http_bodies' => []])->shouldCollectRequestBody()); + $this->assertFalse($this->collector(['http_bodies' => ['outgoingRequest']])->shouldCollectRequestBody()); + } + + public function testCollectRequestBodyPreservesLegacyBehavior(): void + { + $body = ['password' => 'secret']; + + $this->assertSame($body, $this->legacyCollector(false)->collectRequestBody($body)); + $this->assertSame('raw body', $this->legacyCollector(true)->collectRequestBody('raw body')); + } + + public function testCollectRequestBodyFiltersStructuredSensitiveDataRecursively(): void + { + $collector = $this->collector(['http_bodies' => ['incomingRequest']]); + + $this->assertSame([ + 'password' => '[Filtered]', + 'user' => [ + 'api_token' => '[Filtered]', + 'name' => 'alice', + ], + ], $collector->collectRequestBody([ + 'password' => 'secret', + 'user' => [ + 'api_token' => 'token', + 'name' => 'alice', + ], + ])); + } + + public function testCollectRequestBodyFiltersRawData(): void + { + $collector = $this->collector(['http_bodies' => ['incomingRequest']]); + + $this->assertSame('[Filtered]', $collector->collectRequestBody('raw body')); + } + + public function testCollectRequestBodyReturnsNullWhenDisabledOrEmpty(): void + { + $disabled = $this->collector(['http_bodies' => []]); + $enabled = $this->collector(['http_bodies' => ['incomingRequest']]); + + $this->assertNull($disabled->collectRequestBody('raw body')); + $this->assertNull($enabled->collectRequestBody('')); + $this->assertNull($enabled->collectRequestBody([])); + $this->assertNull($enabled->collectRequestBody(null)); + } + + /** + * @param string[] $piiSanitizeHeaders + */ + private function legacyCollector( + bool $sendDefaultPii, + array $piiSanitizeHeaders = RequestDataCollector::DEFAULT_PII_SANITIZE_HEADERS + ): RequestDataCollector { + return new RequestDataCollector(null, $sendDefaultPii, $piiSanitizeHeaders); + } + + /** + * @param array $dataCollection + */ + private function collector(array $dataCollection): RequestDataCollector + { + return new RequestDataCollector(new DataCollectionOptions($dataCollection), false); + } +} diff --git a/tests/Integration/RequestIntegrationTest.php b/tests/Integration/RequestIntegrationTest.php index e53a432ac..5acff6a45 100644 --- a/tests/Integration/RequestIntegrationTest.php +++ b/tests/Integration/RequestIntegrationTest.php @@ -490,6 +490,115 @@ public static function invokeDataProvider(): iterable null, ]; + yield 'data collection can disable all incoming request data' => [ + [ + 'data_collection' => [ + 'user_info' => false, + 'cookies' => ['mode' => 'off'], + 'http_headers' => ['request' => ['mode' => 'off']], + 'http_bodies' => [], + 'url_query_params' => ['mode' => 'off'], + ], + ], + (new ServerRequest('POST', 'http://www.example.com/foo?token=secret', [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) + ->withCookieParams(['session_id' => 'secret']) + ->withHeader('Authorization', 'Bearer secret') + ->withHeader('Content-Length', '3') + ->withBody(Utils::streamFor('foo')), + [ + 'url' => 'http://www.example.com/foo', + 'method' => 'POST', + ], + UserDataBag::createFromUserIdentifier('explicit-user'), + UserDataBag::createFromUserIdentifier('explicit-user'), + ]; + + yield 'data collection applies per-category filtering' => [ + [ + 'data_collection' => [ + 'user_info' => false, + 'cookies' => ['mode' => 'allowList', 'terms' => ['theme']], + 'http_headers' => ['request' => ['mode' => 'allowList', 'terms' => ['x-request-id']]], + 'http_bodies' => [], + 'url_query_params' => ['mode' => 'denyList', 'terms' => ['page']], + ], + ], + (new ServerRequest('GET', 'http://www.example.com/foo?token=secret&page=5')) + ->withCookieParams([ + 'session_id' => 'secret', + 'theme' => 'dark', + ]) + ->withHeader('Authorization', 'Bearer secret') + ->withHeader('X-Request-Id', 'request-id'), + [ + 'url' => 'http://www.example.com/foo?token=%5BFiltered%5D&page=%5BFiltered%5D', + 'method' => 'GET', + 'query_string' => 'token=[Filtered]&page=[Filtered]', + 'cookies' => [ + 'session_id' => '[Filtered]', + 'theme' => 'dark', + ], + 'headers' => [ + 'Host' => ['[Filtered]'], + 'Authorization' => ['[Filtered]'], + 'X-Request-Id' => ['request-id'], + ], + ], + null, + null, + ]; + + yield 'data collection defaults filter sensitive request data' => [ + [ + 'data_collection' => [], + 'max_request_body_size' => 'always', + ], + (new ServerRequest('POST', 'http://www.example.com/foo?api%5Ftoken=secret&q=a%20b%26c', [], null, '1.1', ['REMOTE_ADDR' => '127.0.0.1'])) + ->withCookieParams([ + 'session_id' => 'secret', + 'theme' => 'dark', + ]) + ->withHeader('Authorization', 'Bearer secret') + ->withHeader('Cookie', 'session_id=secret; theme=dark') + ->withHeader('X-Forwarded-For', '203.0.113.7') + ->withHeader('Content-Length', '100') + ->withParsedBody([ + 'password' => 'secret', + 'user' => [ + 'api_token' => 'secret', + 'name' => 'alice', + ], + ]), + [ + 'url' => 'http://www.example.com/foo?api%5Ftoken=%5BFiltered%5D&q=a%20b%26c', + 'method' => 'POST', + 'query_string' => 'api%5Ftoken=[Filtered]&q=a%20b%26c', + 'env' => [ + 'REMOTE_ADDR' => '127.0.0.1', + ], + 'cookies' => [ + 'session_id' => '[Filtered]', + 'theme' => 'dark', + ], + 'headers' => [ + 'Host' => ['www.example.com'], + 'Authorization' => ['[Filtered]'], + 'Cookie' => ['[Filtered]'], + 'X-Forwarded-For' => ['203.0.113.7'], + 'Content-Length' => ['100'], + ], + 'data' => [ + 'password' => '[Filtered]', + 'user' => [ + 'api_token' => '[Filtered]', + 'name' => 'alice', + ], + ], + ], + null, + UserDataBag::createFromUserIpAddress('127.0.0.1'), + ]; + yield [ [], (new ServerRequest('GET', 'http://www.example.com/foo')) diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index b886b0df0..11a3d3451 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -590,9 +590,6 @@ public function testDefaultOptionValues(): void $actual[$callbackOption] = \Closure::class; } - $this->assertInstanceOf(DataCollectionOptions::class, $actual['data_collection']); - $actual['data_collection'] = DataCollectionOptions::class; - $expected = [ 'integrations' => [], 'default_integrations' => true, @@ -636,7 +633,7 @@ public function testDefaultOptionValues(): void 'in_app_exclude' => [], 'in_app_include' => [], 'send_default_pii' => false, - 'data_collection' => DataCollectionOptions::class, + 'data_collection' => null, 'max_value_length' => 1024, 'transport' => null, 'http_client' => null, @@ -697,6 +694,7 @@ public function testDataCollectionOptionNormalizesNestedArray(): void ], ]))->getDataCollection(); + $this->assertInstanceOf(DataCollectionOptions::class, $dataCollection); $this->assertFalse($dataCollection->shouldCollectUserInfo()); $this->assertSame('off', $dataCollection->getHttpHeaders()['request']['mode']); $this->assertSame('denyList', $dataCollection->getHttpHeaders()['response']['mode']); @@ -716,7 +714,9 @@ public function testDataCollectionOptionPreservesObjectIdentityAndCanBeUpdatedTh $options = new Options(['data_collection' => $dataCollection]); $this->assertSame($dataCollection, $options->getDataCollection()); - $options->getDataCollection()->setFrameContextLines(0); + $resolvedDataCollection = $options->getDataCollection(); + $this->assertNotNull($resolvedDataCollection); + $resolvedDataCollection->setFrameContextLines(0); $this->assertSame(0, $dataCollection->getFrameContextLines()); }