-
-
Notifications
You must be signed in to change notification settings - Fork 470
feat(pii): add data collection options #2147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: data-collection
Are you sure you want to change the base?
Changes from 8 commits
fd4ba99
80ce7d2
dc79754
ee33464
143df86
de7ca0d
3b372de
99f2cb7
ab460c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,283 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\DataCollection; | ||
|
|
||
| use Symfony\Component\OptionsResolver\Options as SymfonyOptions; | ||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
|
||
| /** | ||
| * @phpstan-import-type KeyValueCollection from KeyValueCollectionBehavior | ||
| * | ||
| * @phpstan-type HttpHeadersOption KeyValueCollection|array{request?: KeyValueCollection|KeyValueCollectionBehavior, response?: KeyValueCollection|KeyValueCollectionBehavior} | ||
| * @phpstan-type ResolvedDataCollectionOptions array{ | ||
| * user_info: bool, | ||
| * cookies: KeyValueCollectionBehavior, | ||
| * http_headers: HttpHeaders, | ||
| * http_bodies: string[], | ||
| * query_params: KeyValueCollectionBehavior, | ||
| * gen_ai: GenAi, | ||
| * stack_frame_variables: bool, | ||
| * frame_context_lines: int | ||
| * } | ||
| */ | ||
| 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 ResolvedDataCollectionOptions | ||
| */ | ||
| private $options; | ||
|
|
||
| /** | ||
| * @var OptionsResolver | ||
| */ | ||
| private $resolver; | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $options | ||
| */ | ||
| public function __construct(array $options = []) | ||
| { | ||
| $this->resolver = new OptionsResolver(); | ||
| $this->configureOptions($this->resolver); | ||
|
|
||
| $this->options = $this->resolveOptions($options); | ||
|
Check failure on line 85 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public static function default(): self | ||
| { | ||
| return new self(); | ||
| } | ||
|
|
||
| public function shouldCollectUserInfo(): bool | ||
| { | ||
| return $this->options['user_info']; | ||
| } | ||
|
|
||
| public function setUserInfo(bool $userInfo): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['user_info' => $userInfo])); | ||
|
Check failure on line 100 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function getCookies(): KeyValueCollectionBehavior | ||
| { | ||
| return $this->options['cookies']; | ||
| } | ||
|
|
||
| /** | ||
| * @param KeyValueCollection|KeyValueCollectionBehavior $cookies | ||
| */ | ||
| public function setCookies($cookies): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['cookies' => $cookies])); | ||
|
Check failure on line 113 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function getHttpHeaders(): HttpHeaders | ||
| { | ||
| return $this->options['http_headers']; | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed>|HttpHeaders $httpHeaders | ||
| * | ||
| * @phpstan-param HttpHeadersOption|HttpHeaders $httpHeaders | ||
| */ | ||
| public function setHttpHeaders($httpHeaders): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['http_headers' => $httpHeaders])); | ||
|
Check failure on line 128 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| /** | ||
| * @return string[] | ||
| */ | ||
| public function getHttpBodies(): array | ||
| { | ||
| return $this->options['http_bodies']; | ||
| } | ||
|
|
||
| /** | ||
| * @param string[]|null $httpBodies | ||
| */ | ||
| public function setHttpBodies(?array $httpBodies): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['http_bodies' => $httpBodies])); | ||
|
Check failure on line 144 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function getQueryParams(): KeyValueCollectionBehavior | ||
| { | ||
| return $this->options['query_params']; | ||
| } | ||
|
|
||
| /** | ||
| * @param KeyValueCollection|KeyValueCollectionBehavior $queryParams | ||
| */ | ||
| public function setQueryParams($queryParams): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['query_params' => $queryParams])); | ||
|
Check failure on line 157 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function getGenAi(): GenAi | ||
| { | ||
| return $this->options['gen_ai']; | ||
| } | ||
|
|
||
| /** | ||
| * @param array{inputs: bool, outputs: bool} $genAi | ||
| */ | ||
| public function setGenAi(array $genAi): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['gen_ai' => $genAi])); | ||
|
Check failure on line 170 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function shouldCollectStackFrameVariables(): bool | ||
| { | ||
| return $this->options['stack_frame_variables']; | ||
| } | ||
|
|
||
| public function setStackFrameVariables(bool $stackFrameVariables): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['stack_frame_variables' => $stackFrameVariables])); | ||
|
Check failure on line 180 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| public function getFrameContextLines(): int | ||
| { | ||
| return $this->options['frame_context_lines']; | ||
| } | ||
|
|
||
| public function setFrameContextLines(int $frameContextLines): self | ||
| { | ||
| return $this->resolveOptions(array_merge($this->options, ['frame_context_lines' => $frameContextLines])); | ||
|
Check failure on line 190 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| private function configureOptions(OptionsResolver $resolver): void | ||
| { | ||
| $resolver->setDefaults([ | ||
| 'user_info' => true, | ||
| 'cookies' => new KeyValueCollectionBehavior(), | ||
| 'http_headers' => new HttpHeaders(), | ||
| 'http_bodies' => self::HTTP_BODY_TYPES, | ||
| 'query_params' => new KeyValueCollectionBehavior(), | ||
| 'gen_ai' => new GenAi(), | ||
| 'stack_frame_variables' => true, | ||
| 'frame_context_lines' => 5, | ||
| ]); | ||
| $resolver->setAllowedTypes('user_info', 'bool'); | ||
| $resolver->setAllowedTypes('cookies', ['array', KeyValueCollectionBehavior::class]); | ||
| $resolver->setAllowedTypes('http_headers', ['array', HttpHeaders::class]); | ||
| $resolver->setAllowedTypes('http_bodies', ['null', 'string[]']); | ||
| $resolver->setAllowedTypes('query_params', ['array', KeyValueCollectionBehavior::class]); | ||
| $resolver->setAllowedTypes('gen_ai', ['array', GenAi::class]); | ||
| $resolver->setAllowedTypes('stack_frame_variables', 'bool'); | ||
| $resolver->setAllowedTypes('frame_context_lines', 'int'); | ||
| $resolver->setAllowedValues('http_bodies', static function (?array $value): bool { | ||
| if ($value === null) { | ||
| return true; | ||
| } | ||
|
|
||
| /** @var string[] $value */ | ||
| return \count(array_diff($value, self::HTTP_BODY_TYPES)) === 0; | ||
| }); | ||
| $resolver->setAllowedValues('frame_context_lines', static function (int $value): bool { | ||
| return $value >= 0; | ||
| }); | ||
| $resolver->setNormalizer('cookies', \Closure::fromCallable([self::class, 'normalizeKeyValueCollection'])); | ||
| $resolver->setNormalizer('http_headers', \Closure::fromCallable([self::class, 'normalizeHttpHeaders'])); | ||
| $resolver->setNormalizer('http_bodies', static function (SymfonyOptions $options, ?array $value): array { | ||
| return $value ?? self::HTTP_BODY_TYPES; | ||
| }); | ||
| $resolver->setNormalizer('query_params', \Closure::fromCallable([self::class, 'normalizeKeyValueCollection'])); | ||
| $resolver->setNormalizer('gen_ai', \Closure::fromCallable([self::class, 'normalizeGenAi'])); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $options | ||
| * | ||
| * @phpstan-return ResolvedDataCollectionOptions | ||
| */ | ||
| private function resolveOptions(array $options): self | ||
|
Check failure on line 238 in src/DataCollection/DataCollectionOptions.php
|
||
| { | ||
| /** @var ResolvedDataCollectionOptions $resolvedOptions */ | ||
| $resolvedOptions = $this->resolver->resolve($options); | ||
|
|
||
| $this->options = $resolvedOptions; | ||
|
|
||
| return $this; | ||
|
Check failure on line 245 in src/DataCollection/DataCollectionOptions.php
|
||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed>|HttpHeaders $value | ||
| */ | ||
| private static function normalizeHttpHeaders(SymfonyOptions $options, $value): HttpHeaders | ||
| { | ||
| if ($value instanceof HttpHeaders) { | ||
| return $value; | ||
| } | ||
|
|
||
| return new HttpHeaders($value); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed>|KeyValueCollectionBehavior $value | ||
| */ | ||
| private static function normalizeKeyValueCollection(SymfonyOptions $options, $value): KeyValueCollectionBehavior | ||
| { | ||
| if ($value instanceof KeyValueCollectionBehavior) { | ||
| return $value; | ||
| } | ||
|
|
||
| return new KeyValueCollectionBehavior($value); | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed>|GenAi $value | ||
| */ | ||
| private static function normalizeGenAi(SymfonyOptions $options, $value): GenAi | ||
| { | ||
| if ($value instanceof GenAi) { | ||
| return $value; | ||
| } | ||
|
|
||
| return new GenAi($value); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Sentry\DataCollection; | ||
|
|
||
| use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
|
||
| class GenAi | ||
| { | ||
| /** | ||
| * @var OptionsResolver|null | ||
| */ | ||
| private static $resolver; | ||
|
|
||
| /** | ||
| * @var bool | ||
| */ | ||
| private $inputs; | ||
|
|
||
| /** | ||
| * @var bool | ||
| */ | ||
| private $outputs; | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $options | ||
| */ | ||
| public function __construct(array $options = []) | ||
| { | ||
| /** @var array{inputs: bool, outputs: bool} $opts */ | ||
| $opts = self::getResolverInstance()->resolve($options); | ||
| $this->inputs = $opts['inputs']; | ||
| $this->outputs = $opts['outputs']; | ||
| } | ||
|
|
||
| public function setInputs(bool $value): self | ||
| { | ||
| $this->inputs = $value; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| public function getInputs(): bool | ||
| { | ||
| return $this->inputs; | ||
| } | ||
|
|
||
| public function setOutputs(bool $outputs): self | ||
| { | ||
| $this->outputs = $outputs; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| public function getOutputs(): bool | ||
| { | ||
| return $this->outputs; | ||
| } | ||
|
|
||
| private static function getResolverInstance(): OptionsResolver | ||
| { | ||
| if (self::$resolver === null) { | ||
| $resolver = new OptionsResolver(); | ||
| $resolver->setDefaults([ | ||
| 'inputs' => true, | ||
| 'outputs' => true, | ||
| ]); | ||
| $resolver->setAllowedTypes('inputs', 'bool'); | ||
| $resolver->setAllowedTypes('outputs', 'bool'); | ||
|
|
||
| self::$resolver = $resolver; | ||
| } | ||
|
|
||
| return self::$resolver; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.