diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 32580057..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: -- package-ecosystem: composer - directory: "/" - schedule: - interval: monthly - open-pull-requests-limit: 10 - ignore: - - dependency-name: symfony/var-dumper - versions: - - 5.2.4 - - 5.2.5 diff --git a/.github/workflows/doc-links.yml b/.github/workflows/doc-links.yml index 92c56767..9ff2a52f 100644 --- a/.github/workflows/doc-links.yml +++ b/.github/workflows/doc-links.yml @@ -37,4 +37,4 @@ jobs: gem install awesome_bot cd extension awesome_bot --files README.md --allow-dupe --allow 401 --skip-save-results --white-list ddev.site --base-url http://localhost:8080/ - awesome_bot docs/*.md --skip-save-results --allow-dupe --allow 401 --white-list ddev.site,your-wordpress-url,crowdsec:8080 --base-url http://localhost:8080/docs/ + awesome_bot docs/*.md --skip-save-results --allow-dupe --allow 401 --white-list ddev.site,your-wordpress-url,crowdsec:8080,localhost:7422 --base-url http://localhost:8080/docs/ diff --git a/.github/workflows/end-to-end-auto-prepend-test-suite.yml b/.github/workflows/end-to-end-auto-prepend-test-suite.yml index eb1815d1..2cce8353 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -95,6 +95,9 @@ jobs: - name: Prepare for playwright test run: | + ddev exec -s crowdsec apk add iproute2 + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --post_type=page --post_status=publish --post_title="AppSec" - + ddev wp rewrite structure "/%postname%/" mkdir -p crowdsec/tls mkdir -p crowdsec/geolocation cp .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/php/cache-actions-with-wordpress-load.php cache-actions.php @@ -197,6 +200,21 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 8-geolocation.js + - name: Run AppSec tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 11-appsec.js + + - name: Prepare CrowdSec for AppSec timeout tests + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 500ms + + - name: Run AppSec timeout tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 12-appsec-timeout.js + - name: Check tested version run: | CURRENT_VERSION=$(ddev wp core version) diff --git a/.github/workflows/end-to-end-multisite.yml b/.github/workflows/end-to-end-multisite.yml index 0dc76e63..3524efb2 100644 --- a/.github/workflows/end-to-end-multisite.yml +++ b/.github/workflows/end-to-end-multisite.yml @@ -108,6 +108,11 @@ jobs: - name: Prepare for playwright test run: | + ddev exec -s crowdsec apk add iproute2 + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --url='https://${{ env.WP_VERSION_CODE }}.ddev.site/site1' --post_type=page --post_status=publish --post_title="AppSec" - + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --url='https://${{ env.WP_VERSION_CODE }}.ddev.site/site2' --post_type=page --post_status=publish --post_title="AppSec" - + ddev wp rewrite structure "/%postname%/" --url='https://${{ env.WP_VERSION_CODE }}.ddev.site/site1' + ddev wp rewrite structure "/%postname%/" --url='https://${{ env.WP_VERSION_CODE }}.ddev.site/site2' mkdir -p crowdsec/tls mkdir -p crowdsec/geolocation cp .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/php/cache-actions-with-wordpress-load.php cache-actions.php @@ -195,6 +200,23 @@ jobs: file_path: 8-geolocation.js subsite: ${{ matrix.subsite }} + - name: Run AppSec tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 11-appsec.js + subsite: ${{ matrix.subsite }} + + - name: Prepare CrowdSec for AppSec timeout tests + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 500ms + + - name: Run AppSec timeout tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 12-appsec-timeout.js + subsite: ${{ matrix.subsite }} + - name: Check tested version run: | CURRENT_VERSION=$(ddev wp core version) diff --git a/.github/workflows/end-to-end-test-suite.yml b/.github/workflows/end-to-end-test-suite.yml index c2db209b..58c1afd3 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -6,6 +6,11 @@ on: paths-ignore: - '**.md' workflow_dispatch: + inputs: + debug_enabled: + type: boolean + description: Debug with tmate + default: false schedule: - cron: '15 3 * * 4' @@ -36,8 +41,6 @@ jobs: php-version: '8.0' - wp-version: '6.6' php-version: '7.2' - - wp-version: '6.6' - php-version: '8.0' - wp-version: '6.6' php-version: '8.3' @@ -109,6 +112,9 @@ jobs: - name: Prepare for playwright test run: | + ddev exec -s crowdsec apk add iproute2 + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --post_type=page --post_status=publish --post_title="AppSec" - + ddev wp rewrite structure "/%postname%/" mkdir -p crowdsec/tls mkdir -p crowdsec/geolocation cp .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/php/cache-actions-with-wordpress-load.php cache-actions.php @@ -189,6 +195,22 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 8-geolocation.js + - name: Run AppSec tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 11-appsec.js + + + - name: Prepare CrowdSec for AppSec timeout tests + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 500ms + + - name: Run AppSec timeout tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 12-appsec-timeout.js + - name: Check tested version run: | CURRENT_VERSION=$(ddev wp core version) @@ -201,4 +223,12 @@ jobs: echo ${{ matrix.wp-version }} exit 1 fi + + - name: tmate debugging session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + github-token: ${{ secrets.GITHUB_TOKEN }} + timeout-minutes: 15 + if: failure() && github.event.inputs.debug_enabled == 'true' diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index b61a828d..0e2cb17b 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -132,6 +132,9 @@ jobs: - name: Prepare for playwright test run: | + ddev exec -s crowdsec apk add iproute2 + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --post_type=page --post_status=publish --post_title="AppSec" - + ddev wp rewrite structure "/%postname%/" mkdir -p crowdsec/tls mkdir -p crowdsec/geolocation cp .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/php/cache-actions-with-wordpress-load.php cache-actions.php @@ -211,6 +214,12 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 8-geolocation.js + - name: Run AppSec tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 11-appsec.js + - name: tmate debugging session uses: mxschmitt/action-tmate@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b1294d3..adbef03a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,8 +2,6 @@ name: Deploy and Create Release # example: gh workflow run release.yml -f tag_name=v1.1.4 -f deploy_to_wordpress=true on: workflow_dispatch: - branches: - - main inputs: tag_name: type: string @@ -20,6 +18,7 @@ permissions: env: # Allow ddev get to use a GitHub token to prevent rate limiting by tests DDEV_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ github.event.inputs.tag_name }} jobs: deploy-create-release: @@ -29,29 +28,30 @@ jobs: steps: - name: Check naming convention run: | - VERIF=$(echo ${{ github.event.inputs.tag_name }} | grep -E "^v([0-9]{1,}\.)([0-9]{1,}\.)([0-9]{1,})(-(alpha|beta)\.[0-9]{1,})?$") + VERIF=$(echo ${{ env.TAG_NAME }} | grep -E "^v([0-9]{1,}\.)([0-9]{1,}\.)([0-9]{1,})(-(alpha|beta)\.[0-9]{1,})?$") if [ ! ${VERIF} ] then - echo "Tag name '${{ github.event.inputs.tag_name }}' does not comply with naming convention vX.Y.Z" + echo "Tag name '${{ env.TAG_NAME }}' does not comply with naming convention vX.Y.Z" exit 1 fi - name: Set version number without v + id: set-version-number run: | - echo "VERSION_NUMBER=$(echo ${{ github.event.inputs.tag_name }} | sed 's/v//g' )" >> $GITHUB_ENV + echo "version_number=$(echo ${{ github.event.inputs.tag_name }} | sed 's/v//g' )" >> $GITHUB_OUTPUT - name: Clone sources uses: actions/checkout@v4 - - name: Check version ${{ env.VERSION_NUMBER }} consistency in files + - name: Check version consistency in files # Check crowdsec.php (2), readme.txt (1), inc/Constants.php (1) and CHANGELOG.md (3) run: | CURRENT_DATE=$(date +'%Y-%m-%d') CHANGELOG_VERSION=$(grep -o -E "## \[(.*)\].* - $CURRENT_DATE" CHANGELOG.md | head -1 | sed 's/ //g') echo $CURRENT_DATE echo $CHANGELOG_VERSION - echo "##[${{ env.VERSION_NUMBER }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/v${{ env.VERSION_NUMBER }})-$CURRENT_DATE" - if [[ $CHANGELOG_VERSION == "##[${{ env.VERSION_NUMBER }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/v${{ env.VERSION_NUMBER }})-$CURRENT_DATE" ]] + echo "##[${{ steps.set-version-number.outputs.version_number }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/${{ env.TAG_NAME }})-$CURRENT_DATE" + if [[ $CHANGELOG_VERSION == "##[${{ steps.set-version-number.outputs.version_number }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/${{ env.TAG_NAME }})-$CURRENT_DATE" ]] then echo "Version in CHANGELOG.md: OK" else @@ -60,19 +60,19 @@ jobs: fi COMPARISON=$(grep -oP "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/\K(.*)$" CHANGELOG.md | head -1) LAST_TAG=$(curl -Ls -o /dev/null -w %{url_effective} $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/latest | grep -oP "\/tag\/\K(.*)$") - if [[ $COMPARISON == "$LAST_TAG...v${{ env.VERSION_NUMBER }})" ]] + if [[ $COMPARISON == "$LAST_TAG...${{ env.TAG_NAME }})" ]] then echo "VERSION COMPARISON OK" else echo "VERSION COMPARISON KO" echo $COMPARISON - echo "$LAST_TAG...v${{ env.VERSION_NUMBER }})" + echo "$LAST_TAG...${{ env.TAG_NAME }})" exit 1 fi VERSION=$(grep -E "Version: (.*)" crowdsec.php | sed 's/ //g') echo $VERSION - echo "*Version:${{ env.VERSION_NUMBER }}" - if [[ $VERSION == "*Version:${{ env.VERSION_NUMBER }}" ]] + echo "*Version:${{ steps.set-version-number.outputs.version_number }}" + if [[ $VERSION == "*Version:${{ steps.set-version-number.outputs.version_number}}" ]] then echo "Version in crowdsec.php: OK" else @@ -81,8 +81,8 @@ jobs: fi CROWDSEC_STABLE=$(grep -E "Stable tag: (.*)" crowdsec.php | sed 's/ //g') echo $CROWDSEC_STABLE - echo "*Stabletag:${{ env.VERSION_NUMBER }}" - if [[ $CROWDSEC_STABLE == "*Stabletag:${{ env.VERSION_NUMBER }}" ]] + echo "*Stabletag:${{ steps.set-version-number.outputs.version_number }}" + if [[ $CROWDSEC_STABLE == "*Stabletag:${{ steps.set-version-number.outputs.version_number }}" ]] then echo "Stable tag in crowdsec.php: OK" else @@ -91,8 +91,8 @@ jobs: fi README_STABLE=$(grep -E "Stable tag: (.*)" readme.txt | sed 's/ //g') echo $README_STABLE - echo "Stabletag:${{ env.VERSION_NUMBER }}" - if [[ $README_STABLE == "Stabletag:${{ env.VERSION_NUMBER }}" ]] + echo "Stabletag:${{ steps.set-version-number.outputs.version_number }}" + if [[ $README_STABLE == "Stabletag:${{ steps.set-version-number.outputs.version_number }}" ]] then echo "Stable tag in readme.txt: OK" else @@ -101,8 +101,8 @@ jobs: fi CONSTANT_VERSION=$(grep -E "VERSION = 'v(.*)" inc/Constants.php | sed 's/[\x27(),/ ]//g') echo $CONSTANT_VERSION - echo "publicconstVERSION=v${{ env.VERSION_NUMBER }};" - if [[ $CONSTANT_VERSION == "publicconstVERSION=v${{ env.VERSION_NUMBER }};" ]] + echo "publicconstVERSION=${{ env.TAG_NAME }};" + if [[ $CONSTANT_VERSION == "publicconstVERSION=${{ env.TAG_NAME }};" ]] then echo "Version in inc/Constants.php: OK" else @@ -110,7 +110,7 @@ jobs: exit 1 fi - - name: Create Tag ${{ github.event.inputs.tag_name }} + - name: Create Tag uses: actions/github-script@v7 with: github-token: ${{ github.token }} @@ -118,7 +118,7 @@ jobs: github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, - ref: "refs/tags/${{ github.event.inputs.tag_name }}", + ref: "refs/tags/${{ env.TAG_NAME }}", sha: context.sha }) @@ -132,33 +132,32 @@ jobs: SVN_USERNAME: ${{ secrets.SVN_USERNAME }} SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} SLUG: crowdsec - VERSION: ${{ env.VERSION_NUMBER }} + VERSION: ${{ steps.set-version-number.outputs.version_number }} - name: Prepare release notes run: | - VERSION_RELEASE_NOTES=$(awk -v ver="[${{ env.VERSION_NUMBER }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/v${{ env.VERSION_NUMBER }})" '/^## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p && NF' CHANGELOG.md | sed ':a;N;$!ba;s/\n---/ /g') + VERSION_RELEASE_NOTES=$(awk -v ver="[${{ steps.set-version-number.outputs.version_number }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/${{ env.TAG_NAME }})" '/^## / { if (p) { exit }; if ($2 == ver) { p=1; next} } p && NF' CHANGELOG.md | sed ':a;N;$!ba;s/\n---/ /g') echo "$VERSION_RELEASE_NOTES" >> CHANGELOG.txt cat CHANGELOG.txt - - - name: Create release ${{ env.VERSION_NUMBER }} with Wordpress zip + - name: Create release with Wordpress zip if: github.event.inputs.deploy_to_wordpress == 'true' uses: softprops/action-gh-release@v2 with: files: crowdsec.zip body_path: CHANGELOG.txt - name: ${{ env.VERSION_NUMBER }} - tag_name: ${{ github.event.inputs.tag_name }} + name: ${{ steps.set-version-number.outputs.version_number }} + tag_name: ${{ env.TAG_NAME }} draft: false prerelease: false - - name: Create release ${{ env.VERSION_NUMBER }} without Wordpress zip + - name: Create release without Wordpress zip if: github.event.inputs.deploy_to_wordpress != 'true' uses: softprops/action-gh-release@v2 with: body_path: CHANGELOG.txt - name: ${{ env.VERSION_NUMBER }} - tag_name: ${{ github.event.inputs.tag_name }} + name: ${{ steps.set-version-number.outputs.version_number }} + tag_name: ${{ env.TAG_NAME }} draft: false prerelease: false @@ -199,11 +198,12 @@ jobs: - name: Set WP_VERSION_CODE env # used in some directory path and conventional file naming # Example : 5.6.5 => wp565 + id: set-wp-version-code run: | - echo "WP_VERSION_CODE=$(echo wp${{ matrix.wp-version }} | sed 's/\.//g' )" >> $GITHUB_ENV + echo "wp_version_code=$(echo wp${{ matrix.wp-version }} | sed 's/\.//g' )" >> $GITHUB_OUTPUT - name: Create empty WordPress DDEV project (with Apache) - run: ddev config --project-type=wordpress --project-name=${{ env.WP_VERSION_CODE }} --php-version=${{ matrix.php-version }} --webserver-type=apache-fpm + run: ddev config --project-type=wordpress --project-name=${{ steps.set-wp-version-code.outputs.wp_version_code }} --php-version=${{ matrix.php-version }} --webserver-type=apache-fpm - name: Disable automatic update run: | @@ -234,23 +234,24 @@ jobs: - name: Setup WordPress ${{ matrix.wp-version }} with PHP ${{ matrix.php-version }} run: | - ddev exec wp core install --url='https://${{ env.WP_VERSION_CODE }}.ddev.site' --title='WordPress' --admin_user='admin' --admin_password='admin123' --admin_email='admin@admin.com' + ddev exec wp core install --url='https://${{ steps.set-wp-version-code.outputs.wp_version_code }}.ddev.site' --title='WordPress' --admin_user='admin' --admin_password='admin123' --admin_email='admin@admin.com' - name: Set LAST_TAG env + id: set-last-tag run: | - echo "LAST_TAG=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/${{ env.GITHUB_ORIGIN }}/releases/latest | grep -oP "\/tag\/v\K(.*)$")" >> $GITHUB_ENV + echo "last_tag=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/${{ env.GITHUB_ORIGIN }}/releases/latest | grep -oP "\/tag\/v\K(.*)$")" >> $GITHUB_OUTPUT - name: Clone files from last release uses: actions/checkout@v4 with: path: raw_sources - ref: "v${{ env.LAST_TAG }}" + ref: "v${{ steps.set-last-tag.outputs.last_tag }}" repository: "${{ env.GITHUB_ORIGIN }}" - name: Retrieve last stable release zip run: | - curl -fL https://downloads.wordpress.org/plugin/crowdsec.${{ env.LAST_TAG }}.zip -o crowdsec.$LAST_TAG.zip - unzip crowdsec.${{ env.LAST_TAG }}.zip -d ${{ github.workspace }}/wp-content/plugins + curl -fL https://downloads.wordpress.org/plugin/crowdsec.${{ steps.set-last-tag.outputs.last_tag }}.zip -o crowdsec.$LAST_TAG.zip + unzip crowdsec.${{ steps.set-last-tag.outputs.last_tag }}.zip -d ${{ github.workspace }}/wp-content/plugins - name: Copy needed tests files run: | @@ -259,6 +260,9 @@ jobs: - name: Prepare for playwright test run: | + ddev exec -s crowdsec apk add iproute2 + cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --post_type=page --post_status=publish --post_title="AppSec" - + ddev wp rewrite structure "/%postname%/" mkdir -p crowdsec/tls mkdir -p crowdsec/geolocation cp .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/php/cache-actions-with-wordpress-load.php cache-actions.php @@ -338,6 +342,12 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 8-geolocation.js + - name: Run AppSec tests + uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test + with: + test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev + file_path: 11-appsec.js + - name: tmate debugging session uses: mxschmitt/action-tmate@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index c9658528..738a9b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [2.7.0](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.7.0) - 2024-10-18 +[_Compare with previous release_](https://github.com/crowdsecurity/cs-wordpress-bouncer/compare/v2.6.7...v2.7.0) + +### Added + +- Add AppSec component support + +### Changed + +- Make some fields required when necessary in the settings page (LAPI URL, Api key if authentication type is Api key, etc.) +- Update the standalone settings file if the file is already present (even if the setting is disabled) + +--- + ## [2.6.7](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.6.7) - 2024-07-26 [_Compare with previous release_](https://github.com/crowdsecurity/cs-wordpress-bouncer/compare/v2.6.6...v2.6.7) diff --git a/composer.json b/composer.json index 6bafb0a7..20d31cd5 100644 --- a/composer.json +++ b/composer.json @@ -19,17 +19,17 @@ } }, "require": { - "crowdsec/bouncer": "^2.2.0", + "crowdsec/bouncer": "^3.0.0", "symfony/cache": "5.4.40", - "symfony/polyfill-mbstring": "^1.27.0", - "symfony/service-contracts": "^2.5.2" + "symfony/polyfill-mbstring": "^1.31.0", + "symfony/service-contracts": "^2.5.3" }, "replace": { "twig/twig": "*" }, "autoload": { "psr-4": { - "\\": "./" + "CrowdSecWordPressBouncer\\": "./inc/" } } } diff --git a/composer.lock b/composer.lock index adb415c6..3925e3c4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fffe8c480ca9a7eade0afcc65c305ffa", + "content-hash": "1386d965c2bf9d684e8bb660481f1328", "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.0", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", - "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/48a792895a2b7a6ee65dd5442c299d7b835b6137", + "reference": "48a792895a2b7a6ee65dd5442c299d7b835b6137", "shasum": "" }, "require": { @@ -27,8 +27,8 @@ }, "require-dev": { "phpstan/phpstan": "^1.10", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", @@ -64,7 +64,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + "source": "https://github.com/composer/ca-bundle/tree/1.5.2" }, "funding": [ { @@ -80,25 +80,25 @@ "type": "tidelift" } ], - "time": "2024-03-15T14:00:32+00:00" + "time": "2024-09-25T07:49:53+00:00" }, { "name": "crowdsec/bouncer", - "version": "v2.2.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-cs-bouncer.git", - "reference": "0cbb23bfe252e993b6cf04f817471a2ffd461a5d" + "reference": "caa5509bb7aac9176c4a049dd83797fd6e57309c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/0cbb23bfe252e993b6cf04f817471a2ffd461a5d", - "reference": "0cbb23bfe252e993b6cf04f817471a2ffd461a5d", + "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/caa5509bb7aac9176c4a049dd83797fd6e57309c", + "reference": "caa5509bb7aac9176c4a049dd83797fd6e57309c", "shasum": "" }, "require": { - "crowdsec/common": "^2.2.0", - "crowdsec/remediation-engine": "^3.3.0", + "crowdsec/common": "^2.3.0", + "crowdsec/remediation-engine": "^3.4.0", "ext-gd": "*", "ext-json": "*", "gregwar/captcha": "^1.2.1", @@ -135,6 +135,7 @@ "description": "The official PHP bouncer library for the CrowdSec Local API", "keywords": [ "IP", + "appsec", "blocker", "bouncer", "captcha", @@ -148,22 +149,22 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-cs-bouncer/issues", - "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v2.2.0" + "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v3.0.0" }, - "time": "2024-06-20T07:32:01+00:00" + "time": "2024-10-04T04:57:41+00:00" }, { "name": "crowdsec/capi-client", - "version": "v3.1.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-capi-client.git", - "reference": "26414780a847e9a46b7abbc9e60fb38b46d11a58" + "reference": "e43eb8f1e5ab74119f550a30d77fd85f0058090f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-capi-client/zipball/26414780a847e9a46b7abbc9e60fb38b46d11a58", - "reference": "26414780a847e9a46b7abbc9e60fb38b46d11a58", + "url": "https://api.github.com/repos/crowdsecurity/php-capi-client/zipball/e43eb8f1e5ab74119f550a30d77fd85f0058090f", + "reference": "e43eb8f1e5ab74119f550a30d77fd85f0058090f", "shasum": "" }, "require": { @@ -215,22 +216,22 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-capi-client/issues", - "source": "https://github.com/crowdsecurity/php-capi-client/tree/v3.1.0" + "source": "https://github.com/crowdsecurity/php-capi-client/tree/v3.2.0" }, - "time": "2023-12-07T02:15:20+00:00" + "time": "2024-09-12T03:24:06+00:00" }, { "name": "crowdsec/common", - "version": "v2.2.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-common.git", - "reference": "d47b3acaa002c7367c758640991d09d5aea4dc56" + "reference": "639afd34daa36a14c4f47e1d70a9ac5f038541d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-common/zipball/d47b3acaa002c7367c758640991d09d5aea4dc56", - "reference": "d47b3acaa002c7367c758640991d09d5aea4dc56", + "url": "https://api.github.com/repos/crowdsecurity/php-common/zipball/639afd34daa36a14c4f47e1d70a9ac5f038541d5", + "reference": "639afd34daa36a14c4f47e1d70a9ac5f038541d5", "shasum": "" }, "require": { @@ -282,26 +283,26 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-common/issues", - "source": "https://github.com/crowdsecurity/php-common/tree/v2.2.0" + "source": "https://github.com/crowdsecurity/php-common/tree/v2.3.1" }, - "time": "2023-12-07T01:35:43+00:00" + "time": "2024-10-16T05:12:27+00:00" }, { "name": "crowdsec/lapi-client", - "version": "v3.2.0", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-lapi-client.git", - "reference": "25d0f20b154c2f6f7bbfd8b1e2d70d58e4bcc94e" + "reference": "d2a27f4eab5d82ca8c75684881c8c0a3d9c4ea39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-lapi-client/zipball/25d0f20b154c2f6f7bbfd8b1e2d70d58e4bcc94e", - "reference": "25d0f20b154c2f6f7bbfd8b1e2d70d58e4bcc94e", + "url": "https://api.github.com/repos/crowdsecurity/php-lapi-client/zipball/d2a27f4eab5d82ca8c75684881c8c0a3d9c4ea39", + "reference": "d2a27f4eab5d82ca8c75684881c8c0a3d9c4ea39", "shasum": "" }, "require": { - "crowdsec/common": "^2.2.0", + "crowdsec/common": "^2.3.0", "ext-json": "*", "monolog/monolog": "^1.17 || ^2.1", "php": "^7.2.5 || ^8.0", @@ -338,6 +339,7 @@ ], "description": "The official PHP client for the CrowdSec Local API (LAPI)", "keywords": [ + "appsec", "bouncer", "client", "crowdsec", @@ -348,28 +350,28 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-lapi-client/issues", - "source": "https://github.com/crowdsecurity/php-lapi-client/tree/v3.2.0" + "source": "https://github.com/crowdsecurity/php-lapi-client/tree/v3.3.1" }, - "time": "2023-12-07T02:17:45+00:00" + "time": "2024-10-10T09:56:15+00:00" }, { "name": "crowdsec/remediation-engine", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-remediation-engine.git", - "reference": "8457be07a5e03721775b0a71caadb275d2c66758" + "reference": "ea92f7a86b51b91938768e64293187979dbab438" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/8457be07a5e03721775b0a71caadb275d2c66758", - "reference": "8457be07a5e03721775b0a71caadb275d2c66758", + "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/ea92f7a86b51b91938768e64293187979dbab438", + "reference": "ea92f7a86b51b91938768e64293187979dbab438", "shasum": "" }, "require": { - "crowdsec/capi-client": "^3.1.0", - "crowdsec/common": "^2.2.0", - "crowdsec/lapi-client": "^3.2.0", + "crowdsec/capi-client": "^3.2.0", + "crowdsec/common": "^2.3.0", + "crowdsec/lapi-client": "^3.3.0", "ext-json": "*", "geoip2/geoip2": "^2.13.0", "mlocati/ip-lib": "^1.18", @@ -428,9 +430,9 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-remediation-engine/issues", - "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v3.3.0" + "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v3.4.0" }, - "time": "2023-12-14T01:19:48+00:00" + "time": "2024-10-04T02:46:52+00:00" }, { "name": "geoip2/geoip2", @@ -1307,16 +1309,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.40", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995" + "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/26dd9912df6940810ea00f8f53ad48d6a3424995", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/76c3818964e9d32be3862c9318ae3ba9aa280ddc", + "reference": "76c3818964e9d32be3862c9318ae3ba9aa280ddc", "shasum": "" }, "require": { @@ -1354,7 +1356,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.40" + "source": "https://github.com/symfony/filesystem/tree/v5.4.44" }, "funding": [ { @@ -1370,20 +1372,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-09-16T14:52:48+00:00" }, { "name": "symfony/finder", - "version": "v5.4.40", + "version": "v5.4.43", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce" + "reference": "ae25a9145a900764158d439653d5630191155ca0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f51cff4687547641c7d8180d74932ab40b2205ce", - "reference": "f51cff4687547641c7d8180d74932ab40b2205ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", + "reference": "ae25a9145a900764158d439653d5630191155ca0", "shasum": "" }, "require": { @@ -1417,7 +1419,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.40" + "source": "https://github.com/symfony/finder/tree/v5.4.43" }, "funding": [ { @@ -1433,24 +1435,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-08-13T14:03:51+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1496,7 +1498,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -1512,24 +1514,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1576,7 +1578,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -1592,24 +1594,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/ec444d3f3f6505bb28d11afa41e75faadebc10a1", - "reference": "ec444d3f3f6505bb28d11afa41e75faadebc10a1", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1652,7 +1654,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" }, "funding": [ { @@ -1668,24 +1670,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1732,7 +1734,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -1748,24 +1750,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -1808,7 +1810,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -1824,24 +1826,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -1887,7 +1889,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -1903,7 +1905,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/service-contracts", @@ -1990,16 +1992,16 @@ }, { "name": "symfony/uid", - "version": "v5.4.40", + "version": "v5.4.44", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "3e85f7b4d7e04313b88232cd5c27e5ff2ae93218" + "reference": "b36c8947ee835916eac8c341e124c3de19ff43ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/3e85f7b4d7e04313b88232cd5c27e5ff2ae93218", - "reference": "3e85f7b4d7e04313b88232cd5c27e5ff2ae93218", + "url": "https://api.github.com/repos/symfony/uid/zipball/b36c8947ee835916eac8c341e124c3de19ff43ca", + "reference": "b36c8947ee835916eac8c341e124c3de19ff43ca", "shasum": "" }, "require": { @@ -2044,7 +2046,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v5.4.40" + "source": "https://github.com/symfony/uid/tree/v5.4.44" }, "funding": [ { @@ -2060,7 +2062,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2024-09-11T16:39:40+00:00" }, { "name": "symfony/var-exporter", @@ -2139,11 +2141,11 @@ "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, - "platform": [], - "platform-dev": [], + "platform": {}, + "platform-dev": {}, "platform-overrides": { "php": "7.2.5" }, diff --git a/crowdsec.php b/crowdsec.php index fdea3506..7dd7c288 100644 --- a/crowdsec.php +++ b/crowdsec.php @@ -3,8 +3,8 @@ * Plugin Name: CrowdSec * Plugin URI: https://github.com/crowdsecurity/cs-wordpress-bouncer * Description: Safer Together. Protect your WordPress application with CrowdSec. - * Tags: security, captcha, ip-blocker, crowdsec, hacker-protection - * Version: 2.6.7 + * Tags: security, captcha, ip-blocker, crowdsec, hacker-protection, appsec + * Version: 2.7.0 * Author: CrowdSec * Author URI: https://www.crowdsec.net/ * Github: https://github.com/crowdsecurity/cs-wordpress-bouncer @@ -13,7 +13,7 @@ * Requires PHP: 7.2 * Requires at least: 4.9 * Tested up to: 6.6 - * Stable tag: 2.6.7 + * Stable tag: 2.7.0 * Text Domain: crowdsec-wp * First release: 2021. */ @@ -27,7 +27,7 @@ require_once __DIR__.'/inc/scheduling.php'; register_activation_hook(__FILE__, 'activate_crowdsec_plugin'); register_deactivation_hook(__FILE__, 'deactivate_crowdsec_plugin'); -require_once __DIR__.'/inc/admin/init.php'; +require_once __DIR__ . '/inc/Admin/init.php'; require_once __DIR__.'/inc/bounce-current-ip.php'; add_action('plugins_loaded', 'safelyBounceCurrentIp'); diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index ae1c22cf..8d9ef47b 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -3,10 +3,9 @@ ## Developer guide - +**Table of Contents** -**Table of Contents** - [Local development](#local-development) - [DDEV setup](#ddev-setup) @@ -15,12 +14,7 @@ - [WordPress installation](#wordpress-installation) - [DDEV Usage](#ddev-usage) - [Test the module](#test-the-module) - - [Install the module](#install-the-module) - - [End-to-end tests](#end-to-end-tests) - - [Auto_prepend_file mode](#auto_prepend_file-mode) - [Update composer dependencies](#update-composer-dependencies) - - [Development phase](#development-phase) - - [Production release](#production-release) - [Quick start guide](#quick-start-guide) - [Live mode](#live-mode) - [Discover the cache system](#discover-the-cache-system) @@ -167,6 +161,15 @@ tar -xf GeoLite2-Country.tar.gz tar -xf GeoLite2-City.tar.gz ``` +For AppSec post request test, we are using a custom page. You have to create this page in your WordPress site: + +```bash +cd wp-sources +cat .ddev/okaeli-add-on/wordpress/custom_files/crowdsec/html/appsec-post.html | ddev wp post create --post_type=page --post_status=publish --post_title="AppSec" - +``` + + + And we use also a custom PHP script to make some cache test. Thus, you should copy this PHP script too in the root folder: ```bash @@ -198,13 +201,16 @@ For example: ./run-tests.sh host "./2-live-mode-remediations.js" ``` -**N.B**.: +###### Test in docker Before testing with the `docker` or `ci` parameter, you have to install all the required dependencies in the playwright container with this command : ./test-init.sh +###### Test on host + + If you want to test with the `host` parameter, you will have to install manually all the required dependencies: ``` @@ -212,6 +218,39 @@ yarn --cwd ./tests/e2e-ddev --force yarn global add cross-env ``` +You will also have to edit your `/etc/hosts` file to add the following line: + +``` + crowdsec +``` +where `` is the IP of the `crowdsec` container. You can find it with the command `ddev find-ip crowdsec`. + +Example: + +``` +172.19.0.5 crowdsec +``` + +##### Testing timeout in the CrowdSec container + +If you need to test a timeout, you can use the following command: + +Install `iproute2` +```bash +ddev exec -s crowdsec apk add iproute2 +``` +Add the delay you want: +```bash +ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 500ms +``` + +To remove the delay: +```bash +ddev exec -s crowdsec tc qdisc del dev eth0 root netem +``` + + + ##### Auto_prepend_file mode diff --git a/docs/INSTALLATION_GUIDE.md b/docs/INSTALLATION_GUIDE.md index 97c9ce95..4c7e8664 100644 --- a/docs/INSTALLATION_GUIDE.md +++ b/docs/INSTALLATION_GUIDE.md @@ -4,10 +4,9 @@ ## Installation Guide - +**Table of Contents** -**Table of Contents** - [Add the plugin](#add-the-plugin) - [WordPress Marketplace](#wordpress-marketplace) diff --git a/docs/TECHNICAL_NOTES.md b/docs/TECHNICAL_NOTES.md index 6b12741d..dbe04c5c 100644 --- a/docs/TECHNICAL_NOTES.md +++ b/docs/TECHNICAL_NOTES.md @@ -4,9 +4,9 @@ ## Technical notes +**Table of Contents** -**Table of Contents** - [How to use system CRON instead of wp-cron?](#how-to-use-system-cron-instead-of-wp-cron) @@ -27,4 +27,4 @@ Add `define('DISABLE_WP_CRON', true);` in `wp-config.php` then enter this comman > Note: replace : with the local url of your website -More info [here](https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/). \ No newline at end of file +More info [here](https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/). diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 317f2156..99ce8785 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -3,9 +3,9 @@ ## User Guide +**Table of Contents** -**Table of Contents** - [Description](#description) - [Prerequisites](#prerequisites) @@ -84,6 +84,8 @@ Url to join your CrowdSec Local API. If the CrowdSec Agent is installed on this server, you could set this field to `http://localhost:8080`. +Default to `http://localhost:8080` + *** `Connection details → Authentication type` @@ -289,22 +291,60 @@ Example of DSN: memcached://localhost:11211. *** -![Remediations](images/screenshots/config-remediations.jpg) +![AppSec](./images/screenshots/config-appsec.png) + +--- + +`AppSec component → Enable AppSec` + +Enable if you want to ask the AppSec component for a remediation based on the current request, in case the initial LAPI remediation is a bypass. + +Not available if you use TLS certficates as authentication type. + +For more information on the AppSec component, please refer to the [documentation](https://docs.crowdsec.net/docs/appsec/intro/). + +--- + +`AppSec component → AppSec Url ` + +Your AppSec component url. Default to `http://localhost:7422` + +--- + +`AppSec component → AppSec request timeout` + +Maximum execution time (in milliseconds) for an AppSec request. + +Set a negative value to allow unlimited timeout + +Default to 400. + +--- + +`AppSec component → AppSec Fallback to` + +What remediation to apply when AppSec call has failed due to a timeout. + +Recommended: `captcha`. Default: `bypass`. + +--- + +![Remediation](images/screenshots/config-remediations.jpg) *** -`Remediations → Fallback to` +`Remediation → Fallback to` Choose which remediation to apply when CrowdSec advises unhandled remediation. *** -`Remediations → Trust these CDN IPs (or Load Balancer, HTTP Proxy)` +`Remediation → Trust these CDN IPs (or Load Balancer, HTTP Proxy)` If you use a CDN, a reverse proxy or a load balancer, it is possible to indicate in the bouncer settings the IP ranges of these devices in order to be able to check the IP of your users. For other IPs, the bouncer will not trust the X-Forwarded-For header. *** -`Remediations → Hide CrowdSec mentions` +`Remediation → Hide CrowdSec mentions` Enable if you want to hide CrowdSec mentions on the Ban and Captcha walls. @@ -407,6 +447,8 @@ Should be disabled in production. This setting allows the bouncer to bounce IPs before running any PHP script in the project. +**Make sure the generated `standalone-settings.php` file is not publicly accessible.** [See security note below](#security). + *** @@ -484,7 +526,12 @@ Here are some examples of how to set options with the `WP-CLI` tool. | `Captcha flow cache lifetime` | `wp option set crowdsec_captcha_cache_duration 86400` | | `Redis DSN (if applicable)`:warning: | echo -n "redis://localhost:6379" \| wp option set crowdsec_redis_dsn | | `Memcached DSN (if applicable)`:warning: | echo -n "memcached://localhost:11211" \| wp option set crowdsec_memcached_dsn | -| **Advanced settings** → *Remediations* | | +| **Advanced settings** → *AppSec component* | | +| `Enable AppSec` | - wp option set crowdsec_use_appsec on
- echo -n "" \| wp option set crowdsec_use_appsec | +| `AppSec Url` | `wp option set crowdsec_appsec_url http://localhost:7422` | +| `AppSec request timeout` | `wp option set crowdsec_appsec_timeout_ms 150` | +| `AppSec Fallback to` | - wp option set crowdsec_appsec_fallback_remediation ban
- wp option set crowdsec_appsec_fallback_remediation captcha
- wp option set crowdsec_appsec_fallback_remediation bypass | +| **Advanced settings** → *Remediation* | | | `Fallback to` | - wp option set crowdsec_fallback_remediation ban
- wp option set crowdsec_fallback_remediation captcha
- wp option set crowdsec_fallback_remediation bypass | | `Trust these CDN IPs (or Load Balancer, HTTP Proxy)` | When the `crowdsec_trust_ip_forward` is set, the `crowdsec_trust_ip_forward_array` is populated with a serialized array of comparable IPs.
Thus, to maintain consistency between admin display and database data, you should update the 2 options:
`wp option set crowdsec_trust_ip_forward 1.2.3.4`
`wp option set crowdsec_trust_ip_forward_array --format=json '[["001.002.003.004","001.002.003.004"]]'` | | `Hide CrowdSec mentions` | - wp option set crowdsec_hide_mentions on
- echo -n "" \| wp option set crowdsec_hide_mentions | diff --git a/docs/images/screenshots/config-appsec.png b/docs/images/screenshots/config-appsec.png new file mode 100644 index 00000000..87d1c514 Binary files /dev/null and b/docs/images/screenshots/config-appsec.png differ diff --git a/docs/images/screenshots/config-remediations.jpg b/docs/images/screenshots/config-remediations.jpg index 1d165a11..d91436a8 100644 Binary files a/docs/images/screenshots/config-remediations.jpg and b/docs/images/screenshots/config-remediations.jpg differ diff --git a/inc/admin/notice.php b/inc/Admin/AdminNotice.php similarity index 97% rename from inc/admin/notice.php rename to inc/Admin/AdminNotice.php index 09ca8569..a81e28a2 100644 --- a/inc/admin/notice.php +++ b/inc/Admin/AdminNotice.php @@ -1,6 +1,6 @@ '
']); // Field "crowdsec_stream_mode" addFieldCheckbox('crowdsec_stream_mode', 'Enable the "Stream" mode', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_stream_mode', function () { @@ -97,7 +100,7 @@ function crowdsec_multi_save_advanced_settings() // Stream mode just deactivated. unscheduleBlocklistRefresh(); }, ' -

With the stream mode, every decision is retrieved in an asynchronous way. 3 advantages:
 1) Inivisible latency when loading pages
 2) The IP verifications works even if your CrowdSec is not reachable.
 3) The API can never be overloaded by the WordPress traffic

