From 139db7e98865de1528260050d1ebae37ca5773d4 Mon Sep 17 00:00:00 2001 From: IanM <16573496+imorland@users.noreply.github.com> Date: Tue, 31 Oct 2023 08:54:12 +0000 Subject: [PATCH] chore: backend tests, PHPStan (#31) * wip: add tests for existing functionality * add workflow * Apply fixes from StyleCI * any version of var-dumper * temporarily check for a 401 * remove results.cache * wip: fixup a few bits, add more tests * Apply fixes from StyleCI * tests passing locally * Apply fixes from StyleCI * chore: enable phpstan, apply fixes * Apply fixes from StyleCI * chore: npm audit fix --------- Co-authored-by: StyleCI Bot --- .github/workflows/backend.yml | 12 + .github/workflows/{build.yml => frontend.yml} | 5 +- .gitignore | 1 + composer.json | 35 +- extend.php | 7 +- js/package-lock.json | 612 ++++++++---------- phpstan.neon | 13 + src/Access/UserPolicy.php | 12 +- src/Api/Controllers/CheckIPsController.php | 16 +- src/BannedIP.php | 6 +- src/Commands/BanUserHandler.php | 10 +- src/Commands/CreateBannedIPHandler.php | 7 +- src/Commands/DeleteBannedIPHandler.php | 5 - src/Commands/EditBannedIPHandler.php | 17 +- src/Commands/UnbanUserHandler.php | 20 +- src/Listeners/RemoveAccessToBannedUsers.php | 13 +- src/Middleware/RegisterMiddleware.php | 2 +- src/Repositories/BannedIPRepository.php | 8 +- src/Search/NxGambit.php | 1 + src/Validators/BannedIPValidator.php | 6 +- tests/fixtures/.gitkeep | 0 tests/fixtures/IPAddressesTrait.php | 139 ++++ tests/fixtures/IPRequestTrait.php | 63 ++ tests/integration/BannedIPRepositoryTest.php | 268 ++++++++ .../api/CreateBannedIPControllerTest.php | 189 ++++++ tests/integration/forum/AccessTest.php | 424 ++++++++++++ tests/integration/setup.php | 18 + tests/phpunit.integration.xml | 25 + tests/phpunit.unit.xml | 27 + tests/unit/.gitkeep | 0 30 files changed, 1551 insertions(+), 410 deletions(-) create mode 100644 .github/workflows/backend.yml rename .github/workflows/{build.yml => frontend.yml} (88%) create mode 100644 phpstan.neon create mode 100644 tests/fixtures/.gitkeep create mode 100644 tests/fixtures/IPAddressesTrait.php create mode 100644 tests/fixtures/IPRequestTrait.php create mode 100644 tests/integration/BannedIPRepositoryTest.php create mode 100644 tests/integration/api/CreateBannedIPControllerTest.php create mode 100644 tests/integration/forum/AccessTest.php create mode 100644 tests/integration/setup.php create mode 100644 tests/phpunit.integration.xml create mode 100644 tests/phpunit.unit.xml create mode 100644 tests/unit/.gitkeep diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000..589f4ff --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,12 @@ +name: FoF Ban IPs PHP + +on: [workflow_dispatch, push, pull_request] + +jobs: + run: + uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@main + with: + enable_backend_testing: true + enable_phpstan: true + + backend_directory: . diff --git a/.github/workflows/build.yml b/.github/workflows/frontend.yml similarity index 88% rename from .github/workflows/build.yml rename to .github/workflows/frontend.yml index ca3526b..b6b60fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/frontend.yml @@ -1,4 +1,4 @@ -name: Javascript +name: FoF Ban IPs JS on: [workflow_dispatch, push, pull_request] @@ -8,11 +8,12 @@ jobs: with: enable_bundlewatch: false enable_prettier: true - enable_typescript: false + enable_typescript: true frontend_directory: ./js backend_directory: . js_package_manager: npm main_git_branch: master + secrets: bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index d34e123..0b4ff3b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules vendor composer.lock js/dist +tests/.phpunit.result.cache diff --git a/composer.json b/composer.json index 1b3d2db..4ca9288 100644 --- a/composer.json +++ b/composer.json @@ -52,10 +52,43 @@ "flarum/flags", "flarum/tags", "flarum/approval" - ] + ] }, "flagrow": { "discuss": "https://discuss.flarum.org/d/20949" + }, + "flarum-cli": { + "modules": { + "backendTesting": true, + "githubActions": true + } + } + }, + "autoload-dev": { + "psr-4": { + "FoF\\BanIPs\\Tests\\": "tests/" } + }, + "scripts": { + "test": [ + "@test:unit", + "@test:integration" + ], + "test:unit": "phpunit -c tests/phpunit.unit.xml", + "test:integration": "phpunit -c tests/phpunit.integration.xml", + "test:setup": "@php tests/integration/setup.php", + "analyse:phpstan": "phpstan analyse", + "clear-cache:phpstan": "phpstan clear-result-cache" + }, + "scripts-descriptions": { + "test": "Runs all tests.", + "test:unit": "Runs all unit tests.", + "test:integration": "Runs all integration tests.", + "test:setup": "Sets up a database for use with integration tests. Execute this only once.", + "analyse:phpstan": "Run static analysis" + }, + "require-dev": { + "flarum/testing": "^1.0.0", + "flarum/phpstan": "*" } } diff --git a/extend.php b/extend.php index 614d3ca..0c0ff56 100644 --- a/extend.php +++ b/extend.php @@ -14,7 +14,6 @@ use Flarum\Api\Controller; use Flarum\Api\Serializer; use Flarum\Api\Serializer\AbstractSerializer; -use Flarum\Database\AbstractModel; use Flarum\Extend; use Flarum\Post\Post; use Flarum\User\User; @@ -61,8 +60,8 @@ ->modelPolicy(User::class, Access\UserPolicy::class), (new Extend\ApiSerializer(Serializer\PostSerializer::class)) - ->attributes(function (AbstractSerializer $serializer, AbstractModel $post, array $attributes): array { - $attributes['canBanIP'] = $serializer->getActor()->can('banIP', $post); + ->attributes(function (AbstractSerializer $serializer, Post $post, array $attributes): array { + $attributes['canBanIP'] = $serializer->getActor()->can('banIP', $post->user); return $attributes; }) @@ -95,7 +94,7 @@ (new Extend\ApiController(Controller\ListPostsController::class)) ->addInclude(['banned_ip', 'banned_ip.user']), - (new Extend\ApiController(Controller\ShowPostsController::class)) + (new Extend\ApiController(Controller\ShowPostController::class)) ->addInclude(['banned_ip', 'banned_ip.user']), (new Extend\ApiController(Controller\CreatePostController::class)) diff --git a/js/package-lock.json b/js/package-lock.json index d0f38f5..0fa6243 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -19,11 +19,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -66,22 +67,15 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" @@ -127,14 +121,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.14.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.6.tgz", @@ -187,12 +173,12 @@ "@babel/core": "^7.4.0-0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-explode-assignable-expression": { @@ -207,35 +193,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dependencies": { - "@babel/types": "^7.14.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -350,20 +324,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -404,12 +386,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -417,9 +399,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1333,14 +1315,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", @@ -1543,14 +1517,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", @@ -1613,30 +1579,31 @@ } }, "node_modules/@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1645,11 +1612,12 @@ } }, "node_modules/@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1714,12 +1682,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@polka/url": { @@ -2087,14 +2055,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/babel-loader/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -2116,14 +2076,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.3.tgz", @@ -2156,25 +2108,34 @@ } }, "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/buffer-from": { @@ -2195,13 +2156,23 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001239", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", - "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "version": "1.0.30001559", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", + "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, "node_modules/chalk": { "version": "2.4.2", @@ -2251,12 +2222,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/commander": { "version": "2.20.3", @@ -2277,26 +2243,17 @@ } }, "node_modules/core-js-compat": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.1.tgz", - "integrity": "sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", "dependencies": { - "browserslist": "^4.16.6", - "semver": "7.0.0" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2349,9 +2306,9 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, "node_modules/electron-to-chromium": { - "version": "1.3.757", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.757.tgz", - "integrity": "sha512-kP0ooyrvavDC+Y9UG6G/pUVxfRNM2VTJwtLQLvgsJeyf1V+7shMCb68Wj0/TETmfx8dWv9pToGkVT39udE87wQ==" + "version": "1.4.571", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.571.tgz", + "integrity": "sha512-Sc+VtKwKCDj3f/kLBjdyjMpNzoZsU6WuL/wFb6EH8USmHEcebxRXcRrVpOpayxd52tuey4RUDpUsw5OS5LhJqg==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -2400,7 +2357,7 @@ "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { "node": ">=0.8.0" } @@ -2627,7 +2584,7 @@ "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "engines": { "node": ">=4" } @@ -2899,9 +2856,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "node_modules/npm-run-path": { "version": "4.0.1", @@ -3015,6 +2972,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -3187,6 +3149,14 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -3422,6 +3392,35 @@ "node": ">=4" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -3699,11 +3698,12 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.14.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -3731,23 +3731,17 @@ "json5": "^2.1.2", "semver": "^6.3.0", "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, "@babel/generator": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz", - "integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "requires": { - "@babel/types": "^7.14.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" } }, "@babel/helper-annotate-as-pure": { @@ -3776,13 +3770,6 @@ "@babel/helper-validator-option": "^7.14.5", "browserslist": "^4.16.6", "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, "@babel/helper-create-class-features-plugin": { @@ -3820,15 +3807,13 @@ "lodash.debounce": "^4.0.8", "resolve": "^1.14.2", "semver": "^6.1.2" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + }, "@babel/helper-explode-assignable-expression": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz", @@ -3838,29 +3823,20 @@ } }, "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "requires": { - "@babel/types": "^7.14.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -3945,17 +3921,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { - "@babel/types": "^7.14.5" + "@babel/types": "^7.22.5" } }, + "@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.14.5", @@ -3984,19 +3965,19 @@ } }, "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz", - "integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.14.5", @@ -4557,13 +4538,6 @@ "babel-plugin-polyfill-corejs3": "^0.2.2", "babel-plugin-polyfill-regenerator": "^0.2.2", "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, "@babel/plugin-transform-shorthand-properties": { @@ -4712,13 +4686,6 @@ "babel-plugin-polyfill-regenerator": "^0.2.2", "core-js-compat": "^3.15.0", "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, "@babel/preset-modules": { @@ -4765,37 +4732,39 @@ } }, "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz", - "integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==", - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.14.7", - "@babel/types": "^7.14.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz", - "integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "requires": { - "@babel/helper-validator-identifier": "^7.14.5", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -4845,12 +4814,12 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@polka/url": { @@ -5158,11 +5127,6 @@ "ajv": "^6.12.4", "ajv-keywords": "^3.5.2" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -5182,13 +5146,6 @@ "@babel/compat-data": "^7.13.11", "@babel/helper-define-polyfill-provider": "^0.2.2", "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } } }, "babel-plugin-polyfill-corejs3": { @@ -5214,15 +5171,14 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" } }, "buffer-from": { @@ -5240,9 +5196,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001239", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz", - "integrity": "sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==" + "version": "1.0.30001559", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", + "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==" }, "chalk": { "version": "2.4.2", @@ -5283,12 +5239,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "commander": { "version": "2.20.3", @@ -5309,19 +5260,11 @@ } }, "core-js-compat": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.15.1.tgz", - "integrity": "sha512-xGhzYMX6y7oEGQGAJmP2TmtBLvR4nZmRGEcFa3ubHOq5YEp51gGN9AovVa0AoujGZIq+Wm6dISiYyGNfdflYww==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", "requires": { - "browserslist": "^4.16.6", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } + "browserslist": "^4.22.1" } }, "cross-spawn": { @@ -5362,9 +5305,9 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, "electron-to-chromium": { - "version": "1.3.757", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.757.tgz", - "integrity": "sha512-kP0ooyrvavDC+Y9UG6G/pUVxfRNM2VTJwtLQLvgsJeyf1V+7shMCb68Wj0/TETmfx8dWv9pToGkVT39udE87wQ==" + "version": "1.4.571", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.571.tgz", + "integrity": "sha512-Sc+VtKwKCDj3f/kLBjdyjMpNzoZsU6WuL/wFb6EH8USmHEcebxRXcRrVpOpayxd52tuey4RUDpUsw5OS5LhJqg==" }, "emojis-list": { "version": "3.0.0", @@ -5398,7 +5341,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint-scope": { "version": "5.1.1", @@ -5570,7 +5513,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "has-symbols": { "version": "1.0.2", @@ -5754,9 +5697,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "npm-run-path": { "version": "4.0.1", @@ -5831,6 +5774,11 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -5962,6 +5910,11 @@ "ajv-keywords": "^3.5.2" } }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -6119,6 +6072,15 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..03cf261 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,13 @@ +includes: + - vendor/flarum/phpstan/extension.neon + +parameters: + # The level will be increased in Flarum 2.0 + level: 5 + paths: + - extend.php + - src + excludePaths: + - *.blade.php + checkMissingIterableValueType: false + databaseMigrationsPath: ['migrations'] diff --git a/src/Access/UserPolicy.php b/src/Access/UserPolicy.php index e26ab76..96bc301 100644 --- a/src/Access/UserPolicy.php +++ b/src/Access/UserPolicy.php @@ -19,17 +19,17 @@ class UserPolicy extends AbstractPolicy private $key = 'fof.ban-ips.banIP'; /** - * @param User $actor - * @param User $user + * @param User $actor + * @param ?User $user * * @return bool|null */ - public function banIP(User $actor, User $user) + public function banIP(User $actor, ?User $user) { - if ($user == null || $actor->id == $user->id || $user->can($this->key)) { - return false; + if (!$user->isGuest() && ($actor->id === $user->id || $user->hasPermission($this->key))) { + return $this->deny(); } - return $actor->can($this->key, $user); + return $actor->hasPermission($this->key); } } diff --git a/src/Api/Controllers/CheckIPsController.php b/src/Api/Controllers/CheckIPsController.php index 353b2b6..109cd0f 100644 --- a/src/Api/Controllers/CheckIPsController.php +++ b/src/Api/Controllers/CheckIPsController.php @@ -21,6 +21,7 @@ use FoF\BanIPs\Validators\BannedIPValidator; use Illuminate\Support\Arr; use Psr\Http\Message\ServerRequestInterface; +use Symfony\Contracts\Translation\TranslatorInterface; use Tobscure\JsonApi\Document; class CheckIPsController extends AbstractListController @@ -33,21 +34,27 @@ class CheckIPsController extends AbstractListController /** * @var BannedIPRepository */ - private $bannedIPs; + protected $bannedIPs; /** * @var BannedIPValidator */ - private $validator; + protected $validator; + + /** + * @var TranslatorInterface + */ + protected $translator; /** * @param BannedIPRepository $bannedIPs * @param BannedIPValidator $validator */ - public function __construct(BannedIPRepository $bannedIPs, BannedIPValidator $validator) + public function __construct(BannedIPRepository $bannedIPs, BannedIPValidator $validator, TranslatorInterface $translator) { $this->bannedIPs = $bannedIPs; $this->validator = $validator; + $this->translator = $translator; } /** @@ -75,6 +82,7 @@ protected function data(ServerRequestInterface $request, Document $document) $userId = Arr::get($params, 'user'); $ip = Arr::get($params, 'ip'); + $user = $userId != null ? User::where('id', $userId)->orWhere('username', $userId)->first() : null; $actor->assertCan('banIP', $user); @@ -94,7 +102,7 @@ protected function data(ServerRequestInterface $request, Document $document) if (empty($ips)) { throw new ValidationException([ - resolve('translator')->trans('fof-ban-ips.error.no_ips_found_message'), + $this->translator->trans('fof-ban-ips.error.no_ips_found_message'), ]); } diff --git a/src/BannedIP.php b/src/BannedIP.php index ead20af..c9a8b9c 100644 --- a/src/BannedIP.php +++ b/src/BannedIP.php @@ -39,9 +39,9 @@ class BannedIP extends AbstractModel protected $dates = ['created_at', 'deleted_at']; /** - * @param $creatorId - * @param $userId - * @param $address + * @param $creatorId + * @param $userId + * @param $address * @param null $reason * * @return BannedIP diff --git a/src/Commands/BanUserHandler.php b/src/Commands/BanUserHandler.php index 4c8b16c..e07da8a 100644 --- a/src/Commands/BanUserHandler.php +++ b/src/Commands/BanUserHandler.php @@ -16,17 +16,11 @@ use FoF\BanIPs\Events\IPWasBanned; use FoF\BanIPs\Repositories\BannedIPRepository; use FoF\BanIPs\Validators\BannedIPValidator; -use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Events\Dispatcher as DispatcherEvents; use Illuminate\Support\Arr; class BanUserHandler { - /** - * @var Dispatcher - */ - private $bus; - /** * @var DispatcherEvents */ @@ -43,14 +37,12 @@ class BanUserHandler private $validator; /** - * @param Dispatcher $bus * @param DispatcherEvents $events * @param BannedIPRepository $bannedIPs * @param BannedIPValidator $validator */ - public function __construct(Dispatcher $bus, DispatcherEvents $events, BannedIPRepository $bannedIPs, BannedIPValidator $validator) + public function __construct(DispatcherEvents $events, BannedIPRepository $bannedIPs, BannedIPValidator $validator) { - $this->bus = $bus; $this->events = $events; $this->bannedIPs = $bannedIPs; $this->validator = $validator; diff --git a/src/Commands/CreateBannedIPHandler.php b/src/Commands/CreateBannedIPHandler.php index 274fc83..aed0bb5 100644 --- a/src/Commands/CreateBannedIPHandler.php +++ b/src/Commands/CreateBannedIPHandler.php @@ -12,6 +12,7 @@ namespace FoF\BanIPs\Commands; use Carbon\Carbon; +use Flarum\User\Guest; use Flarum\User\User; use FoF\BanIPs\BannedIP; use FoF\BanIPs\Events\IPWasBanned; @@ -55,13 +56,15 @@ public function handle(CreateBannedIP $command) $data = $command->data; $userId = Arr::get($data, 'attributes.userId'); - $user = $userId != null ? User::where('id', $userId)->orWhere('username', $userId)->firstOrFail() : null; + + /** @var User|Guest $user */ + $user = $userId ? User::query()->where('id', $userId)->orWhere('username', $userId)->first() : new Guest(); $actor->assertCan('banIP', $user); $bannedIP = BannedIP::build( $actor->id, - $user ? $user->id : null, + $user->isGuest() ? null : $user->id, Arr::get($data, 'attributes.address'), Arr::get($data, 'attributes.reason') ); diff --git a/src/Commands/DeleteBannedIPHandler.php b/src/Commands/DeleteBannedIPHandler.php index 293cfa6..c9f6d13 100644 --- a/src/Commands/DeleteBannedIPHandler.php +++ b/src/Commands/DeleteBannedIPHandler.php @@ -26,11 +26,6 @@ public function __construct(BannedIPRepository $bannedIPs) $this->bannedIPs = $bannedIPs; } - /** - * @param DeleteBannedIP $command - * - * @return BanIP - */ public function handle(DeleteBannedIP $command) { /** diff --git a/src/Commands/EditBannedIPHandler.php b/src/Commands/EditBannedIPHandler.php index af172c6..42fe912 100644 --- a/src/Commands/EditBannedIPHandler.php +++ b/src/Commands/EditBannedIPHandler.php @@ -15,29 +15,21 @@ use Flarum\User\Exception\PermissionDeniedException; use Flarum\User\User; use FoF\BanIPs\BannedIP; -use FoF\BanIPs\Repositories\BannedIPRepository; use FoF\BanIPs\Validators\BannedIPValidator; use Illuminate\Support\Arr; class EditBannedIPHandler { - /** - * @var BannedIPRepository - */ - private $bannedIPs; - /** * @var BannedIPValidator */ private $validator; /** - * @param BannedIPRepository $bannedIPs - * @param BannedIPValidator $validator + * @param BannedIPValidator $validator */ - public function __construct(BannedIPRepository $bannedIPs, BannedIPValidator $validator) + public function __construct(BannedIPValidator $validator) { - $this->bannedIPs = $bannedIPs; $this->validator = $validator; } @@ -56,6 +48,7 @@ public function handle(EditBannedIP $command) $attributes = Arr::get($data, 'attributes', []); + /** @var BannedIP $bannedIP */ $bannedIP = BannedIP::find($command->bannedId); $actor->assertCan('banIP'); @@ -80,7 +73,9 @@ public function handle(EditBannedIP $command) $this->validator->assertValid($bannedIP->getDirty()); - $bannedIP->save(); + if ($bannedIP->isDirty()) { + $bannedIP->save(); + } return $bannedIP; } diff --git a/src/Commands/UnbanUserHandler.php b/src/Commands/UnbanUserHandler.php index bf35742..b0912f5 100644 --- a/src/Commands/UnbanUserHandler.php +++ b/src/Commands/UnbanUserHandler.php @@ -14,17 +14,10 @@ use Flarum\User\User; use FoF\BanIPs\Events\IPWasUnbanned; use FoF\BanIPs\Repositories\BannedIPRepository; -use FoF\BanIPs\Validators\BannedIPValidator; -use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Events\Dispatcher as DispatcherEvents; class UnbanUserHandler { - /** - * @var Dispatcher - */ - private $bus; - /** * @var DispatcherEvents */ @@ -36,26 +29,17 @@ class UnbanUserHandler private $bannedIPs; /** - * @var BannedIPValidator - */ - private $validator; - - /** - * @param Dispatcher $bus * @param DispatcherEvents $events * @param BannedIPRepository $bannedIPs - * @param BannedIPValidator $validator */ - public function __construct(Dispatcher $bus, DispatcherEvents $events, BannedIPRepository $bannedIPs, BannedIPValidator $validator) + public function __construct(DispatcherEvents $events, BannedIPRepository $bannedIPs) { - $this->bus = $bus; $this->events = $events; $this->bannedIPs = $bannedIPs; - $this->validator = $validator; } /** - * @param BanUser $command + * @param UnbanUser $command * * @return mixed */ diff --git a/src/Listeners/RemoveAccessToBannedUsers.php b/src/Listeners/RemoveAccessToBannedUsers.php index 5dc9277..97cacdd 100644 --- a/src/Listeners/RemoveAccessToBannedUsers.php +++ b/src/Listeners/RemoveAccessToBannedUsers.php @@ -11,7 +11,6 @@ namespace FoF\BanIPs\Listeners; -use Flarum\Http\SessionAuthenticator; use Flarum\User\User; use FoF\BanIPs\Events\IPWasBanned; use FoF\BanIPs\Repositories\BannedIPRepository; @@ -23,14 +22,8 @@ class RemoveAccessToBannedUsers */ private $bannedIPs; - /** - * @var SessionAuthenticator - */ - private $authenticator; - - public function __construct(SessionAuthenticator $authenticator, BannedIPRepository $bannedIPs) + public function __construct(BannedIPRepository $bannedIPs) { - $this->authenticator = $authenticator; $this->bannedIPs = $bannedIPs; } @@ -40,9 +33,7 @@ public function handle(IPWasBanned $event) $users = $this->bannedIPs->findUsers($bannedIP->address); foreach ($users as $user) { - /* - * @var User $user - */ + /** @var User $user */ $user->accessTokens()->delete(); } } diff --git a/src/Middleware/RegisterMiddleware.php b/src/Middleware/RegisterMiddleware.php index 451cdfc..a78a971 100644 --- a/src/Middleware/RegisterMiddleware.php +++ b/src/Middleware/RegisterMiddleware.php @@ -78,7 +78,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->format( resolve(Registry::class) ->handle(new ValidationException([ - 'ip' => resolve('translator')->trans('fof-ban-ips.error.banned_ip_message'), + 'ip' => 'Authorization failed.', ])), $request ); diff --git a/src/Repositories/BannedIPRepository.php b/src/Repositories/BannedIPRepository.php index 0d17a66..1593bb0 100644 --- a/src/Repositories/BannedIPRepository.php +++ b/src/Repositories/BannedIPRepository.php @@ -31,7 +31,7 @@ class BannedIPRepository private static $ips = []; /** - * Get a new query builder for the pages table. + * Get a new query builder for the banned IP table. * * @return Builder */ @@ -47,8 +47,6 @@ public function query() * @param User $actor * * @throws ModelNotFoundException - * - * @return BannedIP */ public function findOrFail($id, User $actor = null) { @@ -111,7 +109,7 @@ public function findUsers($ips) */ public function isUserBanned(User $user) { - if (Arr::has(self::$bans, $user->id)) { + if (Arr::has(self::$bans, [$user->id])) { return (bool) self::$bans[$user->id]; } @@ -120,7 +118,7 @@ public function isUserBanned(User $user) public function getUserIPs(User $user): Collection { - if (Arr::has(self::$ips, $user->id)) { + if (Arr::has(self::$ips, [$user->id])) { return self::$ips[$user->id]; } diff --git a/src/Search/NxGambit.php b/src/Search/NxGambit.php index 98a7d65..a4e7e99 100644 --- a/src/Search/NxGambit.php +++ b/src/Search/NxGambit.php @@ -35,5 +35,6 @@ public function filter(FilterState $filterState, string $filterValue, bool $nega public function apply(SearchState $search, $bit) { // Does nothing + return false; } } diff --git a/src/Validators/BannedIPValidator.php b/src/Validators/BannedIPValidator.php index 168fbcd..3ba0409 100644 --- a/src/Validators/BannedIPValidator.php +++ b/src/Validators/BannedIPValidator.php @@ -16,8 +16,8 @@ class BannedIPValidator extends AbstractValidator { protected $rules = [ - 'userId' => ['required', 'integer'], - 'address' => ['required', 'ip', 'unique:banned_ips,address'], - 'reason' => ['nullable', 'string'], + 'creatorId' => ['required', 'integer'], + 'address' => ['required', 'ip', 'unique:banned_ips,address'], + 'reason' => ['nullable', 'string'], ]; } diff --git a/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/IPAddressesTrait.php b/tests/fixtures/IPAddressesTrait.php new file mode 100644 index 0000000..22a330e --- /dev/null +++ b/tests/fixtures/IPAddressesTrait.php @@ -0,0 +1,139 @@ +getAllBanned(); + $id = 1; + $entries = []; + + foreach ($bannedIPs as $bannedIP) { + $entries[] = [ + 'id' => $id, + 'creator_id' => 1, + 'address' => $bannedIP, + 'reason' => "Testing #{$id}", + 'user_id' => 3, + 'created_at' => Carbon::now(), + ]; + + $id++; + } + + return $entries; + } + + public function getRandomBannedIPv4(): string + { + $bannedIPs = $this->getIPv4Banned(); + + return $bannedIPs[array_rand($bannedIPs)]; + } + + public function getRandomNotBannedIPv4(): string + { + $notBannedIPs = $this->getIPv4NotBanned(); + + return $notBannedIPs[array_rand($notBannedIPs)]; + } + + public function getRandomBannedIPv6(): string + { + $bannedIPs = $this->getIPv6Banned(); + + return $bannedIPs[array_rand($bannedIPs)]; + } + + public function getRandomNotBannedIPv6(): string + { + $notBannedIPs = $this->getIPv6NotBanned(); + + return $notBannedIPs[array_rand($notBannedIPs)]; + } + + public function getRandomBannedIP(): string + { + $bannedIPs = $this->getAllBanned(); + + return $bannedIPs[array_rand($bannedIPs)]; + } + + public function getRandomNotBannedIP(): string + { + $notBannedIPs = $this->getAllNotBanned(); + + return $notBannedIPs[array_rand($notBannedIPs)]; + } +} diff --git a/tests/fixtures/IPRequestTrait.php b/tests/fixtures/IPRequestTrait.php new file mode 100644 index 0000000..5cd3cbc --- /dev/null +++ b/tests/fixtures/IPRequestTrait.php @@ -0,0 +1,63 @@ +requestWithJsonBody( + $request, + $options['json'] + ); + } + + // Authenticate as a given user + if (isset($options['authenticatedAs'])) { + $request = $this->requestAsUser( + $request, + $options['authenticatedAs'] + ); + } + + // Let's copy the cookies from a previous response + if (isset($options['cookiesFrom'])) { + $request = $this->requestWithCookiesFrom( + $request, + $options['cookiesFrom'] + ); + } + + return $request; + } +} diff --git a/tests/integration/BannedIPRepositoryTest.php b/tests/integration/BannedIPRepositoryTest.php new file mode 100644 index 0000000..4f041c5 --- /dev/null +++ b/tests/integration/BannedIPRepositoryTest.php @@ -0,0 +1,268 @@ +getBannedIPsForDB(); + + $this->prepareDatabase([ + 'banned_ips' => $bannedIPs, + 'users' => [ + $this->normalUser(), + ['id' => 3, 'username' => 'ipBanned', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'ipbanned@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ['id' => 4, 'username' => 'doubleIPUser', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'double@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ['id' => 5, 'username' => 'noPostsUser', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'noPosts@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ], + 'discussions' => [ + ['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1], + ['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1], + ['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1], + ['id' => 4, 'title' => 'hidden', 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'hidden_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1], + ['id' => 5, 'title' => 'ipbanned', 'created_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'user_id' => 3, 'comment_count' => 1], + ], + 'posts' => [ + ['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

foo bar

', 'ip_address' => $this->getIPv4NotBanned()[0]], + ['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '

not in text

', 'ip_address' => $this->getIPv4NotBanned()[1]], + ['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4NotBanned()[2]], + ['id' => 4, 'discussion_id' => 4, 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4NotBanned()[3]], + ['id' => 5, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'user_id' => 3, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4Banned()[1]], + ['id' => 6, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 22)->toDateTimeString(), 'user_id' => 3, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv6Banned()[1]], + ['id' => 7, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 23)->toDateTimeString(), 'user_id' => 4, 'type' => 'comment', 'content' => '

