From 1b1daa0f89aa796ac9c1f79112add8628e6bf3d6 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 25 Apr 2025 11:28:34 +0900 Subject: [PATCH 01/10] ci(test): Add test for 6.8 --- .../end-to-end-auto-prepend-test-suite.yml | 10 +++---- .github/workflows/end-to-end-multisite.yml | 4 +-- .github/workflows/end-to-end-test-suite.yml | 8 +++--- .github/workflows/keepalive.yml | 26 ------------------- .github/workflows/release-test.yml | 2 +- CHANGELOG.md | 12 +++++++++ crowdsec.php | 2 +- readme.txt | 2 +- 8 files changed, 26 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/keepalive.yml 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 8774c3b..617e51a 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -18,8 +18,8 @@ jobs: end-to-end-auto-prepend-file-mode-test-suite: strategy: fail-fast: false - # First and last minor versions of each major version - # Highest compatible PHP version + # First and latest minor versions of each major version + # Highest and lowest compatible PHP version matrix: include: - wp-version: '4.9' @@ -34,10 +34,10 @@ jobs: php-version: '7.2' - wp-version: '6.0' php-version: '8.0' - - wp-version: '6.7' + - wp-version: '6.8' php-version: '7.2' - - wp-version: '6.7' - php-version: '8.3' + - wp-version: '6.8' + php-version: '8.4' name: End-to-end auto-prepend-file mode test suite runs-on: ubuntu-latest diff --git a/.github/workflows/end-to-end-multisite.yml b/.github/workflows/end-to-end-multisite.yml index bc946de..23204a8 100644 --- a/.github/workflows/end-to-end-multisite.yml +++ b/.github/workflows/end-to-end-multisite.yml @@ -23,10 +23,10 @@ jobs: end-to-end-multisite: strategy: fail-fast: false - # Last minor version of each major version + # Latest minor version of each major version # Minimum PHP version compatible with all WordPress versions matrix: - wp-version: [ "4.9", "5.9", "6.7" ] + wp-version: [ "4.9", "5.9", "6.8" ] php-version: [ "7.2" ] subsite: ["site1", "site2"] diff --git a/.github/workflows/end-to-end-test-suite.yml b/.github/workflows/end-to-end-test-suite.yml index 404c90f..8233a39 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false # First and last minor versions of each major version - # Highest compatible PHP version + # Highest and lowest compatible PHP version matrix: include: - wp-version: '4.9' @@ -41,10 +41,10 @@ jobs: php-version: '7.2' - wp-version: '6.0' php-version: '8.0' - - wp-version: '6.7' + - wp-version: '6.8' php-version: '7.2' - - wp-version: '6.7' - php-version: '8.3' + - wp-version: '6.8' + php-version: '8.4' name: End-to-end test suite runs-on: ubuntu-latest diff --git a/.github/workflows/keepalive.yml b/.github/workflows/keepalive.yml deleted file mode 100644 index 0a622b0..0000000 --- a/.github/workflows/keepalive.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Keep Alive -on: - - schedule: - - cron: '0 3 * * 4' - -permissions: - actions: write - -jobs: - keep-alive: - - name: Keep Alive - runs-on: ubuntu-latest - - steps: - - - name: Clone project files - uses: actions/checkout@v4 - - # keepalive-workflow keeps GitHub from turning off tests after 60 days - - uses: gautamkrishnar/keepalive-workflow@v2 - with: - time_elapsed: 45 - workflow_files: "end-to-end-test-suite.yml" - diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index 17888b0..fa69c3b 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -23,7 +23,7 @@ jobs: end-to-end-release-zip-test: strategy: fail-fast: false - # First and last minor versions of each major version + # First and latest minor versions of each major version # Highest compatible PHP version matrix: include: diff --git a/CHANGELOG.md b/CHANGELOG.md index e0444a9..36ed75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [2.10.0](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.10.0) - 2025-05-?? +[_Compare with previous release_](https://github.com/crowdsecurity/cs-wordpress-bouncer/compare/v2.9.0...v2.10.0) + + +### Added + +- Add usage metrics preview in UI +- Add compatibility with WordPress 6.8 + +--- + + ## [2.9.0](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.9.0) - 2025-02-21 [_Compare with previous release_](https://github.com/crowdsecurity/cs-wordpress-bouncer/compare/v2.8.1...v2.9.0) diff --git a/crowdsec.php b/crowdsec.php index 745067c..ae65795 100644 --- a/crowdsec.php +++ b/crowdsec.php @@ -12,7 +12,7 @@ * License URI: https://opensource.org/licenses/MIT * Requires PHP: 7.2 * Requires at least: 4.9 - * Tested up to: 6.7 + * Tested up to: 6.8 * Stable tag: 2.9.0 * Text Domain: crowdsec-wp * First release: 2021. diff --git a/readme.txt b/readme.txt index 9ad1fbc..72a66d4 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: crowdsec Donate link: https://crowdsec.net/ Tags: security, captcha, ip-blocker, crowdsec, hacker-protection Requires at least: 4.9 -Tested up to: 6.7 +Tested up to: 6.8 Stable tag: 2.9.0 Requires PHP: 7.2 License: MIT From c633567d5628d6bec2fe9ee80a2dcfc9eaba51f9 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 25 Apr 2025 14:24:29 +0900 Subject: [PATCH 02/10] feat(metrics): Add metrics report table in UI --- .../end-to-end-auto-prepend-test-suite.yml | 2 +- .github/workflows/end-to-end-test-suite.yml | 2 +- .github/workflows/release-test.yml | 4 +- .../screenshots/config-usage-metrics.png | Bin 33696 -> 58839 bytes inc/Admin/advanced-settings.php | 7 +- inc/Admin/init.php | 126 ++++++++++++++++-- 6 files changed, 126 insertions(+), 15 deletions(-) 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 617e51a..2256e7c 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -37,7 +37,7 @@ jobs: - wp-version: '6.8' php-version: '7.2' - wp-version: '6.8' - php-version: '8.4' + php-version: '8.3' name: End-to-end auto-prepend-file mode test suite runs-on: ubuntu-latest diff --git a/.github/workflows/end-to-end-test-suite.yml b/.github/workflows/end-to-end-test-suite.yml index 8233a39..0f9c0a8 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -44,7 +44,7 @@ jobs: - wp-version: '6.8' php-version: '7.2' - wp-version: '6.8' - php-version: '8.4' + php-version: '8.3' name: End-to-end test suite runs-on: ubuntu-latest diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index fa69c3b..2b53d6e 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -39,9 +39,9 @@ jobs: php-version: '7.2' - wp-version: '6.0' php-version: '8.0' - - wp-version: '6.7' + - wp-version: '6.8' php-version: '7.2' - - wp-version: '6.7' + - wp-version: '6.8' php-version: '8.3' name: End-to-end release test suite diff --git a/docs/images/screenshots/config-usage-metrics.png b/docs/images/screenshots/config-usage-metrics.png index c50ce99a6f14ca7b2669d984c078fc63efb600c3..6bf137cd6358c7ef47bbb06a98fd060e32a26af1 100644 GIT binary patch literal 58839 zcmdSBWmH>T+cnyj7I!aNpvB!GxEG2Q_Xdi)JCq{9rD%)0yE`NS+EScCaQ9%vf`7U1 zKJPQm7~eVP@A=O9m9g1-K-Ruw&TGy^l!lrj4kiWWlP6Db-YUtxfAZux;K>sd0(4a5 zm34;hyC+XRJ$Wl9t?gq9U%K+1nt`C6TxDIU`ve~$(o(rGi(}M&s=ZqH`Vo)!QvCre@b_Lc#5VVkVrSA;VPm|2K{+M`s|r?~k(f+fyzZ+~C8h|D7K)i0Gc$&F`)x|O2KF3g<1R^r2uJd_e#Yjwp= zb+d@vtBZh|rg}GS3|c44?7xt*95;}D|7WTCkvntrv`6QgvnnuTh-dT+J|7O zzN_sFGrvwoHO>hULjRvvC82Tb3ddP}6HSNeyMz*iDr%(U>7_-e{%9@fpJT167nBb!mol_G&)6j+&S_0f zHDg+AwkMy_3V08tux&@llek@tN`I-~Gi_aViD^NopUt=&juWA@NQBl{2@#qMSYGx)W2}a!iB~s&z6)!+e?pd? zhJc^ZPOx~0;*v$BZkLTl{oTCf;A2Nves?cn#mxF@4~gO9I*K~2dcc*ue^@OSzeKQ1 zoR$Tk&~|}#Jl|a};It?N1#_zhPk~)G$pt_x+RZp={RjdA&<3x>xUcm2`V(l7r zKs;2MC4d_XU<_DQY&G4be`gX{vYw&;wnR$y?%jd&>F+Daywn44C4jNX)i+-lokM80 zZMMC0r|tX^pvis~^>I&_;IZsLYv(`te%yN{2xnedR;Snup7g~W{kB|HEmJ%v&bj}` zO*>OzZJrDUXlnFOd3y#uWF~#ob;tZF*{&RVl{<;ITFy_)am4v2orc_y(#z&}Rmi#C z!Ese$t7`2h{*nsS%dH6Y@T+EctCm-DWYQ(g_I1-239_?US%-s14hg48ikz5ZS+<^S zwqK3yl#E&JpABdtt_Gag(j_YVJDvh_V}No(*dia={N&8MFs1?})0fHWZZM#frtsxK za~hkBoPdgQg=U0$cwOP03u+H3t>JtD0M)5C0n;{nnwgb71QCBTEkf|`nz%jc#sTo_ zSr=CN$SX|Mj&p1Y-FXV)rT*#5-xjH%S&kP_Ap;(L-qPn?RLA6zz%mX8i2}2N{GTpD zeVa7aT02W9d8#0|OB=s6qFTT5-x54!C;?6qL-^?i&^RYogIPye9>#yZc(=t zA%#=%_%)I?GwL`uc4G+V(TSpq0&m(^pTNBB+3S-l>-UkG4b@!VFwP#@vN2hDXE^vc zRsnm_UuI<8G~!UhsJB}yn0rbFCz2$U&zu;muCRPf3UWNv{!^oK{Jnvo2azB=p)hLd z!JHOsz&TeE2}jTN=URnqUWvxSXF@_&d9$c|Sw^O0$NAv*Mu zE6>nl3bCqz<8-%e;cGbrNj*uWc{PuvRr{0Z1)#!ef+{g!BYZ_Iy6^8Gn31Xmoh^-`9N2i@k9&pa)Aa7vk)|BZLOeU@^E zi5xZ#qbYyv?uF&G52C@zqgF!bg=ExNri&_gn^)k0=DzVf05=^`otT5hTDJI=-Q)!j z69BDzsix+_XDU5G;V9}`V3bZfZk&~10DC&3`V0QXbY%7OnC#AmPtP75i5>CQvTP2q zzW!A2;wKFr4V$%?_SQH8RRi2f(Ke<2xZAFU!BvGgSqOB;w=FMl&~@)8iOI6z^Jc=~ z37T$3sy-A53V&Eb%G$&s6SKhmakwqQplcs0@Wj1_60@M1SvI^aNH-+nW##NS^SsED z>H)NZ4nK3~vSQhnw0uR+H7w$owZtXS+IznLWSsvYbp-Y2Z$|nQH*srvYE>mO2v^us zF(KSOv-{LYtC9J9lTy>&wY?DBL+n;v{tJW1Ohe#mk2QA>KH3p@XWk1-PD=l^_R@e< zzZe|9>@1Nlm|NRKG_PHStqCzoWa<=g6$Kn(+xQb5AFpV=!Ic>>-GpNpc!n|`v`j%O zbP#c>o32g1OH&<|>mwhj!kre0hmmA+vU(!QZq)q38q*#tB-uh#dF@@y)7zp2Eokm& zH@8sUC2L}WNv~Wd?n-`fC60r?3+Fq5G_Lt|t#m2&U%!RR+=u6g{%%Bf?Xn|`uA&W;&4&iA9rm`F0Kdp> z3kr7@U*|Tzcu71RKkMn!;7>0jE}HK-oQ7yKuy6Bn0X^o0fB(UUig)wg_{N=zUM%j| zcAijmAt-@X42&;yU~{Pc({O5alHYreG@~TYJHO!aSd=)&2$WPWv3nv<88BHz@-27{ zV@U?h;(}zWPS)R@#xD}@+#?S9&03HQ-SsP333Jo_hSpNdTCj3}reDi`>b7U%ze9@rmeZiZK zv33@5$^XJkP%?;`BI~gkO~P@KaHRJr@;K;@hI2iYeBF^@<$PdJ?5wni@bA7R@wxn| zE2JRP!;#5T)C;(lH@Yj8r`(&t*4a1yxUw-;{-66q7?|+>2L}R?Ia>zPVM-Hqx~CG-5f}2la+!9i0yD$U%?Z7_X`D20`fOFmr;>_vRcI@u*i9B zCRNC?qgbAu;oqk;Ei90~JdC||-)6B~VrHE)Yb9#If~4)FzSw-wmNqX(kjtt zv4Q#wngP8Qi=*0&x;u6>z0E~CTICxW)89bzj*d_~wm1L0--5wyF`(jzG`7vcvs|y- z5EY8+7yfBN(xjw z8X4JOGgDj{P_L{15F~XqT^Ac@_?r;|CEjFSHZ`lWM;xb`$)pYCB#S#sA-WF_5q=9u zSV8g-Ya5sF?FyR0W`(6;))zKfgZM6h5iGErz8sfF2uNPQxL6>qNX7f$TOWg~-eA5; zKOKC~57MYzt2`n|d)`!Y<+uOmK`O7ZF0hw8R-ELI-k#$vG7_pjM-OIDY><)?O;xj- z8~lv~im6UKz_*~7n*4h7I9}CL@^%JwLGoZ6YykTnjfL$8dlrqG%~Bp=D;nYHtRVzv z#A^9ln5fuPX1A3J3IsRb{N#L5nyTw^eiczD@zA9xu zCbQvx&fL?4D=XVk;8{5~akC?JZsvhGmimQ0-*{ud?XBKeKvbFPi{>+*X7?u&&US4L z+gh!2Wk%VUi}QjQ`AdMjc8y>&|4Yd9y*l=VGbZ3y%}-nL*NXZJtj0fn$`z^WH4(Ke z1RDm0(tZ)zuuYXgXrDd5-NF4dq;6JNZtov3(T$g8N@6-KHW0EGwR>|&hWJ%XJ@19v z4=uF1kilWZW1+A|X4t>``G~WeTL&R#HXlA&+fm`UR{mwj0L~Q9> zELUbnU$e?{!uRVF8}>l|=5t8a!G1?wp@CxAdk}Cn(c|NtFUq(1!xtjR(bmTTI82=mL1{AGt!^q(hJ)zmamsy*F(r{P zrfALvTQs6S<|e^}tR@xthzR9ye09Cy~+u{Oo%NYGgfTQ>^oE}=-( z;X2hb_g}m3uGhwv-3+z2Id$=T{!PGjK)u%#f142xaPHGCg2Ab3m_O7s<#TfF%wwd4s{5blsO>tDjRzT^&T-E=X^sME;tw-y``56X)_DEZo=*_@Q(-y+8X`H zUyTE(NKAe}k#L%M0l5@c2+>%!_MV}W(cc++rYBv&>;A?Fe~Yz?w1Yy#J@C|^(Tw{} z|3$@^YX8{CLZANx@RwFISh4zjUH5yR-fdn+Z~Lf(UbVp+{}+>Yv~a4=wclFgN^SM> z>OCoAetYqKal~6n7C^_B%`@Xo()!pV(A*}zq7LKCExe*tkJDQl&s;}Q{6UAyh77H`PACjPs6dQdB>kqQ`;o=ed(D>()G!}gidv@DgV?A0uvvakO zR3gLO$X=i=jnp@gvjgMUk)rj*fG!Y3QOe{o$n@t#KFjdAIj{!)mgoW-py5ifu{4i< zW*a5w^p-r`6Zgen>jGQv!$9ut;rz2lg4#ZA&Ut$H_%cK+S9iveq(og*y+YdIAVizsJpOQXiZFlj)Pai6GNN+_%%*q*D?wnqm}k2k&{M z3Sk2(uJ9C8fZ4q?ffbi6uZb?=icfe#3fF}Y< zm{C>aTC4wU(}^AYrYXs3gWPx0q0Q8tYHy`uwJ>dv?e+7HbxN2Z0H?c$;3r#3FJv{) zr>5R4=zdrg-Mcv;B4X=iWx)7Y7g5%Z7V+o_E$9M`Z5C+j7JoZ%AJ@^pO0z}NjC&8K zzC%XNGODVH@hNf~R3`JHp|XG|`t`4(Mc2YyMfGnsU)f9xCN zaK?nFe$NveA86}hyB_U4cxOT46{$CuvLYGzVlN+2QcUjWAtg7%aA4$Kw%kCgXYhg~ zPC#I=n!UtGANrP^?LR&G<`@HDhs$t?r1{(u1dmFEmIAnV$Da}$BdE88#oNjL0h}h* zrXW$9><2UE$?}l}r24-16Sj-HjJpvkAZd5=rURM)ZevL%IKFKEok~sHe1xfEj#+>rS}31P zia;E^cHoZxrs9PwrLyLn8$|T!tzvW@n|K64Hq=Pgu)uRxLvC=0uCJ0!%-U2Zz$;Af z%KLevcbhlapkAJED?NwO4vq6q1U*&o{NYMi9~_{gPainQA3UPvuUSl&OL$ zh1O4OMd+EN;ga(Et51{rD(L^_sq#DjpW({-hLt3PY{z@~82?I!@%P5LX_EQY9|;TSKacP2XghEa zt<3cgCLtOkDzsXt<3jHSa&rmb|{f4CcF|f~RNe zM4FUSc}0fEoffmlq8~?b8MZ>1IC`Y~TWw;$cYHSAkol=LD(!MbJ6;gZhU9Y`LD=M< z-&n^K@gn=JT#VlA)rV_%Ik}J}fo=j(E!+ktw-_(3)x+i#j-Wq3JC2>16b(u^_8<5# z^P?koQlK0*VOG|ew#y5d=_qrxGS>^3++@QResxb3Q|Biy+$xIl)C7=c5Hn+Qdcn0j z=}&)25xk&_ocO6|bxri>r~sEl^W1+jZn8QEMTx-^C4x~7hM;+8LWd`|jWaeE$&w1R zKJhWbMB|6Imx!vbY~NZA!c6bC*IatwUs=u#!3ilD;lz(Yt3Vo$NK1x)$&(-DvI$HY zcTZq_M350V_?E#7m@g71^j1#S#qUm=2c`3?E{Tr)NqoAJAb$Oi^YRR+tpWC8< ze;Ew{fyh5p>+jmsCinU`+6V}ew1EGWeAJM<=-(pw|9&%0p?|nmF#AvrNc5Ub!EkX& zkb3Hhq;Ccgxr`?&_QN1*YY6LLR@ZZ^E_l?#p{q2tS4OVc!zaj%gLG#TVyEuv@n8cD zeCK}cUGz&N{(bjd>TF(5XtlfG#uvkf&o~|F!u5VE4YV-*F}#pMM^!aNeJ~qNrgM?~8$H%v35&orW>wfhc%Wap-3a>9{ z!X3vb@;6|gwt7hR+4S)#Lr)b|CVbwEuNhnlBd*54)c!bQ=5rKTxs;Bj)jzk(yFtZ) z?;Oo8=X*^`on@GBC(3zt#UBan!XAH<8V@|U`wH%FUI^GxI=yo)7N_7hHPL18exxsY z1MTAyCOuW*#0;LuKUwsPeVO>=F;D4SZ6&tTkUIJ(Yha*sJ?*a5#?@V3Qm&@WqI)@h zl$P`smRT;s`_gI8UvrwAvU#6d!AG_4f%O_ou%*86!ofqdtHYOy=}7OVSs7X2#G9(0 z?>;MXae4fKhqjaLXYRvJOFT7FL41le1%2x-r&b5oJ&nQ&Jrg3uIXkk76h00fO=B!a zY;w*ok-KJH#0K5w=emOxmkyjKAHU1;nUaWw;9^7K{iYWC?sgyay=Gli_hLBB4+|XI z-a8eNP2l6`9~tHQM-*13S5);lNyK@K^jC_Hoak6zh5 z1aaJnrcrMcq0Z|U%u4`N@j)6A)Ty{i%>@?=Kej{7X&=w>k$WQ7r!V#W4@p=nhc)%J zF-%T@vDom0Ii@pBQ?2lV^kR(z%sSgdu7Dlm&(#d)3s-dE)c@HHyF2kW#4JW;)5oj= zSOUUAOr#TMV+DDJc-$ud>6cTgq6@1rSIKbb=078!FShfCsu$GesOpdcl7jB>6`0! zgh5CTp?5>D>|>pj#~w-S`1zLbm)db@qWz2SpRwgYsD<`;cpiVEAbkUKuy#Abbs24N z!~N=%H>sB-??LulW-IJ#)CrX9sAqRxB_p78RU;L)GB%j;G;noJrRwq3xq&<7H?WD) zMP~d)iNUNG!M+DS{oQ?72m0q=JJOn{#%2RWD+hMH{aE9bkSLZ3mxDdDgp7CW!Z+F- z&C*dvzu#14@}oa2OUQ_XUx7;L-4DbeUf$CYzeM}zGmW_EMV?BSf&)5yA>!u3L4eC& zc6mj^E1!FUv|L~g(9+?R=m$D?S3`~io4E?K7|R%-BC$5gAiW4Ze)iJc+N?8c11H(6 zR}sfH3uUB1SBXX`nwt>A4`*In9dc{N(e**hus3%hfPI;d37DaFKQU6i10rgtMyIcZ zI~@4IIzhYZB;jFZt!v7MvElbNW9+Yo0IbP~5X{9<((RWl)1j~)`a$zX zkH_A7FIMUmg`!tVZ;p;lcWHRZr&{1cv|16b#*J(e#AWeCyeOSA=9AVq*ogN%h~!bk3gPE=dFHyXsl8%F5wtVpg$E2ZDbSN zyfNrIORQcT)T>;Ym6nSmJ_$#>^t+c}%%!tfDKsn`eTW^;1OCQ2L2$;*t0ry!cEsTw zH8IF^{j|QcuJ_GH^9xJJsiv`&o{m@n`15!%=uRJKVOHPlP@sUcb9{d(f>f1=DvpKW z%w=JQrP8O_4j;q9EGB=*MjS>B=irVVYu(CF( zcwfFR%gJtLc<1D`1}UfS-yxDXP1p@L6zSj zWf1cd!QOEe$8-;p*I0i>6{P)C)st?tnmCQmrt-H?NrY#@;HE`*nYO*g^*K3XxG$Yc zfM88`8KK1OGp6m=VDYoO(@Y$+vzF75I2dp@sY6;{9j9rZml)%*nTlC=^;ed#51v7c zR>H7`iB5DE+KaOBh1xt$(={|`J&*ds>%Yq*Rv_HUk zuDZBK#5yWlT^EWJVx@N$R34%>vR2g0kQ*mx_j`e+Z*F40zQ!&0Sk$Qm-B(}Yra=b` z15gaSZ0Ng&XcS)l0(g7v_nXf})Ip5M2}dI=C*FH zz6eSPVBe6;hjqJ=Q0G2<%zJki#7Rp+0le)Jjy|Qc)weD~=R?+&`5w&-4>*aPhv6DI zYm>Nb)-B<+{Ccu*6Zn%k(R-ALRdw5(h`K@}giCBLFA7n~FYx5Xm}a1xs2?h>T*-Os z*Nl1<>|*xb>lul;5N#cL#=~|FDq4FD0|b78>5pMs=ehVR^+U+WZsmT$OObXT9i4E! z_Qwxmj2haF)XH7_wi{Q2wpJ&g+*&;|HGPxpZ;T}WL@>V%le&k}Zo ztRh(PEzO}l(xEkQjg|iB9vo=g!tL zEqGrk8&@ShZyao}FfP6jO82y$y!j9{2kX$#MDr5|1clq9md=AHeXjeM)wZ`>b2x$I z=WX1;T9HLZ#X&J1tsWPDk{Qc5Lh z(z{(WUQQmu5e}B>+~ih3lzeEO(>g8Q$^hf;5iZ-j16X_AR1-=ZOJY-U~4<4HA3GTW>h@W-Tx&u^OlEq#p-4qg?t-8Oqui3|M^7~!@!oEtK-{e zcO}^hCQbQ}son^1Ai}irHpqT-G~8^{+_>rxh(Ucp{qm#&2f#jOFROb{IjlJI2|xDM zNP-E6^NuuhSLZ73!Xz$vsxYyou&$-h5)SR&gm-IG2(lnXv{xA-V3w$8k z8K8Igx@QEn%P*t}$@f>@7wuD~fQ=Sit|mvYpVc~4jsoqJsJ4-9B5+T7jqfW=bK$gw z)~jojcN6I-0a#9o%j+t0D^MlQrfzuIc?L{v0ZrV5OR{)=m*S4K78mXyHX=Md#u?d% zFR&@mP|e5H8Y>z2(|OpcLV%zByl#Ja1EMli$T;CpvJ!GN0@kO%8~pC!%q zs*LQN6xk<$A!+w$OCESIVC(Hkjt96({N>D)7`!g9N@H2ifitr#3F%36Q@ChvfY8J7d#TAoOq10p zM5pG-gfDK1U(1I>*j`aR?Uo(q@FQ`GwAKqxEBvn)H*Gj@o65~#acpE~4va#n>I>V3s|s!Wwid;nXP{!+0TW+-4H+4n$hi*q8@Hla;6C3e z{Bl{w%kE`%IQ}w&$L2GlSh|EuMp7ttJvMd~oYNA())-mRaQ5!dm3 zRvW2?gvh{j&FKe=aYVz=k)qs#AykfhOdhiqz?$BDmQ{`4w~d5&<_2N+V|uVhc1~yF zTH?2%w}_3&%Q`1AUga}^g&6_d8`KLt05nL3RkR)L+lXRDzq(@x?_n31P!z= z4)&Pa&&kDw{X+o#;_77mp5#nN5s1zJ6u^bYZg*!@$`p6nrp953`dc9tA;KB>qTCBm zXFxS1esx^Fp}&FmF!W-VsH2bld6Iae?2Q(N(^l00c|OXHq#Yg&2G+#372Qm*@Qg7D z7Y6C_doL=BXA$J!_~94=;%2JZUpH7tDZ{y8+STj3-4f0U^X)rHBO!E-`A}&IEf*-U z7c8@NTvHSNOW%|HBXEeMp07u{K9ozdi~&n|SOACKn+=;DvbmrRT5LusGSc0@$=?Pl zLgo`MZYo?I-q*R}b7%$9Ovy|ozQJbyolZvQ@jZUt`K)TYP#ceQSa;ur;&V7Eh+5f` z^}X3Z*7qQNKSivisgd3Elg{7WzjcuX5<9dJ6_wzdeFh~9Hu-T(s`Yipd(e4v8r_C4 zoSQAV?l_hR&8P+0#}B7(5RABWclKz<1>ZKb#@%gN)}r4iI?CwQ)c1t4hvH-cL2f37 z&SoJunA_-~v$51y8dU_C#ew0D5bZc3ukZUmqn9k%;CkD)rRIp~@j3|njt&a|-RSj% zxt4Ik>)hFihnFn+p0STbem1-DS`-+8_PZL`9jtBV1K?rQ7b}DFpaTLk|KGV62k}2G zkHwf;j1KVol=g&ePCMQk#3Enz!z?*aajAtAg6kiMl5GylZ$bhaNFdnt!01^VRhmvg ze@x|Vy^HoUu|n&O6D-*btIk-k+idYXHXuIw_PZBE2NWSGzzz3-xJN_kAYJ0hRkJJW z?lF~4`N_=I%}1AL`M5hA9`N*mA&Z>zj}xLZ z)TqObjK7mx{}eBf{@VZkdEIS5WqDx7E=#a;bIHeF>V`&1PdSri?CbL@M042z=qrdC z#~xX(?3v*Brk&iic!Ui{Yv)9T#vm50@%7ep~7((%Hhh+Dhkx7}d zul?HeW`PVCQ@b%&Gl6cDH=eeI44-p(G;Zxv(q#RG16w9|+0U=&tpv9mG+8&uB4w+A z6ph*8fEFi<`W3b61m`$%FZ2ap-(38U+Zp0d=G0*M+UYY3dhBOv*e5x$S@W97zF*Dw z;P^9Jf55_^)_Ry(%VdycPcTIbXsh@T=|=e5Dfg$}`3zNQcg1aG_)x}VTL2x~f2-GV zFi`bRsZq4H-fuK`*vGc|T*Mpf9U|jqX%OiNMB8fc&LLVjoSV~Bx;u;V%+7>~(lV#L z!87r zFDu3hC=UVS4g?NlT{mpi5_B{Ei}1+3VukfFs&$Qo_QLvmO>Mb9AYGGFBam@EUy@M? zGFpAKJ<1I3vycizUgZXtW_gXY!2^i{1sO_0|3Vy|=G1zJPc9-wi<#ij%q)6Ciy{1) z1Cq38u3cWKalVUslo9k5k~KazOo1Pn;mdSd)`TjcGtgGoM*F#_PBvi@O~|EFgE`qW zbiH$dho4)1H*7bro53SU=j-0aoAEAWK62ZIcgRm0$%+`55x zjoQ{vsvLLBD33-nrieryys5LDh!Rm_FB@%SU2iR>1@4c*Envho|mwLY%R4Hx=D>uCBaLP zC-p$0`q`>4``Z?!G#|5tAQ0x-yfWpu{VzVK*)h6*2n7`fi(p%|Z7KCpZL9TjZ7|F&Ck&zExM)vj79a*7A(K#@x**=LD&`p=Jdv zH40n9#*1tk3lg~BwAf>4YUAI9S6n@J=(?fW+6r&$a3p*d%DD0A^r_m+q0HdKKl!C$ z298Z6j~)A=DBoO{yAt@>ynE6QbE~E!;+p4aWJb^vAuKjcEtBXjd+$VNhfnc8EBrla z(`ktd6__Nt&O~*KP%T#DW%8G)QtJ0bNuj6=PRp8$9K22Ui?=V{lWX|;;of~71a9C! zZqmu%(jCqy;P{9ft&d353o{O$+p%-Hk0)Pw$jCW+GN5DW<8^>uGI;$P0N0q;<8Mhb zamO;)7y|xy8Qw##iSjR!9YjlcKo0L6-UDM@NSkS}GeAgJE$Dj=5BbyO<=xFAEFm3i zbEadVyZz7H$B?s00@B?CwI#8M-?ez}tq!3Ajb*0yuPDDxsQppFTu$UAaH5^UfkV}==%*LRJ}_Q=^(uD7 z4QrBb=bWUi#TU~&RC^b`9pfYcM1N}<$uP)Q(*qY2R9Bb%lwK!cI-x`%fgUtr)!KrN zGG=dNRp6}uN;KwRGdFC_$SoDHk3$g_9b6}Q{0+^YTaZ)#Mx@1bf~fu?L@gik>iBuT zM(LzFgCAwkL-p}9Dvn854a3Xgt?0wN^LfE{O7WjF>z2d|_;`+$a=>poP<8NdftQIA zyL(QE_MiDE7Hd2gP5GSgmV6;BK#?=GRzR_z(Boi-lZ4lN^gx5BPog;W+ob%#+zl*w z=*b(H`-+2x1M*-M_ZL1io~Lj1eU^OWlMLEZ4~XAW3XAcOQ$y0$8*Fd)Oh4#ZG2fNk zIo@;39yIZJO2o5MJ0hYKYn7nt^RdIte7d{(3mAZ~uN&uoJP`gq1%-;w*VD6PzY14I zaLSedfgr?FNKY~`#V0j!Xs#elY%$pYFznvTuDO=q4o27WNHS`zniemVz4#fQ*XOml zc0;ANzlxBW8IRUPrkp@GgN2( zhZGkM|KYS_6#DB27Ka3*P96)TXJUT7>()ZSK3KWANerl1ytreuX}1|lMm{u^LyV9i z2-?!K8w=>S#5D*QXK0M)DbH_Ln4@dD9%I|u(KeZp_TWs>A;_Dh-Smm1i=I{4bQk+uc2jZ+prc=1l z3LAXab?Pp(Mzu!^l6Yy*_DbBqU`?1INX`pxH!dPyLI(tmT(sU~p(2kpS(N!y?XF>s zwM{6+oz`|Y4|L>M4q=7|ZVkNHvOE}82f8UaV=tGxP|?{vL3Yjaeu87jp~uH{N26tZm5d{o zQINpx`;R>hpGQ)f?y4|_QrWGu%I`lTWzH))1$#qE{P0CZ@SG%|XKn%>9d=z^&14n~ z7Cev{)hXJ7Z+6;Pjy30(q($aKPJ8VKBFAVM%b za@X-XeT>C#&Byz|^r^C)$9TC)lK;o21+t7Ad8e2Z1y}zls%}o;yt`xH@{=n9F6j%I ztG*uch7)v}`3|Ab{nBkr-J(I|^lEST8rsR|eu-N64Z4i|oBJj`}@XW~z@^@nWNtgyQijqjl=67w2M;pWSfzOX138B$u_N6N`q zlxZIpOselv@jl_IyXl%=pUSM6IVS{Amej@D4d2=E z6S^ZwKU`E+V(15?GjPWmhWdMhNsqdQU0ro(MycAPpBY>ysKrSiU#Eu&IDY)EmYuJU zbDVMSNbivP`B3Q+sh`)U1%;jQ)zgdL4G8)%+E}4LoIIKooY&%QDh!1doux7FGF(AJ3-}>e` zq1#AbK!@{slh7l0yvOzA&xwp;>l4HqRp{i#b#wY~boMoZCTEwuI)-ZzbSM|1w)~Y$ zRAbirgSj&QqTfwF10xYuyhL?2sOOGe_hS(~)GQo{SjG)fBl9cAHi{=xcRP%$l;EK& zHPHekkTb2yL{gGiCmECW7kJPwF+WZC9oIyvsFv_dy936>KfW4t?umL|F6!aaW=;BK zgMMv!iip}DA)6|O*6DVzUD)0H^aowY?ji(h>93UePT=*7uMiLrs}wJTCq&x@!Wo#( z_Wp2VZXl6JdQgDaCyhNI3po-GKO~fVS~=ER(b=GA7Ex(rMbDzGpJVjunbuH#u)dEt z7?+ynIP23ZGqbzNB{CXUqWUAduW?3!9?TtGFE)}3!#zp&A(8|{cqB5V!QsiniVTLv zvq#_#RCUPH@)2P>2BWYG^-l@uc%oj7PU+iKs2BSIR3?HHYpjwUr_nOt+69+l=y?Gw zz95f+oAd{Pc_0E(yQ2NkgtNs@_froyWqA)#U9Z=t{QlzW`bcBX+7_zG7WAA@ErGPH zL}v!1nI9Lgp_GA#72C-vXLpkz;yZy#{Z3Hvsi!!m_s6{1qGZqXWUSk@(Y1bZi>&sW zQ{XQuVus)TdMo7Ei#E);g@FrMh&W%QtH8L%kx+(mlp=EVS`TL^slp#7YWeD}DE#ie4G&`Q1p zcxZ7ZuG{{rgrWF&O{DJS?)V6$b+wPZRZz{T9t zyzZ8xCeE37V9fKl6Z0QwO2H%^B+@vs;EnC|vR9BVxAt;dIV^mvFQoMM_<;bk-yN~M zZZ@8Hbi694;!3!x*Z5PzpPjZY-)GN0hpXLJcKBYBw_)J@6d0dLS$&wWRMH**4jRY~ zwMwiQ@Bi_(aFKe(2=566|KSxAX~ZoL3FJQLTC30TbN#dqzy)Bk=98j68}M#OA|Q-^ z%cL5r#D^RXju_lg!0_D1x`{=d{Iq)mK9?%1r)vM-s$2?Ly+A4Ib_8#9h3B{~$?9|g zaT}8}n86}&kIjs}SE=i_Qp{v#W+Dam^I!>{F6MA*U~>Jnn}l-Z&m=BvstAzWhs6o= z@}Nx=+rwmPB^=7=Vrxu|@^WbM1{OXEKY@R-V;STuaATEPgk-^qxS*hdIAPRL!B0sa znYH=s@-KWeSyEK=Sg?OGfw`^x^W2h4|i|x|&`x(bD6P`iwF$2@(kg zJgHyKrd)9Drh~FKvl<06(k5x3;2A40lMC-9S@f1GcOw2nx3xC&%|j5;fT1g&_)y_z z*79uO`PPv+Jb=gd1z|b;G3qum&Xl)oaOVWG8QfnEtx2rL#u?~iDI~&|b=U&)6vWv^ zu_wD*iEi?)OK$(Wyl#IO^j^hIaUvt23P05jhc%Z5QpC;mJbrAjnGAUx*K8GboF9bE&lSs;bn*;eC3(5~9|Fy?iN+M3|2WcUg8u`LNOumivc>I8VmfAE2*c zT(fcCpLMulK`djP-M8_D!lo)-1a}|u+0Es{YFm|H!i&0DU8-6m3;1tu{7bx^xHJS&G^Hc|D2Sj;CdXxgOK;&3`izH(qnF z4zm9fH&A5sm#WlmQDB=%K}g3g<4i)N+~%JJuwYa0FFJbOp?^#i0_HH_sfdY018UfZ z#gz!BnCpXYF<7g#om6@$y)OLY|0;SBhXUBY-pCADuk3z^iBGmP@bqa4!e5MdDVov( zI4c=VC!I}u-RC@S+i1p){Ko=jvTG6nwI05WTTkSR1}=&*&`n)NszM!Jdp%bLDsJ(7 zyop|+FzgXBYbZ~pA`41Hc+_%f0J6gEF0-ao(j&qy?I;i03oemV>4^Bdiv z{O}@^yai;pEut^~cH7hFwsqEqp~1^`^sXBzw91H=(hc9S^;NiMP!NKA-@mMCjT8PP z&dWkPVee*%!hth75~TpA#VhVTh`D4CFW~}1tXA8a@N8UsrD@NX5ZV+Uy57Qonl$%p z@So`ai@o=bYHEx6Me%S1k5VikHK1}-njk`Gp@@hGNH3uV5koIh5^6w2M5!uGT4(~H zCG;K;QBbPX&=HW91Oh?`J$K`G&i%eS-WzX>SH>ITy?gm<2lifBd#$2Chfi+4Tnljqe|5eOQalY=!)m>B=`0-gEOEc(Dzx{mje9oj`7k?C``V2T z4&*8K&A6Y(Lb2?hMgI!L&4u4%W$GH_Pn`SogJ9x09^`rsRCAV#C{5WDV7Chpe>ovM-;*YLFar`tX~)sS-i?j^k8|(mF)HH1%*^K_;MG3jZz|={@~0ra>}3@*29J3d z?QD*aAMWWeC|4W>oVJ#NT{0Ib6J4iMdV(9Ez5N!@?x-W2Era@zF;(Y1PiiE1;G=$AXXCzgu`TIV;dT%Qx zyf&2k;FC~)CE;O7GW%Bag#EfexNT)Q@$o!(;uXA6#_`x!CYKQ% zVzZT}C+@Ly%Cf#yod3m-`{ikOQ~Gg%Z(r!b#>%VrC~hn1^Ea)mpUOCCVW$dsZ;;P+ zZa!|iWFNrK{^d_Q+_Qbz^yVbqxSWV~yX!I>`&m&tY^J;6ME$G_)>;1A zQEPt_38hq}lyrYp%$o;mzN%PL8+I~pVZ=W4=F<9(`Tq4v``FyQvPqTp*0ddgqyE6QL3b5c@FTJdr}N_@i;GRoQv zr%rWr={nDH`&lcgkk*3%*jaqlVl=n>+cgGEUSRt5Y(VK!_;r*$^hauw^&&YOR6SHz zBXbVcg04`E1^d&F^<91)e!<*?Pi$Vcr+qgy^fw!fPky=)e6BYt0-Dyrf8~nKvHWDH z-p017OVX1~P$_6H*J^ElmOVh?ZK`V!JfnH_qjaDwTgw~PVBHM6sQp3^>tlFWfM!O3 z@o2f#W%4Pj$I@is&R!FKC3C+&!&{w?PP(C!pWQY+eK9>*wcykDZ-~q%lMTiqhP?jP zZIfR)N*W~Dl&}wDzxVfzda-}uwd);+j>4nwx&b{tXW_!F zKRPZoVt5^ww!YNa!@ur}fy#^5)G}H)ze(x1GB?NYAe9cgIGRtbnl%+Urdu>tbKmDy zH9R`iG18}IWFT*J)Ff1@sZKQ5f2h3oLgEb*4^xFB@_wjjYiW8lw-C$i1I^V|q0&RZ ze44n`47+YHc^#{>x6Bqg$K%fzBOlkj>vG&1YiPCX7hw67@?0pttZn+(W)i|dP?ps$ zMe%l7@a+|xrr~NaRGG>Vn9gTw|2AQk}@j-j4%h$^-Mc9d1BbJ;er2 z$GIEHFP=U3P3pM(f|U)jW@izswD?+oK-&IL0jL;%+svYH;m+GSmA(q&!Y*|)?>BSs zUet(kMZecTt^vyxCd$pp{Ec8{qXWkuo@p5Y8%8VsJG!dw$ik`(*`zJyfzq3vyhv_t zER$E?sHAdytM%~fmGx6ruQs)zwesBluk^nQ{!r#os6JWvx!LdtC;$TqoC)))%^n*F zTmD&FZVlr;6SvWkAW2JFTZ{x(X{FY%!@|#YnQSz)G(dYg74`-40;jfIFeQgRo zY>Q(?1u$tXP+MOHbxu$F*We^IyNP96JKEQ*@d;D$Qw!q|QGTKA^v{zozR6!Qso36N z`kQJM5nA*3l3VNWcOwpwLkP*4YTI_#1>!F^&WICZue99XBgkxZTsI+tnR1ZQtbVyqa3XJ_@m)iewGpb zp2q18^)CC@Ci>H-Ys ztYcGd@BSj5dr4VpSkw8o*4x|N>#^wP=ukuKdrquLbm@wHuAa&Z(Q`e-g6F%(@?Icc zTapx6^HT%1F%{&pG7o+pK5*6igHKqAk0zCDz1XZo9MPjLZH}q3M|6renhHE|-i$u|P=Edq1feoy&|>TdDLj z=bsWB*%)?m$qZyn4LsskmHi{;FFnfGtxs3r`G1RnXLx>inI?l`nF+pofk}}dHcrQM z0yGTb^eUj}1)=i&$=0x`LC$`!3bS-wC=B+p7mF^!Y`5R;C*>4ya{UnhK2_-|b1lEJ zow7~7ot!?k5-)&0pQm2XBb!_m#y=-#u)DDQ;8wTc1jeRnX6 zQe+${ep!OIcB8?&8zLVKYLjY)M+8XKxhiOgN(5(#HKHVc5EZ#peKAQ>zLM4!za)tu zUfgF>$HXLP$y}4~)vHrR&s^cWxDK@bkgRvYMZx=q4tB3kSH7#N^W3d1m-k)zA-U(! zSE?PxvZ+na5ISG2Bx2l7d{g=3={3!hqLA(ulcOA!S-ebu`n!S6o~61xvBLHPv+-}T z78Cc1CM58?)3vudmbQ&X*9Cn?{f`d1i^pYh1!Pqy-W9 z1dB$1-a=82q6~9z7qf;)NbiZfpWG7$c1e2T%LUuminhXZ5|^(??tQj-)O!Rx=H6Xa z877ffV(qyllzRqu@p^yD^5>~w&d1Y3Zx>#ljuLCRU0A;A&X4b)O!B9hEh{<=b|iC zoO3P;{8g6DfYwd`Jp~OnkZ-Q}pyIFjQhw~6t{br_a2iHvKJ75IL2{rE%utWU3k@sJ zg4rnfCo(U)oa>cY_ZmG&82?c!Ir4hpk_-%Dgj!Y z3VGz*xP);uspGA2D!|yOu2{dO_coPF`+8JgV_(k6N}4^rmWokmG#0$b6K9E(mu6oc zRLK}B(J14sS;LGP>QpW1<>Bp<-%YeQhupCZuVyoB%Mz~UE4#mMlWRv00EhlmUcbr2 z($P%#x+dkq%aMPrb#+)ao~W zo8n)vd$vzQ#x<27s&d9<)Jz-eY4C`^O{I?&`-7Q2TXkp_w=B8BPyrH?N;}Xz#Ss0m z%Q~d|AK!r!qQDPS_cDZxsK={IO;W>g%fju|b5+G3i0oC-KSd$hIG2SFmm_brbk>5tfM>?srTMktx%iJ;+-d-d0`Z94ZvZjG92wq^|b%u3Za|LhKy?9nsms9A)L zHzeDw@N6{cvJU8inSPSk`)377Lh8l$+Fi$Ii?BN<7|NbkFn+WTub7uI3f9g< z^5iCR&SdQTd?IgR_AVPF*dew?D%N+hc(?@MY_uy^qIb*fymnX9Hz8(S7aA}m0?am^ zMG3BbI?7~G+@y2SIZ|LfUKGW{#*k(9yJx}>0ijqo8!c_rmAFV@#r4AHVOKbITM3F= zpFJMPN4W}30W@lY%Q^!wuDaTkt%o|YOQe_t?IWt0Eo zqUO&3ZbO%{D#X%cHap@wX)P!8>u=duD631?OeVI`uN{b_2RK#*kGN!rQCk+`43k21 zjB6(iUk=hQGBBX6f$wztm<{2(CR#!FwvpIuxpC8RQ3RDSs@YnqJ}mqpqFLT>E;O00 z;)ulxC}nM0Obf-1OHK&p`O`E{^Hy7=gpM#^ZklQ$Gly;Nb#sd2;#)i#-)JKqo23Uz zv=q|!jOovgqWhj!G&)vO&50CqNjByC1SSc(nI$>hmYogXvR2N@@dCk1l#+WI{$ROZz?35L5Re3vMrkw{m zuT#E=53Q)KNQQ;pDEK8=GMFwHyV9JOL45?;Z%S+ERW54O&{NPkZL)ry^@>a0*7HT= zD((xk$*;$?(JeGxwUI2`NR0~A?CbCEu$xl7R`UBj$~4C|9D|wU6DtFnEFX;KUZJOn zi}fElOYznb(S3%}381)Zq;)lXg^X$Vk(|l5Y>=v(QTOzn8ER&b{uwjdK`GLOKNysl zfcb=YCxXstkM1rvPSz~e<-r*Pmp3~~cmFa+9w3AvNBd}iIS8wm*WYEyBKuDs7y{Pu zJ{weGqO%o1mj_4#XA%^&th4WqqBV^0Ue-~x-BkHpm)xF`0uKy zKva{ZV?`IO1f)GB1r2NXQlw!3Ym9>|?Rf=v?bL+4uN_u30gMY{LvQZ-o(KKXSZ`YPq&grs7tbXxyiwkdO! zyP!pM;;xX-!s08I_tv@#Qu$Qb~o*clB9QtM!%ynr#^qK*Qq3)wuAD zK(w`rhS%zqp}M>c0g4nfs8fHw$Rl++o!$50H+clO*4{oO4Y6}~qT5;ijBh}j8#NA` zl#5{g5^b3?<&2IgFY0?r6fhp%RQf3a;gvh%qhE>m&=5)*%P$J*+q>BnD{QYEuqY-E ztJF(q=!{h>g^1K>a#g$W6kR)q2hXL=H&Cb{B3<(NdGmWBRR}(N2!D>2SbV~TWf7Jy zJkF`jP7%ad>*CP3sX~u4ek))~MXtE5)d&bki4^fS1Ea1qw-nh=b5XpKh?A|xkr@q! zbb)3)&j$)0C~JzQ$o-s8`RM+)5qvL=p|z`q^R|?(;10zzl$Fi!#$jPEJesfJo}~U{ zF*_~a>4-gudi1_}Z|PNx%P!S4V|)CrPUQgg3lu`lm%>TvkCtW!-7;+^Y8zvV`j#fB z+soB~$Yo2sBE02VX0IQr`F(fAyu$nvU3OZNE>I7%ORJ2TNY_?*VjgpT1faDEg=<@F zmhY{v(EjeJun$9n9HwW!VoG^K$L!obeTzI?e_xZRUpG#*7vIdj5Q%Qqz{Q%|KXAoQ z?;gA``=DGte2GD+{Mbhn)sUZv`cPOjeA6+RdNZaTum(L`dI=H~{fx>;TSF_na*^6` zwCNHvwa~)e+B`Vgb7tcRL=0;NNt+lL6+NE_0)u5u4C7^&C-HX@FHQ}5X41^MKChym znLpF(Cgqi7cloY6V1Ef%_qXc@iVclwH7|@?NDgmEHVAth@xT-1O;R&U)35mas5J|V zIkBB1q&E*9!^akJWlXcxAxeo1kxcDkLgS5mhs;>_RK;QF9t zNW`(O6_b$s>mVfCuxaE7LU74Q@r1W^#4G%QFHe-yploX zg3b#oG7B7wBCA_CUeJ`q1={0!W~QrzEq8h>QND_^BgkVYrsG_xo& zq41Kl)k!`3iLu@CaT>b7Mx^~mt+0AW5*sv)-K;)2JF@R7ub}ZtK$8G$;VlT<r=+=^x}?r^r5 z!l;|sthGv*>90Pxwcr4Eo(e^-Bu2nB3)xD?v4KAERbu$N3JMl`eZN3~n_Z7>Xx%1z z8$v~@D`iQr8wPq>DYpiE;K)1RB#?vin={_9tSQ>Z?l)M?nIJ7Ane@ksUi?iXnFSMH!BS8bTD$Gn-m3!w(=Y@jT?Q9=_j3D?SbIc@}rz|yE zZ9d5hB)|qcpg*$K%6oRFuvcO^uUAJx^+3mSY?v|(-qy(w2K#Z;IzLX!wnP)F3!wfF z26G!-V_kU{A7YDV>^AZgH{tAGZs3hJqzSj>wC#~5Mp^LN%AT?T2_X}wNQ zCj=3A6UC;IB`a(G2tG*qS<0FQFl_&LduY^`695B&2jM}Yg}qhDJ=A&q_V40^L}l^^8Uck z9u?<2{*0iNMT?-2<^yjugbMX>m)&WFU46+uOp`nG#N2!8em@{WrW)0x;)8$R4A<1{ z`q{T%Ev&4d{}v!XqGz0{%m!IxTCjsA4a9hR7^UG8V=9M&kyv*OEiN!R?ju|?hGHQ& zoB&cOC{I{9$bk`WQqoCyll2Lf>fMLcYf8ay_VFXgkqzwygVwS6KvMkQ8MMGZD1+wd z0V2e=WtvfIrjyt?->VNM^F_X4Ax`~Ee8c+~i_F~$6QsZ3<=L-;pZerneT}|!e^{Ns z5t{LG^h9lqK<`hfjZkE;Mp$RHI&?_$& zGV?Zw{}kbrvaesYz-~Z{%yw~R`DzV$lgLybBD%T-@g*%~yX-rEiLKt3oX&LeeC_yV zAmHuH7sJh}m9|r?u>FstV3wQjeOQ~?hU8_&i_2CpBiV+$4YIidKWg$Ta>?VgNJ`V~ zf^NZ+b%%xoodvb~SlO$0U7`byUPtWKCFRQ23qq0l%q?m8DmIEDRW{eFN!+ClHZ3D3 zeOhlMfWX);hOWg*vP!yWU4?1oJsPwTDdl3%(%fx)>7Kg}9W4L-SCY+vWjVIBPFe{J*oaRzINwI4#$vlbL@U;+j#R4SoheEt{=Ujk>&$#sBH76%;g$rwL}{~HVM5N zQmyMqfsUUNJyXV-(p4>W`3|F58buiZqkMSLUiD4EU>&~vEfc5YjnvBRgaM@+YB7Ub z7o9?M*qn@SkepAquGllSh4x$4&@sD< zT!~$=LY(jpYUYrT1Dfz-sFwWXPyW#$AJIXjcaL*|4jV&%2KBJT3rT7Oh3;9S#{C99 z3QBhI=bPZsG0%EwVb%pDCJ(Z`$NDcjge*`wZ?D4WsRbbaj6eNDzt)oLHrjrOcCyue zs=8pQa9z#t93zj>uTDb9$`9m3X8~?ZDrD-y}gb34ahCIzXMg$CgGZ0-}Z}CpSOOo zxzyAwUb z4Qm0NvCtNLDZ};oI)F;4k(gK(0sWB-f;)3@$|749uX24;ivMEx#44;~q=Jm`IQ&x_ zvgSH4G?j{K{H4gqa1ii2BE*uPd~n%)x_v^Qu)Luj>AEET{SrYlFXM>V1^8&$m{b1T zesX;>f3PlUBlfyHPNS=8JEO<}`%~}^A#8GI*c&2KW4m|Aw8qBFHC1c9&HJx=*}Gzk zEhj4r7SlID4+2y@V@S6?R+yRs#Nr6BtxR)#_sP&agp!y{$WBvbH+@qsPK(au6gV)k z+t6V|w?B&Jj1HJ8v2$;C%_W38p*mx?s(HB;jQ5MhA<+Rl?k}G+9~I1vGo9#+1z6Cx zf1k_b9V`w}URq5rN>XltY83no<&Bb>`7LeqIbF*d#~*?9kC*<|(K-rsdb`iyel0^m z`OXtT*6+9Mih&x=70HjXAKv+-f(RPYHhgdC&f>#2jH$SAc1J8yFV>KfX=G}-Ds%W} zs_kY%LdaX*ruJGH|78G6GCyS)bI3T5+Riwox&-&_2@m)8HDG9QwZSk_=R}I%nB9Zk z;7o4H#{3DDUx%FOs@Z{y{amn#_kGUd^&&)c88nJ9Jzq*;tS(FlYTJ7{m^VtA_Hyn5 z8&P$dixm8`q7BulYg4IIc!2aPEF=rddzs_JA8hP)%f|5sz+n{v0*?)ou>JGEx;zxC z5%$(ZQ}$>oF?sn^b%4c*W9?-77+WL;i3XK3Hx`KH%s(!bPs27fv1HgNn;NdF4#@Ti z*e|GIGqoXy!vKZJu)Z@1JI!|###XQ>s=8Su+q0beeb#kB<;|7#hgk7lE-G6LbgrmU zyGzW>3)b-NoyV>X$M;pgWKdikOqBgj5g(bCcAHb0H)B-jJec)y&TIV50Q zsyu9NQ2-if_x6_YyTnw=R?sx>2>dA=fINc@FVy)djd@uZGK}eno1GcbK) z=gW9TdfumZcjl`BiXS$!XoN-LT6UlE=7XMO{23Cua*!fm)Ml(9^5ukKUZSJ`|VP=)mH!en| zhWz2Sxwtt!CJ$>}RT+}gG)go!$vKE7Df%f-;8&0A;x^#0VvJ7XhGj}Rso_3(fwWkw zIlPXh&VHX7&K(Y1_s-}NHEKP4Y=bA&pY;aABTn}@djvxe=yF{xo|B>yorc8vuG2dw zFTpKRA+EWp^d~kQ(mJNrmPJFknp;VPliKVD4K~z8o1y8TtDQYPBRJKy?`fM@kum9( z!U(pNp4Smu!->K66W+q4kQ!_%A@|8o=M64Cj$f{+*abeEvsXD==~&^tICCUsr&(ps zA8&c|K4;J(PgGQ-pVdA{L{iliV3UpGhXToJsARL8PoMtCXt8gS^RO}K`pjtQ+qM%r zuh*L+!^#JRA-TJ1JX>IyD8uV9eTfI@Zdc)Zp?o^Jq=J_fZ7IbaatE+Kc8SY z5IByqOs$abPa_bUN~~o5ylbU79N@<_BQ_hn=_k2=2mV2hB)}9*Kznn4TzQ;C&mASnm)X8SN`a6VT4V#+d8^7uE zemp$9@a?W?*0~MhcK=9Z291;^lO4oNAV6!@6g7&m5K&c6#BXP6fqZ|mubcv|2#QW+ z3NO@p5>y={n5}`vUAv)US@;$;$2qBZmvU00$er@dvZ{}^POFHX-*foHQ2m!juEXk{ zYj9?z}2|pF4{z^E!5kpn$C<0&mVH+=7Q*4vyZ&Z7g;V&0fvr;Bj+{|5t}N$Zw#P z2s*;vHPg!?ESbVFT8~3fO`GTqmCc#ngHE=3Tl(%_D9*(|)w_jLPc_CVGXjC~-nXiD zBJeIi#c=0{;ss>U;9etVrWAa4ylE}V@^9?mW^S!;@E9jZim$@E_k6}^koD@dQi4z0 zNa0!nes9PJW740&5pxtA1runn?Bks{gbdQ0x6aYtp0AdGbcd$d#y9#GO!L;?>tmbA zqN%7BgU|s0biAp&A4H%K8goaMGuwmmR#SZ!-{m6 z>(4HVPh%C^MwY|NAzX+S9I}Wb8n*fD z(W31WL9bsXPeko|xKHwhEoc+e%Wh!+1;W4E5YQOV61{FV`VHAzP3IN(H5x*_DzAM; z3_cL!QYt$GpzH%;emTTvGV}!P-m(1P_7o5?)_p}(a&b(4J{jo2G7YP6Ur;Ha4jIDo z3|lf1Md#<>c_)d|!VtGr`pc4Qp>_-mbmjl+`o^*N@&AZW^8ac>ALsLTa+0>4a=2h; zM!q8oy?17grJgKHz-vu$=DE--;)dlK5~uyCoQH>*lEmFwOW~>xb5!}!^9uCx+bVTG zWKaJOfpQm6?d<}cLr*#>!Dc4wTUZdree}`|ox&+?iAE*cQaV-guqE_BhrZlwWO3wo z{E7xQfY_p4+ep^McinX_GqjGm_h{EDH_rFNDBV#1olyo|$%O7Ip)o&owY~vDTE=+ zQio-gOCxf1DpyymrN3%6gHw7xS|B&%$GOp}-;&jOBapGw{QOSEDH3@I=lF4Ab6q}w zN)?qnhkqSdI`FaSZp1b4$V{~J0et2+~ z9`R91dOcSaF{+$6qGImZY^F)4^#O#m9B(o8b4I6kLjm!tQf{WoTEPgVxzG_Eq#-F6 zJMvTkk~oKW@p|*f?uIr$hRsebaFYt9#)YcjT3XEYDK&iglE|5DQ70g!$jmiEyMHK; z_(M)`*U`*rGRxz%>(~R&5tMG<0Z9YD?(t!-cPziHvAI8Ds?C%#oJ^&0O`9CPy1e&j z#%34FX_ho=H#RHnNjK|KNVXX)M%r)+8qGoa8rKAs=&6015FbfrYP8yE6#a(%wC#6k z#I*6Co^h4RFzP_luoKzC%U|k8;wAN%*DR4vic}H!R|9?o91Dj}3mAZ**No+}JiD9Y z`t-0xH-E*6wV=pc5)xT$_b@}dpzWY#sd2fnQ7_RZz2H(`^)=C7P1C7QzM2}yQz(Xy zzKbOa(&Mu?VWysB2>8exjOXy#3^5zs&X~`lq%``l^KwFMWXCl#*owc01NK zpJx2dQJZYSi0>Jvm$nAhGem#2O!eg^faqKbR+`3ZM2~Sqh62)Xg$;M&ppxS90|!k^ z3W3T=W(*bbEqYxI7>A4~A;cT2gbTeL5Unu(y$s(HzMbnIF$sVDOGE)YcT({>ZqGm^|Av z75-XkW+N(O@XL@%hLxO|E?Sx}+cF$D&8a{85XB>^XYX7vmVA10)6}G7?iMZDv=RG9 z5=cYw7Wk`jW*^wBRf>Sqj>1;gK4C6)84QAtr&BYlBG(^xZ$vtC zKLDRt1`TMAdm^H+T|OitzTl>4F0OXf`C}Q2obB!pdCwlfx--*>2CbUWYqzB9QbHx}hUf!9?>= zHaU}GhyfqMkUbO9c(X1goe#2I>zc_R&c4IQz|!Km)8csmaUYB#xH|Oq(T7=0X>pkAP~nO6dGRg5W%Q0Dkmx8@4VO}2*OZkn-B%YI zg3}jB^%>6 zfcJfBbBrhpLt%`WqTL9YrwYq#Z;}Iv*P#=JS#5*QsnPUR^nnyS;%6Nmb~xjc37gNP zyZ?F}8^or3N-5(bJVM&FQ3XReiX4t?!mNK0;p94tTBap|Mv~QHWe$f9b^R1(6CH!F zNZsG#FA8CS*N$NNAe^>0_HXtz<35L7?X-|H=u4!Nd2ocov6sBf(wf1S&j4LRUj$Zq z59f@^2TxVr53jQPsFWq!?{uZjoTJ@)LST}-`Guslf$CqbHc8X{Xvqk%953a=P20W| z&pvz(*NjJg)^am%^G+G5SU1#j%uZBSHOI4^W>id@9;l=T!sz=4e1W+ZK?qQzHkV*F z`R1AE^h#A%-v@7qO*_wzwsDF@gED49P@7Qm^Cmlo@Q81nx8C#9;+$r`JK;C?y7p!q zZHG2JnZ*|8K@IbpVEl;@Q%-mUDZeVrGU2;!&rD&lBO0X_N~c-5oE_1$6%hN5$8Qcv zHdIezHs2j>$1QDzO3E3uVJ`0hyA(`)64`-w{)qfPU9AE<_5ZNg%(Sn&KV^>OQ(B!FoF z&xnBe${rIy3=EK5@>9;^hJarIt@H{*9-jQ)w!rfHadLD8quKwAkM7{p6`)l<3%72J zC5gYhz#t%(06H%JDobg}Aq&^9S1W?uv0ulooBRm1zf`X&ldJ9nCYL|p0s`hg84iZE z`??KDax){1@obp8)raYRMSow{<zhaH)`ew_o`p_;96|CAZ(?JnFvN~^jX7wf2`0$DqPYnG}kfoiu9 z2av#xRXfXg5_NMxCv~v-8LWg48>c^X(0G!e2q^dx16EjCKmt%8;a+*~1vezWJ(es+ z2^XzWuS%75`*GwKWoP-;x=qh{j4@-PQL|tzwehoAg7E5)z^Y#7&vhpT{p5koJ@HRF z!KmC6OTDH7ke4-5ozEfK#O6;8`B9g%AIs=k4~R57Z+J zEck$huif<=3DT#w?GbZJYn5m?=fIZ(ka6YEgezgZq-v%c!`H4EdEyN>>S08>f^DFh z4zsVx;I$Z&rP7x!-}7k5PlZf%DY9@*p$4_Ws%u$t6rlrY^;DhWzI$*45^uLG#Q z0pa(f=X!V&26ZTL{q5p2-mNKkW1-Y0ck=t~k&21fh^t?ss1w2t>%y$O-=Xof@Z!&& z*GE%=%#A(#aPRqDLVj9YY< zPMBP(=m**<*5z}Bax*t|!uN`1YH)koQNxX%yz~&y_hyag+?OkbCR|*8!=%Ojt7;qV zOY3J5EFO=8*`UVAgsY_ex2T~Y^XD&FSM##`qL+diBa)_ti-jS?mqk9G?#A%QX~l=) zKMVlcxOy!i)<7M|mpq2lZ1!nJI-7nPQ#Vm{V#a!jE8_$H!%|DRhnHCS6%E{Z335q| z;V<2LE+XXDw{re*u0u6sVP#ok-5y|1kN&NF7Qx=2y~4P9bx@9$rnYDz$3n2*^Na=> z>Fh8V*^s6;THxm6S+$kppb5rglh;Lc)^N}kzn+ODVl-is;o}8d6B)yo)B9IEbv7b* zSh*;iB-}vASzN9gTTem{iqES^f_d^1ztMatu$LYl0=RpgG6rACn~6G%nyE?Dn{-e? zIH5a(9=ys=Fp|Xg8_lyN3@PCUF6;@{HN%1(pN2H83MlPlw$ITO4&PLogolS~?=~lw zv#^4n*u#baWu(tNV> zq?D(OR^wRO$%oe$7=v-fO0eKV_hCx4$@N=Pf}eVsi-(KRv{i5hn^De8e$|K$q7B@G znC**v_walEbKU}|Z)jp5cP-(yMNx6+@C`)<2KYOzyJ{xra&=uG`dUhF){lnHGf`~@ z19o*cTPQ^xl04p);*idzV!f^NHll)+2?B(nk@hNHz3YdPKYU(nD5|ZW(~0MA+g#kfh2Z% zhJ2K$_n1Ri5|P$kcVwhFcH)N!bR`uOu(z7~1b+~S0`t^rVs)-3qDQXVwL30aNu_#S z!Oe|xv@bQf5}K2Mii<%p3?g@J^sjATg(@g%_Pi&>s>!n%zP~iC6v+W7q42D!n zZ;70+0qDl95{H**1?9ekx#G39ozr^3*t455NjriG=YPmt1j?DO{TI|wU!e~3p8cs( z(xBINv-{(<+ofZ!peHXdB7|PU$uGJglrgBf}UuGpWVjXXJ?=#}gBp4wX)`3dyfAP4@#Cr*o zq{~-?EKMWD1hSg8Ag)u$GtCG+YJ2yC(e zAtKuBL?>HiNnKf1JjqIPT4!`tr2$Bd;#Kub2{meg^QC?!wOa2#1@;agHz8108|2VU zWx}e0Vx#xVz-o46X!TB`n#*)^?LAx*QE@MKV9EDLa@ZkzSB81?~ zPe1;PM8JlVgYr%jqUlTdHLRKB=7eceFY__0Apk_GC0S$!w?&|I@mGzrdDkfo8ejHh z;NR0$d;w69`qG6y%-N3sZv8>a`e(r^u>q@jO%F9BZK=!Gf>ZPan}mJ$;H_Df%j@?& zzSHktrw!bB{4MK_|E~4_6(aw$sQ!=B0RRGk1O>=aM!^Xn%wNaZ!~$GX&F=DX-g4@4 z&6$5@?PW#Sf83_b1m@=V z)c?=f$WJf58V{R>oV5BQ)$1?LJ^}{%cRuoa1OuYugA6XHGp|GpJ@2eNcjr_&$A8Y- zqnOviN%)>>(%Cx#mH%}r zeoFO*;>p4W%&cJV#!+hF*@Uy+%i)(R)knP=~yBj3X{pYUnW);>7g zcuox|=B8#;>JT<3YnNuM3(mcDAHG!;lltPbm{$+H&(Q%JE6!7bbZ;A0l7A4BT5Gsp z-a<@Yl2$}b{0VkX!La%r>Ldi`k!|x5HH{O?71St!$)KZe5Zsv5stoms$HK&^%Ri-n zoiqR8cnmvZ4$NRR_ec!}ww0{5UmT#W5O5qI>+p(uE-m9m} z(_qfLh8}8Co2SOpvU|}L_C%~3u7g7u)gca)<`;JDCIAck{IX0EnQ@SF$ho{XAa>3F zeUs1L6sr!Qfym?nbiVvFWy|g@^3mno&*Yz8*#Zi`M|y2c2_p@r)__Q zRD9K3!@g5&QyO2wO{dpv@E{MGB;+)(m)I_OF?zZ5Xa;R8TTuSHGxM!$-(1E63MC%f zjy#ue2mAIQ{GPq_Gu%}4!#0pd)3aW-RFc28_!$f;A_gMTj3*2F&))fZ;p{)_vl|d7 z+IlMH)fOh2;(Z)afSThRtA&CFjt{O~*V|<5>5P3XJrzQ*)rM`TbzC!%n-9l^IuAyS zow&O6KJK?~^c#~hn3?fT293#WFVFYd5%F^kzka=wsr5-{U)A!<)PCouwb^Jc2Jw#f zbD4eT-e6tqiu}3qbF$ZE^V?f(%^;vZ^Zfq?Pwv2$^OBh@E!w#~qBWwR{4r_$xmx5h zH|{Gp&X=6?ex=d3L;SXqCi21P6XQZZKvr=;&jidI+sC7j`ERv?z9yAfXMx?PMc3Se zoVqLQ*ZP35;NR!M_?hs5S)+{ZKMo4_eegXF+ATyZl7tM@#zNz>*sHMvwdE!sfWR!+ zBXnj-dD#6T_F}fX@@tc1je_0uYcG^W{>9{>2C{HnflVfx0d0&~88UI=UBLayz0e%0 zb2FrWmnTT3n&4a7Vq>r?lcfDGc(kbgJKvVcpnJcJkWSv2&AhilE7@#y2l33bMC4`; z+nPLikGCxvU&*QJTxoRJ_2#*+`(9n9igl<}!14P5NcFSjzfPP(g{Q!9pHD$iC=MM- z{%*P@y{W06PC}rQGL#HS&LFOaNj92>%$6z!tcF zVh`Zc(+tD^9-9CkA3FXg@&X@60iN}ru;f^WL;|%6|37`yKb@0w6qD8|OrV5rot9p~yYdiKzJW+n6t!^bQC!{30qf?V>iM;fwjd0Azr{H{2<=)b1pfsKX$ zQT^{wJ?$8^H9#hycdXqn_!W=RnHc=<{FhPxA?1(+x;z`XGGWnU^=Gjm$(;2)J#Ec@ zdn|({>+0Khs`~U9=pYc&w@7KMbp<65x8wvdsJm9xr<`-!d)hda?J}?z>jje=9V=3+ zd2#Mzi8={$85f3-i&}lYKu>_=^*?*^-U9ie-TA+<_ugSmX5YUkj-!kqFpMHdRZ%Gl zh*Swp0qMPWHPV}ugc`7dQW6VIdasfgigZvZQIJjuy+|h^p@kNb+?V+cGxI(7dCvXk zJomZxoL~Nfoj3d4Yp?oQYp>n@hH|s#vhUpG^{eDMWccK)PNVL$&zk68djVaP_jUR2@!uA?k zE-!sB&LSXR5XuVqAg zPH7vbgGz&Qt-5NtiU>Et%LR=|ag)~d9=`GReFw=}0GQc-@dCqI>rv1IL_WZasg4*2 zO$*AzPz=7eiR_Ci-w~88sAY~V$J-2EL9Ud87p-JIc#X_x;Wj2Oa#K>PW^Xf0`~FLM z9%GVA30xmI&J)XX=ZLqvUV7vkfUb*-i<#fr0R`CQS6c4f1f(99gB>?04}` zY^mI9Sy#o07nXjO`C^hUkXFWe5nOepV&jG0v1#r9U>d6tb1{K)xeK>1x{oU8=?jL4 ztQS`JMZAhOWrJTgZr+}8A1Y|PGX^no{b5zs;O1adxnC|AxN}*Cg}YFUHq~wtWU@)o zD0QnybRYgu^|Vf}H?XjL!ikANG5a4y3P|D6t$VLeLVvO!R^fh>fo^o z#y^dJyVb}&&n?e7X=yQYjl)@`e|EfI#|3)hP(M$b0?qVO2Rj{X3pu#`#AQoZtheda8}87L zpJ9m@Tdu>y@oQ{joHTOHc(LK6CLNp#?)KHMWPoZid^N8!uk zr8du;y$OBStt8&tY00V-v8H8xMS?s>R8sOkTUt4c#AtGgibC^|3UqrB1B$NNrIXi! z=}Jxt4$B(hbtW03<~AwWVuGO_2VXpQ4886j^3CVM_M_KX8=l$t`G)m+YTo3`%*t?X z^`$7};Bs1_!i~`6r_b(SFWxY@|Lk2A(LH=5|Js#~rgC%B$ktL1)#N%d_XjjlK-ad! zMJTbv5q00jw=fxfoT23X+ou5&Fv9Fjb%x#88-TrRXZ?JGjvYS6u8oJ$2bJ;_5*}9N%ObqFKT?ecuOnCgT~yE~DgM^8>qJ)X3qK zHrPP>;*MQ#soE`$d4K~7m^A7ti;}`UaoS}Oo`q1La@x~-r1Y}VL}}}ExBGf-rj3FR zS*tmGPt#N}&Cni;^p~qb(9A9EGQNQZu7&3Au@+YIs7|yY5?3NEDyW;Cx_=?*r%1el^g{91Lg$81x z8TphN+YW*Etw?7)X4rN4_shp|8IKK7Dx z_>t&bgbf08F%{pH54E@dyZOxZ(Ac*!Bai{DcI9mZGrpy1pGcC!qFO9trS5Ef=9cvmSC!8Fel( z$0KD%pxB8mXye#)Cg$IhkynQ0VFDG_?XeV|fQ?kBg5m$caDnas%6>Vq1MB`ED zT(eqRN-^#A@sMQ?!ndjHV z)Y-Bs)6eETktZhLeq{4ku{b_d0MuRR1S;5b26k+E_CNVxVsKnlg-oMrE6t37<#^Ya`l! zbHY{lxmJVy`>opmZbBbZbN(?lG!`4QC#zl_*IDc^H74Y`9dv=O~WRV z5+o{O(3dlfOo7(+SKVM(L)`lpIYL4cJ~^5`s4KB9g}P55D}GsY80<_E+4r|Iq^^M( zsl({faN=;+-ZrzJmjJ*Jy7&tx7@r-2 z7%E^o;iuD+&tzXc9w=J>bBx)*{Rcv9Yodp5u*1Cmw=0>~iozl;<+z_c@=d;gqgE3w zPV*LNSSo^nUOi&myiyut1C!*q8ntB{0>8V;D^Lw{p6Bm--g|kqlMKu`pK1T$W8YXp z$MEef`V>8k@Q4P3xuq6h7{9Az<*uAw;|6VPyM3ewq)(S~U)qm7Qnt!a!bg?HB9%mX zzU~NV|E{7V#u?+b`$?$Wylz!02L0?MEvdF0o_R1RRuW}XxvxD%{k>??pLO|3mw&yI zSN%!IN|vk&rPzAxM%;La<{pu9Zi&#}v@)Iz91|Ken5y1-M=$ECv)B&ARlvZ54P9ER z-_~gLW5~f-+sfr}uoICVdDj6rp!)r!#*6EH)G=dtI)OSA2a-Njac*M^Hh(So+PK#75u zyOSHFNz>b~0(?&>^j0})1yP*Kah^RJevF;z{YO&~=puzPZ+WSsI68(GA=ngCBcvqQ`MR}Ok7`O9OqUm z83K*SFoZn7nvvt+CX0`aAaNO`ROHlVAdF^CyA_PVy<3_}L3CPe7#JAi`E>)jC^P144g7AV7R zEOTUs_TmBAs*;J0OGQzOgUdloThdpm2pZzB^pP2(jzlz5dZlT#UDD^_^7yxJiOSSb zXhi1QtKtJsG?6~U^r)K{PRgL!rJmEP2Ny8G~;H*yu5&l z=3)|yh1O4nbgl1Z3j?;JGA=Lgw1-6gJ~o@qJ?a06bv_8*Li22H-~P?KnMK42<2=xo ze_SR=x5N{EJv})o>*Fh1h_aaO$a_S9+fIWVSd(UbMU!RVtN(Jh5F6`!fl6-+0RAc~ z>NcK~z-3cLI(kBLDm>CM@k^_9i~Og`At8MXFJi4x@4CT1x?o7g9e$Bcyk0b$f&~t; zt+^_l44?=Y{GTKVHF8I%;ILVO!Z{Mf;*PB1Y{!+hDk-2Q0f%mT|m_Png z`XTxJL}*6o2aFu#-n!O_LpDW#{N;r3)+wn zH|?R7LW=vZ@He%y)_c>G@32=GCZs1AX~50O1o(5;88gfk%=gPGbULQC36I+&ueeiN zm<-?g_jp*4j@b|toKrWy?`6?a=+%q6{s!VkB$^u5f%)_UUXXd!QD)Pa%fuX`>2#{#(l8WQ|HXmM;3R8BKk4BsIG2|p8$AQ-b)A8 zb7qfC-l7LrT-{DAU4!^3;wFnFS4jMtieSuRoZ73xw0D~v3Xjsg&2}YPIV8aAWSQ>z zIvYu)#id7|@LYD_D4df%Y41-eVh?2^4S^P11oRjJ=^zzO`pG!7NOF^ z%v2IW7*?{=S6&{zKagL0dqcO*YFpu=x;j>>oz?2g@G-6hQ#c>%*zr{z?!4Dc1C>FT zDIMVS#&lCA;M^O15)zULG)lX4CdqyFd%TW|Y)xmNvNrsY(y>pLbzQhK>j&4aaZI@6 z)57sv#OtdWXN%u}GCGI~JP$@xuN;J9lo;&nWcA){^O~4s-cpEP4f!#_%~5UUVmBMN ziO%_Hxopyg3Bk*N{2-7{Gjc39QoEFG$s}atT>wrpDzd~w{lini&KTo+!>(nuPMf^B zJF^O%3evhtc1e5k_@TfA@zDufC==na-{j}-%gcgB+t5{I z$Mm~cR&x3@JMJ2cd5QgomW7_p;~mV&rvxlMbEs%3h{B;n$_Nn^BRf!cD@-?cV9tTx ztxGv8=Z`G7HimVFa&~DwfodZg{QBvcb1SbY&3Di3@`A=kGV!C?lLw~7h1sDpXMLjA^$q>n|0y% z)OfwXHSc_Ji(5ctlXiWUN|K)0!#OQ6rA(1}d~%DXYgc*)hM?TunE~G72N7y};#0P@ z*wXWr!-@PCKJer5=-D#VyDD@p=7*_(JTwjWq3FKN9uhsvm)R`j7-67TZN493Q76&Y zEIbA&BXDM&D;p~rVN)r!j>_QvwBL|Xhq)icFW^{IBp&WQiHsSAZdi^zp4q1&@e!dU zeo)4$zFEbPU}a;!AvoAHAKkLvy0>GP7w$T7SKp?xFK{(i+I?2WX|G0t-53)6m+!x~ z1=h3%?j=lNFMN{9uaIfSx%A^}Q#YM{dmf$JFs_?2p3c7uue>=AoF^LYd%AoGuw>i~vC3mSrBXM%R8?A!f9 zvhVz~T=|KwxHhQ*9VU8N&|f~}3&J>EC+#0ht(2(nb8Y11X-?-vNXF|T>jYhW}2f=eE?Sm5w30t5# zvb7sb9@K_kW*V5(1+>=&a2pb%&m^j z9^=s`)18rE`L~ZnFCpa3CP^S*5 zPyQG6?F=8XNtoP0W!r>cw}+-*`>(UHz+agZdS|cvG3nTHj(;${{NmRPn0|yFchP6n zMXxt431Ra3xKDb7^l}Qk>XCX;_?rl9^ee8iDZ3=?8s!2()1>My+yxw% zam(z{I9FLsvSr(_@e-}CLes*)+_UF5h%dEf`2AQwA2nBhssVvFdFya{w(?dPVHIAu z?{O#Z(mDGuN*l!!5pb~ckW8A;O|MfxC|$aJs@bzI{}Tt%kaj&x^oDA07yO z80y2RIA9?^n5E^#ZPL?sHW9oz>tn=*y;mn{cNA6nhxYaY34OLoQP**Oq6R|MVIN-9 z)>ZkukKB1>k;LD(BIEvwR%Z@f&@=~YW_yIyd>~g~N-XzC8e2Ew>>%B+yDumnyf@8q zg%CzsM#-C-0(r)r@Fnj(-1y?~j6!>f3cZAu*071!2I9jl$G>6D-QnBxf)d{Nm%vB) z-9K@Ch-EC_;Wuml2_<{$9}q5Fx|GwS^p^pO1R@OJqX+*x{s)FHU3zzf0CRy3hriAJ z^Q(u$H*ftP_;~ULfB-Wv++hDZP(M6o^6n@k{Synnab$r2;PC&LLF|aGlaijP^P8Qq zV+K~OCO9v_E7#?dJd5VkxKG_3#`282Jh|elq0N6%}-~us`R(prP zF;EBl02DdOZ8*}lJhb^=_<1s*%A*BbyRvTEKsz_t?yf&u<>m(I*Jn23dDczlCqbqD zY|M>5AE&`YW`@jKI(c~J02J<~#mF+csfWDg5}mm^ko76sCMZeh6oazk(Ys*aeEGGw zN;fS3L_h=ZOO^^C2?N1Er{Y#h!Ong%<(JPc-7?ThFVyGB+wLPPLNGcwuuV)pyU=N> zB1w6wEIS>v5(Qnw9xOh(LezsG&US-VHlo8fNW-_wM)!-|I=5GVn;MCYYvZWtZ$VYN z7mX%TW?}Rxnt6Tn|~PSN=6uYsQ%dJ#pg%nco^N1f;fL&d7+76?Y(~- zp9-VL=*acjg~+!DsW$9m4%r^2^EBILcUch`-@Y{RXJCndscQc915)dpQ!)M|;=DYcerpagYM>Y_K@%>>dz<8 zE`2j~t><;Wwh7Dbz06RUYFru;SQ+sDAgdSvixnl}&EEQ(^Sr$)@*|k;Z7e7gJj>yf zAaMykHY%-T+<58IpyY)+7tid7q*wj=d9fX^@2GA=%hW7KNDqaw{j)x0N>F{1RO2ly z0CGs2uqq*lQ3u1uMRr^_#5LT;1~C#w>+cGLKj}V>Bz6gdY=31?HayyCAU=sE(Xwll zV*;dS^L2D%;&(0s1R(Xm($kGgwpM33WSuhk>%^ake7Z)}hEsS}-eRI;JT{p4LLHRj zgQjrB!sa}Y828U8m#rH;*$tKcHnNHa$7K~CMF^6Vk!zP%zlRmSeVf$LBQf{mcZN&> zAiP>{F<+r=N~c3;FP3YnH3v@KEqNEg206uK(n$iE7g9(rBy`JO;&DN2(9-S3;21Uu z@oOSC?tHhjEA)%+NG#WFsSChuHFtLZe(Z72??>*f2nL?xaqaA_>A;B(TS{oummGCA z*;uolU{13&WD;d9*l|&Al`~1Wa!R1ienv&^+{9Ft@*8Hz!VBDaA%?Zu4N0J}ep=DF zFQxGtH_bc0D!BJ{2lPCiIiLjlYCXb&F<*?`H!{$N_8QUb;0J+1@r-|rX)R0RE?P*woug<_z>R_mDoldlArSP!h ztigP}W0YDOTb1;W(P86*xtc_je2z5gM6NU`>m;2EdCQV%9AWKVpS^cKoV!w`XH$}4 zTIz2As~_D`re$3Xcby;n!bGfA7aFI_lz+qAGcL$e-GTcIZC&(@PJ?P8u2DgA_@*l% zz=P9MS-TnGJiiwu0zUU1Vs)?qED?7wTcW6%3_E%$?BX%-``k4fK2D*{JpZHvlMPj%@gh}t zb-%QeKCqR8p<1n^#LMz@`c?;6vzAk?Qa=1+c;hOS?G1_2RP(VWy3^|1lJuWx)GPoq z{;w?R7qF%qaLkTSw$4~Fp|bt)XOYp{)Dj-Iy%1}glBAJpGG&SD3bD)Ll@2*<=Q5eA zQKNd(vE+u3Ih&MY@pK>GL{E6}+x|=QD#NAg#<^RX?%E^F@yP3;PfQ$l#NIk6_14Xx zxodu=^=~jI(3hSjB3_!)AtFS&`}aTX8|P6N`wYXHurl&0e+B1}Y%wkp?qL?>3nZG` z?-rX@8$nJ9WU=eQMan!qg(2hE0+DBDxt1El{GKnQ1r^js=$n$(9zmAQAwLhJbw(kE z+a|?J3#8~ct-(k}Cl`cS9cuAy;#)$gjS4vL>(dr&74A3v@kiG0oeCrV%#(k9ailQU z{xs0LVMmGC|8q_9{}R7s9vqeX@%5knU(J8}fA@g={r{uyTSDYm3U)>!s?V^$N%$Ad za@_3}yjeVljH(z{Es#52K>i27)c=Hl4n6Q9?wzpG96Xj9!42}Pt9N_dRBCTYKmXBm z#0dzoJ{p>aQcCYXyIMl`_lcYGwG2)m>Z+T;j+F4Bk6z+SM}RfAUz}L!|A=%CGrbQW z>jkw+DN8YTk40xHsGXwelQkw(uh@Sp4FGZ-D}Vg$#NJjni@U}HYVsVhUbT_I%CSV4 zQJ!lkBs*`8XF$~7g#;MAyBcR+;h)+ii>Yt2u)#ZiZFg4PK ztIcJFB#gi;f%Q_!r+*wr03fT`Ko>Qb>e@)T)j40c zD;Q~3$5VXpCD0!y^A(s90w2Y450#)2eYI2S?-e`%NgKy09Mfs?^q(X(6hK|GxZ`Fk z7oJSuXBU$%Dr>P)HJ0E7yiuP@JdRp2Mh z3SBBW`5*5lPwKSdT^{YNE_NS`>r0VFN!=uf*qj?Ra-`kRz%X@S;CGoQt8WP1V^V>q z%_n@gMGg=cqT^Ub;Y#;xhsyjFdb=OQW3BO*q$#uTNg@+CNQb|aZT91^2p*?=`70+HOcm4;{rtJ*hZ$>%Yk2B8=ZwLoqun1Pq5{Z>4*RPH)5E==b!alf` z4%XZpWxL5xxF>gZ_73u|Nht*}uu&Ut|oemYV8(|K>EG4kN zT(ZO5Cy?axBovMKzI@9m(Jyg|RzlT>$?!F4BKY0|M)hvGL9}C~aq1`JfENAaP&@06 z2UL2HGY(u;_tOI^e=~r*dw&%a^lf0WmI+Bx@CSd|whNhSm~y(pC=H#IP(20o#Y1^W z0sZb&2YNOKtX>xFAH~i8MNB^fH;VwaD)&A!dhvi3#D-DSy+hXi`}TN7s7HPK79sC0 z3-?mzXxx@p%=Sw1N1LRu24{@zVS)tU0i;&82gtZ1SI#Hb3U;2)F(C0&l zil*E6Hi*6{5TmQUb@AW)^S^cO*P^56yK%;${xjeCH!DTBu|^RleKr%Q*+cDBgAw7$ zIp7n@-hgZ^9MLATbNIe)^9QeH)L_m;pxN{+^wOpB)qn-0SyH(xC^ z*xvVc)+qimd@j&Auaw&X#@A@z#!A`|y0m{}r;_Q^;Tfaw1@J_JpC-a#RNzFQXYS8PxQy-ZXoBiv|GU~ z>zS9rVp9xR5*AZTT(~6#$VU+DuEr{qJFr|8DY?J=g3Qe^q7la}J&4mkI9t1s!qm|Y z=Z4r6X-h3i4tf;Jz8R>RZR+xmwbqm_)?}1%>mP>Mmae@T4U*~!w$147W;SmrP?BCX zgXZ^?Du(U@mdV^vrvAs!NLmN5b1=_;E%wET0@|c)x8gYbX zIcb%4lI(6{=qSzH_j#E)K22I92JX_NT#?`kOBH6yqrid7p(nQ2-F$ueBFzjioyv|94hWgg4Bj-SQZ}?59dFRRJ zojodc_tk{lT0hG}HrPyXGqwSpWNyBBuqG~u#UKTBzGA_Hh#VuDmiil%4ZfBfknz1+ zewQuQSd)J5#gALFw3C^KOM%^rZ$hJ*Xa#R_WP@3L2MQ~!6N;w0R|h}dKZEriRCk08 z7=0-hrC2aHuXTnH5g?{fTe_-=0z>B^rkN5-U0qIr#8%rwW z1M!B`MMT!^F0O>Yg&DtKRyai88t`n?Jc@23oTOQG6Qt> zK6&U6M^znZiuQ$~X(PVj1#8Zb#+tuwUxcnr^~9!iAiO z;M%}I4?nxdDblW45_oCs&TZy^t19-Gb8Nz4_YjlwNFRrEQZ?);{$`@pl}xC8yUF&fhy6}H zWubEZX=y3PgqYTZbJ|*3iC2C0{HcpR?eNZP93wJX_JsnWi!od5?8ft*R=@;Z|6-L# ziPY+It@YufI))nXnj9-%k*p>$OJ57L9Ly_3|Yk>PxGCFGd+E-)RUc zGat|tQIxi`|N4jYqlHX`RN4ZJFoLV{-GB6A>o?;lqGa^@Cc~XosFDITs+|pPsjB)> z;aBw{&8^>A3?@rKP#mVSe_rf+G>M~l$;q-d{>5QzdHtwP?f^*S99F{hxg6nK|Gz7R zGlrm4d!+nf4e2jme)vzP{br!Yy^6>ZCD&@d#{Vc~JIv8wYZh%e>`}x;5Oi(=H`$hp zZ9&Dk0_gn$3xD#&y4_w9PWY3i3yf}m2K#~1m=X}%S5(EhH8>MYIPj0* z=%)!`QB$fiJoM}IndarNf)<$VHd&0DWAZi`FJrRm9KNDB|2pH=ly^k@xVaesbtvMNUCnkYEN@hfvdhnR`nEu$d(~B4*OT$6>i>9rk2uEcEpD2m z5XVuq7o6WSk%2ZnC1z7PFs=Tgs-)*|g^>STVHm;nOulhNl5ntH9uSEelEHGqZ5gG{ zz+)>h3b#{2TObAM1)o?vkrLc2y^#S?8?12S78^45rgy=DcyjS`q}Y~5S>j3+@kwZy za7~5%z}2oHLR*Xs+EA0am>LN*lq+E3b+b7$q3$TXlzmY*9M>_=9Nz%c58p{eiUUr` zBKTKYxk;&|1TOXpB^~o7zcDwWKdEZ8h~9mAq){bmgNe9=`{ul#KfQAMjtej zK}{Hyg}{f3+&U|10q!rPtUNn9y5BWUH(ad(13Hwh{2zeBBJ6yOpgqsNta)6Wq|Lhp zqwaNWcZd2X3KBKh2Wii^M;n^lF&-P1b%i)}_qUo^7+g9Z4)yG|$*sZ*HPy z$g<&!P#*NGA{C-=GhuZvN!Hcwr*PX2t9fTT**w=m_k>vU{`M81aliW3niFsX z2k;4KKEM5k=hNcu`xAlt1;(Z@(^4c&>RutGO|1gy-T#r_%WN~XZokg_`5)C2zHH~W zT^6eX?mb?Ku(foQUNhbOp%!v)$9J5gvpyGc{J?B;Ec0yDN=bQxk3{Eo5Gs;p{u{_n*=M(3^`aKIVop z$Isz)x_#r^?F}6vT7v=@iItzV3DKT_3$YeIM+ zr&7vY6D*JpA86^^rFUD7jqX;e;^g!#A8h!EIo99RUJobYmbt=(8woNX8_SR`)-fuU z$&a6I>BT5oOeie7PK6{4@rMeh&c280^JHB#<#f&1^iP$R*CMB5#y(ET<|jsYBLOsQ zN$@C*^*Wi;~X+hsmOd&=lg6N2U+?QcbF4GCLNTa z(yn6~Q*!QkHeGeXVJqGfzK)k3n0C;tSFn4GSfr_=Y_>;5 z(z?qX2ca(w%V7rsXW=3}898FJAuELzU0azxJWHw0=^Sahsof9bGjE%m0}LbV;lFfL zpNq{GbFY+UEB}IRQ+x=^pdW$V35Y~;`kk(;tJC}c&4E2qW6xMtCdyyA5BZcqsfqYO zJZ^C9Fld{5_1{T<;K;=Pn*|RAVSgTN)ceO!AD&qI*VY1m>iq*y{ckjAW|UG05@K`h zL|*H-PHticmX;~_mqjYrkL7Fgd#EzBO6ehgr4-N;EIH%P{*lK@9a?6k^8*c&xt<*5 zc&34@WRnG%`HkcB+12mV=Y=Z62#-5E7a-iVrpxBao@j5MjG{^7z74kDEGXVHOhD(Q z``_`9-E1}U$*vN_d5{iJ?nt@A0$@=W#(vU7 zlrqtnB6;0mB1FApv1ndlcPW1d^qm~OXE`K#QCLT+$8zsp$of)Vq9J$5G=KIAudHgz zIRSI`wy0|`q*rdsVD{H_NUERNo^ph#g9v%ek0mb7Bq>XIMI{vFJqBx>r65)(9IGGL zTMt?C9Xy-cvB>5|U&EJ8@yIbZNAXL02L-0=fXN9Wh{-0~k0d zbv@QNlzxw_#Z@@-3d|wEw;cX;SUDi)lM?cva_d=G>#*_0*6RhJ!EKBolbLJJAlYkx zgS%F>urrg8=oi^+66hzloTv?_1m2Q<;q{<@FL20s3lIo0qjljoGQbL!u zZsHT6;(BIgC@u&LlZo9y@cDGz>zD*tPZVVIjy2ckmrsorJz=LxdrxRj1idY5Xz*03 z3$}^ii@4zE2(Y@*QCZODcMSaJS+5LvGj?_*tjGmBrJoUfPg@Xkw@!WDn_H!YcX5vx zKABL%id=Spy<-*bA~&D_^l#I#x<#7yx(n|aw}BQk8R&^u!d~kdSPnk5GA)p^3t=<2 zdyCX>QJcp#9=vicz8R=*CbT#kH=2q+z=Gh}o`f|#f9^-E#q~gh$=tUqqtws-_q#el zw$hhx+Mr`3zTpaKiIx!_7IQ{rwWPhMs<}Q3GD2(U&C?A~Q}4(g>}ZwA z5787GXv^8CZs_gHyKNKDg$oV!R}P+((|Gc#KEaqa6HRp2xFgV2XEYOV8Y5Q5b<3EC z=;B6d9Tq)#!cm9(5N`!Z58vo|x0zF#BA{Z4nF?hw$u{VMn`cg$Iz&ufF{JfRe>|^X zv~KpI1l|>H><^xm4yXGIxY+FiE}BtFsoDUeD}koh1JkX5+_MoNrv-NJSpVVB9EAj{ zG|O5uLnqAIN3JkYi=;r;WBD{hY@|L`-(A&7LKy;0&#aD4bcO*5ra3fCCnXC#u;{)r zxW2+M$IaI6-udp2o!@^p`KUUw=sT3u`oEKY(5Kz=$Ve7iNxEh8Zc*H1V?2K}WPtL% z0s&NRs7nUY&jNUw#nR7G(U7U(btD+5O!;Lt{)V#ltmd{UC;bA1_oMMnw9@fAE)lr* z0(i)d65Szjfp;+ZrQ2xg>>l?B;&Sm=k$uSizOF?{!|{PZuW@Ckz1^D%-9Ids*1R&< z+w-N>#{LkT7vL`acKe--$ACt@Wv%|%<%6g4ZOBH)`&z;IQvMI(KVGaNylS$Kkl+EX zs?02@k)|KVz%A=&cvTHl19d^WX7%~i7teuvlz%0kSF$XH7k;0a-85NO0?LaEZ$(%J z?K_0ocK$q5x#m97*MuK-m>5XRyIi(k|JmcW&7v(x>Gb^S6xL;0ppLqafa@m>bq&Us z0r&SB#O{#pYiMxN_t3dZLq5@PyF95TR^Sx6)!%9aAhn$v+My|^c&oX+^j$>FRep$k z=2H5=*7OC)QiVwpuLFsGI|&`s4)6cT*`ck3nr{)DaCv%~dQI&Li3c@qOkf!y_eBFwlU(@P1dsX zb=LQ*s|TU>ol&QU#y1mG48C_N=G!afwF!bbGQw;|CP~xIxG&$|a5q)_#G9PO53PKO zw({#zGHPg`MxBID6ieeU+x%xIXEi!xSrCRpPjr%=#;=&)j`ZfWMYT?%b;dUyR2je{vrX{EWM3$*VY2-?q6=)=*3{eA^?=ldt?8lR(=S z&)1ofwU{W#+~5YCv*ZTpcvWwWEq&pxm%=WNoSfJ3N4Vcn&yiIUc{96fNB%APb#sOH zspjPnvwtF|K>H?oYL%>MjC zqZNhKCrQ}$UFq_R$>g0q7!wLF6D3jV5}XD4hl?KaZ3H()b7nS2`A{-r z`J&zpV)rB`(^!55?)c<6LgN8^HyQ$-XE?+}pT7u&{QT*}=O}5z=h*BJiMRKIYryk*&lzczxolQS)x?Dr!rM2|`pN|XC{F8-eA*_TmzYr5Gm38y zo&my1Xu?SpEQfq;a-O>oH?rQlcxJmgUa`L}+c(7@t(7maK_-m&JIRM8T4z~0C81lX zi5o*s*IgFdi%4~59Jexe5zGh^#5b!G^)be(_deD=Z}-@8Toy%c1@TMp6NZMHfHZaF zjz_VXMM~)B&4*JI!ucJi>oDmZleSJPqHA!Nqgai}9RHO`h-XuGM-Uy?wdZoL=OJ*% z)C{HSmS_kDkVX<8$Wv2B`C&OUlR zcsk=|+}Jnp3aYw3HIo?3uraa(T;xs&+A~|+CiL}Dn!4SEEk@zm(fdxQ{xDt}?G*e8 z9T)r(Uf|Y-$s%WK>COr$1J@|rH8gYnxOC8jeK6^`a${Y|2}!le{>7N6_(s*`^~!(CWtD7TTXU zNrqX?K$qpAt#N8-*B_>mPT)65lMD=ee}IG|hmXD5YeVNgRF4f?uS%9*?rva@sdkJL zBvDb*XDu1{lKkxRPhMWEd4T|CFw9sg) zC~+{x5V!@Y@_yhCewxm#(-poBfq0)}S%vn7MyYP;@t4;am1A9^nz&1OeDvN#KJSq)pm#gc@L5$pR6v`~H+!>i1mmT)!IM zB%B9XV?dmCR4}V^oh;Tj4MG5&a z6;wXYK)MUj_>sO_FTAXBVgGda)vIM*u=95dl84^}Zkl*HN($aplDyo^`YeYuT{v6( zDu10O$6}9COG3{&kIV1-DuM!n6ssOt)*~_+A@2IItC{r4x5vtyAH~}2Z3^X#BX4mD z=tU@>R8+pqe_l!^(X)Q|!0oC&WBrIwN97)kmixeAwlHyyEhMl}Kms9fhAP0v1v{z8 zH|reETQ3eFCvC5OgY9U<8w4rn@bjHhbF|~-F@TsUU-p%h>-TOZ z;CKY9SkM4#U2eEXncB1bCP?`d2IoBm-@BCyRjPR{K_!bm) zL#RED_Z)14a{v3gGvJPmRo{XMXX16}L+>`M8Xi0|$ob&g^Z{+$M@vy*B|pfkjaV~` z^*GKT*2)@v5vuxtr~owA`HXt`Upp8J)G?}d{58}Hcr7MZp+=|C$NvxnzV-Sh`q_h* ze6P8Hy28itulUZY5gxa`1bW`&=@GKH9xz_M3qhzy^?p-RL;6f071%Cb|dze3u}C5BM(PnCBN%$``DwO z5rLIw5!mRSkdi7z@UA&Mux|EhxA}jH94dm!v&E;A~3u@`)47h zR`j1mqL(%P#5~?!`tQdW{%<5ved@iq_=0V_)u$a57r{jl0(0-Y@XY+S%>pn``tY$+ z9jxvci79p&NUdv4_(XC$?BbYEISe?6Vj=`peOgX$<5bX_c}P+Jef{Mf4S~>-czaGA?z;npZ8LI#DA*y3L+(VPKEyh+{)1DDQ zeqIdn71mkcjYavd3Q*|shg+@m1b9WDL(`xIrd3W>9d(PtmmVAEYdEr<9QyI<3Uel| zYmhK}ZYRR;qB|&vfKy>XlLyk#OF?Rk2pK~)`-4@xRn*n(Lmt}t6%eSRqG8|FpwpSP zDo~lsH;?l7trR68p$E%;j6JIl`r&-FR@FI|96+NQgx!R=L(w(708k-Zo8anN^eT{U z#4km&5Qwn0U^hj`2u$NLqv^?25VSAU4(V;mLo(FE{k&1C$9&smu)PiN2E99&#(86 zR``n1DYcz0VB%YSh^J+zn&XGb3ne z8@sC??9mJ3>5GucG`kPB+inN=4`Px_Z03?qmimK^#qjSwiJF;mavIGq7{QzGb0zQ5 z-pEh(RzM2Z`CKMPgi!gfyxL z>yLiq=A`(VJ~>%Y`e485zyp^Vr!56-6~d)t_?JJ>9S;?f#%9KRuL#8K*A0y|QU9j@ z5)rJa78bE}Z!5D-Y6u%}PH5#Pg7i=_Z`GRBtD8^*Rd7ga60>mhqS^#jH)Iwf*FQvy zj97Ej>aIImJN=Ni!0lR1tRG*Wt{2L=N>7uRoZ?0CdNT1sToCP9)vTB|7Pl`3BF$rq zntzva_KrT`lEM!%-ZLMkFM3XZYz-(Tk+13FTR!*qc#hk$WG^3GM znpJoaVoL0b%#OMD4s}$>FMx74^CWP|u2<9q0vwT+d5lDiVMEBJclG+FeG`cFnL5vw zxEdQ=Fa9YlyLbC$;(q%xO8=QMHX_M8xDde$cj5GUbvvdcXRfN}!8p-wFjMId>LNef zCpB~650KyD&#L0gHW+j1gKs&WvYI5h=2s>AnNu!f@^50QbUVQz{$WK*<#HinD%dtZ z;#KI}&zJS=sw9*0LC{&$PFD`4+g_Zc*`V zMWrAbGVv;VIdmOas{k#>GA+xOS~+tCxH;*SpeTKn-3E=R-)(4 z;*L$Kk2}0IVrxZkPepq6lxN$EW4}I>*i;eCQ};XKv{k7oJj8$fPI50RsI^+Q_iO4M z>71OMi2u1?M(<{aPOPt~{d4+~PEg8eJK@ys(~s|`0gHz}$>&32b3cFF zdGu)Cx#_{hk1}{>?=!4=dcF3>ug`^RyKh{au)4D}dtX=F<G=AJbMNj5s%D>8|5dpC|89iblO%WN+%?gARpn;x*8FuISW3N@>FB+wD%*PA z_--G(ke<{n`>Z|(o&{CPW}E{xpBAVC+fI&2?Jsdj?U&qdmi4OWEnkW6`FY4SW7WfE zTf@4adADh=-*VryoA_25*ebE2Q>)}ZFC!n{gEtq% Rfps{ro#g80vd$@?2>`m$WMKdR literal 33696 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 usage metrics to gain visibility: monitor incoming traffic and blocked threats for better security insights.

