diff --git a/.circleci/config.yml b/.circleci/config.yml index 74a10e308..fb202186f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,7 @@ jobs: MYSQL_DATABASE: upont MYSQL_USER: upont MYSQL_PASSWORD: upont + - image: redis:5-alpine working_directory: ~/upont steps: - run: sudo apt update && sudo apt install -y libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng-dev zlib1g-dev diff --git a/.env.dist b/.env.dist index 4ba976b33..1cb5a5208 100644 --- a/.env.dist +++ b/.env.dist @@ -43,3 +43,8 @@ APP_SECRET=c97499ba41de9fe24ad8d2ab083a7e66 #TRUSTED_PROXIES=127.0.0.1,127.0.0.2 #TRUSTED_HOSTS=localhost,example.com ###< symfony/framework-bundle ### + +###> snc/redis-bundle ### +# passwords that contain special characters (@, %, :, +) must be urlencoded +REDIS_URL=redis://localhost +###< snc/redis-bundle ### diff --git a/back/composer.json b/back/composer.json index 7376d3946..f38067d47 100644 --- a/back/composer.json +++ b/back/composer.json @@ -37,7 +37,10 @@ "nesbot/carbon": "^2.11", "sensio/framework-extra-bundle": "^5.2", "sensiolabs/security-checker": "^5.0", + "noxlogic/ratelimit-bundle": "^1.14", + "predis/predis": "^1.1", "sentry/sentry-symfony": "~2.0", + "snc/redis-bundle": "^2.1", "stof/doctrine-extensions-bundle": "~1.1", "symfony/asset": "^4.2", "symfony/console": "^4.2", diff --git a/back/composer.lock b/back/composer.lock index b2077379c..2cae4ab49 100644 --- a/back/composer.lock +++ b/back/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": "b14fe2251fda993d4216c13ec25ac352", + "content-hash": "2c3606ae85d0b20c57c0aa88717627e4", "packages": [ { "name": "behat/transliterator", @@ -2686,6 +2686,63 @@ ], "time": "2019-03-29T12:23:12+00:00" }, + { + "name": "noxlogic/ratelimit-bundle", + "version": "1.14.0", + "target-dir": "Noxlogic/RateLimitBundle", + "source": { + "type": "git", + "url": "https://github.com/jaytaph/RateLimitBundle.git", + "reference": "6bd1126cafe69635fec02f374e46f413544469d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jaytaph/RateLimitBundle/zipball/6bd1126cafe69635fec02f374e46f413544469d5", + "reference": "6bd1126cafe69635fec02f374e46f413544469d5", + "shasum": "" + }, + "require": { + "sensio/framework-extra-bundle": "^2.3|^3.0|^4.0|^5.0", + "symfony/framework-bundle": "^2.3|^3.0|^4.0" + }, + "require-dev": { + "doctrine/cache": "^1.5", + "friendsofsymfony/oauth-server-bundle": "^1.5", + "phpunit/phpunit": "^4.8|^5.0", + "predis/predis": "^0.8|^1.1", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" + }, + "suggest": { + "doctrine/doctrine-cache-bundle": "Use Doctrine Cache as a storage engine.", + "friendsofsymfony/oauth-server-bundle": "Throttle using OAuth access tokens.", + "leaseweb/memcache-bundle": "Use Memcache as a storage engine.", + "snc/redis-bundle": "Use Redis as a storage engine." + }, + "type": "symfony-bundle", + "autoload": { + "psr-0": { + "Noxlogic\\RateLimitBundle": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joshua Thijssen", + "email": "jthijssen@noxlogic.nl" + } + ], + "description": "This bundle provides functionality to limit calls to actions based on rate limits", + "keywords": [ + "api", + "rest", + "x-rate-limit" + ], + "time": "2019-02-19T08:49:10+00:00" + }, { "name": "ocramius/package-versions", "version": "1.4.0", @@ -3100,6 +3157,56 @@ ], "time": "2015-07-25T16:39:46+00:00" }, + { + "name": "predis/predis", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/f0210e38881631afeafb56ab43405a92cafd9fd1", + "reference": "f0210e38881631afeafb56ab43405a92cafd9fd1", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete Redis client for PHP and HHVM", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2016-06-16T16:22:20+00:00" + }, { "name": "psr/cache", "version": "1.0.1", @@ -3540,6 +3647,72 @@ ], "time": "2019-01-28T09:23:48+00:00" }, + { + "name": "snc/redis-bundle", + "version": "2.1.9", + "source": { + "type": "git", + "url": "https://github.com/snc/SncRedisBundle.git", + "reference": "af3ac967b0351ff880f646486bff87247abb5286" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/snc/SncRedisBundle/zipball/af3ac967b0351ff880f646486bff87247abb5286", + "reference": "af3ac967b0351ff880f646486bff87247abb5286", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/framework-bundle": "^2.7 || ^3.0 || ^4.0", + "symfony/yaml": "^2.7 || ^3.0 || ^4.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "predis/predis": "^1.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0", + "symfony/phpunit-bridge": "^2.7 || ^3.0 || ^4.0" + }, + "suggest": { + "monolog/monolog": "If you want to use the monolog redis handler.", + "predis/predis": "If you want to use predis.", + "symfony/console": "If you want to use commands to interact with the redis database", + "symfony/proxy-manager-bridge": "If you want to lazy-load some services" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Snc\\RedisBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Henrik Westphal", + "email": "henrik.westphal@gmail.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/snc/SncRedisBundle/contributors" + } + ], + "description": "A Redis bundle for Symfony", + "homepage": "https://github.com/snc/SncRedisBundle", + "keywords": [ + "nosql", + "redis", + "symfony" + ], + "time": "2019-02-20T07:03:43+00:00" + }, { "name": "stof/doctrine-extensions-bundle", "version": "v1.3.0", diff --git a/back/config/bundles.php b/back/config/bundles.php index 3b9d6f1c7..b95a28848 100644 --- a/back/config/bundles.php +++ b/back/config/bundles.php @@ -20,4 +20,6 @@ Sentry\SentryBundle\SentryBundle::class => ['prod' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true], + Noxlogic\RateLimitBundle\NoxlogicRateLimitBundle::class => ['all' => true], + Snc\RedisBundle\SncRedisBundle::class => ['all' => true], ]; diff --git a/back/config/packages/noxlogic_rate_limit.yaml b/back/config/packages/noxlogic_rate_limit.yaml new file mode 100644 index 000000000..4a5087810 --- /dev/null +++ b/back/config/packages/noxlogic_rate_limit.yaml @@ -0,0 +1,4 @@ +noxlogic_rate_limit: + storage_engine: redis + display_headers: false + rate_response_message: null diff --git a/back/config/packages/snc_redis.yaml b/back/config/packages/snc_redis.yaml new file mode 100644 index 000000000..1ad969779 --- /dev/null +++ b/back/config/packages/snc_redis.yaml @@ -0,0 +1,6 @@ +snc_redis: + clients: + default: + type: predis + alias: default + dsn: "%env(REDIS_URL)%" diff --git a/back/config/services.yaml b/back/config/services.yaml index d069af3de..1f405fbd1 100644 --- a/back/config/services.yaml +++ b/back/config/services.yaml @@ -283,4 +283,6 @@ services: tags: - { name: form.type, alias: user } - BOMO\IcalBundle\Provider\IcsProvider: '@bomo_ical.ics_provider' + App\Listener\RateLimitGenerateKeyListener: + tags: + - { name: kernel.event_listener, event: ratelimit.generate.key, method: onGenerateKey } diff --git a/back/phpunit.xml.circle b/back/phpunit.xml.circle index 26825ee6a..5b6d89296 100644 --- a/back/phpunit.xml.circle +++ b/back/phpunit.xml.circle @@ -24,6 +24,11 @@ + + + + + diff --git a/back/phpunit.xml.dist b/back/phpunit.xml.dist index 8cf0fbbca..687648e2e 100644 --- a/back/phpunit.xml.dist +++ b/back/phpunit.xml.dist @@ -23,6 +23,11 @@ + + + + + diff --git a/back/src/Controller/CoreController.php b/back/src/Controller/CoreController.php index 8f766cd3e..95ac5a39c 100644 --- a/back/src/Controller/CoreController.php +++ b/back/src/Controller/CoreController.php @@ -99,6 +99,18 @@ public function dirtyAction() * @Operation( * tags={"Général"}, * summary="Se loger et recevoir un JSON Web Token", + * @SWG\Parameter( + * name="username", + * in="formData", + * required=true, + * type="string" + * ), + * @SWG\Parameter( + * name="password", + * in="formData", + * required=true, + * type="string" + * ), * @SWG\Response( * response="200", * description="Requête traitée avec succès" @@ -106,10 +118,6 @@ public function dirtyAction() * @SWG\Response( * response="401", * description="Mauvaise combinaison username/password ou champ nom rempli" - * ), - * @SWG\Response( - * response="502", - * description="Erreur Proxy : l'utilisateur se connecte pour la première fois, mais le proxy DSI n'est pas configuré" * ) * ) * diff --git a/back/src/Controller/Publications/EventsController.php b/back/src/Controller/Publications/EventsController.php index 8eaabcb23..7a4619e64 100644 --- a/back/src/Controller/Publications/EventsController.php +++ b/back/src/Controller/Publications/EventsController.php @@ -12,6 +12,7 @@ use App\Service\NotifyService; use Carbon\Carbon; use Nelmio\ApiDocBundle\Annotation\Operation; +use Noxlogic\RateLimitBundle\Annotation\RateLimit; use Swagger\Annotations as SWG; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -374,6 +375,12 @@ public function deleteEventAction($slug) * @Operation( * tags={"Publications"}, * summary="Shotgunne un événement", + * @SWG\Parameter( + * name="motivation", + * in="formData", + * required=true, + * type="string", + * ), * @SWG\Response( * response="201", * description="Requête traitée avec succès avec création d’un document" @@ -392,6 +399,7 @@ public function deleteEventAction($slug) * ) * ) * + * @RateLimit(limit=3, period=10) * @Route("/events/{slug}/shotgun", methods={"POST"}) */ public function postEventUserAction(Request $request, $slug) @@ -412,7 +420,7 @@ public function postEventUserAction(Request $request, $slug) if (count($userEvent) != 0) throw new BadRequestHttpException('Tu es déjà inscrit !'); - //S'il est l'heure, on accepte le shotgun + // S'il est l'heure, on accepte le shotgun if (Carbon::now() >= $event->getShotgunDate()) { $userEvent = new EventUser(); $userEvent->setEvent($event); diff --git a/back/src/Listener/RateLimitGenerateKeyListener.php b/back/src/Listener/RateLimitGenerateKeyListener.php new file mode 100644 index 000000000..15addadfe --- /dev/null +++ b/back/src/Listener/RateLimitGenerateKeyListener.php @@ -0,0 +1,29 @@ +tokenStorage = $tokenStorage; + } + + /** + * @param GenerateKeyEvent $event + */ + public function onGenerateKey(GenerateKeyEvent $event) + { + $token = $this->tokenStorage->getToken(); + + $event->addToKey($token->getUsername()); + } +} diff --git a/back/symfony.lock b/back/symfony.lock index 93ed02a36..608f7361c 100644 --- a/back/symfony.lock +++ b/back/symfony.lock @@ -170,6 +170,9 @@ "nesbot/carbon": { "version": "1.24.2" }, + "noxlogic/ratelimit-bundle": { + "version": "1.14.0" + }, "ocramius/proxy-manager": { "version": "2.1.1" }, @@ -191,6 +194,9 @@ "phpoption/phpoption": { "version": "1.5.0" }, + "predis/predis": { + "version": "v1.1.1" + }, "psr/cache": { "version": "1.0.1" }, @@ -236,6 +242,15 @@ "ref": "fa1a2dfc020798cd7076b5419596e72dca07047a" } }, + "snc/redis-bundle": { + "version": "2.0", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "master", + "version": "2.0", + "ref": "e9c58bfc414cfb7f06e8e5ae9f589868498f5d6a" + } + }, "stof/doctrine-extensions-bundle": { "version": "1.2", "recipe": { diff --git a/docker-compose.yml b/docker-compose.yml index 5c86a6904..964806147 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,10 @@ services: - 127.0.0.1:10102:80 restart: always + redis: + image: redis:5-alpine + restart: always + upont-back: image: quay.io/kiclubinfo/upont-back:${TAG:-latest} volumes: @@ -43,8 +47,10 @@ services: - upont-uploads:/app/public/uploads:rw depends_on: - db + - redis environment: - DATABASE_HOST=db + - REDIS_URL=redis://redis env_file: - .env restart: always