+

With the stream mode, every decision is retrieved in an asynchronous way. 3 advantages:
 1) Invisible latency when loading pages
 2) The IP verifications works even if your CrowdSec is not reachable.
 3) The API can never be overloaded by the WordPress traffic

Note: This method has one limit: all the decisions updates since the previous resync will not be taken in account until the next resync.

'. ($streamMode ? '

' : @@ -108,7 +111,7 @@ function crowdsec_multi_save_advanced_settings() $input = (int) $input; if ($input < 1) { $input = 1; - $message = 'The "Resync decisions each" value should be more than 1sec (WP_CRON_LOCK_TIMEOUT). We just reset the frequency to 1 seconds.'; + $message = 'The "Resync decisions each" value should be more than 1 sec (WP_CRON_LOCK_TIMEOUT). We just reset the frequency to 1 seconds.'; if(is_multisite()){ AdminNotice::displayError($message); }else{ @@ -127,7 +130,7 @@ function crowdsec_multi_save_advanced_settings() $refresh = $bouncer->refreshBlocklistCache(); $new = $refresh['new']??0; $deleted = $refresh['deleted']??0; - $message = __('As the stream mode refresh duration changed, the cache has just been refreshed. New decision(s): '.$new.'. Deleted decision(s): '. $deleted); + $message = __('Settings saved.
As the stream mode refresh duration changed, the cache has just been refreshed. New decision(s): '.$new.'. Deleted decision(s): '. $deleted); AdminNotice::displaySuccess($message); scheduleBlocklistRefresh(); } @@ -144,11 +147,12 @@ function crowdsec_multi_save_advanced_settings() ** Section "Cache" ** ********************/ + add_settings_section('crowdsec_admin_advanced_cache', 'Caching configuration ', function () { ?>

