diff --git a/galette/composer.json b/galette/composer.json index f33d8d0058..b9d7244760 100644 --- a/galette/composer.json +++ b/galette/composer.json @@ -50,7 +50,8 @@ "doctrine/annotations": "^1.8", "laminas/laminas-servicemanager": "3.7", "symfony/polyfill-php80": "^1.23", - "ezyang/htmlpurifier": "^4.13" + "ezyang/htmlpurifier": "^4.13", + "slim/csrf": "0.8.3" }, "require-dev": { "atoum/atoum": "dev-master", diff --git a/galette/composer.lock b/galette/composer.lock index 79e9819a4e..511eb07ece 100644 --- a/galette/composer.lock +++ b/galette/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "47d56d47701d9038105bd93cf3b44a02", + "content-hash": "be12f467246cd29921b64f8f84dc17a8", "packages": [ { "name": "akrabat/rka-slim-session-middleware", @@ -2213,6 +2213,56 @@ }, "time": "2021-04-09T13:42:10+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "php-di/invoker", "version": "2.3.2", @@ -2808,6 +2858,60 @@ }, "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "slim/csrf", + "version": "0.8.3", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Csrf.git", + "reference": "5f2bcf5d89adf86dc0455a32bea84d912ab466a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/5f2bcf5d89adf86dc0455a32bea84d912ab466a7", + "reference": "5f2bcf5d89adf86dc0455a32bea84d912ab466a7", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.1|^2.0|^9.99", + "php": ">=5.5.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "slim/slim": "~3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 CSRF protection middleware", + "homepage": "http://slimframework.com", + "keywords": [ + "csrf", + "framework", + "middleware", + "slim" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Csrf/issues", + "source": "https://github.com/slimphp/Slim-Csrf/tree/master" + }, + "time": "2018-08-22T16:12:18+00:00" + }, { "name": "slim/flash", "version": "0.4.0", diff --git a/galette/includes/dependencies.php b/galette/includes/dependencies.php index 8cf6b47d92..c1e86bebb0 100644 --- a/galette/includes/dependencies.php +++ b/galette/includes/dependencies.php @@ -432,6 +432,31 @@ ) ); +$container->set( + 'csrf', + function (ContainerInterface $c) { + $storage = null; + $guard = new \Slim\Csrf\Guard( + 'csrf', + $storage, + null, + 200, + 16, + true + ); + + $guard->setFailureCallable(function ($request, $response, $next) { + Analog::log( + 'CSRF check has failed', + Analog::CRITICAL + ); + throw new \RuntimeException(_T('Failed CSRF check!')); + }); + + return $guard; + } +); + //For bad existing globals can be used... global $translator, $i18n; if ( diff --git a/galette/includes/main.inc.php b/galette/includes/main.inc.php index 7c1f190fcb..013bbbc5c9 100644 --- a/galette/includes/main.inc.php +++ b/galette/includes/main.inc.php @@ -92,6 +92,8 @@ // Set up dependencies require GALETTE_ROOT . '/includes/dependencies.php'; +$app->add(new \Galette\Middleware\SmartyCsrf($app->getContainer())); +$app->add($app->getContainer()->get('csrf')); if ($needs_update) { $app->add( diff --git a/galette/lib/Galette/Middleware/SmartyCsrf.php b/galette/lib/Galette/Middleware/SmartyCsrf.php new file mode 100644 index 0000000000..2305f5028b --- /dev/null +++ b/galette/lib/Galette/Middleware/SmartyCsrf.php @@ -0,0 +1,96 @@ +. + * + * @category Core + * @package Galette + * + * @author Johan Cwiklinski + * @copyright 2021 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link http://galette.tuxfamily.org + * @since Available since 0.9.6dev - 2021-11-08 + */ + +namespace Galette\Middleware; + +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; +use Analog\Analog; +use DI\Container; + +/** + * Galette CSRF middleware + * + * @category Middleware + * @name SmartyCsrf + * @package Galette + * @author Johan Cwiklinski + * @copyright 2020 The Galette Team + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version + * @link http://galette.tuxfamily.org + * @since Available since 0.9.4dev - 2020-05-06 + */ +class SmartyCsrf +{ + private $smarty; + private $csrf; + + /** + * Constructor + * + * @param Container $container Container instance + */ + public function __construct(Container $container) + { + $view = $container->get('Slim\Views\Smarty'); + $this->smarty = $view->getSmarty(); + $this->csrf = $container->get('csrf'); + } + + /** + * Middleware invokable class + * + * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request + * @param \Psr\Http\Message\ResponseInterface $response PSR7 response + * @param callable $next Next middleware + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function __invoke(Request $request, Response $response, $next): Response + { + $nameKey = $this->csrf->getTokenNameKey(); + $valueKey = $this->csrf->getTokenValueKey(); + $name = $request->getAttribute($nameKey); + $value = $request->getAttribute($valueKey); + + $this->smarty->assign('csrf_name_key', $nameKey); + $this->smarty->assign('csrf_value_key', $valueKey); + $this->smarty->assign('csrf_name', $name); + $this->smarty->assign('csrf_value', $value); + + return $next($request, $response); + } +} diff --git a/galette/templates/default/admintools.tpl b/galette/templates/default/admintools.tpl index b1a9028d60..e72ac324bd 100644 --- a/galette/templates/default/admintools.tpl +++ b/galette/templates/default/admintools.tpl @@ -31,10 +31,11 @@

- + {include file="forms_types/csrf.tpl"}
{/block} diff --git a/galette/templates/default/advanced_search.tpl b/galette/templates/default/advanced_search.tpl index 4c8767959b..1aa267893b 100644 --- a/galette/templates/default/advanced_search.tpl +++ b/galette/templates/default/advanced_search.tpl @@ -312,6 +312,7 @@ + {include file="forms_types/csrf.tpl"} {/block} diff --git a/galette/templates/default/ajouter_contribution.tpl b/galette/templates/default/ajouter_contribution.tpl index 69cb889be1..42ee6c91fd 100644 --- a/galette/templates/default/ajouter_contribution.tpl +++ b/galette/templates/default/ajouter_contribution.tpl @@ -184,6 +184,7 @@ {/if} + {include file="forms_types/csrf.tpl"} {else} {* No members *}
diff --git a/galette/templates/default/ajouter_transaction.tpl b/galette/templates/default/ajouter_transaction.tpl index 18a3ddde14..2f34546e46 100644 --- a/galette/templates/default/ajouter_transaction.tpl +++ b/galette/templates/default/ajouter_transaction.tpl @@ -46,6 +46,7 @@ + {include file="forms_types/csrf.tpl"}

{_T string="NB : The mandatory fields are in"} {_T string="red"}

diff --git a/galette/templates/default/attendance_sheet_details.tpl b/galette/templates/default/attendance_sheet_details.tpl index a52d37ffb0..bd9c2254e3 100644 --- a/galette/templates/default/attendance_sheet_details.tpl +++ b/galette/templates/default/attendance_sheet_details.tpl @@ -31,6 +31,7 @@ {foreach $selection as $member} {/foreach} + {include file="forms_types/csrf.tpl"}

{if not $ajax} diff --git a/galette/templates/default/change_passwd.tpl b/galette/templates/default/change_passwd.tpl index c793ad2cb5..187200b217 100644 --- a/galette/templates/default/change_passwd.tpl +++ b/galette/templates/default/change_passwd.tpl @@ -16,5 +16,6 @@ + {include file="forms_types/csrf.tpl"} {/block} diff --git a/galette/templates/default/common_scripts.tpl b/galette/templates/default/common_scripts.tpl index f4e1dc719d..499a7e8099 100644 --- a/galette/templates/default/common_scripts.tpl +++ b/galette/templates/default/common_scripts.tpl @@ -1,5 +1,23 @@