Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
257 changes: 257 additions & 0 deletions src/DataCollection/DataCollectionOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<?php

declare(strict_types=1);

namespace Sentry\DataCollection;

final class DataCollectionOptions
{
/**
* @internal
*/
public const HTTP_BODY_TYPES = [
'incomingRequest',
'outgoingRequest',
'incomingResponse',
'outgoingResponse',
];

public const SENSITIVE_DEFAULTS = [
'auth',
'token',
'secret',
'password',
'passwd',
'pwd',
'key',
'jwt',
'bearer',
'sso',
'saml',
'csrf',
'xsrf',
'credentials',
'session',
'sid',
'identity',
];

public const EXTENDED_DENY_TERMS = [
'forwarded',
'-ip',
'remote-',
'via',
'-user',
];

/**
* @var array<string, mixed>
*
* @phpstan-var array{
* user_info: bool,
* cookies: array{mode: string, terms: string[]},
* http_headers: array{request: array{mode: string, terms: string[]}, response: array{mode: string, terms: string[]}},
* http_bodies: string[],
* query_params: array{mode: string, terms: string[]},
* gen_ai: array{inputs: bool, outputs: bool},
* stack_frame_variables: bool,
* frame_context_lines: int
* }
*/
private $options;

/**
* @param array<string, mixed> $options
*
* @phpstan-param array{
* user_info: bool,
* cookies: array{mode: string, terms: string[]},
* http_headers: array{request: array{mode: string, terms: string[]}, response: array{mode: string, terms: string[]}},
* http_bodies: string[],
* query_params: array{mode: string, terms: string[]},
* gen_ai: array{inputs: bool, outputs: bool},
* stack_frame_variables: bool,
* frame_context_lines: int
* } $options
*/
public function __construct(array $options)
{
$this->options = $options;
}

public static function default(): self
{
return new self([
'user_info' => true,
'cookies' => self::getDefaultKeyValueCollection(),
'http_headers' => [
'request' => self::getDefaultKeyValueCollection(),
'response' => self::getDefaultKeyValueCollection(),
],
'http_bodies' => self::HTTP_BODY_TYPES,
'query_params' => self::getDefaultKeyValueCollection(),
'gen_ai' => [
'inputs' => true,
'outputs' => true,
],
'stack_frame_variables' => true,
'frame_context_lines' => 5,
]);
}

/**
* @return array{mode: string, terms: string[]}
*/
public static function getDefaultKeyValueCollection(): array
{
return [
'mode' => 'denyList',
'terms' => [],
];
}

public function shouldCollectUserInfo(): bool
{
return $this->options['user_info'];
}

public function setUserInfo(bool $userInfo): self
{
$this->options['user_info'] = $userInfo;

return $this;
}

/**
* @return array{mode: string, terms: string[]}
*/
public function getCookies(): array
{
return $this->options['cookies'];
}

/**
* @param array{mode: string, terms: string[]} $cookies
*/
public function setCookies(array $cookies): self
{
$this->options['cookies'] = $cookies;

return $this;
}

/**
* @return array{request: array{mode: string, terms: string[]}, response: array{mode: string, terms: string[]}}
*/
public function getHttpHeaders(): array
{
return $this->options['http_headers'];
}

/**
* @param array{mode?: string, terms?: string[]}|array{request?: array{mode?: string, terms?: string[]}, response?: array{mode?: string, terms?: string[]}} $httpHeaders
*/
public function setHttpHeaders(array $httpHeaders): self
{
$this->options['http_headers'] = DataCollectionOptionsNormalizer::normalizeHttpHeaders($httpHeaders);

return $this;
}

/**
* @return string[]
*/
public function getHttpBodies(): array
{
return $this->options['http_bodies'];
}

/**
* @param string[] $httpBodies
*/
public function setHttpBodies(array $httpBodies): self
{
$this->options['http_bodies'] = $httpBodies;

return $this;
}
Comment thread
Litarnus marked this conversation as resolved.
Outdated

/**
* @return array{mode: string, terms: string[]}
*/
public function getQueryParams(): array
{
return $this->options['query_params'];
}

/**
* @param array{mode: string, terms: string[]} $queryParams
*/
public function setQueryParams(array $queryParams): self
{
$this->options['query_params'] = $queryParams;

return $this;
}

/**
* @return array{inputs: bool, outputs: bool}
*/
public function getGenAi(): array
{
return $this->options['gen_ai'];
}

/**
* @param array{inputs: bool, outputs: bool} $genAi
*/
public function setGenAi(array $genAi): self
{
$this->options['gen_ai'] = $genAi;

return $this;
}

public function shouldCollectStackFrameVariables(): bool
{
return $this->options['stack_frame_variables'];
}

public function setStackFrameVariables(bool $stackFrameVariables): self
{
$this->options['stack_frame_variables'] = $stackFrameVariables;

return $this;
}

public function getFrameContextLines(): int
{
return $this->options['frame_context_lines'];
}

public function setFrameContextLines(int $frameContextLines): self
{
$this->options['frame_context_lines'] = $frameContextLines;

return $this;
}

/**
* @return array<string, mixed>
*
* @phpstan-return array{
* user_info: bool,
* cookies: array{mode: string, terms: string[]},
* http_headers: array{request: array{mode: string, terms: string[]}, response: array{mode: string, terms: string[]}},
* http_bodies: string[],
* query_params: array{mode: string, terms: string[]},
* gen_ai: array{inputs: bool, outputs: bool},
* stack_frame_variables: bool,
* frame_context_lines: int
* }
*/
public function toArray(): array
{
return $this->options;
Comment thread
Litarnus marked this conversation as resolved.
Outdated
}
}
94 changes: 94 additions & 0 deletions src/DataCollection/DataCollectionOptionsNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace Sentry\DataCollection;