same IPv4 twice

', 'ip_address' => $this->getIPv4NotBanned()[0]], + ['id' => 8, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 24)->toDateTimeString(), 'user_id' => 4, 'type' => 'comment', 'content' => '

same IPv4 twice

', 'ip_address' => $this->getIPv4NotBanned()[0]], + ['id' => 9, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 24)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

admin post from banned ip

', 'ip_address' => $this->getIPv6Banned()[3]], + ], + ]); + + $this->repository = new BannedIPRepository(); + + $this->extension('fof-ban-ips'); + + $this->app(); + } + + /** + * @test + */ + public function it_checks_if_user_is_banned() + { + $user = User::find(2); + $isBanned = $this->repository->isUserBanned($user); + $this->assertFalse($isBanned, 'User should not be banned'); + + $bannedUser = User::find(3); + $isBanned = $this->repository->isUserBanned($bannedUser); + $this->assertTrue($isBanned, 'User should be banned'); + } + + /** + * @test + */ + public function it_retrieves_user_ips() + { + $user = User::find(3); + $ips = $this->repository->getUserIPs($user); + $this->assertContains($this->getIPv4Banned()[1], $ips, 'The IPs should contain the banned IPv4'); + $this->assertContains($this->getIPv6Banned()[1], $ips, 'The IPs should contain the banned IPv6'); + } + + /** + * @test + */ + public function it_identifies_banned_ips_for_user() + { + $user = User::find(3); + $bannedIps = $this->repository->getUserBannedIPs($user)->pluck('address')->toArray(); + $this->assertContains($this->getIPv4Banned()[1], $bannedIps, "The user's banned IPs should contain the given IPv4"); + $this->assertContains($this->getIPv6Banned()[1], $bannedIps, "The user's banned IPs should contain the given IPv6"); + } + + /** + * @test + */ + public function it_finds_banned_ip_by_id() + { + $bannedIP = $this->repository->findOrFail(1); + $this->assertInstanceOf(BannedIP::class, $bannedIP); + $this->assertEquals($this->getIPv4Banned()[0], $bannedIP->address); + } + + /** + * @test + */ + public function it_finds_banned_ip_by_address() + { + $bannedIP = $this->repository->findByIPAddress($this->getIPv4Banned()[3]); + $this->assertInstanceOf(BannedIP::class, $bannedIP); + $this->assertEquals($this->getIPv4Banned()[3], $bannedIP->address); + + $bannedIP = $this->repository->findByIPAddress($this->getIPv6Banned()[2]); + $this->assertInstanceOf(BannedIP::class, $bannedIP); + $this->assertEquals($this->getIPv6Banned()[2], $bannedIP->address); + } + + /** + * @test + */ + public function it_finds_other_users_by_ips() + { + $ips = [$this->getIPv4Banned()[1], $this->getIPv4Banned()[0]]; + $user = User::find(2); // normal user + $otherUsers = $this->repository->findOtherUsers($user, $ips); + $this->assertNotEmpty($otherUsers); + $this->assertContains(3, $otherUsers->pluck('id')->toArray()); // User ID of ipBanned + + $ips = [$this->getIPv6Banned()[1], $this->getIPv6Banned()[0]]; + $user = User::find(2); // normal user + $otherUsers = $this->repository->findOtherUsers($user, $ips); + $this->assertNotEmpty($otherUsers); + $this->assertContains(3, $otherUsers->pluck('id')->toArray()); // User ID of ipBanned + } + + /** + * @test + */ + public function it_finds_users_by_ips() + { + $ips = [$this->getIPv4Banned()[1]]; + $users = $this->repository->findUsers($ips); + $this->assertNotEmpty($users); + $this->assertContains(3, $users->pluck('id')->toArray()); // User ID of ipBanned + + $ips = [$this->getIPv6Banned()[1]]; + $users = $this->repository->findUsers($ips); + $this->assertNotEmpty($users); + $this->assertContains(3, $users->pluck('id')->toArray()); // User ID of ipBanned + } + + /** + * @test + */ + public function it_gets_user_banned_ips() + { + $user = User::find(3); // ipBanned user + $bannedIPs = $this->repository->getUserBannedIPs($user)->get(); + $this->assertNotEmpty($bannedIPs); + $this->assertContains($this->getIPv4Banned()[1], $bannedIPs->pluck('address')->toArray()); + $this->assertContains($this->getIPv6Banned()[1], $bannedIPs->pluck('address')->toArray()); + } + + /** + * @test + */ + public function it_handles_multiple_posts_with_same_ip_correctly() + { + $user = User::find(4); // doubleIPUser + $ips = $this->repository->getUserIPs($user); + $this->assertCount(1, $ips, 'There should be only one unique IP for the user'); + $this->assertContains($this->getIPv4NotBanned()[0], $ips, 'The IPs should contain the IPv4 used twice'); + } + + /** + * @test + */ + public function it_handles_users_with_same_banned_ip_correctly() + { + $ips = [$this->getIPv4Banned()[1]]; + $users = $this->repository->findUsers($ips); + $this->assertContains(3, $users->pluck('id')->toArray()); + // If there's another user with the same IP, check for their ID here. + } + + /** + * @test + */ + public function it_does_not_flag_non_banned_user_as_banned() + { + $user = User::find(2); // normal user + $isBanned = $this->repository->isUserBanned($user); + $this->assertFalse($isBanned, 'User should not be banned'); + } + + /** + * @test + */ + public function it_handles_users_with_no_posts() + { + $userWithoutPosts = User::find(5); + $ips = $this->repository->getUserIPs($userWithoutPosts); + $this->assertCount(0, $ips, 'A user with no posts should have no IP records'); + } + + /** + * @test + */ + public function it_identifies_multiple_banned_ips_for_user() + { + $user = User::find(3); + $bannedIps = $this->repository->getUserBannedIPs($user)->pluck('address')->toArray(); + $this->assertContains($this->getIPv4Banned()[1], $bannedIps, "The user's banned IPs should contain the first banned IPv4"); + $this->assertContains($this->getIPv4Banned()[2], $bannedIps, "The user's banned IPs should contain the second banned IPv4"); + } + + /** + * @test + */ + public function it_returns_null_for_invalid_ip_format() + { + $bannedIP = $this->repository->findByIPAddress('InvalidIP'); + $this->assertNull($bannedIP); + } + + /** + * @test + */ + public function it_handles_large_list_of_ips_efficiently() + { + // Generate a large list of IPs + $largeListOfIps = range(1, 30000); // Adjust as needed, this is just for demonstration + $users = $this->repository->findUsers($largeListOfIps); + $this->assertNotNull($users, 'The system should handle large IP lists without crashing'); + } + + /** + * @test + */ + public function it_does_not_ban_admins_based_on_ip() + { + $admin = User::find(1); + $isBanned = $this->repository->isUserBanned($admin); + $this->assertFalse($isBanned, 'Admins should not be banned based on IP'); + } + + /** + * @test + */ + public function it_does_ban_normal_user_based_on_ip() + { + $user = User::find(3); + $isBanned = $this->repository->isUserBanned($user); + $this->assertTrue($isBanned, 'Normal user with a banned IP should be banned'); + } +} diff --git a/tests/integration/api/CreateBannedIPControllerTest.php b/tests/integration/api/CreateBannedIPControllerTest.php new file mode 100644 index 0000000..62c0d16 --- /dev/null +++ b/tests/integration/api/CreateBannedIPControllerTest.php @@ -0,0 +1,189 @@ +extension('fof-ban-ips'); + + $this->prepareDatabase([ + 'users' => [ + $this->normalUser(), + ['id' => 3, 'username' => 'moderator', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'moderator@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ['id' => 4, 'username' => 'normal2', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'normal2@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ], + 'group_user' => [ + ['group_id' => 4, 'user_id' => 3], + ], + 'group_permission' => [ + ['group_id' => 4, 'permission' => 'fof.ban-ips.banIP'], + ['group_id' => 4, 'permission' => 'fof.ban-ips.viewBannedIPList'], + ['group_id' => 4, 'permission' => 'discussion.viewIpsPosts'], + ], + 'banned_ips' => $this->getBannedIPsForDB(), + ]); + } + + public function adminAndModeratorUserIdProvider(): array + { + return [ + ['actorId' => 1, 'userId' => 2], + ['actorId' => 1, 'userId' => null], + ['actorId' => 3, 'userId' => 2], + ['actorId' => 3, 'userId' => null], + ]; + } + + public function test_user_cannot_create_ip_ban_with_no_data() + { + $response = $this->send( + $this->request('POST', '/api/fof/ban-ips', [ + 'authenticatedAs' => 1, + 'json' => [], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode()); + } + + /** + * @dataProvider adminAndModeratorUserIdProvider + */ + public function test_user_with_permission_can_ban_ip_not_already_banned(int $actorId, ?int $userId) + { + $response = $this->send( + $this->request('POST', '/api/fof/ban-ips', [ + 'authenticatedAs' => $actorId, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'address' => $this->getIPv6NotBanned()[2], + 'reason' => 'Testing', + 'userId' => $userId, + ], + ], + ], + ]) + ); + + $this->assertEquals(201, $response->getStatusCode()); + + $body = $response->getBody(); + + $this->assertJson($body); + + $attrs = json_decode($body, true)['data']['attributes']; + + $this->assertEquals($this->getIPv6NotBanned()[2], $attrs['address']); + $this->assertEquals('Testing', $attrs['reason']); + $this->assertEquals($userId, $attrs['userId']); + $this->assertEquals($actorId, $attrs['creatorId']); + } + + /** + * @dataProvider adminAndModeratorUserIdProvider + */ + public function test_user_with_permission_can_ban_ip_not_already_banned_without_associated_user(int $userId) + { + $response = $this->send( + $this->request('POST', '/api/fof/ban-ips', [ + 'authenticatedAs' => $userId, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'address' => $this->getIPv4NotBanned()[0], + 'reason' => 'Testing', + ], + ], + ], + ]) + ); + + $this->assertEquals(201, $response->getStatusCode()); + + $body = $response->getBody(); + + $this->assertJson($body); + + $attrs = json_decode($body, true)['data']['attributes']; + + $this->assertEquals($this->getIPv4NotBanned()[0], $attrs['address']); + $this->assertEquals('Testing', $attrs['reason']); + $this->assertNull($attrs['userId']); + $this->assertEquals($userId, $attrs['creatorId']); + } + + public function test_user_with_permission_cannot_ban_ip_already_banned() + { + $response = $this->send( + $this->request('POST', '/api/fof/ban-ips', [ + 'authenticatedAs' => 3, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'address' => $this->getIPv6Banned()[1], + 'reason' => 'Testing', + ], + ], + ], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode()); + + $body = (string) $response->getBody(); + $this->assertJson($body); + $this->assertEquals([ + 'errors' => [ + [ + 'status' => '422', + 'code' => 'validation_error', + 'detail' => 'The address has already been taken.', + 'source' => ['pointer' => '/data/attributes/address'], + ], + ], + ], json_decode($body, true)); + } + + public function test_user_without_permission_cannnot_ban_ip() + { + $response = $this->send( + $this->request('POST', '/api/fof/ban-ips', [ + 'authenticatedAs' => 2, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'address' => $this->getIPv4NotBanned()[1], + 'reason' => 'Testing', + ], + ], + ], + ]) + ); + + $this->assertEquals(403, $response->getStatusCode()); + } + + // ... More test cases ... +} diff --git a/tests/integration/forum/AccessTest.php b/tests/integration/forum/AccessTest.php new file mode 100644 index 0000000..50f4c7b --- /dev/null +++ b/tests/integration/forum/AccessTest.php @@ -0,0 +1,424 @@ +prepareDatabase([ + 'banned_ips' => $this->getBannedIPsForDB(), + 'users' => [ + $this->normalUser(), + ['id' => 3, 'username' => 'ipBanned', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => 'ipbanned@machine.local', 'is_email_confirmed' => 1, 'last_seen_at' => Carbon::now()->subSecond()], + ], + 'discussions' => [ + ['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1], + ['id' => 2, 'title' => 'lightsail in title', 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1], + ['id' => 3, 'title' => 'not in title', 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'comment_count' => 1], + ['id' => 4, 'title' => 'hidden', 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'hidden_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1], + ['id' => 5, 'title' => 'ipbanned', 'created_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'last_posted_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'user_id' => 3, 'comment_count' => 2], + ], + 'posts' => [ + ['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::createFromDate(1975, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

foo bar

', 'ip_address' => $this->getIPv4NotBanned()[0]], + ['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::createFromDate(1985, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '

not in text

', 'ip_address' => $this->getIPv4NotBanned()[1]], + ['id' => 3, 'discussion_id' => 3, 'created_at' => Carbon::createFromDate(1995, 5, 21)->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4NotBanned()[2]], + ['id' => 4, 'discussion_id' => 4, 'created_at' => Carbon::createFromDate(2005, 5, 21)->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4NotBanned()[3]], + ['id' => 5, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'user_id' => 3, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv4Banned()[1]], + ['id' => 6, 'discussion_id' => 5, 'created_at' => Carbon::createFromDate(2015, 5, 21)->toDateTimeString(), 'user_id' => 3, 'type' => 'comment', 'content' => '

lightsail in text

', 'ip_address' => $this->getIPv6Banned()[1]], + ], + ]); + + $this->extend( + (new Extend\Csrf()) + ->exemptRoute('register') + ->exemptRoute('login') + ); + + $this->extension('fof-ban-ips'); + } + + public function bannedIPsProvider(): array + { + return [ + [$this->getIPv4Banned()[0]], + [$this->getIPv6Banned()[0]], + [$this->getIPv4Banned()[1]], + [$this->getIPv6Banned()[1]], + [$this->getIPv4Banned()[2]], + [$this->getIPv6Banned()[2]], + [$this->getIPv4Banned()[3]], + [$this->getIPv6Banned()[3]], + ]; + } + + public function notBannedIPsProvider(): array + { + return [ + [$this->getIPv4NotBanned()[0]], + [$this->getIPv6NotBanned()[0]], + [$this->getIPv4NotBanned()[1]], + [$this->getIPv6NotBanned()[1]], + [$this->getIPv4NotBanned()[2]], + [$this->getIPv6NotBanned()[2]], + [$this->getIPv4NotBanned()[3]], + [$this->getIPv6NotBanned()[3]], + ]; + } + + /** + * @dataProvider bannedIPsProvider + */ + public function test_banned_ips_cannot_register($bannedIP) + { + $response = $this->send($this->enhancedRequest('POST', '/register', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $bannedIP, + ], + 'json' => [ + 'username' => 'test', + 'password' => 'too-obscure', + 'email' => 'test@machine.local', + ], + ])); + + $this->assertEquals(422, $response->getStatusCode()); + + $body = (string) $response->getBody(); + $this->assertJson($body); + $this->assertEquals([ + 'errors' => [ + [ + 'status' => '422', + 'code' => 'validation_error', + 'detail' => 'Authorization failed.', + 'source' => ['pointer' => '/data/attributes/ip'], + ], + ], + ], json_decode($body, true)); + } + + /** + * @dataProvider notBannedIPsProvider + */ + public function test_not_banned_ips_can_register($notBannedIP) + { + $response = $this->send($this->enhancedRequest('POST', '/register', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $notBannedIP, + ], + 'json' => [ + 'username' => 'test', + 'password' => 'too-obscure', + 'email' => 'test@machine.local', + ], + ])); + + $this->assertEquals(201, $response->getStatusCode()); + } + + /** + * @dataProvider notBannedIPsProvider + */ + public function test_not_banned_ip_can_login_when_user_is_not_ip_banned($notBannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $notBannedIP, + ], + 'json' => [ + 'identification' => 'normal', + 'password' => 'too-obscure', + ], + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + + // The response body should contain the user ID... + $body = (string) $response->getBody(); + $this->assertJson($body); + + $data = json_decode($body, true); + $this->assertEquals(2, $data['userId']); + + // ...and an access token belonging to this user. + $token = $data['token']; + $this->assertEquals(2, AccessToken::whereToken($token)->firstOrFail()->user_id); + } + + /** + * @dataProvider bannedIPsProvider + */ + public function test_banned_ip_can_login_if_user_is_not_associated_with_a_banned_ip($bannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $bannedIP, + ], + 'json' => [ + 'identification' => 'normal', + 'password' => 'too-obscure', + ], + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + + // The response body should contain the user ID... + $body = (string) $response->getBody(); + $this->assertJson($body); + + $data = json_decode($body, true); + $this->assertEquals(2, $data['userId']); + + // ...and an access token belonging to this user. + $token = $data['token']; + $this->assertEquals(2, AccessToken::whereToken($token)->firstOrFail()->user_id); + } + + /** + * @dataProvider notBannedIPsProvider + */ + public function test_non_banned_ip_can_login($notBannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $notBannedIP, + ], + 'json' => [ + 'identification' => 'normal', + 'password' => 'too-obscure', + ], + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + + // The response body should contain the user ID... + $body = (string) $response->getBody(); + $this->assertJson($body); + + $data = json_decode($body, true); + $this->assertEquals(2, $data['userId']); + + // ...and an access token belonging to this user. + $token = $data['token']; + $this->assertEquals(2, AccessToken::whereToken($token)->firstOrFail()->user_id); + } + + public function test_banned_user_cannot_login_using_non_associated_ip() + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $this->getIPv6Banned()[3], + ], + 'json' => [ + 'identification' => 'ipBanned', + 'password' => 'too-obscure', + ], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode()); + + $body = (string) $response->getBody(); + $this->assertJson($body); + $this->assertEquals([ + 'errors' => [ + [ + 'status' => '422', + 'code' => 'validation_error', + 'detail' => 'Authorization failed.', + 'source' => ['pointer' => '/data/attributes/ip'], + ], + ], + ], json_decode($body, true)); + } + + public function test_banned_user_cannot_login_using_associated_ip() + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $this->getIPv6Banned()[1], + ], + 'json' => [ + 'identification' => 'ipBanned', + 'password' => 'too-obscure', + ], + ]) + ); + + $this->assertEquals(422, $response->getStatusCode()); + + $body = (string) $response->getBody(); + $this->assertJson($body); + $this->assertEquals([ + 'errors' => [ + [ + 'status' => '422', + 'code' => 'validation_error', + 'detail' => 'Authorization failed.', + 'source' => ['pointer' => '/data/attributes/ip'], + ], + ], + ], json_decode($body, true)); + } + + /** + * @dataProvider notBannedIPsProvider + */ + public function test_admin_can_access_restriced_path_from_not_banned_ip($notBannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $notBannedIP, + ], + 'json' => [ + 'identification' => 'admin', + 'password' => 'password', + ], + ]) + ); + + $response = $this->send($this->enhancedRequest('GET', '/settings', [ + 'serverParams' => ['REMOTE_ADDR' => $notBannedIP], + 'cookiesFrom' => $response, + ])); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @dataProvider bannedIPsProvider + */ + public function test_admin_can_access_restriced_path_from_banned_ip($bannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $bannedIP, + ], + 'json' => [ + 'identification' => 'admin', + 'password' => 'password', + ], + ]) + ); + + $response = $this->send($this->enhancedRequest('GET', '/settings', [ + 'serverParams' => ['REMOTE_ADDR' => $bannedIP], + 'cookiesFrom' => $response, + ])); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @dataProvider bannedIPsProvider + */ + public function test_not_banned_user_is_able_to_access_restriced_path_from_banned_ip($bannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $bannedIP, + ], + 'json' => [ + 'identification' => 'normal', + 'password' => 'too-obscure', + ], + ]) + ); + + $response = $this->send($this->enhancedRequest('GET', '/settings', [ + 'serverParams' => ['REMOTE_ADDR' => $bannedIP], + 'cookiesFrom' => $response, + ])); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @dataProvider notBannedIPsProvider + */ + public function test_not_banned_user_is_able_to_access_restriced_path_from_not_banned_ip($notBannedIP) + { + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $notBannedIP, + ], + 'json' => [ + 'identification' => 'normal', + 'password' => 'too-obscure', + ], + ]) + ); + + $response = $this->send($this->enhancedRequest('GET', '/settings', [ + 'serverParams' => ['REMOTE_ADDR' => $notBannedIP], + 'cookiesFrom' => $response, + ])); + + $this->assertEquals(200, $response->getStatusCode()); + } + + public function test_banned_user_cannot_access_restricted_path_from_banned_ip() + { + // For the purposes of the test, we will login from a non-banned IP + $response = $this->send( + $this->enhancedRequest('POST', '/login', [ + 'serverParams' => [ + 'REMOTE_ADDR' => $this->getIPv4NotBanned()[1], + ], + 'json' => [ + 'identification' => 'ipBanned', + 'password' => 'too-obscure', + ], + ]) + ); + + // Make sure we have logged in successfully + $this->assertEquals(200, $response->getStatusCode()); + + // Now send another request, which is authenticated with a cookie, but from a banned IP + $response = $this->send($this->enhancedRequest('GET', '/settings', [ + 'serverParams' => ['REMOTE_ADDR' => $this->getIPv4Banned()[1]], + 'cookiesFrom' => $response, + ])); + + // assert that we are redirected to /logout + $this->assertEquals(302, $response->getStatusCode()); + $this->assertStringContainsString('/logout?token=', $response->getHeaderLine('Location')); + } +} diff --git a/tests/integration/setup.php b/tests/integration/setup.php new file mode 100644 index 0000000..802c562 --- /dev/null +++ b/tests/integration/setup.php @@ -0,0 +1,18 @@ +run(); diff --git a/tests/phpunit.integration.xml b/tests/phpunit.integration.xml new file mode 100644 index 0000000..90fbbff --- /dev/null +++ b/tests/phpunit.integration.xml @@ -0,0 +1,25 @@ + + + + + ../src/ + + + + + ./integration + ./integration/tmp + + + diff --git a/tests/phpunit.unit.xml b/tests/phpunit.unit.xml new file mode 100644 index 0000000..d3a4a3e --- /dev/null +++ b/tests/phpunit.unit.xml @@ -0,0 +1,27 @@ + + + + + ../src/ + + + + + ./unit + + + + + + diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep new file mode 100644 index 0000000..e69de29