Polish the decisions cache settings by selecting the best technology or the cache durations best suited to your use.

'
']); // Field "crowdsec_redis_dsn" addFieldString('crowdsec_redis_dsn', 'Redis DSN
(if applicable)', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_cache', function ($input) { @@ -333,24 +337,78 @@ function crowdsec_multi_save_advanced_settings() /*************************** - ** Section "Remediation" ** + ** Section "AppSec" ** **************************/ - - add_settings_section('crowdsec_admin_advanced_remediations', 'Remediations', function () { - echo 'Configure some details about remediations.'; - }, 'crowdsec_advanced_settings'); - - // Field "crowdsec_fallback_remediation" $choice = []; - $remediations = [Constants::REMEDIATION_BAN, Constants::REMEDIATION_CAPTCHA, Constants::REMEDIATION_BYPASS]; + $remediations = [Constants::REMEDIATION_BYPASS, Constants::REMEDIATION_CAPTCHA, Constants::REMEDIATION_BAN,]; foreach ($remediations as $remediation) { $choice[$remediation] = $remediation; } + + add_settings_section('crowdsec_admin_advanced_appsec', 'AppSec component', function () { + echo 'Configure bouncer interaction with AppSec component'; + }, 'crowdsec_advanced_settings', ['after_section' => '
']); + + // Field "AppSec enabled" + addFieldCheckbox('crowdsec_use_appsec', 'Enable AppSec', 'crowdsec_plugin_advanced_settings', + 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_appsec', function () {}, function () {}, ' +

Enable if you want to ask the AppSec component for a remediation based on the current request, in case the initial LAPI remediation is a bypass.

+

For more information on the AppSec component, please refer to the documentation.

+

This AppSec feature is not available when using TLS certificates for authentication.

'); + + addFieldString('crowdsec_appsec_url', 'AppSec Url', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_appsec', function ($input, $default = '') { + if(empty($input) && $default){ + add_settings_error('AppSec URL', 'crowdsec_error', 'AppSec URL: Can not be empty. Default value used: '.$default); + $input = $default; + } + + return $input; + }, '', 'Your AppSec URL (e.g. http://localhost:7422)', ''); + + // Field "timeout" + addFieldString('crowdsec_appsec_timeout_ms', 'AppSec request timeout', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', + 'crowdsec_admin_advanced_appsec', function ($input) { + if ((int) $input === 0) { + add_settings_error('AppSec timeout', 'crowdsec_error', 'AppSec timeout: Must be different than 0.'); + + return Constants::APPSEC_TIMEOUT_MS; + } + + return $input ; + }, ' milliseconds.

