diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cc35b3..9063ea5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: CQ on: push: @@ -8,8 +8,12 @@ on: branches: - '*' +permissions: + contents: read + jobs: - build: + code-quality: + name: Coding standards, code quality and static code analysis runs-on: ubuntu-latest strategy: @@ -24,22 +28,52 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} + coverage: xdebug extensions: dom - name: Validate composer.json and composer.lock run: composer validate --strict - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - name: Install dependencies run: composer install --prefer-dist --no-progress - - name: Run test suite - run: vendor/bin/pest + - name: Run the linter + run: composer lint + + - name: Run PHPStan + if: success() || failure() + run: composer phpstan -- --error-format=github > phpstan.json + + - name: Run psalm + if: success() || failure() + run: composer psalm --output-format=github + + integration-tests: + name: Test suite and coverage + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: ['8.1', '8.2', '8.3'] + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: xdebug + extensions: dom + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run test suite + run: | + if [[ ${{ matrix.php-version }} == '8.1' ]]; then + composer coverage --min=90 --coverage-clover=coverage.xml + else + composer pest + fi diff --git a/.gitignore b/.gitignore index f43341f..eeed333 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ examples/hashresult.php *.iws *.ipr .idea/ +.tmp/ # eclipse project file .settings/ diff --git a/composer.json b/composer.json index 7954add..ed7ce8e 100644 --- a/composer.json +++ b/composer.json @@ -27,13 +27,37 @@ "pestphp/pest": "^2.4", "friendsofphp/php-cs-fixer": "^3.16", "phpstan/phpstan": "^1.10", - "phpstan/phpstan-deprecation-rules": "^1.1" + "phpstan/phpstan-deprecation-rules": "^1.1", + "vimeo/psalm": "^5.23" }, "autoload-dev": { "psr-4": { "HashSensitiveTests\\": "tests/" } }, + "scripts": { + "test": [ + "composer exec phpunit" + ], + "lint": [ + "vendor/bin/php-cs-fixer fix src --dry-run --allow-risky=yes" + ], + "lint:fix": [ + "vendor/bin/php-cs-fixer fix src --allow-risky=yes" + ], + "phpstan": [ + "php -d memory_limit=1G ./vendor/bin/phpstan analyse -c phpstan.neon" + ], + "psalm": [ + "php ./vendor/bin/psalm" + ], + "pest": [ + "php ./vendor/bin/pest" + ], + "coverage": [ + "php ./vendor/bin/pest --coverage" + ] + }, "config": { "allow-plugins": { "pestphp/pest-plugin": true diff --git a/composer.lock b/composer.lock index c24c9bc..deb19fa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "61ea55852dd2e82845f64db508d80a55", + "content-hash": "520050ad19f20d4da1041add2e8b941b", "packages": [ { "name": "monolog/monolog", @@ -159,6 +159,166 @@ } ], "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.4", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "react/promise": "^2", + "vimeo/psalm": "^3.12" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.4" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-03-21T18:52:26+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-13T18:00:56+00:00" + }, { "name": "brianium/paratest", "version": "v7.3.1", @@ -472,6 +632,43 @@ ], "time": "2024-03-26T18:29:49+00:00" }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, { "name": "doctrine/deprecations", "version": "1.1.3", @@ -519,6 +716,107 @@ }, "time": "2024-01-30T19:34:25+00:00" }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, { "name": "fidry/cpu-core-counter", "version": "1.1.0", @@ -862,29 +1160,78 @@ ], "time": "2023-03-08T13:26:56+00:00" }, + { + "name": "netresearch/jsonmapper", + "version": "v4.4.1", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" + }, + "time": "2024-01-31T06:18:54+00:00" + }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { - "ext-ctype": "*", - "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.4" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -892,7 +1239,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "4.9-dev" } }, "autoload": { @@ -916,9 +1263,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "nunomaduro/collision", @@ -3238,6 +3585,69 @@ ], "time": "2023-02-07T11:34:05+00:00" }, + { + "name": "spatie/array-to-xml", + "version": "3.2.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "reference": "c95fd4db94ec199f798d4b5b4a81757bd20d88ab", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/3.2.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-02-07T10:39:02+00:00" + }, { "name": "symfony/console", "version": "v6.4.6", @@ -4623,6 +5033,116 @@ ], "time": "2024-03-03T12:36:25+00:00" }, + { + "name": "vimeo/psalm", + "version": "5.23.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "8471a896ccea3526b26d082f4461eeea467f10a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/8471a896ccea3526b26d082f4461eeea467f10a4", + "reference": "8471a896ccea3526b26d082f4461eeea467f10a4", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.16", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "nikic/php-parser": "4.17.0" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "project", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev", + "dev-4.x": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php", + "static analysis" + ], + "support": { + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" + }, + "time": "2024-03-11T20:33:46+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/examples/00_hello_world.php b/examples/00_hello_world.php index 29c1336..bbefafa 100644 --- a/examples/00_hello_world.php +++ b/examples/00_hello_world.php @@ -3,7 +3,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; use Monolog\Handler\StreamHandler; -use HashSensitive\HashSensitiveProcessor; +use GlobyApp\HashSensitive\HashSensitiveProcessor; use Monolog\Logger; $sensitive_keys = ['api_key']; diff --git a/examples/01_nested.php b/examples/01_nested.php index d5d8675..8ddc54e 100644 --- a/examples/01_nested.php +++ b/examples/01_nested.php @@ -2,7 +2,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use HashSensitive\HashSensitiveProcessor; +use GlobyApp\HashSensitive\HashSensitiveProcessor; use Monolog\Handler\StreamHandler; use Monolog\Logger; diff --git a/examples/02_exclusive_subtree.php b/examples/02_exclusive_subtree.php index a907e25..b9a1cb0 100644 --- a/examples/02_exclusive_subtree.php +++ b/examples/02_exclusive_subtree.php @@ -2,7 +2,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; -use HashSensitive\HashSensitiveProcessor; +use GlobyApp\HashSensitive\HashSensitiveProcessor; use Monolog\Handler\StreamHandler; use Monolog\Logger; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..816a6f1 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,14 @@ +parameters: + tmpDir: .tmp + level: 9 + paths: + - src + - tests + - examples + ignoreErrors: + - + message: '#Undefined variable: \$this#' + path: */tests/* + +includes: + - vendor/phpstan/phpstan-deprecation-rules/rules.neon diff --git a/phpunit.xml b/phpunit.xml index 7b4f1a9..0017caa 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,14 +1,14 @@ - + ./tests - + ./app ./src - + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..26abfd8 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/src/HashSensitiveProcessor.php b/src/HashSensitiveProcessor.php index ee9cdc6..7ef1af8 100644 --- a/src/HashSensitiveProcessor.php +++ b/src/HashSensitiveProcessor.php @@ -10,16 +10,20 @@ /** * The processor to be added to your Monolog instance. + * @api * @package GlobyApp\HashSensitive */ class HashSensitiveProcessor extends Hasher implements ProcessorInterface { + /** + * @var array $sensitiveKeys + */ private array $sensitiveKeys; /** * Creates a new HashSensitiveProcessor instance. * - * @param array $sensitiveKeys Keys that should trigger the redaction. + * @param array $sensitiveKeys Keys that should trigger the redaction. * @param int|null $lengthLimit Max length after redaction. */ public function __construct(array $sensitiveKeys, string $algorithm = 'sha256', ?int $lengthLimit = null, bool $exclusiveSubtree = true) diff --git a/src/Hasher.php b/src/Hasher.php index 02176ab..5ed6673 100644 --- a/src/Hasher.php +++ b/src/Hasher.php @@ -7,6 +7,7 @@ /** * Class to manage scrubbing the keys from an array * + * @api * @author sjustein */ class Hasher @@ -25,10 +26,10 @@ public function __construct(string $algorithm = 'sha256', ?int $lengthLimit = nu /** * Function to hash sensitive keys in an input array * - * @param array $inputArray The array to hash values in - * @param array $sensitiveKeys The keys to hash + * @param array $inputArray The array to hash values in + * @param array $sensitiveKeys The keys to hash * - * @return array The input array with sensitive keys hashed + * @return array The input array with sensitive keys hashed */ public function scrubKeys(array $inputArray, array $sensitiveKeys): array { @@ -61,34 +62,29 @@ protected function hash(string $value): ?string /** * Function to handle traversing arrays and objects * - * @param string $key The key being processed - * @param array|object $value The value of the key in the input data - * @param array $sensitiveKeys The list of keys to hash + * @param array|object $value The value of the key in the input data + * @param array $sensitiveKeys The list of keys to hash * * @throws UnexpectedValueException if $value was not either an array of an object * - * @return array|object The processed array or object + * @return array|object The processed array or object */ - protected function traverse(string $key, array|object $value, array $sensitiveKeys): array|object + protected function traverse(array|object $value, array $sensitiveKeys): array|object { if (is_array($value)) { return $this->traverseInputArray($value, $sensitiveKeys); } - if (is_object($value)) { - return $this->traverseObject($value, $sensitiveKeys); - } - - throw new UnexpectedValueException("Don't know how to traverse value at key $key"); + return $this->traverseObject($value, $sensitiveKeys); } /** * Traverse an array and replace all values to be redacted with a hashed version of the value * - * @param array $inputArray Array to redact values from - * @param array $sensitiveKeys Keys to redact + * @param array $inputArray Array to redact values from + * @param array $sensitiveKeys Keys to redact * - * @return array Input array with redacted values hashed + * @return array Input array with redacted values hashed */ protected function traverseInputArray(array $inputArray, array $sensitiveKeys): array { @@ -108,8 +104,10 @@ protected function traverseInputArray(array $inputArray, array $sensitiveKeys): } // The value is either an array or an object, let traverse handle the specifics - if (in_array($key, $sensitiveKeys) || array_key_exists($key, $sensitiveKeys)) { - // If the sensitivekeys are not a subtree, hash the entire subtree + // If the current key is a sensitive key, traverse the subtree. + if ((in_array($key, $sensitiveKeys) || array_key_exists($key, $sensitiveKeys))) { + // If the current key doesn't have a subtree of sensitive keys (indicating the entire subtree, + // and not a value somewhere in the subtree should be hashed) if (!array_key_exists($key, $sensitiveKeys)) { $inputArray[$key] = $this->hash(print_r($value, true)); @@ -117,16 +115,21 @@ protected function traverseInputArray(array $inputArray, array $sensitiveKeys): continue; } - $inputArray[$key] = $this->traverse($key, $value, $sensitiveKeys[$key]); + /* @phpstan-ignore-next-line The above if statement asserts that $sensitiveKeys[$key] is a subtree */ + $inputArray[$key] = $this->traverse($value, $sensitiveKeys[$key]); // ExclusiveSubtree turned off means that sub keys should be checked according to ALL keys, not just // the keys in their sensitive keys subtree if (!$this->exclusiveSubtree) { - $inputArray[$key] = $this->traverse($key, $inputArray[$key], $sensitiveKeys); + $inputArray[$key] = $this->traverse($inputArray[$key], $sensitiveKeys); } - } else { - $inputArray[$key] = $this->traverse($key, $value, $sensitiveKeys); + + continue; } + + // The current key is not a sensitive key, traverse the subtree in search of sensitive keys with the same level in sensitiveKeys + /* @phpstan-ignore-next-line is_scalar above this if block asserts that $value is not a scalar */ + $inputArray[$key] = $this->traverse($value, $sensitiveKeys); } return $inputArray; @@ -136,7 +139,7 @@ protected function traverseInputArray(array $inputArray, array $sensitiveKeys): * Traverse an object and replace all values to be redacted with a hashed version of the value * * @param object $object Object to redact values from - * @param array $sensitiveKeys Keys for which to hash the value + * @param array $sensitiveKeys Keys for which to hash the value * * @return object The object with redacted values hashed */ @@ -153,8 +156,10 @@ protected function traverseObject(object $object, array $sensitiveKeys): object } // The value is either an array or an object, let traverse handle the specifics + // If the current key is a sensitive key, traverse the sub-object. if (in_array($key, $sensitiveKeys) || array_key_exists($key, $sensitiveKeys)) { - // If the sensitivekeys are not a subtree, hash the entire sub-object + // If the current key doesn't have a subtree of sensitive keys (indicating the entire sub-object, + // and not a value somewhere in the sub-object should be hashed) if (!array_key_exists($key, $sensitiveKeys)) { $object->{$key} = $this->hash(print_r($value, true)); @@ -162,16 +167,23 @@ protected function traverseObject(object $object, array $sensitiveKeys): object continue; } - $object->{$key} = $this->traverse($key, $value, $sensitiveKeys[$key]); + /* @phpstan-ignore-next-line The above if statement asserts that $sensitiveKeys[$key] is a subtree */ + $object->{$key} = $this->traverse($value, $sensitiveKeys[$key]); + // ExclusiveSubtree turned off means that sub keys should be checked according to ALL keys, not just + // the keys in their sensitive keys sub-object if (!$this->exclusiveSubtree) { - $object->{$key} = $this->traverse($key, $object->{$key}, $sensitiveKeys); + $object->{$key} = $this->traverse($object->{$key}, $sensitiveKeys); } - } else { - $object->{$key} = $this->traverse($key, $value, $sensitiveKeys); + + continue; } + + // The current key is not a sensitive key, traverse the sub-object in search of sensitive keys with the same level in sensitiveKeys + /* @phpstan-ignore-next-line is_scalar above this if block asserts that $value is not a scalar */ + $object->{$key} = $this->traverse($value, $sensitiveKeys); } return $object; } -} \ No newline at end of file +} diff --git a/tests/TestCase.php b/tests/TestCase.php index c6a035c..69ab349 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,14 +9,21 @@ use Monolog\Logger; use Monolog\LogRecord; use PHPUnit\Framework\TestCase as BaseTestCase; +use Psr\Log\LogLevel; use Stringable; abstract class TestCase extends BaseTestCase { /** - * @phpstan-param value-of|value-of|Level|LogLevel::* $level + * @param value-of|value-of|Level|LogLevel::* $level + * @param string|Stringable $message + * @param array $context + * @param string $channel + * @param \DateTimeImmutable $datetime + * @param array $extra + * @return LogRecord */ - protected function getRecord(int|string|Level $level = Level::Warning, string|Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new DateTimeImmutable(true), array $extra = []): LogRecord + protected function getRecord(string|int|Level $level = Level::Warning, string|Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new DateTimeImmutable(true), array $extra = []): LogRecord { return new LogRecord( datetime: $datetime,