If this option is enabled, a cron job will push usage metrics to the Local API every 15 minutes.

For more information about usage metrics, please refer to the documentation.

+
+

'.displayBouncerMetricsInAdminPage().'

+
'. ($isUsageMetricsEnabled ? '

' : - '

')); + '

+ +')); /********************* diff --git a/inc/Admin/init.php b/inc/Admin/init.php index 37efe04..8b13f16 100644 --- a/inc/Admin/init.php +++ b/inc/Admin/init.php @@ -1,5 +1,6 @@ Important: Until you fix this problem, the website will not be protected against attacks.'; - } function clearBouncerCacheInAdminPage() { @@ -130,6 +121,121 @@ function pushBouncerMetricsInAdminPage() } } + function displayBouncerMetricsInAdminPage() + { + try { + $configs = getDatabaseConfigs(); + $bouncer = new Bouncer($configs); + $metrics = $bouncer->getRemediationEngine()->getOriginsCount(); + + if (empty($metrics)) { + return '

No usage metrics available.

'; + } + + $cacheItem = $bouncer->getRemediationEngine()->getCacheStorage()->getItem(AbstractCache::CONFIG); + $cacheConfig = $cacheItem->isHit() ? $cacheItem->get() : []; + $lastSent = $cacheConfig[AbstractCache::LAST_METRICS_SENT] ?? null; + + $lastSentDate = $lastSent + ? (new DateTime("@$lastSent"))->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s') . ' UTC' + : 'Not available'; + + $totalCounts = []; + + $html = '

Current metrics

'; + + // Sort origins alphabetically by key + ksort($metrics); + + $html .= ' + + + + + + +'; + + foreach ($metrics as $origin => $remediations) { + // Always add to totalCounts + foreach ($remediations as $type => $count) { + $totalCounts[$type] = ($totalCounts[$type] ?? 0) + $count; + } + + if ($origin === AbstractCache::CLEAN) { + continue; // Don't display "clean" origin + } + + // Sort remediations by remediation type + ksort($remediations); + + $html .= ' + + + '; + } + + // Sort total counts + ksort($totalCounts); + + // Total row + $html .= ' + + + '; + + // Last Push row + $html .= ' + + '; + + $html .= '
OriginRemediation
' . esc_html($origin) . ' +
    '; + + foreach ($remediations as $type => $count) { + $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; + } + + $html .= '
+
Total +
    '; + + + foreach ($totalCounts as $type => $count) { + if ($type === Constants::REMEDIATION_BYPASS) { + continue; // Display "bypass" type after other types + } + $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; + } + if (isset($totalCounts[Constants::REMEDIATION_BYPASS])) { + $html .= '
  • ' . esc_html(Constants::REMEDIATION_BYPASS) . ': ' . intval($totalCounts[Constants::REMEDIATION_BYPASS]) . '
  • '; + } + + $html .= '
+
+ Last Push: ' . esc_html($lastSentDate) . ' +
'; + + return $html; + } catch (Exception $e) { + if (isset($bouncer) && $bouncer->getLogger()) { + $bouncer->getLogger()->error('', [ + 'type' => 'WP_EXCEPTION_WHILE_DISPLAYING_USAGE_METRICS', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + + AdminNotice::displayError('Technical error while displaying usage metrics: ' . esc_html($e->getMessage())); + return ''; + } + } + + + + function pruneBouncerCacheInAdminPage() { try { From 338a025795634b6342c2b3b32cb81a74d51b460e Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 25 Apr 2025 16:10:16 +0900 Subject: [PATCH 03/10] test(e2e): Add test for metrics report UI --- docs/DEVELOPER.md | 12 --------- inc/Admin/init.php | 26 ++++++++++++++------ tests/e2e-ddev/__tests__/3-live-mode-more.js | 15 +++++++++++ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index 429ee18..3f91039 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -221,18 +221,6 @@ 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 diff --git a/inc/Admin/init.php b/inc/Admin/init.php index 8b13f16..0ea543e 100644 --- a/inc/Admin/init.php +++ b/inc/Admin/init.php @@ -141,6 +141,8 @@ function displayBouncerMetricsInAdminPage() : 'Not available'; $totalCounts = []; + $totalCountsByOrigin = []; + $totalRemediations = 0; $html = '

Current metrics

'; @@ -160,10 +162,12 @@ function displayBouncerMetricsInAdminPage() // Always add to totalCounts foreach ($remediations as $type => $count) { $totalCounts[$type] = ($totalCounts[$type] ?? 0) + $count; + $totalCountsByOrigin[$origin] = ($totalCountsByOrigin[$origin] ?? 0) + $count; + $totalRemediations += $count; } - if ($origin === AbstractCache::CLEAN) { - continue; // Don't display "clean" origin + if ($origin === AbstractCache::CLEAN || $totalCountsByOrigin[$origin] <= 0) { + continue; // Don't display "clean" origin or origin with 0 remediations } // Sort remediations by remediation type @@ -175,7 +179,7 @@ function displayBouncerMetricsInAdminPage()
    '; foreach ($remediations as $type => $count) { - $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; + $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; } $html .= '
@@ -183,6 +187,13 @@ function displayBouncerMetricsInAdminPage() '; } + if ($totalRemediations === 0) { + $html .= ' + No new metrics since the last push + '; + + } + // Sort total counts ksort($totalCounts); @@ -194,13 +205,14 @@ function displayBouncerMetricsInAdminPage() foreach ($totalCounts as $type => $count) { - if ($type === Constants::REMEDIATION_BYPASS) { + if ($type === Constants::REMEDIATION_BYPASS || $count <= 0) { continue; // Display "bypass" type after other types } - $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; + $html .= '
  • ' . esc_html($type) . ': ' . intval($count) . '
  • '; } - if (isset($totalCounts[Constants::REMEDIATION_BYPASS])) { - $html .= '
  • ' . esc_html(Constants::REMEDIATION_BYPASS) . ': ' . intval($totalCounts[Constants::REMEDIATION_BYPASS]) . '
  • '; + if (!empty($totalCounts[Constants::REMEDIATION_BYPASS])) { + $html .= '
  • ' . esc_html(Constants::REMEDIATION_BYPASS) . ': ' . intval + ($totalCounts[Constants::REMEDIATION_BYPASS]) . '
  • '; } $html .= ' diff --git a/tests/e2e-ddev/__tests__/3-live-mode-more.js b/tests/e2e-ddev/__tests__/3-live-mode-more.js index a05425b..dcdc8b7 100644 --- a/tests/e2e-ddev/__tests__/3-live-mode-more.js +++ b/tests/e2e-ddev/__tests__/3-live-mode-more.js @@ -118,6 +118,20 @@ describe(`Run in Live mode`, () => { // metrics: cscli/captcha = 2 | cscli/ban = 1 | clean/bypass = 2 }); + it("Should display metrics report in UI", async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + + await expect(page).toHaveText("#metrics-cscli-captcha", "captcha: 2"); + await expect(page).toHaveText("#metrics-cscli-ban", "ban: 1"); + await expect(page).toHaveText("#metrics-total-ban", "ban: 1"); + await expect(page).toHaveText( + "#metrics-total-captcha", + "captcha: 2", + ); + await expect(page).toHaveText("#metrics-total-bypass", "bypass: 2"); + }); + it("Should push usage metrics", async () => { await deleteFileContent(DEBUG_LOG_PATH); let logContent = await getFileContent(DEBUG_LOG_PATH); @@ -148,6 +162,7 @@ describe(`Run in Live mode`, () => { "#wpbody-content > div.wrap > div.notice.notice-success", "CrowdSec usage metrics have just been pushed.", ); + await expect(page).toHaveText("#metrics-no-new", "No new metrics since the last push"); // Disable usage metrics for future tests await goToAdmin(); From 448f199ce321e945d953917a2dc012df100e2535 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 25 Apr 2025 16:29:43 +0900 Subject: [PATCH 04/10] ci(multisite): Update test for multisite --- tests/e2e-ddev/__tests__/3-live-mode-more.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/e2e-ddev/__tests__/3-live-mode-more.js b/tests/e2e-ddev/__tests__/3-live-mode-more.js index dcdc8b7..d853593 100644 --- a/tests/e2e-ddev/__tests__/3-live-mode-more.js +++ b/tests/e2e-ddev/__tests__/3-live-mode-more.js @@ -129,7 +129,11 @@ describe(`Run in Live mode`, () => { "#metrics-total-captcha", "captcha: 2", ); - await expect(page).toHaveText("#metrics-total-bypass", "bypass: 2"); + // In multisite, it's 4, not sure why...(perhaps some admin pages is considered as "non admin" and bounced) + await expect(page).toMatchText( + "#metrics-total-bypass", + /bypass: 2|4/, + ); }); it("Should push usage metrics", async () => { From 01ba37932408e6ca14d1d1124870babf544025cf Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Wed, 30 Apr 2025 13:45:20 +0900 Subject: [PATCH 05/10] feat(baas): Handle baas uri: show reset metrics button, unallow push metrics and appsec --- composer.json | 2 +- composer.lock | 42 +-- .../screenshots/config-usage-metrics.png | Bin 58839 -> 63147 bytes inc/Admin/advanced-settings.php | 45 ++-- inc/Admin/init.php | 108 +++++++- inc/Admin/settings.php | 10 +- inc/templates/advanced-settings.php | 5 + vendor/autoload.php | 5 +- vendor/composer/InstalledVersions.php | 20 +- vendor/composer/ca-bundle/res/cacert.pem | 97 ++++--- vendor/composer/installed.json | 46 ++-- vendor/composer/installed.php | 22 +- .../crowdsec/bouncer/src/AbstractBouncer.php | 24 ++ vendor/crowdsec/bouncer/src/Constants.php | 4 +- .../crowdsec/remediation-engine/CHANGELOG.md | 10 + .../crowdsec/remediation-engine/composer.json | 4 +- .../remediation-engine/docs/USER_GUIDE.md | 1 + .../src/AbstractRemediation.php | 13 +- .../src/CacheStorage/AbstractCache.php | 10 +- .../src/CapiRemediation.php | 255 ++++++++++++++---- .../remediation-engine/src/Constants.php | 11 +- .../remediation-engine/src/Decision.php | 7 + .../remediation-engine/src/Geolocation.php | 12 +- .../remediation-engine/tests/Constants.php | 5 + .../remediation-engine/tests/MockedData.php | 10 + .../tests/Unit/AppSecLapiRemediationTest.php | 2 +- .../tests/Unit/CapiRemediationTest.php | 171 +++++++++++- .../tests/Unit/LapiRemediationTest.php | 11 +- .../tests/scripts/clear-cache-capi.php | 3 +- .../tests/scripts/get-remediation-capi.php | 10 +- .../tests/scripts/get-remediation-lapi.php | 5 +- .../tests/scripts/refresh-decisions-capi.php | 3 +- 32 files changed, 750 insertions(+), 223 deletions(-) diff --git a/composer.json b/composer.json index beb49b6..b8ffb89 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ } }, "require": { - "crowdsec/bouncer": "^4.2.0", + "crowdsec/bouncer": "^4.3.0", "symfony/cache": "5.4.40", "symfony/polyfill-mbstring": "^1.31.0", "symfony/service-contracts": "^2.5.3", diff --git a/composer.lock b/composer.lock index 0ac605e..811a951 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": "cc6337aca0fe554e1b7b06d51568db25", + "content-hash": "f66775f03f91adfc9c2fced2690c3622", "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.5", + "version": "1.5.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6" + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/08c50d5ec4c6ced7d0271d2862dec8c1033283e6", - "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f65c239c970e7f072f067ab78646e9f0b2935175", + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175", "shasum": "" }, "require": { @@ -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.5" + "source": "https://github.com/composer/ca-bundle/tree/1.5.6" }, "funding": [ { @@ -80,20 +80,20 @@ "type": "tidelift" } ], - "time": "2025-01-08T16:17:16+00:00" + "time": "2025-03-06T14:30:56+00:00" }, { "name": "crowdsec/bouncer", - "version": "v4.2.0", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-cs-bouncer.git", - "reference": "9d5d1679edae3ca874d75153d92e3ca374098dd6" + "reference": "bc1fe9531619506e6ef1c2fd17360c3b30cf77aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/9d5d1679edae3ca874d75153d92e3ca374098dd6", - "reference": "9d5d1679edae3ca874d75153d92e3ca374098dd6", + "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/bc1fe9531619506e6ef1c2fd17360c3b30cf77aa", + "reference": "bc1fe9531619506e6ef1c2fd17360c3b30cf77aa", "shasum": "" }, "require": { @@ -157,9 +157,9 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-cs-bouncer/issues", - "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v4.2.0" + "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v4.3.0" }, - "time": "2025-01-31T04:27:22+00:00" + "time": "2025-04-30T01:44:30+00:00" }, { "name": "crowdsec/capi-client", @@ -363,22 +363,22 @@ }, { "name": "crowdsec/remediation-engine", - "version": "v4.2.0", + "version": "v4.3.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-remediation-engine.git", - "reference": "779724acdb8fb2b703d3faf45f563b17caf9fcf2" + "reference": "27abce725e25c4adefaf977614f69fe4fb52aa6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/779724acdb8fb2b703d3faf45f563b17caf9fcf2", - "reference": "779724acdb8fb2b703d3faf45f563b17caf9fcf2", + "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/27abce725e25c4adefaf977614f69fe4fb52aa6a", + "reference": "27abce725e25c4adefaf977614f69fe4fb52aa6a", "shasum": "" }, "require": { - "crowdsec/capi-client": "^3.3.0", + "crowdsec/capi-client": "^3.4.0", "crowdsec/common": "^3.0.0", - "crowdsec/lapi-client": "^3.5.0", + "crowdsec/lapi-client": "^3.6.0", "ext-json": "*", "geoip2/geoip2": "^2.13.0", "mlocati/ip-lib": "^1.18", @@ -439,9 +439,9 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-remediation-engine/issues", - "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v4.2.0" + "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v4.3.0" }, - "time": "2025-01-31T02:50:02+00:00" + "time": "2025-04-11T01:49:32+00:00" }, { "name": "cweagans/composer-patches", diff --git a/docs/images/screenshots/config-usage-metrics.png b/docs/images/screenshots/config-usage-metrics.png index 6bf137cd6358c7ef47bbb06a98fd060e32a26af1..e35d95406f269358251d1fdac1ba1737674bd252 100644 GIT binary patch literal 63147 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 XK_&gJVe8AyPqi?$zJ$MU{ocO;EL&lb literal 58839 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 Rfps{ro#g80vd$@?2>`m$WMKdR diff --git a/inc/Admin/advanced-settings.php b/inc/Admin/advanced-settings.php index 85992c3..301c95b 100644 --- a/inc/Admin/advanced-settings.php +++ b/inc/Admin/advanced-settings.php @@ -98,9 +98,11 @@ function crowdsec_multi_save_advanced_settings() $message = __('Settings saved.
    As the stream mode is enabled, the cache has just been refreshed. New decision(s): '.$new.'. Deleted decision(s): '. $deleted); AdminNotice::displaySuccess($message); scheduleBlocklistRefresh(); + return true; }, function () { // Stream mode just deactivated. unscheduleBlocklistRefresh(); + return false; }, '

    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.

    '. @@ -155,11 +157,18 @@ function crowdsec_multi_save_advanced_settings() // Field "crowdsec_usage_metrics" addFieldCheckbox('crowdsec_usage_metrics', 'Enable Usage Metrics', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_usage_metrics', function () { - // Usage metrics just activated. + // Usage metrics push just activated. + $lapiUrl = is_multisite() ? get_site_option('crowdsec_api_url') : get_option('crowdsec_api_url'); + if (0 === strpos($lapiUrl, Constants::BAAS_URL)) { + AdminNotice::displayError('Pushing usage metrics with a Block as a Service LAPI ('.esc_html($lapiUrl).') is not supported. '); + return false; + } scheduleUsageMetricsPush(); + return true; }, function () { - // Stream mode just deactivated. + // Usage metrics push just deactivated. unscheduleUsageMetricsPush(); + return false; }, '

    Enable usage metrics to gain visibility: monitor incoming traffic and blocked threats for better security insights.

    If this option is enabled, a cron job will push usage metrics to the Local API every 15 minutes.

    @@ -167,12 +176,8 @@ function crowdsec_multi_save_advanced_settings()

    '.displayBouncerMetricsInAdminPage().'

    - '. - ($isUsageMetricsEnabled ? - '

    ' : - '

    - -')); + ' .displayPushMetricsInAdminPage($isUsageMetricsEnabled).displayResetMetricsInAdminPage() + ); /********************* @@ -385,7 +390,16 @@ function crowdsec_multi_save_advanced_settings() // Field "AppSec enabled" addFieldCheckbox('crowdsec_use_appsec', 'Enable AppSec', 'crowdsec_plugin_advanced_settings', - 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_appsec', function () {}, function () {}, ' + 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_appsec', function () { + $lapiUrl = is_multisite() ? get_site_option('crowdsec_api_url') : get_option('crowdsec_api_url'); + if (0 === strpos($lapiUrl, Constants::BAAS_URL)) { + AdminNotice::displayError('Using AppSec with a Block as a Service LAPI ('.esc_html($lapiUrl).') is not supported. '); + return false; + } + return true; + + }, function () + {return false;}, '

    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.

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

    '); @@ -565,7 +579,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra 'fill the IPs or IPs ranges here...', ''); // Field "crowdsec_hide_mentions" - addFieldCheckbox('crowdsec_hide_mentions', 'Hide CrowdSec mentions', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_remediations', function () {}, function () {}, ' + addFieldCheckbox('crowdsec_hide_mentions', 'Hide CrowdSec mentions', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_remediations', function () {return true;}, function () {return false;}, '

    Enable if you want to hide CrowdSec mentions on the Ban and Captcha pages

    '); /*************************** @@ -580,7 +594,8 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra // Field "Geolocation enabled" addFieldCheckbox('crowdsec_geolocation_enabled', 'Enable geolocation feature', 'crowdsec_plugin_advanced_settings', - 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_geolocation', function () {}, function () {}, ' + 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_geolocation', function () {return true;}, function () + {return false;}, '

    Enable if you want to use also CrowdSec country scoped decisions.
    If enabled, bounced IP will be geolocalized and the final remediation will take into account any country related decision.

    '); $geolocationTypes = [Constants::GEOLOCATION_TYPE_MAXMIND => 'MaxMind database' ]; @@ -650,11 +665,11 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra }, '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 () {}, ' + addFieldCheckbox('crowdsec_debug_mode', 'Enable debug mode', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_debug', function () {return true;}, function () {return false;}, '

    Should not be used in production.
    When this mode is enabled, a debug.log file will be written in the wp-content/uploads/crowdsec/logs folder.

    '); // Field "crowdsec_disable_prod_log" - addFieldCheckbox('crowdsec_disable_prod_log', 'Disable prod log', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_debug', function () {}, function () {}, ' + addFieldCheckbox('crowdsec_disable_prod_log', 'Disable prod log', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_debug', function () {return true;}, function () {return false;}, '

    By default, a prod.log file is written in the wp-content/uploads/crowdsec/logs folder.
    You can disable this log here.

    '); // Field "Custom User Agent" @@ -683,7 +698,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra }, '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 () {}, ' + addFieldCheckbox('crowdsec_display_errors', 'Enable errors display', 'crowdsec_plugin_advanced_settings', 'crowdsec_advanced_settings', 'crowdsec_admin_advanced_display_errors', function () {return true;}, function () {return false;}, '

    Do not use in production. When this mode is enabled, you will see every unexpected bouncing errors in the browser.

    '); @@ -696,7 +711,7 @@ function convertInlineIpRangesToComparableIpBounds(string $inlineIpRanges): arra }, '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 () {}, ' + 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 () {return true;}, function () {return false;}, '

    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.

    '); diff --git a/inc/Admin/init.php b/inc/Admin/init.php index 0ea543e..d6c825f 100644 --- a/inc/Admin/init.php +++ b/inc/Admin/init.php @@ -121,16 +121,35 @@ function pushBouncerMetricsInAdminPage() } } + function resetBouncerMetricsInAdminPage() + { + try { + $configs = getDatabaseConfigs(); + $bouncer = new Bouncer($configs); + $bouncer->resetUsageMetrics(); + AdminNotice::displaySuccess(__('CrowdSec usage metrics have been reset successfully.')); + } catch (Exception $e) { + if(isset($bouncer) && $bouncer->getLogger()) { + $bouncer->getLogger()->error('', [ + 'type' => 'WP_EXCEPTION_WHILE_RESETTING_USAGE_METRICS', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + AdminNotice::displayError('Technical error while resetting usage metrics: '.$e->getMessage()); + } + } + function displayBouncerMetricsInAdminPage() { try { $configs = getDatabaseConfigs(); $bouncer = new Bouncer($configs); $metrics = $bouncer->getRemediationEngine()->getOriginsCount(); - - if (empty($metrics)) { - return '

    No usage metrics available.

    '; - } + $html = '

    Current metrics

    '; + $html .= '

    Only metrics collected since last push or cache reset are displayed here.

    '; $cacheItem = $bouncer->getRemediationEngine()->getCacheStorage()->getItem(AbstractCache::CONFIG); $cacheConfig = $cacheItem->isHit() ? $cacheItem->get() : []; @@ -144,8 +163,6 @@ function displayBouncerMetricsInAdminPage() $totalCountsByOrigin = []; $totalRemediations = 0; - $html = '

    Current metrics

    '; - // Sort origins alphabetically by key ksort($metrics); @@ -166,7 +183,8 @@ function displayBouncerMetricsInAdminPage() $totalRemediations += $count; } - if ($origin === AbstractCache::CLEAN || $totalCountsByOrigin[$origin] <= 0) { + if ($origin === AbstractCache::CLEAN || $origin === AbstractCache::CLEAN_APPSEC || + $totalCountsByOrigin[$origin] <= 0) { continue; // Don't display "clean" origin or origin with 0 remediations } @@ -189,7 +207,7 @@ function displayBouncerMetricsInAdminPage() if ($totalRemediations === 0) { $html .= ' - No new metrics since the last push + No new metrics '; } @@ -246,6 +264,65 @@ function displayBouncerMetricsInAdminPage() } + function displayResetMetricsInAdminPage() + { + try { + $configs = getDatabaseConfigs(); + $bouncer = new Bouncer($configs); + if ($bouncer->hasBaasUri()) { + return '

    '; + } + + return ''; + } + catch (Exception $e) { + if (isset($bouncer) && $bouncer->getLogger()) { + $bouncer->getLogger()->error('', [ + 'type' => 'WP_EXCEPTION_WHILE_DISPLAYING_RESET_METRICS', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + + AdminNotice::displayError('Technical error while displaying reset metrics button: ' . esc_html($e->getMessage())); + return ''; + } + + } + + function displayPushMetricsInAdminPage($isPushEnabled = false) + { + try { + $configs = getDatabaseConfigs(); + $bouncer = new Bouncer($configs); + if($bouncer->hasBaasUri()) { + return ''; + } + if( $isPushEnabled) { + return '

    '; + } + return '

    '; + + } + catch (Exception $e) { + if (isset($bouncer) && $bouncer->getLogger()) { + $bouncer->getLogger()->error('', [ + 'type' => 'WP_EXCEPTION_WHILE_DISPLAYING_RESET_METRICS', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]); + } + + AdminNotice::displayError('Technical error while displaying reset metrics button: ' . esc_html($e->getMessage())); + return ''; + } + + } + function pruneBouncerCacheInAdminPage() @@ -363,6 +440,15 @@ function testGeolocationInAdminPage($ip) header("Location: {$_SERVER['HTTP_REFERER']}"); exit(0); }); + add_action('admin_post_crowdsec_reset_usage_metrics', function () { + if ( + !isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'crowdsec_reset_usage_metrics')) { + die('This link expired.'); + } + resetBouncerMetricsInAdminPage(); + header("Location: {$_SERVER['HTTP_REFERER']}"); + exit(0); + }); add_action('admin_post_crowdsec_prune_cache', function () { if ( !isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'crowdsec_prune_cache')) { @@ -427,14 +513,14 @@ function addFieldCheckbox(string $optionName, string $label, string $optionGroup if ($previousState !== $currentState) { if (!$previousState && $currentState) { - $onActivation(); + $currentState = $onActivation($currentState); } if ($previousState && !$currentState) { - $onDeactivation(); + $currentState = $onDeactivation($currentState); } } - return $input; + return $currentState; }); add_settings_field($optionName, $label, function ($args) use ($optionName, $descriptionHtml) { $name = $args['label_for']; diff --git a/inc/Admin/settings.php b/inc/Admin/settings.php index 2a38c63..6f786cc 100644 --- a/inc/Admin/settings.php +++ b/inc/Admin/settings.php @@ -112,7 +112,7 @@ function crowdsec_multi_save_settings() // Field "TLS verify peer" addFieldCheckbox('crowdsec_tls_verify_peer', 'Verify peer', 'crowdsec_plugin_settings', - 'crowdsec_settings', 'crowdsec_admin_connection', function () {}, function () {}, '

    This option determines whether request handler verifies the authenticity of the peer\'s certificate

    '); + 'crowdsec_settings', 'crowdsec_admin_connection', function () {return true;}, function () {return false;}, '

    This option determines whether request handler verifies the authenticity of the peer\'s certificate

    '); // Field "crowdsec_tls_ca_cert_path" addFieldString('crowdsec_tls_ca_cert_path', 'Path to the CA used to process peer verification', 'crowdsec_plugin_settings', 'crowdsec_settings', @@ -123,7 +123,7 @@ function crowdsec_multi_save_settings() // Field "Use cURL" addFieldCheckbox('crowdsec_use_curl', 'Use cURL to call Local API', 'crowdsec_plugin_settings', - 'crowdsec_settings', 'crowdsec_admin_connection', function () {}, function () {}, '

    If checked, calls to Local API will be done with cURL (be sure to have cURL enabled on your system before enabling). + 'crowdsec_settings', 'crowdsec_admin_connection', function () {return true;}, function () {return false;}, '

    If checked, calls to Local API will be done with cURL (be sure to have cURL enabled on your system before enabling).
    If not checked, calls are done with file_get_contents method (allow_url_fopen is required for this).

    '); // Field "timeout" @@ -173,10 +173,8 @@ function crowdsec_multi_save_settings() ]); addFieldCheckbox('crowdsec_public_website_only', 'Public website only', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_bouncing', function () { - // Stream mode just activated. - scheduleBlocklistRefresh(); + return true; }, function () { - // Stream mode just deactivated. - unscheduleBlocklistRefresh(); + return false; }, '

    If checked, Admin related requests are not protected.

    Important notes: We recommend to leave this setting to OFF in order to apply protection to your WordPress admin:

    1. WordPress admin is a frequent target of cyberattack.
    2. Also, some critical public endpoints are considered "admin" and would be unprotected If this setting was ON.

    '); } diff --git a/inc/templates/advanced-settings.php b/inc/templates/advanced-settings.php index e1448c5..7c7c9bb 100644 --- a/inc/templates/advanced-settings.php +++ b/inc/templates/advanced-settings.php @@ -137,6 +137,11 @@ function updateDsnDisplay () {
    +
    +
    + +

    diff --git a/vendor/autoload.php b/vendor/autoload.php index bae20dc..dd4d579 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -14,10 +14,7 @@ echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 6d29bff..2052022 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -26,6 +26,12 @@ */ class InstalledVersions { + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null @@ -322,6 +328,18 @@ public static function reload($data) self::$installedIsLocalDir = false; } + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + /** * @return array[] * @psalm-return list}> @@ -336,7 +354,7 @@ private static function getInstalled() $copiedLocalDir = false; if (self::$canGetVendors) { - $selfDir = strtr(__DIR__, '\\', '/'); + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { diff --git a/vendor/composer/ca-bundle/res/cacert.pem b/vendor/composer/ca-bundle/res/cacert.pem index e8cc6c1..584af3c 100644 --- a/vendor/composer/ca-bundle/res/cacert.pem +++ b/vendor/composer/ca-bundle/res/cacert.pem @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue Dec 31 04:12:05 2024 GMT +## Certificate data from Mozilla as of: Tue Feb 25 04:12:03 2025 GMT ## ## Find updated versions here: https://curl.se/docs/caextract.html ## @@ -16,7 +16,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: c99d6d3f8d3d4e47719ba2b648992f5b58b150128d3aca3c05c566d8dc98e116 +## SHA256: 620fd89c02acb0019f1899dab7907db5d20735904f5a9a0d3a8771a5857ac482 ## @@ -371,37 +371,6 @@ NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- -SwissSign Silver CA - G2 -======================== ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - SecureTrust CA ============== -----BEGIN CERTIFICATE----- @@ -3609,3 +3578,65 @@ AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= -----END CERTIFICATE----- + +D-TRUST BR Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT +cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z +sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ +WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6 +++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL +QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv +x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV +MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX +/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9 +CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr +d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv +U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4 +nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij +YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff +/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP +pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB +WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/ +5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt +n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +D-TRUST EV Root CA 2 2023 +========================= +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG +EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg +MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT +BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1 +sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/ +MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf +vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM +lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3 +YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910 +7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo +nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa +QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF +AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC +MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS +Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2 +QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD +UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V +4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo +dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi +TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi +S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/ +HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L ++KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 5829e9e..4232213 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "composer/ca-bundle", - "version": "1.5.5", - "version_normalized": "1.5.5.0", + "version": "1.5.6", + "version_normalized": "1.5.6.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6" + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/08c50d5ec4c6ced7d0271d2862dec8c1033283e6", - "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f65c239c970e7f072f067ab78646e9f0b2935175", + "reference": "f65c239c970e7f072f067ab78646e9f0b2935175", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, - "time": "2025-01-08T16:17:16+00:00", + "time": "2025-03-06T14:30:56+00:00", "type": "library", "extra": { "branch-alias": { @@ -61,7 +61,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.5" + "source": "https://github.com/composer/ca-bundle/tree/1.5.6" }, "funding": [ { @@ -81,17 +81,17 @@ }, { "name": "crowdsec/bouncer", - "version": "v4.2.0", - "version_normalized": "4.2.0.0", + "version": "v4.3.0", + "version_normalized": "4.3.0.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-cs-bouncer.git", - "reference": "9d5d1679edae3ca874d75153d92e3ca374098dd6" + "reference": "bc1fe9531619506e6ef1c2fd17360c3b30cf77aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/9d5d1679edae3ca874d75153d92e3ca374098dd6", - "reference": "9d5d1679edae3ca874d75153d92e3ca374098dd6", + "url": "https://api.github.com/repos/crowdsecurity/php-cs-bouncer/zipball/bc1fe9531619506e6ef1c2fd17360c3b30cf77aa", + "reference": "bc1fe9531619506e6ef1c2fd17360c3b30cf77aa", "shasum": "" }, "require": { @@ -117,7 +117,7 @@ "suggest": { "ext-curl": "*" }, - "time": "2025-01-31T04:27:22+00:00", + "time": "2025-04-30T01:44:30+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -157,7 +157,7 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-cs-bouncer/issues", - "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v4.2.0" + "source": "https://github.com/crowdsecurity/php-cs-bouncer/tree/v4.3.0" }, "install-path": "../crowdsec/bouncer" }, @@ -372,23 +372,23 @@ }, { "name": "crowdsec/remediation-engine", - "version": "v4.2.0", - "version_normalized": "4.2.0.0", + "version": "v4.3.0", + "version_normalized": "4.3.0.0", "source": { "type": "git", "url": "https://github.com/crowdsecurity/php-remediation-engine.git", - "reference": "779724acdb8fb2b703d3faf45f563b17caf9fcf2" + "reference": "27abce725e25c4adefaf977614f69fe4fb52aa6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/779724acdb8fb2b703d3faf45f563b17caf9fcf2", - "reference": "779724acdb8fb2b703d3faf45f563b17caf9fcf2", + "url": "https://api.github.com/repos/crowdsecurity/php-remediation-engine/zipball/27abce725e25c4adefaf977614f69fe4fb52aa6a", + "reference": "27abce725e25c4adefaf977614f69fe4fb52aa6a", "shasum": "" }, "require": { - "crowdsec/capi-client": "^3.3.0", + "crowdsec/capi-client": "^3.4.0", "crowdsec/common": "^3.0.0", - "crowdsec/lapi-client": "^3.5.0", + "crowdsec/lapi-client": "^3.6.0", "ext-json": "*", "geoip2/geoip2": "^2.13.0", "mlocati/ip-lib": "^1.18", @@ -410,7 +410,7 @@ "suggest": { "ext-curl": "*" }, - "time": "2025-01-31T02:50:02+00:00", + "time": "2025-04-11T01:49:32+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -451,7 +451,7 @@ ], "support": { "issues": "https://github.com/crowdsecurity/php-remediation-engine/issues", - "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v4.2.0" + "source": "https://github.com/crowdsecurity/php-remediation-engine/tree/v4.3.0" }, "install-path": "../crowdsec/remediation-engine" }, diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 79d56ce..97c358e 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'crowdsec/wordpress-bouncer', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '1005c99a3fd017d6be35044e4753bea57307ea1c', + 'reference' => '448f199ce321e945d953917a2dc012df100e2535', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -11,18 +11,18 @@ ), 'versions' => array( 'composer/ca-bundle' => array( - 'pretty_version' => '1.5.5', - 'version' => '1.5.5.0', - 'reference' => '08c50d5ec4c6ced7d0271d2862dec8c1033283e6', + 'pretty_version' => '1.5.6', + 'version' => '1.5.6.0', + 'reference' => 'f65c239c970e7f072f067ab78646e9f0b2935175', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), 'dev_requirement' => false, ), 'crowdsec/bouncer' => array( - 'pretty_version' => 'v4.2.0', - 'version' => '4.2.0.0', - 'reference' => '9d5d1679edae3ca874d75153d92e3ca374098dd6', + 'pretty_version' => 'v4.3.0', + 'version' => '4.3.0.0', + 'reference' => 'bc1fe9531619506e6ef1c2fd17360c3b30cf77aa', 'type' => 'library', 'install_path' => __DIR__ . '/../crowdsec/bouncer', 'aliases' => array(), @@ -56,9 +56,9 @@ 'dev_requirement' => false, ), 'crowdsec/remediation-engine' => array( - 'pretty_version' => 'v4.2.0', - 'version' => '4.2.0.0', - 'reference' => '779724acdb8fb2b703d3faf45f563b17caf9fcf2', + 'pretty_version' => 'v4.3.0', + 'version' => '4.3.0.0', + 'reference' => '27abce725e25c4adefaf977614f69fe4fb52aa6a', 'type' => 'library', 'install_path' => __DIR__ . '/../crowdsec/remediation-engine', 'aliases' => array(), @@ -67,7 +67,7 @@ 'crowdsec/wordpress-bouncer' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '1005c99a3fd017d6be35044e4753bea57307ea1c', + 'reference' => '448f199ce321e945d953917a2dc012df100e2535', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/vendor/crowdsec/bouncer/src/AbstractBouncer.php b/vendor/crowdsec/bouncer/src/AbstractBouncer.php index ec48f93..e84422f 100644 --- a/vendor/crowdsec/bouncer/src/AbstractBouncer.php +++ b/vendor/crowdsec/bouncer/src/AbstractBouncer.php @@ -225,6 +225,16 @@ abstract public function getRequestUri(): string; */ abstract public function getRequestUserAgent(): string; + /** + * Check if the bouncer is connected to a "Blocklist as a service" Lapi. + */ + public function hasBaasUri(): bool + { + $url = $this->getRemediationEngine()->getClient()->getConfig('api_url'); + + return 0 === strpos($url, Constants::BAAS_URL); + } + /** * This method prune the cache: it removes all the expired cache items. * @@ -276,6 +286,20 @@ public function refreshBlocklistCache(): array } } + /** + * @throws InvalidArgumentException + */ + public function resetUsageMetrics(): void + { + // Retrieve metrics cache item + $metricsItem = $this->getRemediationEngine()->getCacheStorage()->getItem(AbstractCache::ORIGINS_COUNT); + if ($metricsItem->isHit()) { + // Reset the metrics + $metricsItem->set([]); + $this->getRemediationEngine()->getCacheStorage()->getAdapter()->save($metricsItem); + } + } + /** * Handle a bounce for current IP. * diff --git a/vendor/crowdsec/bouncer/src/Constants.php b/vendor/crowdsec/bouncer/src/Constants.php index 501aa2e..a6dba13 100644 --- a/vendor/crowdsec/bouncer/src/Constants.php +++ b/vendor/crowdsec/bouncer/src/Constants.php @@ -18,6 +18,8 @@ */ class Constants extends RemConstants { + /** @var string The URL prefix for Blocklist As A Service LAPI */ + public const BAAS_URL = 'https://admin.api.crowdsec.net'; /** @var int The duration we keep a captcha flow in cache */ public const CACHE_EXPIRATION_FOR_CAPTCHA = 86400; /** @var string The "MEMCACHED" cache system */ @@ -31,7 +33,7 @@ class Constants extends RemConstants /** @var string Path for html templates folder (e.g. ban and captcha wall) */ public const TEMPLATES_DIR = __DIR__ . '/templates'; /** @var string The last version of this library */ - public const VERSION = 'v4.2.0'; + public const VERSION = 'v4.3.0'; /** @var string The "disabled" x-forwarded-for setting */ public const X_FORWARDED_DISABLED = 'no_forward'; } diff --git a/vendor/crowdsec/remediation-engine/CHANGELOG.md b/vendor/crowdsec/remediation-engine/CHANGELOG.md index 07a8336..5d66a22 100644 --- a/vendor/crowdsec/remediation-engine/CHANGELOG.md +++ b/vendor/crowdsec/remediation-engine/CHANGELOG.md @@ -13,6 +13,16 @@ The [public API](https://semver.org/spec/v2.0.0.html#spec-item-1) of this libra As far as possible, we try to adhere to [Symfony guidelines](https://symfony.com/doc/current/contributing/code/bc.html#working-on-symfony-code) when deciding whether a change is a breaking change or not. +--- + +## [4.3.0](https://github.com/crowdsecurity/php-remediation-engine/releases/tag/v4.3.0) - 2025-04-11 +[_Compare with previous release_](https://github.com/crowdsecurity/php-remediation-engine/compare/v4.2.0...v4.3.0) + + +### Added + +- Add support for `Allowlists` decisions + --- ## [4.2.0](https://github.com/crowdsecurity/php-remediation-engine/releases/tag/v4.2.0) - 2025-01-31 diff --git a/vendor/crowdsec/remediation-engine/composer.json b/vendor/crowdsec/remediation-engine/composer.json index 312dd5c..276ff8c 100644 --- a/vendor/crowdsec/remediation-engine/composer.json +++ b/vendor/crowdsec/remediation-engine/composer.json @@ -46,8 +46,8 @@ "symfony/config": "^4.4.27 || ^5.2 || ^6.0 || ^7.2", "symfony/cache": "^5.4.11 || ^6.0.11 || ^7.2.1", "crowdsec/common": "^3.0.0", - "crowdsec/capi-client": "^3.3.0", - "crowdsec/lapi-client": "^3.5.0", + "crowdsec/capi-client": "^3.4.0", + "crowdsec/lapi-client": "^3.6.0", "mlocati/ip-lib": "^1.18", "geoip2/geoip2": "^2.13.0", "monolog/monolog": "^1.17 || ^2.1 || ^3.8", diff --git a/vendor/crowdsec/remediation-engine/docs/USER_GUIDE.md b/vendor/crowdsec/remediation-engine/docs/USER_GUIDE.md index 91263a3..d491afc 100644 --- a/vendor/crowdsec/remediation-engine/docs/USER_GUIDE.md +++ b/vendor/crowdsec/remediation-engine/docs/USER_GUIDE.md @@ -64,6 +64,7 @@ This kind of action is called a remediation and can be: - Handle Range scoped decisions for IPv4 - Handle Country scoped decisions using [MaxMind](https://www.maxmind.com) database - Handle List decisions + - Handle Allow list decisions for CAPI - Determine remediation for a given IP - Use the cached decisions for CAPI and for LAPI in stream mode - For LAPI in live mode, call LAPI if there is no cached decision diff --git a/vendor/crowdsec/remediation-engine/src/AbstractRemediation.php b/vendor/crowdsec/remediation-engine/src/AbstractRemediation.php index 1f7f139..736df8c 100644 --- a/vendor/crowdsec/remediation-engine/src/AbstractRemediation.php +++ b/vendor/crowdsec/remediation-engine/src/AbstractRemediation.php @@ -16,6 +16,8 @@ abstract class AbstractRemediation { + /** @var string The CrowdSec name for allowlist */ + public const CS_ALLOW = 'allowlists'; /** @var string The CrowdSec name for blocklist */ public const CS_BLOCK = 'blocklists'; /** @var string The CrowdSec name for deleted decisions */ @@ -487,6 +489,15 @@ private function normalize(string $value): string private function retrieveRemediationFromCachedDecisions(array $cacheDecisions): array { $cleanDecisions = $this->cacheStorage->cleanCachedValues($cacheDecisions); + // Early return for Allow list + foreach ($cleanDecisions as $decision) { + if (Constants::ALLOW_LIST_REMEDIATION === $decision[AbstractCache::INDEX_MAIN]) { + return [ + self::INDEX_REM => Constants::REMEDIATION_BYPASS, + self::INDEX_ORIGIN => $decision[AbstractCache::INDEX_ORIGIN], + ]; + } + } $sortedDecisions = $this->sortDecisionsByPriority($cleanDecisions); $this->logger->debug('Decisions have been sorted by priority', [ 'type' => 'REM_SORTED_DECISIONS', @@ -550,7 +561,7 @@ private function validateRawDecision(array $rawDecision): bool && !empty($rawDecision['duration']) ) { $result = true; - // We don't want blocklists decisions without a scenario + // We don't want blocklists or allowlists decisions without a scenario if ( Constants::ORIGIN_LISTS === $rawDecision['origin'] && empty($rawDecision['scenario']) diff --git a/vendor/crowdsec/remediation-engine/src/CacheStorage/AbstractCache.php b/vendor/crowdsec/remediation-engine/src/CacheStorage/AbstractCache.php index 832651c..108927b 100644 --- a/vendor/crowdsec/remediation-engine/src/CacheStorage/AbstractCache.php +++ b/vendor/crowdsec/remediation-engine/src/CacheStorage/AbstractCache.php @@ -48,6 +48,8 @@ abstract class AbstractCache public const LAST_PULL = 'last_pull'; /** @var string Internal name for list */ public const LIST = 'list'; + /** @var string Internal name for allow list */ + public const ALLOW_LIST = 'allow_list'; /** @var string Internal name for cache remediation origin count item */ public const ORIGINS_COUNT = 'origins_count'; /** @var string Internal name for cache clean item */ @@ -398,7 +400,7 @@ protected function saveDeferred(CacheItemInterface $item): bool */ private function format(Decision $decision, ?int $bucketInt = null): array { - $mainValue = $bucketInt ? $decision->getValue() : $decision->getType(); + $mainValue = null !== $bucketInt ? $decision->getValue() : $decision->getType(); return [ self::INDEX_MAIN => $mainValue, @@ -470,7 +472,7 @@ private function getRangeIntForIp(string $ip): int */ private function getTags(Decision $decision, ?int $bucketInt = null): array { - return $bucketInt ? [self::RANGE_BUCKET_TAG] : [self::CACHE_TAG_REM, $decision->getScope()]; + return null !== $bucketInt ? [self::RANGE_BUCKET_TAG] : [self::CACHE_TAG_REM, $decision->getScope()]; } /** @@ -529,7 +531,7 @@ private function manageRange(Decision $decision): ?RangeInterface private function remove(Decision $decision, ?int $bucketInt = null): array { $result = [self::DONE => 0, self::DEFER => 0, self::REMOVED => []]; - $cacheKey = $bucketInt ? $this->getCacheKey(self::IPV4_BUCKET_KEY, (string) $bucketInt) : + $cacheKey = null !== $bucketInt ? $this->getCacheKey(self::IPV4_BUCKET_KEY, (string) $bucketInt) : $this->getCacheKey($decision->getScope(), $decision->getValue()); $item = $this->getItem($cacheKey); @@ -605,7 +607,7 @@ private function saveItemWithDuration( */ private function store(Decision $decision, ?int $bucketInt = null): array { - $cacheKey = $bucketInt ? $this->getCacheKey(self::IPV4_BUCKET_KEY, (string) $bucketInt) : + $cacheKey = null !== $bucketInt ? $this->getCacheKey(self::IPV4_BUCKET_KEY, (string) $bucketInt) : $this->getCacheKey($decision->getScope(), $decision->getValue()); $item = $this->getItem($cacheKey); $cachedValues = $item->isHit() ? $item->get() : []; diff --git a/vendor/crowdsec/remediation-engine/src/CapiRemediation.php b/vendor/crowdsec/remediation-engine/src/CapiRemediation.php index a377711..643bad7 100644 --- a/vendor/crowdsec/remediation-engine/src/CapiRemediation.php +++ b/vendor/crowdsec/remediation-engine/src/CapiRemediation.php @@ -66,6 +66,42 @@ public function getIpRemediation(string $ip): array return $this->processCachedDecisions($cachedDecisions); } + /** + * @throws CacheStorageException + * @throws InvalidArgumentException + * @throws CacheException|ClientException + */ + public function refreshDecisions(): array + { + $rawDecisions = $this->client->getStreamDecisions(); + $newDecisions = $this->convertRawCapiDecisionsToDecisions($rawDecisions[self::CS_NEW] ?? []); + $deletedDecisions = $this->convertRawCapiDecisionsToDecisions($rawDecisions[self::CS_DEL] ?? []); + $listDecisions = $this->handleListDecisions($rawDecisions[self::CS_LINK][self::CS_BLOCK] ?? []); + $allowListDecisions = $this->handleAllowListDecisions($rawDecisions[self::CS_LINK][self::CS_ALLOW] ?? []); + + $stored = $this->storeDecisions(array_merge( + $newDecisions, + $listDecisions, + $allowListDecisions + )); + $removed = $this->removeDecisions($deletedDecisions); + + return [ + self::CS_NEW => $stored[AbstractCache::DONE] ?? 0, + self::CS_DEL => $removed[AbstractCache::DONE] ?? 0, + ]; + } + + /** + * Process and validate input configurations. + */ + private function configure(array $configs): void + { + $configuration = new CapiRemediationConfig(); + $processor = new Processor(); + $this->configs = $processor->processConfiguration($configuration, [$configuration->cleanConfigs($configs)]); + } + private function convertRawCapiDecisionsToDecisions(array $rawDecisions): array { $decisions = []; @@ -100,31 +136,90 @@ private function formatIfModifiedSinceHeader(int $timestamp): string return gmdate('D, d M Y H:i:s \G\M\T', $timestamp); } - /** - * This method allows to know if the "If-Modified-Since" should be added when pulling list decisions. - * - * @param int $pullTime // Moment when the list is pulled - * @param int $listExpirationTime // Expiration of the cached list decisions - * @param int $frequency // Amount of time in seconds that represents the average decision pull frequency - */ - private function shouldAddModifiedSince(int $pullTime, int $listExpirationTime, int $frequency): bool + private function getDurationInSeconds(string $futureDate): int { - return ($listExpirationTime - $frequency) > $pullTime; + try { + // Remove microseconds for better compatibility with older PHP versions + $cleanDate = preg_replace('/\.\d{3,6}/', '', $futureDate); + + $expiration = new \DateTime($cleanDate, new \DateTimeZone('UTC')); + $now = new \DateTime('now', new \DateTimeZone('UTC')); + + $duration = $expiration->getTimestamp() - $now->getTimestamp(); + + return max(0, $duration); + } catch (\Throwable $e) { + // If parsing fails, return 0 as a fallback + return 0; + } } - private function handleListPullHeaders(array $headers, array $lastPullContent, int $pullTime): array + /** + * @throws InvalidArgumentException|CacheException + */ + private function handleAllowListDecisions(array $allowlists): array { - $shouldAddModifiedSince = false; - if (isset($lastPullContent[AbstractCache::INDEX_EXP])) { - $frequency = $this->getConfig('refresh_frequency_indicator') ?? Constants::REFRESH_FREQUENCY; - $shouldAddModifiedSince = $this->shouldAddModifiedSince( - $pullTime, - (int) $lastPullContent[AbstractCache::INDEX_EXP], - (int) $frequency - ); + $decisions = []; + try { + foreach ($allowlists as $allowlist) { + $headers = []; + if ($this->validateAllowlist($allowlist)) { + // The existence of the following indexes must be guaranteed by the validateAllowlist method + $listName = strtolower((string) $allowlist['name']); + $listId = (string) $allowlist['id']; + $url = (string) $allowlist['url']; + $origin = Constants::ORIGIN_LISTS; + $allowDecision = [ + 'type' => Constants::ALLOW_LIST_REMEDIATION, + 'origin' => $origin, + 'scenario' => $listName, + ]; + + $lastPullCacheKey = $this->getCacheStorage()->getCacheKey( + AbstractCache::ALLOW_LIST, + $listId + ); + + $lastPullItem = $this->getCacheStorage()->getItem($lastPullCacheKey); + + $pullTime = time(); + if ($lastPullItem->isHit()) { + $lastPullContent = $lastPullItem->get(); + $headers = $this->handleAllowListPullHeaders($headers, $lastPullContent); + } + + $listResponse = rtrim( + $this->client->getCapiHandler()->getListDecisions($url, $headers), + \PHP_EOL + ); + + if ($listResponse) { + $this->cacheStorage->upsertItem( + $lastPullCacheKey, + [ + AbstractCache::LAST_PULL => $pullTime, + ], + 0, + [AbstractCache::ALLOW_LIST, $listName] + ); + $decisions = $this->handleAllowListResponse($listResponse, $allowDecision); + } + } + } + } catch (\Exception $e) { + $this->logger->info('Something went wrong during allowlists decisions process', [ + 'type' => 'CAPI_REM_HANDLE_ALLOW_LIST_DECISIONS', + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ]); } - if ($shouldAddModifiedSince && isset($lastPullContent[AbstractCache::LAST_PULL])) { + return $decisions; + } + + private function handleAllowListPullHeaders(array $headers, array $lastPullContent): array + { + if (isset($lastPullContent[AbstractCache::LAST_PULL])) { $headers['If-Modified-Since'] = $this->formatIfModifiedSinceHeader( (int) $lastPullContent[AbstractCache::LAST_PULL] ); @@ -133,6 +228,38 @@ private function handleListPullHeaders(array $headers, array $lastPullContent, i return $headers; } + private function handleAllowListResponse(string $listResponse, array $allowDecision): array + { + $decisions = []; + $listedAllows = explode(\PHP_EOL, $listResponse); + $this->logger->debug('Handle allowlists decisions', [ + 'type' => 'CAPI_REM_HANDLE_ALLOW_LIST_DECISIONS', + 'list_count' => count($listedAllows), + ]); + foreach ($listedAllows as $listedAllow) { + $decoded = json_decode($listedAllow, true); + $allowDecision['value'] = $decoded['value']; + $allowDecision['scope'] = $decoded['scope']; + /* + * This hardcoded value ill be overwritten below by the duration in the allowlist. + * We have to set it to avoid an exception from the convertRawDecision method. + */ + $allowDecision['duration'] = '1s'; + $decision = $this->convertRawDecision($allowDecision); + + if ($decision) { + $durationInSeconds = isset($decoded['expiration']) ? + $this->getDurationInSeconds($decoded['expiration']) : + Constants::MAX_DURATION; + + $decision->setExpiresAt(time() + $durationInSeconds); + $decisions[] = $decision; + } + } + + return $decisions; + } + /** * @throws InvalidArgumentException|CacheException */ @@ -168,7 +295,9 @@ private function handleListDecisions(array $blocklists): array $pullTime = time(); if ($lastPullItem->isHit()) { $lastPullContent = $lastPullItem->get(); - $headers = $this->handleListPullHeaders($headers, $lastPullContent, $pullTime); + $frequency = $this->getConfig('refresh_frequency_indicator') ?? Constants::REFRESH_FREQUENCY; + $headers = $this->handleListPullHeaders($headers, $lastPullContent, $pullTime, (int) + $frequency); } $listResponse = rtrim( @@ -192,7 +321,7 @@ private function handleListDecisions(array $blocklists): array } } } catch (\Exception $e) { - $this->logger->info('Something went wrong during list decisions process', [ + $this->logger->info('Something went wrong during blocklists decisions process', [ 'type' => 'CAPI_REM_HANDLE_LIST_DECISIONS', 'message' => $e->getMessage(), 'code' => $e->getCode(), @@ -202,11 +331,31 @@ private function handleListDecisions(array $blocklists): array return $decisions; } + private function handleListPullHeaders(array $headers, array $lastPullContent, int $pullTime, int $frequency): array + { + $shouldAddModifiedSince = false; + if (isset($lastPullContent[AbstractCache::INDEX_EXP])) { + $shouldAddModifiedSince = $this->shouldAddModifiedSince( + $pullTime, + (int) $lastPullContent[AbstractCache::INDEX_EXP], + $frequency + ); + } + + if ($shouldAddModifiedSince && isset($lastPullContent[AbstractCache::LAST_PULL])) { + $headers['If-Modified-Since'] = $this->formatIfModifiedSinceHeader( + (int) $lastPullContent[AbstractCache::LAST_PULL] + ); + } + + return $headers; + } + private function handleListResponse(string $listResponse, array $blockDecision): array { $decisions = []; $listedIps = explode(\PHP_EOL, $listResponse); - $this->logger->debug('Handle list decisions', [ + $this->logger->debug('Handle blocklist decisions', [ 'type' => 'CAPI_REM_HANDLE_LIST_DECISIONS', 'list_count' => count($listedIps), ]); @@ -221,6 +370,37 @@ private function handleListResponse(string $listResponse, array $blockDecision): return $decisions; } + /** + * This method allows to know if the "If-Modified-Since" should be added when pulling list decisions. + * + * @param int $pullTime // Moment when the list is pulled + * @param int $listExpirationTime // Expiration of the cached list decisions + * @param int $frequency // Amount of time in seconds that represents the average decision pull frequency + */ + private function shouldAddModifiedSince(int $pullTime, int $listExpirationTime, int $frequency): bool + { + return ($listExpirationTime - $frequency) > $pullTime; + } + + private function validateAllowlist(array $allowlist): bool + { + if ( + !empty($allowlist['id']) + && !empty($allowlist['name']) + && !empty($allowlist['description']) + && !empty($allowlist['url']) + ) { + return true; + } + + $this->logger->error('Retrieved allowlist is not as expected', [ + 'type' => 'REM_RAW_DECISION_NOT_AS_EXPECTED', + 'raw_decision' => json_encode($allowlist), + ]); + + return false; + } + private function validateBlocklist(array $blocklist): bool { if ( @@ -240,35 +420,4 @@ private function validateBlocklist(array $blocklist): bool return false; } - - /** - * @throws CacheStorageException - * @throws InvalidArgumentException - * @throws CacheException|ClientException - */ - public function refreshDecisions(): array - { - $rawDecisions = $this->client->getStreamDecisions(); - $newDecisions = $this->convertRawCapiDecisionsToDecisions($rawDecisions[self::CS_NEW] ?? []); - $deletedDecisions = $this->convertRawCapiDecisionsToDecisions($rawDecisions[self::CS_DEL] ?? []); - $listDecisions = $this->handleListDecisions($rawDecisions[self::CS_LINK][self::CS_BLOCK] ?? []); - - $stored = $this->storeDecisions(array_merge($newDecisions, $listDecisions)); - $removed = $this->removeDecisions($deletedDecisions); - - return [ - self::CS_NEW => $stored[AbstractCache::DONE] ?? 0, - self::CS_DEL => $removed[AbstractCache::DONE] ?? 0, - ]; - } - - /** - * Process and validate input configurations. - */ - private function configure(array $configs): void - { - $configuration = new CapiRemediationConfig(); - $processor = new Processor(); - $this->configs = $processor->processConfiguration($configuration, [$configuration->cleanConfigs($configs)]); - } } diff --git a/vendor/crowdsec/remediation-engine/src/Constants.php b/vendor/crowdsec/remediation-engine/src/Constants.php index 0b322ca..badbde6 100644 --- a/vendor/crowdsec/remediation-engine/src/Constants.php +++ b/vendor/crowdsec/remediation-engine/src/Constants.php @@ -18,6 +18,10 @@ */ class Constants extends CommonConstants { + /** + * @var string The remediation related to the allow list + */ + public const ALLOW_LIST_REMEDIATION = 'allow'; /** * @var string The AppSec action name to allow the request */ @@ -70,6 +74,10 @@ class Constants extends CommonConstants * @var string The Maxmind "Country" database type */ public const MAXMIND_COUNTRY = 'country'; + /** + * @var int The duration (in seconds) to use to simulate an infinite TTL + */ + public const MAX_DURATION = 631152000; // 20 years /** * @var string The key to get the origin from getIpRemediation */ @@ -82,6 +90,7 @@ class Constants extends CommonConstants * @var int The default refresh frequency (in seconds) */ public const REFRESH_FREQUENCY = 14400; + /** * @var string The key to get the remediation from getIpRemediation */ @@ -89,5 +98,5 @@ class Constants extends CommonConstants /** * @var string The current version of this library */ - public const VERSION = 'v4.2.0'; + public const VERSION = 'v4.3.0'; } diff --git a/vendor/crowdsec/remediation-engine/src/Decision.php b/vendor/crowdsec/remediation-engine/src/Decision.php index b091e00..232017b 100644 --- a/vendor/crowdsec/remediation-engine/src/Decision.php +++ b/vendor/crowdsec/remediation-engine/src/Decision.php @@ -53,6 +53,13 @@ public function getExpiresAt(): int return $this->expiresAt; } + public function setExpiresAt(int $expiresAt): Decision + { + $this->expiresAt = $expiresAt; + + return $this; + } + public function getIdentifier(): string { return $this->identifier; diff --git a/vendor/crowdsec/remediation-engine/src/Geolocation.php b/vendor/crowdsec/remediation-engine/src/Geolocation.php index 9f01de8..5f96f04 100644 --- a/vendor/crowdsec/remediation-engine/src/Geolocation.php +++ b/vendor/crowdsec/remediation-engine/src/Geolocation.php @@ -14,22 +14,22 @@ class Geolocation { - /** - * @var array - */ - private $configs; /** * @var AbstractCache */ private $cacheStorage; /** - * @var LoggerInterface + * @var array */ - private $logger; + private $configs; /** * @var string[] */ private $geolocTemplate = ['country' => '', 'not_found' => '', 'error' => '']; + /** + * @var LoggerInterface + */ + private $logger; /** * @var array */ diff --git a/vendor/crowdsec/remediation-engine/tests/Constants.php b/vendor/crowdsec/remediation-engine/tests/Constants.php index b3237a1..2f21e86 100644 --- a/vendor/crowdsec/remediation-engine/tests/Constants.php +++ b/vendor/crowdsec/remediation-engine/tests/Constants.php @@ -26,6 +26,11 @@ class Constants public const IP_V4_4 = '12.13.14.15'; public const IP_V4_5 = '16.17.18.19'; public const IP_V4_2_CACHE_KEY = RemConstants::SCOPE_IP . AbstractCache::SEP . self::IP_V4_2; + public const ALLOW_LIST = << '24h', ], ], + 'allowlists' => [ + [ + 'id' => 'some-id', + 'name' => 'some-allow-list-name', + 'url' => 'some-url', + 'description' => 'some-description', + 'scope' => 'ip', + ], + ], + ], ], 'new_ip_v4_other' => [ diff --git a/vendor/crowdsec/remediation-engine/tests/Unit/AppSecLapiRemediationTest.php b/vendor/crowdsec/remediation-engine/tests/Unit/AppSecLapiRemediationTest.php index 623f92c..53830dd 100644 --- a/vendor/crowdsec/remediation-engine/tests/Unit/AppSecLapiRemediationTest.php +++ b/vendor/crowdsec/remediation-engine/tests/Unit/AppSecLapiRemediationTest.php @@ -287,7 +287,7 @@ public function testGetAppSecRemediation($cacheType) $this->assertEquals( ['clean_appsec' => ['bypass' => 1]], $originsCount, - 'Origins count should be empty' + 'Origins count should not be empty' ); $appSecHeaders[Constants::HEADER_APPSEC_IP] = '1.2.3.4'; diff --git a/vendor/crowdsec/remediation-engine/tests/Unit/CapiRemediationTest.php b/vendor/crowdsec/remediation-engine/tests/Unit/CapiRemediationTest.php index 89c6aa0..344b3e0 100644 --- a/vendor/crowdsec/remediation-engine/tests/Unit/CapiRemediationTest.php +++ b/vendor/crowdsec/remediation-engine/tests/Unit/CapiRemediationTest.php @@ -125,6 +125,12 @@ * @covers \CrowdSec\RemediationEngine\AbstractRemediation::capRemediationLevel * * @uses \CrowdSec\RemediationEngine\AbstractRemediation::getOriginsCountItem + * @covers \CrowdSec\RemediationEngine\CapiRemediation::handleAllowListDecisions + * @covers \CrowdSec\RemediationEngine\CapiRemediation::getDurationInSeconds + * @covers \CrowdSec\RemediationEngine\CapiRemediation::handleAllowListPullHeaders + * @covers \CrowdSec\RemediationEngine\CapiRemediation::handleAllowListResponse + * @covers \CrowdSec\RemediationEngine\CapiRemediation::validateAllowlist + * @covers \CrowdSec\RemediationEngine\Decision::setExpiresAt */ final class CapiRemediationTest extends AbstractRemediation { @@ -367,7 +373,24 @@ public function testGetIpRemediation($cacheType) 'capi-ban-ip-1.2.3.4', 'capi', ]]], // Test 4 : retrieve expired ban ip - [AbstractCache::STORED => []] // Test 4 : retrieve empty range + [AbstractCache::STORED => []], // Test 4 : retrieve empty range, + [AbstractCache::STORED => + [ + [ + 'ban', + 9999999999999, // never expires + 'capi-ban-ip-1.2.3.4', + 'capi', + ], + [ + 'allow', + 9999999999999, // never expires + 'lists:my-allow-list-allow-ip-1.2.3.4', + 'lists:my-allow-list', + ], + ] + ], // Test 5 : Allow list vs BAN + [AbstractCache::STORED => []] // Test 5 : retrieve empty range, ) ); // Test 1 @@ -407,6 +430,18 @@ public function testGetIpRemediation($cacheType) $result['remediation'], 'Expired cached remediations should have been cleaned' ); + // Test 5 + $result = $remediation->getIpRemediation(TestConstants::IP_V4); + $this->assertEquals( + Constants::REMEDIATION_BYPASS, + $result['remediation'], + 'Allow list should have priority over ban' + ); + $this->assertEquals( + 'lists:my-allow-list', + $result['origin'], + 'Allow list should be the origin' + ); } public function testPrivateOrProtectedMethods() @@ -537,6 +572,40 @@ public function testPrivateOrProtectedMethods() $result ); + // validateAllowlist + $blocklist = [ + 'name' => 'my-list', + 'url' => 'https://', + 'id' => 'an-id', + 'description' => 'hre is a description', + ]; + + $result = PHPUnitUtil::callMethod( + $remediation, + 'validateAllowlist', + [$blocklist] + ); + + $this->assertEquals( + true, + $result + ); + $blocklist = [ + 'name' => 'tor-exit-node', + 'scope' => 'ip', + 'duration' => '24h', + ]; + $result = PHPUnitUtil::callMethod( + $remediation, + 'validateAllowlist', + [$blocklist] + ); + + $this->assertEquals( + false, + $result + ); + // comparePriorities $a = [ 'ban', @@ -1017,10 +1086,11 @@ public function testPrivateOrProtectedMethods() AbstractCache::INDEX_EXP => 1677888000, AbstractCache::LAST_PULL => 1677801600]; $pullTime = 1677884400; + $frequency = 14400; $result = PHPUnitUtil::callMethod( $remediation, 'handleListPullHeaders', - [$headers, $lastPullContent, $pullTime] + [$headers, $lastPullContent, $pullTime, $frequency] ); $this->assertEquals( @@ -1041,7 +1111,22 @@ public function testPrivateOrProtectedMethods() $result = PHPUnitUtil::callMethod( $remediation, 'handleListPullHeaders', - [$headers, $lastPullContent, $pullTime] + [$headers, $lastPullContent, $pullTime, $frequency] + ); + + $this->assertEquals( + ['If-Modified-Since' => 'Fri, 03 Mar 2023 00:00:00 GMT'], + $result + ); + + // handleAllowListPullHeaders + $headers = []; + $lastPullContent = [ + AbstractCache::LAST_PULL => 1677801600]; + $result = PHPUnitUtil::callMethod( + $remediation, + 'handleAllowListPullHeaders', + [$headers, $lastPullContent] ); $this->assertEquals( @@ -1079,6 +1164,29 @@ public function testPrivateOrProtectedMethods() ['ban'] ); $this->assertEquals('captcha', $result, 'Remediation should be capped as captcha'); + // getDurationInSeconds + $now = new \DateTime('now', new \DateTimeZone('UTC')); + $inOneHour = $now->add(new \DateInterval('PT1H')); + $result = PHPUnitUtil::callMethod( + $remediation, + 'getDurationInSeconds', + [$inOneHour->format('Y-m-d H:i:s')] + ); + $this->assertEquals( + 3600, + $result, + 'Should return the duration in seconds' + ); + $result = PHPUnitUtil::callMethod( + $remediation, + 'getDurationInSeconds', + ['bad-hour-exception'] + ); + $this->assertEquals( + 0, + $result, + 'Should return the 0 if error' + ); } /** @@ -1113,7 +1221,10 @@ public function testRefreshDecisions($cacheType) MockedData::DECISIONS_CAPI_V3['new_ip_v4_and_list'], // Test 11: IPv4 and list MockedData::DECISIONS_CAPI_V3['new_ip_v4_and_list'], // Test 12: IPv4 and list MockedData::DECISIONS_CAPI_V3['new_ip_v4_and_list'], // Test 13: IPv4 and list but error is thrown - MockedData::DECISIONS_CAPI_V3['new_ip_v4_with_0_duration'] // Test 14: IPv4 and 0h duration + // for blocklist + MockedData::DECISIONS_CAPI_V3['new_ip_v4_and_list'], // Test 13: IPv4 and list but error is thrown + // for allowlist + MockedData::DECISIONS_CAPI_V3['new_ip_v4_with_0_duration'] // Test 15: IPv4 and 0h duration ) ); $this->watcher->method('getCapiHandler')->will( @@ -1124,9 +1235,15 @@ public function testRefreshDecisions($cacheType) $capiHandlerMock->method('getListDecisions')->will( $this->onConsecutiveCalls( - TestConstants::IP_V4_2, // Test 11 : new IP v4 + list - '', // Test 12 : new IP v4 + list again (not modified) - $this->throwException(new Exception('UNIT TEST EXCEPTION')) // Test 13 : will throw an error + TestConstants::IP_V4_2, // Test 11 : new IP v4 + list: blocklist, + TestConstants::ALLOW_LIST, // Test 11 : new IP v4 + list: allowlist, + '', // Test 12 : new IP v4 + list again (blocklist not modified), + '', // Test 12 : new IP v4 + list again (allowlist not modified), + $this->throwException(new Exception('UNIT TEST EXCEPTION')), // Test 13 : will throw an error + // (blocklist) + '',// Test 13: empty Allow list + '', // Test 14: empty blocklist list + $this->throwException(new Exception('UNIT TEST EXCEPTION FOR ALLOW')) // Test 14 : will throw an error ) ); @@ -1322,21 +1439,21 @@ public function testRefreshDecisions($cacheType) 'Prod log content should be correct' ); - // Test 11 : new + list + // Test 11 : new + list + allowlist $result = $remediation->refreshDecisions(); $time = time(); $listExpiration = $time + 24 * 60 * 60; $this->assertEquals( - ['new' => 2, 'deleted' => 0], + ['new' => 4, 'deleted' => 0], $result, 'Refresh count should be correct' ); + // Last pull blocklist $lastPullCacheKey = $remediation->getCacheStorage()->getCacheKey( AbstractCache::LIST, 'tor-exit-nodes' ); - $lastPullItem = $remediation->getCacheStorage()->getItem($lastPullCacheKey); $this->assertEquals( true, @@ -1348,6 +1465,22 @@ public function testRefreshDecisions($cacheType) $this->assertTrue($lastPullItemContent['last_pull'] <= $time && $time - 1 <= $lastPullItemContent['last_pull']); + // Last pull allowlist + $lastPullCacheKeyAllow = $remediation->getCacheStorage()->getCacheKey( + AbstractCache::ALLOW_LIST, + 'some-id' + ); + $lastPullItemAllow = $remediation->getCacheStorage()->getItem($lastPullCacheKeyAllow); + $this->assertEquals( + true, + $lastPullItemAllow->isHit() + ); + $lastPullItemContentAllow = $lastPullItemAllow->get(); + // Avoid false positive with tme manipulation (strict equality sometimes leads to error of 1 second) + $this->assertTrue($lastPullItemContentAllow['last_pull'] <= $time && $time - 1 <= + $lastPullItemContentAllow['last_pull']); + + $item = $adapter->getItem(base64_encode(TestConstants::IP_V4_2_CACHE_KEY)); $cachedValue = $item->get(); $this->assertEquals( @@ -1364,7 +1497,7 @@ public function testRefreshDecisions($cacheType) 'captcha', $cachedValue[1][0] ); - // Test 12 : new + list again + // Test 12 : new + list + blocklist again // We wait to test that expiration and pull date won't change sleep(1); $result = $remediation->refreshDecisions(); @@ -1396,8 +1529,8 @@ public function testRefreshDecisions($cacheType) $this->assertTrue($lastPullItemContent[AbstractCache::INDEX_EXP] <= $listExpiration && $listExpiration - 1 <= $lastPullItemContent[1]); $this->assertTrue($lastPullItemContent[AbstractCache::LAST_PULL] <= $time && $time - 1 <= $lastPullItemContent['last_pull']); - // Test 13 : new + list again - // We wait to test that expiration and pull date won't change + // Test 13 : new + list + blocklist again + // Will throw error for blocklist $result = $remediation->refreshDecisions(); PHPUnitUtil::assertRegExp( $this, @@ -1406,7 +1539,17 @@ public function testRefreshDecisions($cacheType) 'Prod log content should be correct' ); - // Test 14 : new + 1 new with 0h duration + // Test 14 : new + list + blocklist again + // Will throw error for allowlist + $result = $remediation->refreshDecisions(); + PHPUnitUtil::assertRegExp( + $this, + '/.*200.*"type":"CAPI_REM_HANDLE_ALLOW_LIST_DECISIONS.*UNIT TEST EXCEPTION FOR ALLOW"/', + file_get_contents($this->root->url() . '/' . $this->prodFile), + 'Prod log content should be correct' + ); + + // Test 15 : new + 1 new with 0h duration $remediation->clearCache(); $result = $remediation->refreshDecisions(); $this->assertEquals( diff --git a/vendor/crowdsec/remediation-engine/tests/Unit/LapiRemediationTest.php b/vendor/crowdsec/remediation-engine/tests/Unit/LapiRemediationTest.php index 8de0c08..d13c238 100644 --- a/vendor/crowdsec/remediation-engine/tests/Unit/LapiRemediationTest.php +++ b/vendor/crowdsec/remediation-engine/tests/Unit/LapiRemediationTest.php @@ -204,8 +204,7 @@ public function setUp(): void $currentDate = date('Y-m-d'); $this->debugFile = 'debug-' . $currentDate . '.log'; $this->prodFile = 'prod-' . $currentDate . '.log'; - $this->logger = new FileLog(['log_directory_path' => $this->root->url(), 'debug_mode' => true, 'log_rotator' - => true]); + $this->logger = new FileLog(['log_directory_path' => $this->root->url(), 'debug_mode' => true, 'log_rotator' => true]); $this->bouncer = $this->getBouncerMock(); $cachePhpfilesConfigs = ['fs_cache_path' => $this->root->url()]; @@ -788,7 +787,7 @@ public function testPushUsageMetricsInLiveMode($cacheType) 'remediation' => 'ban', ], ], - 'Should have CAPI/ban metrics'. json_encode($items[0]) + 'Should have CAPI/ban metrics' . json_encode($items[0]) ); $this->assertEquals( $items[1], @@ -801,7 +800,7 @@ public function testPushUsageMetricsInLiveMode($cacheType) 'remediation' => 'captcha', ], ], - 'Should have lists:tor/captcha metrics'. json_encode($items[1]) + 'Should have lists:tor/captcha metrics' . json_encode($items[1]) ); $this->assertEquals( $items[2], @@ -810,7 +809,7 @@ public function testPushUsageMetricsInLiveMode($cacheType) 'value' => 3, 'unit' => 'request', ], - 'Should have processed metrics'. json_encode($items[2]) + 'Should have processed metrics' . json_encode($items[2]) ); $firstPushTime = time(); $item = $this->cacheStorage->getItem(AbstractCache::CONFIG); @@ -1106,7 +1105,7 @@ public function testGetIpRemediationInLiveModeWithGeolocation($cacheType) $this->assertEquals( true, $item->isHit(), - 'Remediation for country should have not been cached' + 'Remediation for country should have been cached' ); $cachedItem = $item->get(); diff --git a/vendor/crowdsec/remediation-engine/tests/scripts/clear-cache-capi.php b/vendor/crowdsec/remediation-engine/tests/scripts/clear-cache-capi.php index c7a4bfc..a0d4296 100644 --- a/vendor/crowdsec/remediation-engine/tests/scripts/clear-cache-capi.php +++ b/vendor/crowdsec/remediation-engine/tests/scripts/clear-cache-capi.php @@ -16,8 +16,9 @@ $clientConfigs = [ 'machine_id_prefix' => 'remediationtest', 'scenarios' => ['crowdsecurity/http-sensitive-files'], + 'env' => getenv('ENV') ?: 'dev', ]; -$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__), null, $logger); +$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__, $clientConfigs['env']), null, $logger); // Init PhpFiles cache storage $cacheFileConfigs = [ diff --git a/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-capi.php b/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-capi.php index 58e0a87..0013421 100644 --- a/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-capi.php +++ b/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-capi.php @@ -26,8 +26,9 @@ $clientConfigs = [ 'machine_id_prefix' => 'remediationtest', 'scenarios' => ['crowdsecurity/http-sensitive-files'], + 'env' => getenv('ENV') ?: 'dev', ]; -$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__), null, $logger); +$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__, $clientConfigs['env']), null, $logger); // Init PhpFiles cache storage $cacheFileConfigs = [ @@ -46,7 +47,8 @@ $redisCache = new Redis($cacheRedisConfigs, $logger); // Init CAPI remediation $remediationConfigs = []; -$remediationEngine = new CapiRemediation($remediationConfigs, $capiClient, $phpFileCache, $logger); +$remediationEngine = new CapiRemediation($remediationConfigs, $capiClient, $redisCache, $logger); // Determine the remediation for the given IP -echo 'Remediation: ' . $remediationEngine->getIpRemediation($ip) . \PHP_EOL; -echo 'Origins count: ' . json_encode($remediationEngine->getOriginsCount()) . \PHP_EOL; +$remediationData = $remediationEngine->getIpRemediation($ip); +echo 'Remediation: ' . $remediationData['remediation'] . \PHP_EOL; +echo 'Remediation origin: ' . $remediationData['origin'] . \PHP_EOL; diff --git a/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-lapi.php b/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-lapi.php index 8ed2885..23a47a9 100644 --- a/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-lapi.php +++ b/vendor/crowdsec/remediation-engine/tests/scripts/get-remediation-lapi.php @@ -58,5 +58,6 @@ ]; $remediationEngine = new LapiRemediation($remediationConfigs, $lapiClient, $phpFileCache, $logger); // Determine the remediation for the given IP -echo 'Remediation: ' . $remediationEngine->getIpRemediation($ip) . \PHP_EOL; -echo 'Origins count: ' . json_encode($remediationEngine->getOriginsCount()) . \PHP_EOL; +$remediationData = $remediationEngine->getIpRemediation($ip); +echo 'Remediation: ' . $remediationData['remediation'] . \PHP_EOL; +echo 'Remediation origin: ' . $remediationData['origin'] . \PHP_EOL; diff --git a/vendor/crowdsec/remediation-engine/tests/scripts/refresh-decisions-capi.php b/vendor/crowdsec/remediation-engine/tests/scripts/refresh-decisions-capi.php index a3cd09d..cad8d0d 100644 --- a/vendor/crowdsec/remediation-engine/tests/scripts/refresh-decisions-capi.php +++ b/vendor/crowdsec/remediation-engine/tests/scripts/refresh-decisions-capi.php @@ -16,8 +16,9 @@ $clientConfigs = [ 'machine_id_prefix' => 'capiclienttest', 'scenarios' => ['crowdsecurity/http-backdoors-attempts', 'crowdsecurity/http-bad-user-agent'], + 'env' => getenv('ENV') ?: 'dev', ]; -$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__), null, $logger); +$capiClient = new Watcher($clientConfigs, new FileStorage(__DIR__, $clientConfigs['env']), null, $logger); // Init PhpFiles cache storage $cacheFileConfigs = [ 'fs_cache_path' => __DIR__ . '/.cache/capi', From 7e2d0eb8c502fc60a9a332ae60b24725ac4aa2c6 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 2 May 2025 11:25:49 +0900 Subject: [PATCH 06/10] feat(BLaaS): Display warning for BLaaS url and disallow some unsupported setting when using BLaaS --- inc/Admin/advanced-settings.php | 5 +++++ inc/Admin/settings.php | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/inc/Admin/advanced-settings.php b/inc/Admin/advanced-settings.php index 301c95b..fda5768 100644 --- a/inc/Admin/advanced-settings.php +++ b/inc/Admin/advanced-settings.php @@ -100,6 +100,11 @@ function crowdsec_multi_save_advanced_settings() scheduleBlocklistRefresh(); return true; }, function () { + $lapiUrl = is_multisite() ? get_site_option('crowdsec_api_url') : get_option('crowdsec_api_url'); + if (0 === strpos($lapiUrl, Constants::BAAS_URL)) { + AdminNotice::displayError("Using Live mode with a Block As A Service LAPI ($lapiUrl) is not supported. Rolling back to Stream mode."); + return true; + } // Stream mode just deactivated. unscheduleBlocklistRefresh(); return false; diff --git a/inc/Admin/settings.php b/inc/Admin/settings.php index 6f786cc..987bd53 100644 --- a/inc/Admin/settings.php +++ b/inc/Admin/settings.php @@ -1,6 +1,7 @@ '


    ']); // Field "crowdsec_api_url" - addFieldString('crowdsec_api_url', 'Local API URL', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_connection', function ($input, $default = '') { + 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; } + $baasUrl = \CrowdSecBouncer\Constants::BAAS_URL; + if( 0 === strpos($input, $baasUrl)) { + $message = __("You have just defined a \"Block As A Service\" URL (url starting with $baasUrl)."); + $message .= '
    Please note the following:
      '; + $message .= '
    • - The Authentication type must be "Bouncer API key".
    • '; + $message .= '
    • - Stream mode must be enabled (see Communication mode with the Local API in Advanced Settings).
    • '; + $message .= '
    • - Usage Metrics cannot be sent (see Usage Metrics in Advanced Settings).
    • '; + $message .= '
    • - AppSec component cannot be used (see Appsec Component in Advanced Settings).
    • '; + $message .= '
    '; + AdminNotice::displayWarning($message); + } return $input; }, '', 'Your Local API URL (e.g. http://localhost:8080)', '', 'text', false, LapiConstants::DEFAULT_LAPI_URL); - // Field "crowdsec_bouncing_level" + // Field "crowdsec_auth_type" addFieldSelect('crowdsec_auth_type', 'Authentication type', 'crowdsec_plugin_settings', 'crowdsec_settings', 'crowdsec_admin_connection', function ($input) { if (!in_array($input, [ Constants::AUTH_KEY, @@ -84,6 +96,11 @@ function crowdsec_multi_save_settings() add_settings_error('Bouncing auth type', 'crowdsec_error', 'Auth type: Incorrect authentication type selected.'); } + $lapiUrl = is_multisite() ? get_site_option('crowdsec_api_url') : get_option('crowdsec_api_url'); + if ($input === Constants::AUTH_TLS && 0 === strpos($lapiUrl, Constants::BAAS_URL)) { + AdminNotice::displayError('Using TLS authentication with a Block As A Service LAPI is not supported. Rolling back to Bouncer API key authentication.'); + $input = Constants::AUTH_KEY; + } return $input; }, '

    Important note: If you are using TLS authentication, make sure files are not publicly accessible.
    Please refer to the documentation to deny direct access to this folder.

    ', [ From bb34e467467dc7cfa7e064156ce72b4f889edb91 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 2 May 2025 11:52:44 +0900 Subject: [PATCH 07/10] test(blaas): Add e2e blaas test --- .github/workflows/doc-links.yml | 2 +- .../end-to-end-auto-prepend-test-suite.yml | 6 + .github/workflows/end-to-end-multisite.yml | 6 + .github/workflows/end-to-end-test-suite.yml | 6 + .github/workflows/release-test.yml | 6 + docs/USER_GUIDE.md | 10 +- inc/Admin/settings.php | 2 +- tests/e2e-ddev/__tests__/14-blaas.js | 169 ++++++++++++++++++ tests/e2e-ddev/__tests__/3-live-mode-more.js | 12 +- tests/e2e-ddev/__tests__/5-stream-mode.js | 2 +- tests/e2e-ddev/utils/constants.js | 2 + 11 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 tests/e2e-ddev/__tests__/14-blaas.js diff --git a/.github/workflows/doc-links.yml b/.github/workflows/doc-links.yml index 21b37d9..cfc9f55 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,429 --skip-save-results --white-list ddev.site --base-url http://localhost:8080/ - awesome_bot docs/*.md --skip-save-results --allow-dupe --allow 401,429 --white-list ddev.site,your-wordpress-url,crowdsec:8080,localhost:7422 --base-url http://localhost:8080/docs/ + awesome_bot docs/*.md --skip-save-results --allow-dupe --allow 401,429 --white-list ddev.site,your-wordpress-url,crowdsec:8080,localhost:7422,admin.api.crowdsec.net --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 2256e7c..c547ebf 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -244,6 +244,12 @@ jobs: file_path: 13-redis-acl.js + - name: Run BLaaS 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: 14-blaas.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 23204a8..e270457 100644 --- a/.github/workflows/end-to-end-multisite.yml +++ b/.github/workflows/end-to-end-multisite.yml @@ -236,6 +236,12 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 13-redis-acl.js + - name: Run BLaaS 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: 14-blaas.js + - 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 0f9c0a8..6921d3e 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -228,6 +228,12 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 13-redis-acl.js + - name: Run BLaaS 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: 14-blaas.js + - name: Check tested version run: | CURRENT_VERSION=$(ddev wp core version) diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index 2b53d6e..1ac939e 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -258,6 +258,12 @@ jobs: test_path: ${{ github.workspace }}/${{ env.EXTENSION_PATH }}/tests/e2e-ddev file_path: 13-redis-acl.js + - name: Run BLaaS 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: 14-blaas.js + - name: Check tested version run: | CURRENT_VERSION=$(ddev wp core version) diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index c2b523c..ade552f 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -134,7 +134,15 @@ 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` +Default to `http://localhost:8080`. + +If you are using a Block as a Service (BLaaS) LAPI URL (i.e. starting with `https://admin.api.crowdsec.net`), please +note the following: + +- The Authentication type must be "Bouncer API key" +- Stream mode must be enabled (see Communication mode with the Local API in Advanced Settings) +- Usage Metrics cannot be sent (see Usage Metrics in [Advanced settings](#advanced-settings)). +- AppSec component cannot be used (see Appsec Component in [Advanced settings](#advanced-settings)) *** diff --git a/inc/Admin/settings.php b/inc/Admin/settings.php index 987bd53..c047420 100644 --- a/inc/Admin/settings.php +++ b/inc/Admin/settings.php @@ -98,7 +98,7 @@ function crowdsec_multi_save_settings() $lapiUrl = is_multisite() ? get_site_option('crowdsec_api_url') : get_option('crowdsec_api_url'); if ($input === Constants::AUTH_TLS && 0 === strpos($lapiUrl, Constants::BAAS_URL)) { - AdminNotice::displayError('Using TLS authentication with a Block As A Service LAPI is not supported. Rolling back to Bouncer API key authentication.'); + AdminNotice::displayError("Using TLS authentication with a Block As A Service LAPI ($lapiUrl) is not supported. Rolling back to Bouncer API key authentication."); $input = Constants::AUTH_KEY; } return $input; diff --git a/tests/e2e-ddev/__tests__/14-blaas.js b/tests/e2e-ddev/__tests__/14-blaas.js new file mode 100644 index 0000000..4474059 --- /dev/null +++ b/tests/e2e-ddev/__tests__/14-blaas.js @@ -0,0 +1,169 @@ +const { + goToAdmin, + onAdminGoToAdvancedPage, + onAdminGoToSettingsPage, + onAdminSaveSettings, + onAdvancedPageEnableStreamMode, + publicHomepageShouldBeBanWall, + publicHomepageShouldBeAccessible, + banOwnIpForSeconds, + removeAllDecisions, + forceCronRun, + onLoginPageLoginAsAdmin, + setDefaultConfig, + setToggle, + fillInput, + onAdvancedPageEnableUsageMetrics, + getFileContent, + deleteFileContent, + onAdvancedPageDisableUsageMetrics, + runCacheAction, + wait, + selectByName, +} = require("../utils/helpers"); + +const { + CURRENT_IP, + DEBUG_LOG_PATH, + FAKE_BLAAS_URL, + BOUNCER_KEY_FILE, + CA_CERT_FILE, + BOUNCER_CERT_FILE, +} = require("../utils/constants"); + +describe("Check BLaaS URL behavior", () => { + beforeAll(async () => { + await removeAllDecisions(); + await goToAdmin(); + await onLoginPageLoginAsAdmin(); + await setDefaultConfig(); + }); + + it("Should activate WP-CRON", async () => { + // Enable and disable usage metrics before all to make WP-cron working + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await onAdvancedPageEnableUsageMetrics(); + await onAdminSaveSettings(false); + await onAdvancedPageDisableUsageMetrics(); + await onAdminSaveSettings(); + await runCacheAction("clear"); // To reset metrics + }); + + it("Should enable the stream mode", async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await onAdvancedPageEnableStreamMode(); + await onAdminSaveSettings(); + await deleteFileContent(DEBUG_LOG_PATH); + }); + + it("Should display a ban wall via stream mode", async () => { + await banOwnIpForSeconds(15 * 60, CURRENT_IP); + await forceCronRun(); + await publicHomepageShouldBeBanWall(); + // metrics: cscli/ban = 1 + }); + + it("Should display back the homepage with no remediation via stream mode", async () => { + await removeAllDecisions(); + await forceCronRun(); + await publicHomepageShouldBeAccessible(); + // metrics: cscli/ban = 1, clean/bypass = 1 + }); + + it("Should set a Blass URL", async () => { + await goToAdmin(); + await onAdminGoToSettingsPage(); + await fillInput("crowdsec_api_url", FAKE_BLAAS_URL); + await onAdminSaveSettings(false); + // There can be other warning like "New WP version available" + const warnings = await page + .locator(".notice-warning") + .allTextContents(); + const found = warnings.some((text) => + text.includes('You have just defined a "Block As A Service" URL'), + ); + expect(found).toBe(true); + }); + + it("Should block TLS auth", async () => { + await goToAdmin(); + await onAdminGoToSettingsPage(); + await selectByName("crowdsec_auth_type", "tls"); + await fillInput( + "crowdsec_tls_key_path", + `/var/www/html/${BOUNCER_KEY_FILE}`, + ); + await setToggle("crowdsec_tls_verify_peer", true); + await fillInput( + "crowdsec_tls_ca_cert_path", + `/var/www/html/${CA_CERT_FILE}`, + ); + await fillInput( + "crowdsec_tls_cert_path", + `/var/www/html/${BOUNCER_CERT_FILE}`, + ); + + await onAdminSaveSettings(false); + await expect(page).toHaveText( + ".notice-error", + "Rolling back to Bouncer API key authentication", + ); + }); + + it("Should block Live Mode", async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await setToggle("crowdsec_stream_mode", false); + + await onAdminSaveSettings(false); + await expect(page).toHaveText( + ".notice-error", + "Rolling back to Stream mode.", + ); + }); + + it("Should block Usage Metrics", async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await setToggle("crowdsec_usage_metrics", true); + + await onAdminSaveSettings(false); + await expect(page).toHaveText( + ".notice-error", + `Pushing usage metrics with a Block as a Service LAPI (${FAKE_BLAAS_URL}) is not supported.`, + ); + }); + + it('Should block AppSec"', async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await setToggle("crowdsec_use_appsec", true); + + await onAdminSaveSettings(false); + await expect(page).toHaveText( + ".notice-error", + `Using AppSec with a Block as a Service LAPI (${FAKE_BLAAS_URL}) is not supported.`, + ); + }); + + it("Should interact with usage metrics", async () => { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await expect(page).toHaveText("#metrics-cscli-ban", "ban: 1"); + await expect(page).toHaveText("#metrics-total-ban", "ban: 1"); + const count = await page + .locator("#crowdsec_push_usage_metrics") + .count(); + await expect(count).toBe(0); + + await page.click("#crowdsec_reset_usage_metrics"); + + await expect(page).toHaveText( + "#wpbody-content > div.wrap > div.notice.notice-success", + "CrowdSec usage metrics have been reset successfully.", + ); + await expect(page).toHaveText("#metrics-no-new", "No new metrics"); + }); +}); diff --git a/tests/e2e-ddev/__tests__/3-live-mode-more.js b/tests/e2e-ddev/__tests__/3-live-mode-more.js index d853593..a340691 100644 --- a/tests/e2e-ddev/__tests__/3-live-mode-more.js +++ b/tests/e2e-ddev/__tests__/3-live-mode-more.js @@ -125,15 +125,9 @@ describe(`Run in Live mode`, () => { await expect(page).toHaveText("#metrics-cscli-captcha", "captcha: 2"); await expect(page).toHaveText("#metrics-cscli-ban", "ban: 1"); await expect(page).toHaveText("#metrics-total-ban", "ban: 1"); - await expect(page).toHaveText( - "#metrics-total-captcha", - "captcha: 2", - ); + await expect(page).toHaveText("#metrics-total-captcha", "captcha: 2"); // In multisite, it's 4, not sure why...(perhaps some admin pages is considered as "non admin" and bounced) - await expect(page).toMatchText( - "#metrics-total-bypass", - /bypass: 2|4/, - ); + await expect(page).toMatchText("#metrics-total-bypass", /bypass: 2|4/); }); it("Should push usage metrics", async () => { @@ -166,7 +160,7 @@ describe(`Run in Live mode`, () => { "#wpbody-content > div.wrap > div.notice.notice-success", "CrowdSec usage metrics have just been pushed.", ); - await expect(page).toHaveText("#metrics-no-new", "No new metrics since the last push"); + await expect(page).toHaveText("#metrics-no-new", "No new metrics"); // Disable usage metrics for future tests await goToAdmin(); diff --git a/tests/e2e-ddev/__tests__/5-stream-mode.js b/tests/e2e-ddev/__tests__/5-stream-mode.js index e215ab7..0899072 100644 --- a/tests/e2e-ddev/__tests__/5-stream-mode.js +++ b/tests/e2e-ddev/__tests__/5-stream-mode.js @@ -31,7 +31,7 @@ describe(`Run in Stream mode`, () => { }); it("Should activate WP-CRON", async () => { - // Enable and disable usage metrcis before all to make WP-cron working + // Enable and disable usage metrics before all to make WP-cron working await goToAdmin(); await onAdminGoToAdvancedPage(); await onAdvancedPageEnableUsageMetrics(); diff --git a/tests/e2e-ddev/utils/constants.js b/tests/e2e-ddev/utils/constants.js index f821367..48e6b91 100644 --- a/tests/e2e-ddev/utils/constants.js +++ b/tests/e2e-ddev/utils/constants.js @@ -41,6 +41,7 @@ const CA_CERT_FILE = `crowdsec/tls/ca-chain.pem`; const BOUNCER_CERT_FILE = `crowdsec/tls/bouncer.pem`; const BOUNCER_KEY_FILE = `crowdsec/tls/bouncer-key.pem`; const DEBUG_LOG_PATH = `${VARHTML_PATH}wp-content/uploads/crowdsec/logs/debug.log`; +const FAKE_BLAAS_URL = "https://admin.api.crowdsec.net/v1/integrations/1234567"; module.exports = { ADMIN_URL, @@ -85,4 +86,5 @@ module.exports = { BOUNCER_KEY_FILE, VARHTML_PATH, MULTISITE, + FAKE_BLAAS_URL, }; From 53c23b6a6a5c4354a9e03ae462293c55b402d02a Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 2 May 2025 15:24:13 +0900 Subject: [PATCH 08/10] test(multisite): Skip test as the decativation callback on checkbox is not working --- tests/e2e-ddev/__tests__/14-blaas.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/e2e-ddev/__tests__/14-blaas.js b/tests/e2e-ddev/__tests__/14-blaas.js index 4474059..4e1425f 100644 --- a/tests/e2e-ddev/__tests__/14-blaas.js +++ b/tests/e2e-ddev/__tests__/14-blaas.js @@ -14,11 +14,9 @@ const { setToggle, fillInput, onAdvancedPageEnableUsageMetrics, - getFileContent, deleteFileContent, onAdvancedPageDisableUsageMetrics, runCacheAction, - wait, selectByName, } = require("../utils/helpers"); @@ -29,6 +27,7 @@ const { BOUNCER_KEY_FILE, CA_CERT_FILE, BOUNCER_CERT_FILE, + MULTISITE, } = require("../utils/constants"); describe("Check BLaaS URL behavior", () => { @@ -113,15 +112,21 @@ describe("Check BLaaS URL behavior", () => { }); it("Should block Live Mode", async () => { - await goToAdmin(); - await onAdminGoToAdvancedPage(); - await setToggle("crowdsec_stream_mode", false); - - await onAdminSaveSettings(false); - await expect(page).toHaveText( - ".notice-error", - "Rolling back to Stream mode.", - ); + if (MULTISITE) { + console.warn( + "In multisite mode, deactivation callback for checkboxes doesn't work, so we skip this test", + ); + } else { + await goToAdmin(); + await onAdminGoToAdvancedPage(); + await setToggle("crowdsec_stream_mode", false); + + await onAdminSaveSettings(false); + await expect(page).toHaveText( + ".notice-error", + "Rolling back to Stream mode.", + ); + } }); it("Should block Usage Metrics", async () => { From 7f49d1b3fac54240146e57e04449dc8df0754a29 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 2 May 2025 17:35:48 +0900 Subject: [PATCH 09/10] feat(*): Bump to release 2.10.0 --- .../workflows/end-to-end-auto-prepend-test-suite.yml | 1 - .github/workflows/end-to-end-multisite.yml | 1 - .github/workflows/end-to-end-test-suite.yml | 1 - CHANGELOG.md | 11 +++++++++-- crowdsec.php | 4 ++-- inc/Constants.php | 2 +- readme.txt | 7 ++++++- 7 files changed, 18 insertions(+), 9 deletions(-) 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 c547ebf..4d17fce 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -41,7 +41,6 @@ jobs: name: End-to-end auto-prepend-file mode test suite runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'chore(') }} env: EXTENSION_NAME: "CrowdSec_Bouncer" diff --git a/.github/workflows/end-to-end-multisite.yml b/.github/workflows/end-to-end-multisite.yml index e270457..460b797 100644 --- a/.github/workflows/end-to-end-multisite.yml +++ b/.github/workflows/end-to-end-multisite.yml @@ -32,7 +32,6 @@ jobs: name: End-to-end Multisite runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'chore(') }} env: EXTENSION_NAME: "CrowdSec_Bouncer" diff --git a/.github/workflows/end-to-end-test-suite.yml b/.github/workflows/end-to-end-test-suite.yml index 6921d3e..7fa9a27 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -48,7 +48,6 @@ jobs: name: End-to-end test suite runs-on: ubuntu-latest - if: ${{ !contains(github.event.head_commit.message, 'chore(') }} env: EXTENSION_NAME: "CrowdSec_Bouncer" diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ed75e..286febc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [2.10.0](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.10.0) - 2025-05-?? +## [2.10.0](https://github.com/crowdsecurity/cs-wordpress-bouncer/releases/tag/v2.10.0) - 2025-05-09 [_Compare with previous release_](https://github.com/crowdsecurity/cs-wordpress-bouncer/compare/v2.9.0...v2.10.0) +### Changed + +- Handle BLaaS (Blocklist as a Service) LAPI specific behavior: + - Block some settings if BLaaS URL is detected: Live Mode, TLS authentication, AppSec component, Usage Metrics Push. + - Show a new `Reset usage metrics now` button. + ### Added -- Add usage metrics preview in UI +- Add Usage Metrics table in UI - Add compatibility with WordPress 6.8 + --- diff --git a/crowdsec.php b/crowdsec.php index ae65795..7dcc468 100644 --- a/crowdsec.php +++ b/crowdsec.php @@ -4,7 +4,7 @@ * 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, appsec - * Version: 2.9.0 + * Version: 2.10.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.8 - * Stable tag: 2.9.0 + * Stable tag: 2.10.0 * Text Domain: crowdsec-wp * First release: 2021. */ diff --git a/inc/Constants.php b/inc/Constants.php index b89a75e..c671d9f 100644 --- a/inc/Constants.php +++ b/inc/Constants.php @@ -21,5 +21,5 @@ class Constants extends LibConstants public const BOUNCER_NAME = 'wordpress-bouncer'; public const DEFAULT_BASE_FILE_PATH = __DIR__ . '/../../../../wp-content/uploads/crowdsec/'; public const STANDALONE_CONFIG_PATH = __DIR__ . '/standalone-settings.php'; - public const VERSION = 'v2.9.0'; + public const VERSION = 'v2.10.0'; } diff --git a/readme.txt b/readme.txt index 72a66d4..ef8ab69 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://crowdsec.net/ Tags: security, captcha, ip-blocker, crowdsec, hacker-protection Requires at least: 4.9 Tested up to: 6.8 -Stable tag: 2.9.0 +Stable tag: 2.10.0 Requires PHP: 7.2 License: MIT License URI: https://opensource.org/licenses/MIT @@ -44,6 +44,11 @@ This WordPress plugin is a "bouncer", which purpose is to block detected attacks == Changelog == += 2.10 (2025-05-09) = + +- Add Usage Metrics table in UI +- Handle BLaaS LAPI specific behavior + = 2.9 (2025-02-21) = - Add usage metrics support From 03d99765be7c984a6d021426fd8e57a163e39da7 Mon Sep 17 00:00:00 2001 From: Julien Loizelet Date: Fri, 9 May 2025 10:55:10 +0900 Subject: [PATCH 10/10] ci(timeout): Add 100ms to delay to avoid failing tests --- .github/workflows/end-to-end-auto-prepend-test-suite.yml | 2 +- .github/workflows/end-to-end-multisite.yml | 2 +- .github/workflows/end-to-end-test-suite.yml | 2 +- .github/workflows/release-test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 4d17fce..9bc7494 100644 --- a/.github/workflows/end-to-end-auto-prepend-test-suite.yml +++ b/.github/workflows/end-to-end-auto-prepend-test-suite.yml @@ -220,7 +220,7 @@ jobs: 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 + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 600ms - name: Run AppSec timeout tests uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test diff --git a/.github/workflows/end-to-end-multisite.yml b/.github/workflows/end-to-end-multisite.yml index 460b797..41ee5d8 100644 --- a/.github/workflows/end-to-end-multisite.yml +++ b/.github/workflows/end-to-end-multisite.yml @@ -212,7 +212,7 @@ jobs: subsite: ${{ matrix.subsite }} - name: Prepare CrowdSec for AppSec timeout tests - run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 500ms + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 600ms - name: Run AppSec timeout tests uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test diff --git a/.github/workflows/end-to-end-test-suite.yml b/.github/workflows/end-to-end-test-suite.yml index 7fa9a27..eb663a5 100644 --- a/.github/workflows/end-to-end-test-suite.yml +++ b/.github/workflows/end-to-end-test-suite.yml @@ -205,7 +205,7 @@ jobs: 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 + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 600ms - name: Run AppSec timeout tests uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index 1ac939e..e333492 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -236,7 +236,7 @@ jobs: 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 + run: ddev exec -s crowdsec tc qdisc add dev eth0 root netem delay 600ms - name: Run AppSec timeout tests uses: ./wp-content/plugins/crowdsec/.github/workflows/end-to-end/run-single-test

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