Maximum execution time (in milliseconds) for an AppSec request.
Set a negative value (e.g. -1) to allow unlimited request timeout.
Default to ' . Constants::APPSEC_TIMEOUT_MS .'.', + Constants::APPSEC_TIMEOUT_MS, 'width: 115px;', 'number'); + + addFieldSelect('crowdsec_appsec_fallback_remediation', 'AppSec Fallback to', 'crowdsec_plugin_advanced_settings', + 'crowdsec_advanced_settings', + 'crowdsec_admin_advanced_appsec', function ($input) { + $remediations = [Constants::REMEDIATION_BAN, Constants::REMEDIATION_CAPTCHA, Constants::REMEDIATION_BYPASS]; + if (!in_array($input, $remediations)) { + $input = Constants::REMEDIATION_BYPASS; + $message = 'Fallback to: Incorrect Fallback selected.'; + if(is_multisite()){ + AdminNotice::displayError($message); + }else{ + add_settings_error('AppSec Fallback to', 'crowdsec_error', $message); + } + } + + return $input; + }, '

What remediation to apply when the AppSec call has failed due to a timeout.

', $choice); + + /*************************** + ** Section "Remediation" ** + **************************/ + + add_settings_section('crowdsec_admin_advanced_remediations', 'Remediation', function () { + echo 'Configure some details about remediation.'; + }, 'crowdsec_advanced_settings', ['after_section' => '
']); + + // Field "crowdsec_fallback_remediation" addFieldSelect('crowdsec_fallback_remediation', 'Fallback to', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_remediations', function ($input) { $remediations = [Constants::REMEDIATION_BAN, Constants::REMEDIATION_CAPTCHA, Constants::REMEDIATION_BYPASS]; if (!in_array($input, $remediations)) { - $input = Constants::BOUNCING_LEVEL_DISABLED; + $input = Constants::REMEDIATION_BYPASS; $message = 'Fallback to: Incorrect Fallback selected.'; if(is_multisite()){ AdminNotice::displayError($message); @@ -360,7 +418,7 @@ function crowdsec_multi_save_advanced_settings() } return $input; - }, '

Which remediation to apply when CrowdSec advises unhandled remediation.

', $choice); + }, '

What remediation to apply when CrowdSec advises unmanaged remediation.

', $choice); function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): array { @@ -445,7 +503,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra echo 'Configure some details about geolocation.
Important note: If you use this feature, make sure the geolocation database is not publicly accessible.
Please refer to the documentation to deny direct access to this folder.'; - }, 'crowdsec_advanced_settings'); + }, 'crowdsec_advanced_settings', ['after_section' => '
']); // Field "Geolocation enabled" addFieldCheckbox('crowdsec_geolocation_enabled', 'Enable geolocation feature', 'crowdsec_plugin_advanced_settings', @@ -516,7 +574,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra echo 'Configure the debug mode.
Important note: Make sure the wp-content/uploads/crowdsec/logs path is not publicly accessible.
Please refer to the documentation to deny direct access to this folder.'; - }, 'crowdsec_advanced_settings'); + }, 'crowdsec_advanced_settings', ['after_section' => '
']); // Field "crowdsec_debug_mode" addFieldCheckbox('crowdsec_debug_mode', 'Enable debug mode', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_debug', function () {}, function () {}, ' @@ -549,7 +607,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra add_settings_section('crowdsec_admin_advanced_display_errors', 'Display errors', function () { echo 'Configure the errors display.'; - }, 'crowdsec_advanced_settings'); + }, 'crowdsec_advanced_settings', ['after_section' => '
']); // Field "crowdsec_display_errors" addFieldCheckbox('crowdsec_display_errors', 'Enable errors display', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_display_errors', function () {}, function () {}, ' @@ -562,11 +620,13 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra add_settings_section('crowdsec_admin_advanced_auto_prepend_file_mode', 'Auto prepend file mode', function () { echo ''; - }, 'crowdsec_advanced_settings'); + }, 'crowdsec_advanced_settings', ['after_section' => '
']); // Field "crowdsec_standalone_mode" addFieldCheckbox('crowdsec_auto_prepend_file_mode', 'Enable auto_prepend_file mode', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_auto_prepend_file_mode', function () {}, function () {}, ' -

This setting allows the bouncer to bounce IPs before running any PHP script in the project. Discover how to setup with this guide.

Enable this option before adding the "auto_prepend_file" directive for your PHP setup.

'); +

This setting allows the bouncer to bounce IPs before running any PHP script in the project. Discover how to setup with this guide.

Enable this option before adding the "auto_prepend_file" directive for your PHP setup.

+

Important note: If you use this feature, make sure the "standalone-settings" file is not publicly accessible.
+Please refer to the documentation to deny direct access to this file.

'); /******************************* @@ -575,7 +635,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra add_settings_section('crowdsec_admin_advanced_test', 'Test settings', function () { echo 'Configure some test parameters.'; - }, 'crowdsec_advanced_settings'); + }, 'crowdsec_advanced_settings', ['after_section' => '
']); // Field "test ip" addFieldString('crowdsec_forced_test_ip', 'Forced test IP', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_test', function ($input) { diff --git a/inc/admin/init.php b/inc/Admin/init.php similarity index 93% rename from inc/admin/init.php rename to inc/Admin/init.php index 6fcfb087..8efc28d5 100644 --- a/inc/admin/init.php +++ b/inc/Admin/init.php @@ -3,14 +3,10 @@ use CrowdSecWordPressBouncer\Constants; use CrowdSecBouncer\BouncerException; use CrowdSec\RemediationEngine\Geolocation; -use CrowdSecWordPressBouncer\AdminNotice; +use CrowdSecWordPressBouncer\Admin\AdminNotice; use CrowdSecWordPressBouncer\Bouncer; -require_once __DIR__ . '/notice.php'; -require_once __DIR__ . '/../Constants.php'; -require_once __DIR__ . '/../Bouncer.php'; require_once __DIR__ . '/../options-config.php'; - require_once __DIR__.'/settings.php'; require_once __DIR__.'/theme.php'; require_once __DIR__.'/advanced-settings.php'; @@ -21,7 +17,6 @@ add_action('admin_notices', [new AdminNotice(), 'displayAdminNotice']); } - function crowdsec_option_update_callback($name, $oldValue, $newValue) { if (0 === strpos($name, 'crowdsec_')) { @@ -298,12 +293,28 @@ function addFieldCheckbox(string $optionName, string $label, string $optionGroup ]); } - function addFieldString(string $optionName, string $label, string $optionGroup, string $pageName, string $sectionName, callable $onChange, $descriptionHtml, $placeholder, $inputStyle, $inputType = 'text', $disabled = false) + function addFieldString( + string $optionName, + string $label, + string $optionGroup, + string $pageName, + string $sectionName, + callable $onChange, + $descriptionHtml, + $placeholder, + $inputStyle, + $inputType = 'text', + $disabled = false, + $default = '' + ) { - register_setting($optionGroup, $optionName, function ($input) use ($onChange, $optionName) { + register_setting($optionGroup, $optionName, function ($input) use ($onChange, $optionName, $default) { $currentState = esc_attr($input); $previousState = is_multisite() ? esc_attr(get_site_option($optionName)) : esc_attr(get_option($optionName)); + if (empty($currentState) && !empty($default)) { + $currentState = $onChange($currentState, $default); + } if ($previousState !== $currentState) { $currentState = $onChange($currentState); } @@ -314,7 +325,8 @@ function addFieldString(string $optionName, string $label, string $optionGroup, $name = $args['label_for']; $placeholder = $args['placeholder']; $value = $previousState = is_multisite() ? esc_attr(get_site_option($optionName)) : esc_attr(get_option($optionName)); - echo '$descriptionHtml"; + echo '$descriptionHtml"; }, $pageName, $sectionName, [ 'label_for' => $optionName, 'placeholder' => $placeholder, @@ -353,13 +365,6 @@ function addFieldSelect(string $optionName, string $label, string $optionGroup, }, $pageName, $sectionName); } - /*add_menu_page('CrowdSec Plugin', 'CrowdSec', 'manage_options', 'crowdsec_plugin', function () { - require_once(CROWDSEC_PLUGIN_PATH . "/templates/dashboard.php"); - }, 'dashicons-shield', 110); - add_submenu_page('crowdsec_plugin', 'Settings', 'Settings', 'manage_options', 'crowdsec_settings', function () { - require_once(CROWDSEC_PLUGIN_PATH . "/templates/settings.php"); - }); - */ add_menu_page('CrowdSec Plugin', 'CrowdSec', 'manage_options', 'crowdsec_plugin', function () { require_once CROWDSEC_PLUGIN_PATH.'/inc/templates/settings.php'; }, 'dashicons-shield', 110); diff --git a/inc/admin/settings.php b/inc/Admin/settings.php similarity index 93% rename from inc/admin/settings.php rename to inc/Admin/settings.php index dc15354e..952aa119 100644 --- a/inc/admin/settings.php +++ b/inc/Admin/settings.php @@ -1,6 +1,6 @@ '
']); // Field "crowdsec_api_url" - addFieldString('crowdsec_api_url', 'Local API URL', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_connection', function ($input) { + addFieldString('crowdsec_api_url', 'Local API URL', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_connection', function ($input, $default = '') { + + if(empty($input) && $default){ + add_settings_error('Local API URL', 'crowdsec_error', 'Local API URL: Can not be empty. Default value used: ' . $default); + $input = $default; + } + return $input; }, '', - 'Your Local API URL (e.g. http://localhost:8080)', ''); + 'Your Local API URL (e.g. http://localhost:8080)', '', 'text', false, LapiConstants::DEFAULT_LAPI_URL); // Field "crowdsec_bouncing_level" addFieldSelect('crowdsec_auth_type', 'Authentication type', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_connection', function ($input) { @@ -129,7 +135,7 @@ function crowdsec_multi_save_settings() return Constants::API_TIMEOUT; } - return (int) $input !== 0 ? (int) $input : Constants::API_TIMEOUT ; + return $input ; }, ' seconds.

Maximum execution time (in seconds) for a Local API request.
Set a negative value (e.g. -1) to allow unlimited request timeout.
Default to ' . Constants::API_TIMEOUT .'.', Constants::API_TIMEOUT, 'width: 115px;', 'number'); @@ -138,7 +144,7 @@ function crowdsec_multi_save_settings() ***********************************/ add_settings_section('crowdsec_admin_bouncing', 'Bouncing', function () { echo 'Refine bouncing according to your needs.'; - }, 'crowdsec_settings'); + }, 'crowdsec_settings', ['after_section' => '


']); // Field "crowdsec_bouncing_level" addFieldSelect('crowdsec_bouncing_level', 'Bouncing level', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_bouncing', function ($input) { diff --git a/inc/admin/theme.php b/inc/Admin/theme.php similarity index 97% rename from inc/admin/theme.php rename to inc/Admin/theme.php index 86f1d3bc..4f0fdae3 100644 --- a/inc/admin/theme.php +++ b/inc/Admin/theme.php @@ -69,7 +69,7 @@ function crowdsec_multi_save_theme_settings() add_settings_section('crowdsec_theme_captcha_texts', 'Adapt the wording of the Captcha Wall', function () { echo 'You can customize the text display on the captcha wall.'; - }, 'crowdsec_theme_settings'); + }, 'crowdsec_theme_settings',['after_section' => '
']); // Field "crowdsec_theme_text_captcha_wall_tab_title" addFieldString('crowdsec_theme_text_captcha_wall_tab_title', 'Browser tab text', 'crowdsec_plugin_theme_settings', 'crowdsec_theme_settings', 'crowdsec_theme_captcha_texts', function ($input) { @@ -117,7 +117,7 @@ function crowdsec_multi_save_theme_settings() add_settings_section('crowdsec_theme_ban_texts', 'Adapt the wording of the Ban Wall', function () { echo 'You can customize the text display on the ban wall.'; - }, 'crowdsec_theme_settings'); + }, 'crowdsec_theme_settings',['after_section' => '
']); // Field "crowdsec_theme_text_ban_wall_tab_title" addFieldString('crowdsec_theme_text_ban_wall_tab_title', 'Browser tab text', 'crowdsec_plugin_theme_settings', 'crowdsec_theme_settings', 'crowdsec_theme_ban_texts', function ($input) { @@ -145,7 +145,7 @@ function crowdsec_multi_save_theme_settings() add_settings_section('crowdsec_theme_colors', 'Use your own colors', function () { echo 'You can customize remediation wall colors (ban wall and captcha wall).'; - }, 'crowdsec_theme_settings'); + }, 'crowdsec_theme_settings',['after_section' => '
']); // Field "crowdsec_theme_color_text_primary" addFieldString('crowdsec_theme_color_text_primary', 'Primary text color', 'crowdsec_plugin_theme_settings', 'crowdsec_theme_settings', 'crowdsec_theme_colors', function ($input) { @@ -193,7 +193,7 @@ function crowdsec_multi_save_theme_settings() add_settings_section('crowdsec_theme_css', 'Use your own CSS code', function () { echo 'You can customize remediation walls with CSS code (ban wall and captcha wall).'; - }, 'crowdsec_theme_settings'); + }, 'crowdsec_theme_settings',['after_section' => '
']); // Field "crowdsec_theme_custom_css" addFieldString('crowdsec_theme_custom_css', 'Custom CSS code', 'crowdsec_plugin_theme_settings', 'crowdsec_theme_settings', 'crowdsec_theme_css', function ($input) { diff --git a/inc/Bouncer.php b/inc/Bouncer.php index e20b7624..4e4b91f1 100644 --- a/inc/Bouncer.php +++ b/inc/Bouncer.php @@ -4,6 +4,7 @@ namespace CrowdSecWordPressBouncer; +use CrowdSec\LapiClient\Constants as LapiConstants; use CrowdSec\RemediationEngine\CacheStorage\CacheStorageException; use CrowdSec\RemediationEngine\LapiRemediation; use CrowdSec\Common\Logger\FileLog; @@ -26,9 +27,8 @@ class Bouncer extends AbstractBouncer { - protected $shouldNotBounceWpAdmin = true; - protected $baseFilesPath; + protected $shouldNotBounceWpAdmin = true; /** * @throws BouncerException @@ -48,70 +48,99 @@ public function __construct(array $configs, LoggerInterface $logger = null) parent::__construct($configs, $remediation, $this->logger); } - private function renderTemplate(string $templatePath,array $configs = []): string + /** + * @return string The current IP, even if it's the IP of a proxy + */ + public function getHttpMethod(): string { - ob_start(); - $config = array_merge($this->configs, $configs); - include __DIR__ . '/templates/' . $templatePath; - $html = ob_get_contents(); - ob_end_clean(); - return $html; + return $_SERVER['REQUEST_METHOD']; } - protected function getBanHtml(): string + /** + * @return string Ex: "X-Forwarded-For" + */ + public function getHttpRequestHeader(string $name): ?string { - return $this->renderTemplate('ban-wall.php'); + $headerName = 'HTTP_' . str_replace('-', '_', strtoupper($name)); + if (!array_key_exists($headerName, $_SERVER)) { + return null; + } + + return $_SERVER[$headerName]; } - protected function getCaptchaHtml( - bool $error, - string $captchaImageSrc, - string $captchaResolutionFormUrl - ): string { - $configs = [ - 'error' => $error, - 'captcha_img' => $captchaImageSrc, - 'captcha_resolution_url' => $captchaResolutionFormUrl, - ]; + /** + * Get the value of a posted field. + */ + public function getPostedVariable(string $name): ?string + { + if (!isset($_POST[$name])) { + return null; + } + return $_POST[$name]; + } - return $this->renderTemplate('captcha-wall.php', $configs); + /** + * @return string The current IP, even if it's the IP of a proxy + */ + public function getRemoteIp(): string + { + return $_SERVER['REMOTE_ADDR']; } - protected function escape(string $value): string + public function getRequestHeaders(): array { - return htmlspecialchars($value, \ENT_QUOTES, 'UTF-8'); + $allHeaders = []; + + if (function_exists('getallheaders')) { + $allHeaders = getallheaders(); + } else { + foreach ($_SERVER as $name => $value) { + if ('HTTP_' == substr($name, 0, 5)) { + $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); + $allHeaders[$name] = $value; + } elseif ('CONTENT_TYPE' == $name) { + $allHeaders['Content-Type'] = $value; + } + } + } + // Remove Content-Length header for AppSec + unset($allHeaders['Content-Length']); + + return $allHeaders; } - protected function specialcharsDecodeEntQuotes(string $value): string + /** + * Get current request host. + */ + public function getRequestHost(): string { - return htmlspecialchars_decode($value, \ENT_QUOTES); + return $_SERVER['HTTP_HOST'] ?? ''; } - private function getBaseFilesPath(): string + /** + * Get current request raw body. + */ + public function getRequestRawBody(): string { - if ($this->baseFilesPath === null) { - $result = Constants::DEFAULT_BASE_FILE_PATH; + return file_get_contents('php://input'); + } - if (function_exists('wp_upload_dir')) { - if(is_multisite()){ - $mainSiteId = get_main_site_id(); - switch_to_blog($mainSiteId); - } - $dir = wp_upload_dir(null, false); - if (is_array($dir) && array_key_exists('basedir', $dir)) { - $result = $dir['basedir'] . '/crowdsec/'; - } elseif (defined('WP_CONTENT_DIR')) { - $result = WP_CONTENT_DIR . '/uploads/crowdsec/'; - } - if(is_multisite()) { - restore_current_blog(); - } - } - $this->baseFilesPath = $result; - } + /** + * The current URI. + */ + public function getRequestUri(): string + { + return $_SERVER['REQUEST_URI'] ?? ""; + } - return $this->baseFilesPath; + /** + * Get current request user agent. + */ + public function getRequestUserAgent(): string + { + return $_SERVER['HTTP_USER_AGENT'] ?? ''; } /** @@ -121,6 +150,9 @@ private function getBaseFilesPath(): string */ public function handleRawConfigs(array $rawConfigs): array { + $apiUrl = (string)$this->handleRawConfig($rawConfigs, 'crowdsec_api_url', LapiConstants::DEFAULT_LAPI_URL); + $appSecUrl = (string)$this->handleRawConfig($rawConfigs, 'crowdsec_appsec_url', LapiConstants::DEFAULT_APPSEC_URL); + return [ // LAPI connection 'api_key' => $this->escape((string)$rawConfigs['crowdsec_api_key'] ?? ''), @@ -133,7 +165,7 @@ public function handleRawConfigs(array $rawConfigs): array 'tls_key_path' => (string)($this->handleRawConfig($rawConfigs, 'crowdsec_tls_key_path', '/')), 'tls_verify_peer' => (bool)($this->handleRawConfig($rawConfigs, 'crowdsec_tls_verify_peer', false)), 'tls_ca_cert_path' => (string)($this->handleRawConfig($rawConfigs, 'crowdsec_tls_ca_cert_path', '/')), - 'api_url' => $this->escape((string)$rawConfigs['crowdsec_api_url'] ?? ''), + 'api_url' => $this->escape($apiUrl), 'use_curl' => (bool)($this->handleRawConfig($rawConfigs, 'crowdsec_use_curl', false)), 'api_timeout' => (int)($this->handleRawConfig( $rawConfigs, @@ -141,6 +173,19 @@ public function handleRawConfigs(array $rawConfigs): array Constants::API_TIMEOUT )), 'user_agent_suffix' => 'WordPress' . $this->handleRawConfig($rawConfigs, 'crowdsec_custom_user_agent', ''), + // AppSec + 'use_appsec' => (bool)($this->handleRawConfig($rawConfigs, 'crowdsec_use_appsec', false)), + 'appsec_url' => $this->escape($appSecUrl), + 'appsec_timeout_ms' => (int)($this->handleRawConfig( + $rawConfigs, + 'crowdsec_appsec_timeout_ms', + Constants::APPSEC_TIMEOUT_MS + )), + 'appsec_fallback_remediation' => (string)($this->handleRawConfig( + $rawConfigs, + 'crowdsec_appsec_fallback_remediation', + Constants::REMEDIATION_BYPASS + )), // Debug 'debug_mode' => (bool)($this->handleRawConfig($rawConfigs, 'crowdsec_debug_mode', false)), 'disable_prod_log' => (bool)($this->handleRawConfig($rawConfigs, 'crowdsec_disable_prod_log', false)), @@ -290,64 +335,6 @@ public function handleRawConfigs(array $rawConfigs): array ]; } - private function handleRawConfig(array $rawConfigs, string $key, $defaultValue) - { - if (!empty($rawConfigs[$key])) { - return $rawConfigs[$key]; - } - - return $defaultValue; - } - - /** - * @return string Ex: "X-Forwarded-For" - */ - public function getHttpRequestHeader(string $name): ?string - { - $headerName = 'HTTP_' . str_replace('-', '_', strtoupper($name)); - if (!array_key_exists($headerName, $_SERVER)) { - return null; - } - - return $_SERVER[$headerName]; - } - - /** - * @return string The current IP, even if it's the IP of a proxy - */ - public function getRemoteIp(): string - { - return $_SERVER['REMOTE_ADDR']; - } - - /** - * The current URI. - */ - public function getRequestUri(): string - { - return $_SERVER['REQUEST_URI'] ?? ""; - } - - /** - * @return string The current IP, even if it's the IP of a proxy - */ - public function getHttpMethod(): string - { - return $_SERVER['REQUEST_METHOD']; - } - - /** - * Get the value of a posted field. - */ - public function getPostedVariable(string $name): ?string - { - if (!isset($_POST[$name])) { - return null; - } - - return $_POST[$name]; - } - /** * If the current IP should be bounced or not, matching custom business rules. */ @@ -406,4 +393,79 @@ public function shouldBounceCurrentIp(): bool return true; } + + protected function escape(string $value): string + { + return htmlspecialchars($value, \ENT_QUOTES, 'UTF-8'); + } + + protected function getBanHtml(): string + { + return $this->renderTemplate('ban-wall.php'); + } + + protected function getCaptchaHtml( + bool $error, + string $captchaImageSrc, + string $captchaResolutionFormUrl + ): string { + $configs = [ + 'error' => $error, + 'captcha_img' => $captchaImageSrc, + 'captcha_resolution_url' => $captchaResolutionFormUrl, + ]; + + return $this->renderTemplate('captcha-wall.php', $configs); + } + + protected function specialcharsDecodeEntQuotes(string $value): string + { + return htmlspecialchars_decode($value, \ENT_QUOTES); + } + + private function getBaseFilesPath(): string + { + if ($this->baseFilesPath === null) { + $result = Constants::DEFAULT_BASE_FILE_PATH; + + if (function_exists('wp_upload_dir')) { + if (is_multisite()) { + $mainSiteId = get_main_site_id(); + switch_to_blog($mainSiteId); + } + $dir = wp_upload_dir(null, false); + if (is_array($dir) && array_key_exists('basedir', $dir)) { + $result = $dir['basedir'] . '/crowdsec/'; + } elseif (defined('WP_CONTENT_DIR')) { + $result = WP_CONTENT_DIR . '/uploads/crowdsec/'; + } + if (is_multisite()) { + restore_current_blog(); + } + } + $this->baseFilesPath = $result; + } + + return $this->baseFilesPath; + } + + private function handleRawConfig(array $rawConfigs, string $key, $defaultValue) + { + if (!empty($rawConfigs[$key])) { + return $rawConfigs[$key]; + } + + return $defaultValue; + } + + private function renderTemplate(string $templatePath, array $configs = []): string + { + ob_start(); + $config = array_merge($this->configs, $configs); + include __DIR__ . '/templates/' . $templatePath; + $html = ob_get_contents(); + ob_end_clean(); + + return $html; + } } diff --git a/inc/Constants.php b/inc/Constants.php index e237edf9..faa7795f 100644 --- a/inc/Constants.php +++ b/inc/Constants.php @@ -21,5 +21,5 @@ class Constants extends LibConstants public const DEFAULT_BASE_FILE_PATH = __DIR__ . '/../../../../wp-content/uploads/crowdsec/'; public const STANDALONE_CONFIG_PATH = __DIR__ . '/standalone-settings.php'; - public const VERSION = 'v2.6.7'; + public const VERSION = 'v2.7.0'; } diff --git a/inc/bounce-current-ip.php b/inc/bounce-current-ip.php index feb1d78b..86d750af 100644 --- a/inc/bounce-current-ip.php +++ b/inc/bounce-current-ip.php @@ -1,9 +1,6 @@ run(); - } catch (\Throwable $e) { - // Try to log in the debug.log file of WordPress if bouncer logger is not ready - if (!isset($bouncer) || !$bouncer->getLogger()) { - error_log(print_r('safelyBounce error:' . $e->getMessage() . - ' in file:' . $e->getFile() . - '(line ' . $e->getLine() . ')', true - )); - return; - } - $displayErrors = $bouncer->getConfig('display_errors'); - if (true === $displayErrors) { - throw new BouncerException($e->getMessage(), $e->getCode(), $e); - } + } catch (CacheException|\Throwable $e) { + handleException($e, $bouncer ?? null); + } +} + +function handleException($e, ?Bouncer $bouncer = null) +{ + // Try to log in the debug.log file of WordPress if bouncer logger is not ready + if (!$bouncer || !$bouncer->getLogger()) { + error_log(print_r('[CrowdSec Plugin] safelyBounce error: ' . $e->getMessage() . + ' in file: ' . $e->getFile() . + '(line ' . $e->getLine() . ')', true + )); + return; + } + $displayErrors = $bouncer->getConfig('display_errors'); + if (true === $displayErrors) { + throw new BouncerException($e->getMessage(), $e->getCode(), $e); } } diff --git a/inc/options-config.php b/inc/options-config.php index 77e6f827..353f9f46 100644 --- a/inc/options-config.php +++ b/inc/options-config.php @@ -1,5 +1,4 @@ 'crowdsec_geolocation_maxmind_database_type', 'default' => Constants::MAXMIND_COUNTRY, 'autoInit' => true], ['name' => 'crowdsec_geolocation_maxmind_database_path', 'default' => '', 'autoInit' => true], ['name' => 'crowdsec_auto_prepend_file_mode', 'default' => '', 'autoInit' => true], + ['name' => 'crowdsec_use_appsec', 'default' => '', 'autoInit' => true], + ['name' => 'crowdsec_appsec_url', 'default' => '', 'autoInit' => true], + ['name' => 'crowdsec_appsec_timeout_ms', 'default' => Constants::APPSEC_TIMEOUT_MS, 'autoInit' => true], + ['name' => 'crowdsec_appsec_fallback_remediation', 'default' => Constants::REMEDIATION_CAPTCHA, + 'autoInit' => true] ]; } diff --git a/inc/plugin-setup.php b/inc/plugin-setup.php index 097215fa..def292ed 100644 --- a/inc/plugin-setup.php +++ b/inc/plugin-setup.php @@ -2,9 +2,7 @@ use CrowdSecWordPressBouncer\Constants; -require_once __DIR__.'/options-config.php'; -require_once __DIR__ . '/Constants.php'; - +require_once __DIR__ . '/options-config.php'; function writeStaticConfigFile($name = null, $newValue = null) { @@ -16,9 +14,17 @@ function writeStaticConfigFile($name = null, $newValue = null) if ($name) { $data[$name] = $newValue; } - if (!empty($data['crowdsec_auto_prepend_file_mode'])) { + + if (!empty($data['crowdsec_auto_prepend_file_mode']) || @file_exists(Constants::STANDALONE_CONFIG_PATH)) { $json = json_encode($data); - file_put_contents(Constants::STANDALONE_CONFIG_PATH, "getLogger(); + $logger->debug('Running in auto_prepend_file mode', + [ + 'type' => 'AUTO_PREPEND_FILE_MODE', + 'message' => 'Server is configured to auto_prepend this file '. __FILE__ + ] + ); + if(empty($crowdSecConfigs['crowdsec_auto_prepend_file_mode'])){ + $logger->warning('Will not bounce because the auto_prepend_file mode is not enabled', + [ + 'type' => 'AUTO_PREPEND_FILE_MODE', + 'message' => 'Please enable the auto_prepend_file mode in the settings to use the bouncer. Or remove the auto_prepend_file directive from your server configuration.' + ] + ); + return; + } $bouncer->run(); } else { throw new BouncerException('No setting file found for the auto_prepend_file mode.'); } -} catch (\Throwable $e) { - // Try to log error if bouncer logger is not ready - if (!isset($bouncer) || !$bouncer->getLogger()) { - error_log(print_r('[CrowdSec] safelyBounce error:' . $e->getMessage() . - ' in file:' . $e->getFile() . - '(line ' . $e->getLine() . ')', true - )); - return; - } - $displayErrors = $bouncer->getConfig('display_errors'); - if (true === $displayErrors) { - throw new BouncerException($e->getMessage(), $e->getCode(), $e); - } +} catch (CacheException|\Throwable $e) { + handleException($e, $bouncer ?? null); } -define("ALREADY_BOUNCED_WITH_STANDALONE", true); + diff --git a/inc/templates/advanced-settings.php b/inc/templates/advanced-settings.php index b9fe93e6..028541b0 100644 --- a/inc/templates/advanced-settings.php +++ b/inc/templates/advanced-settings.php @@ -3,29 +3,99 @@ diff --git a/inc/templates/settings.php b/inc/templates/settings.php index 84ec9f1a..14e54f46 100644 --- a/inc/templates/settings.php +++ b/inc/templates/settings.php @@ -3,18 +3,30 @@