From 0531f416c755bd98fa51227d1c7e4d5cac8cc0d6 Mon Sep 17 00:00:00 2001 From: Alex Godbehere <114239316+AlexGodbehere@users.noreply.github.com> Date: Wed, 4 Oct 2023 00:38:04 +0100 Subject: [PATCH] [Feature] Add custom replacement value on a per-class basis (#42) * Add custom replacement value on a per-class basis The updates in this commit allow for the return of a customizable, per-class, replacement value instead of always defaulting to the configured redaction setting. - A `return ''` option has been placed in the stubs alongside example values. - The .gitignore file has also been updated to ignore .idea files. - Previous tests have been modified to incorporate the new feature - The README and comments in the stubs have been updated to reflect the new feature --- .gitignore | 1 + README.md | 16 ++++- src/Commands/Stubs/RegexCollectionClass.stub | 21 ++++++ src/Repositories/RegexRepository.php | 4 +- src/Services/ScrubberService.php | 14 +++- .../Traits/ProcessArrayTrait.php | 4 +- .../Unit/Repositories/RegexRepositoryTest.php | 3 +- tests/Unit/Services/ScrubberServiceTest.php | 70 ++++++++++++++++++- tests/Unit/Services/SecretServiceTest.php | 2 +- .../ContentProcessingStrategyTest.php | 2 +- .../Strategies/RegexLoaderStrategyTest.php | 2 +- .../Unit/Strategies/TapLoaderStrategyTest.php | 2 +- 12 files changed, 125 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index c290300..232d88f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.phpunit.result.cache /coverage /vendor +.idea diff --git a/README.md b/README.md index 603c500..2ecd829 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ class. ### Opting Into Custom Extended Classes +> To create custom scrubbers, see the [Extending the Scrubber](#extending-the-scrubber) section. + The `regex_loader` array takes strings, not objects. To opt in to specific custom extended regex classes, define the class name as a string. @@ -167,7 +169,6 @@ class TestRegex implements RegexCollectionInterface public function getPattern(): string { /** - * @todo * @note return a regex pattern to detect a specific piece of sensitive data. */ return '(?<=basic) [a-zA-Z0-9=:\\+\/-]{5,100}'; @@ -176,11 +177,19 @@ class TestRegex implements RegexCollectionInterface public function getTestableString(): string { /** - * @todo * @note return a string that can be used to verify the regex pattern provided. */ return 'basic f9Iu+YwMiJEsQu/vBHlbUNZRkN/ihdB1sNTU'; } + + public function getReplacementValue(): string + { + + /** + * @note return a string that replaces the regex pattern provided. + */ + return config('scrubber.redaction'); + } public function isSecret(): bool { @@ -230,7 +239,7 @@ php artisan make:regex-class {name} This command will create a stubbed out class in `App\Scrubber\RegexCollection`. The Scrubber package will autoload everything from the `App\Scrubber\RegexCollection` folder with the wildcard value on the `regex_loader` array in the -scrubber config file. You will need to provide a `Regex Pattern` and a `Testable String` for the class. +scrubber config file. You will need to provide a `Regex Pattern` and a `Testable String` for the class and you may also provide a `Replacement Value` if you want to replace the detected value with something other than the default value in the config file. ## Testing @@ -244,5 +253,6 @@ composer test - [Whizboy-Arnold](https://github.com/Whizboy-Arnold) - [majchrosoft](https://github.com/majchrosoft) - [Lucaxue](https://github.com/lucaxue) +- [AlexGodbehere](https://github.com/AlexGodbehere) - [All Contributors](../../contributors) diff --git a/src/Commands/Stubs/RegexCollectionClass.stub b/src/Commands/Stubs/RegexCollectionClass.stub index bea90d2..91ed6f5 100644 --- a/src/Commands/Stubs/RegexCollectionClass.stub +++ b/src/Commands/Stubs/RegexCollectionClass.stub @@ -12,6 +12,9 @@ class DummyClass implements RegexCollectionInterface * @todo * @note return a regex pattern to detect a specific piece of sensitive data. */ + + // e.g. 'sensitive_data=[a-zA-Z0-9]{5,100}' + return ''; } public function getTestableString(): string @@ -20,6 +23,24 @@ class DummyClass implements RegexCollectionInterface * @todo * @note return a string that can be used to verify the regex pattern provided. */ + + // e.g. 'sensitive_data=adfa734jwfsdkf234' + return ''; + } + + /** + * Optional. Remove this function to use the default replacement pattern. + * + * @return string + */ + public function getReplacementValue(): string { + /** + * @todo + * @note return a string that replaces the regex pattern provided. + */ + + // e.g. '**redacted_key**=**redacted_data**' + return config('scrubber.redaction'); } public function isSecret(): bool diff --git a/src/Repositories/RegexRepository.php b/src/Repositories/RegexRepository.php index 73e3fdd..b1f1507 100644 --- a/src/Repositories/RegexRepository.php +++ b/src/Repositories/RegexRepository.php @@ -11,9 +11,9 @@ public function __construct( ) { } - public static function checkAndSanitize(string $regex, string $content, int &$hits = 0): string + public static function checkAndSanitize(string $regex, string $replace, string $content, int &$hits = 0): string { - return preg_replace("~$regex~i", config('scrubber.redaction'), $content, -1, $hits); + return preg_replace("~$regex~i", $replace, $content, -1, $hits); } public static function check(string $regex, string $content): int diff --git a/src/Services/ScrubberService.php b/src/Services/ScrubberService.php index 25dce70..b43f7a1 100644 --- a/src/Services/ScrubberService.php +++ b/src/Services/ScrubberService.php @@ -46,14 +46,22 @@ public static function autoSanitize(string &$jsonContent): void ? Secret::decrypt($regexClass->getPattern()) : $regexClass->getPattern(); - self::patternChecker($pattern, $jsonContent); + if (method_exists($regexClass, 'getReplacementValue')) { + // Check if getReplacementValue() exists on the regex class and if it does, use it. + $replace = $regexClass->getReplacementValue(); + } else { + // Otherwise, use the default replacement pattern. + $replace = config('scrubber.redaction'); + } + + self::patternChecker($pattern, $jsonContent, $replace); }); } - protected static function patternChecker(string $regexPattern, string &$jsonContent): void + protected static function patternChecker(string $regexPattern, string &$jsonContent, string $replace): void { $hits = 0; - $jsonContent = RegexRepository::checkAndSanitize($regexPattern, $jsonContent, $hits); + $jsonContent = RegexRepository::checkAndSanitize($regexPattern, $replace, $jsonContent, $hits); /** * @todo diff --git a/src/Strategies/ContentProcessingStrategy/Traits/ProcessArrayTrait.php b/src/Strategies/ContentProcessingStrategy/Traits/ProcessArrayTrait.php index 3785923..fce1876 100644 --- a/src/Strategies/ContentProcessingStrategy/Traits/ProcessArrayTrait.php +++ b/src/Strategies/ContentProcessingStrategy/Traits/ProcessArrayTrait.php @@ -9,7 +9,7 @@ trait ProcessArrayTrait public function processArrayRecursively(array $content): array { foreach ($content as $key => $value) { - if (null !== $value) { + if ($value !== null) { if (is_array($value)) { $content[$key] = $this->processArray($value); } elseif (is_object($value) && ! method_exists($value, '__toString')) { @@ -30,7 +30,7 @@ public function processArrayRecursively(array $content): array public function processArray(array $content): array { $jsonContent = ScrubberService::encodeRecord($content); - if ('' === $jsonContent) { + if ($jsonContent === '') { // failed to convert array to JSON, so process array recursively return $this->processArrayRecursively($content); } diff --git a/tests/Unit/Repositories/RegexRepositoryTest.php b/tests/Unit/Repositories/RegexRepositoryTest.php index 2b1a03a..6acd14a 100644 --- a/tests/Unit/Repositories/RegexRepositoryTest.php +++ b/tests/Unit/Repositories/RegexRepositoryTest.php @@ -21,7 +21,7 @@ public function it_can_verify_that_all_regex_patterns_have_testable_counter_part $this->assertStringContainsString( config('scrubber.redaction'), - app(RegexRepository::class)->checkAndSanitize($regexClass->getPattern(), $regexClass->getTestableString(), $hits) + app(RegexRepository::class)->checkAndSanitize($regexClass->getPattern(), config('scrubber.redaction'), $regexClass->getTestableString(), $hits) ); $this->assertEquals(1, $hits); @@ -46,6 +46,7 @@ public function it_can_sanitize_a_string_with_multiple_sensitive_pieces() config('scrubber.redaction'), app(RegexRepository::class)->checkAndSanitize( app(RegexRepository::class)->getRegexCollection()->get('google_api')->getPattern(), + config('scrubber.redaction'), $content, $hits ) diff --git a/tests/Unit/Services/ScrubberServiceTest.php b/tests/Unit/Services/ScrubberServiceTest.php index fe0d406..2ccc0cb 100644 --- a/tests/Unit/Services/ScrubberServiceTest.php +++ b/tests/Unit/Services/ScrubberServiceTest.php @@ -1,8 +1,9 @@ assertInstanceOf(RegexRepository::class, ScrubberService::getRegexRepository()); } + + public function test_it_can_handle_get_replacement_value_on_custom_class() + { + $withReplacement = new class() implements RegexCollectionInterface + { + public function isSecret(): bool + { + return false; + } + + public function getPattern(): string + { + return 'something_with'; + } + + public function getTestableString(): string + { + return 'something_with'; + } + + public function getReplacementValue(): string + { + return 'not_something'; + } + }; + + $withoutReplacement = new class() implements RegexCollectionInterface + { + public function isSecret(): bool + { + return false; + } + + public function getPattern(): string + { + return 'without_something'; + } + + public function getTestableString(): string + { + return 'without_something'; + } + }; + + $regexCollection = collect([ + 'with_replacement' => $withReplacement, + 'without_replacement' => $withoutReplacement, + ]); + + $regexRepository = new RegexRepository($regexCollection); + $this->app->instance(RegexRepository::class, $regexRepository); + + $content = 'something_with'; + ScrubberService::autoSanitize($content); + $this->assertEquals($withReplacement->getReplacementValue(), $content); + + $content = 'without_something'; + ScrubberService::autoSanitize($content); + + $defaultReplacementValue = config('scrubber.redaction'); + $this->assertEquals($defaultReplacementValue, $content); + + $this->assertNotEquals( + $withReplacement->getReplacementValue(), + $defaultReplacementValue + ); + } } diff --git a/tests/Unit/Services/SecretServiceTest.php b/tests/Unit/Services/SecretServiceTest.php index b36f702..8f697cd 100644 --- a/tests/Unit/Services/SecretServiceTest.php +++ b/tests/Unit/Services/SecretServiceTest.php @@ -1,6 +1,6 @@