use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* @internal
*/
final class DataCollectionOptionsNormalizer
{
private function __construct()
{
}

/**
* @param array<string, mixed> $value
*
* @return array{mode: string, terms: string[]}
*/
public static function normalizeKeyValueCollection(array $value): array
{
$resolver = new OptionsResolver();
$resolver->setDefaults(DataCollectionOptions::getDefaultKeyValueCollection());
$resolver->setAllowedTypes('mode', 'string');
$resolver->setAllowedTypes('terms', 'string[]');
$resolver->setAllowedValues('mode', [
'off',
'denyList',
'allowList',
]);

/** @var array{mode: string, terms: string[]} $resolvedOptions */
$resolvedOptions = $resolver->resolve($value);

return $resolvedOptions;
}

/**
* @param array<string, mixed> $value
*
* @return array{request: array{mode: string, terms: string[]}, response: array{mode: string, terms: string[]}}
*/
public static function normalizeHttpHeaders(array $value): array
{
if (!isset($value['request']) && !isset($value['response'])) {
$headers = self::normalizeKeyValueCollection($value);

return [
'request' => $headers,
'response' => $headers,
];
Comment thread
Litarnus marked this conversation as resolved.
Outdated
}

$resolver = new OptionsResolver();
$resolver->setDefaults([
'request' => [],
'response' => [],
]);
$resolver->setAllowedTypes('request', 'array');
$resolver->setAllowedTypes('response', 'array');

/** @var array{request: array<string, mixed>, response: array<string, mixed>} $resolvedOptions */
$resolvedOptions = $resolver->resolve($value);

return [
'request' => self::normalizeKeyValueCollection($resolvedOptions['request']),
'response' => self::normalizeKeyValueCollection($resolvedOptions['response']),
];
}

/**
* @param array<string, mixed> $value
*
* @return array{inputs: bool, outputs: bool}
*/
public static function normalizeGenAi(array $value): array
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
'inputs' => true,
'outputs' => true,
]);
$resolver->setAllowedTypes('inputs', 'bool');
$resolver->setAllowedTypes('outputs', 'bool');

/** @var array{inputs: bool, outputs: bool} $resolvedOptions */
$resolvedOptions = $resolver->resolve($value);

return $resolvedOptions;
}
}
Loading