From 5c8bd20761cc57c7739271602457370a908c0d7f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 23 Nov 2016 21:49:56 +0300 Subject: [PATCH 01/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=20README.md=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f96d4ad..1f0aeab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Accounts Ely.by -## Развёртывание dev +## Развёртывание dev [backend] Предварительно нужно установить [git](https://git-scm.com/downloads), [docker](https://docs.docker.com/engine/installation/) и его @@ -15,8 +15,8 @@ За тем сливаем репозиторий: ```sh -git clone git@gitlab.com:elyby/account.git account.ely.by -cd account.ely.by.local +git clone git@gitlab.ely.by:elyby/accounts.git account.ely.by +cd account.ely.by ``` Далее нужно создать `.env`, `docker-compose.yml` и `id_rsa` файлы: @@ -27,12 +27,12 @@ cp docker-compose.dev.yml docker-compose.yml cp ~/.ssh/id_rsa id_rsa # Использовать ссылку нельзя ``` -Касательно файла id_rsa: часть зависимостей находятся в наших приватных репозиториях, получить +**Касательно файла id_rsa**: часть зависимостей находятся в наших приватных репозиториях, получить доступ куда можно только в том случае, если в контейнере окажется ключ, который имеет доступ к этим репозиториям. -Все вышеперечисленные файла находятся под gitignore, так что с полученными файлами можно произвести -все необходимые манипуляции под конкретный кейс использования. **В файле `.env` обязательно следует +Все вышеперечисленные файлы находятся под gitignore, так что с конечными файлами можно произвести +все необходимые манипуляции под конкретную задачу разработки. **В файле `.env` обязательно следует задать `JWT_USER_SECRET`, иначе авторизация на бекенде не заработает.** После этого просто выполняем старт всех контейнеров: @@ -41,10 +41,50 @@ cp ~/.ssh/id_rsa id_rsa # Использовать ссылку нельзя docker-compose up -d ``` -Они автоматически сбилдятся и начнут свою работу. +Контейнеры автоматически сбилдятся и начнут свою работу. + +## Развёртывание dev [frontend] + +Чтобы поднять сборку frontend приложения, необходимо иметь установленный в системе [Node.js](https://nodejs.org) +версии 5.x или 6.x, а так же npm 3-ей версии (`npm i -g npm` для обновления). + +За тем переходим в папку `frontend` и устанавливаем зависимости: + +```sh +cd frontend +npm i +``` + +После того, как все зависимости будут установлены, можно поднять dev-сервер. Здесь есть 2 пути: можно, следуя +инструкции выше, поднять backend на своей машине через Docker. Если же разработка не привязывается к специфичной +версии backend, то более быстрым и удобным способ будет использовать наш dev-сервер, расположенный под адресу +https://dev.account.ely.by. + +В любом из случаев необходимо в папке `frontend/config` скопировать файл `template.env.js` в `env.js` (находится +под .gitignore) и указать в параметре `apiHost` или свой локальный сервер (тот хост, что был указан в .env +как `VIRTUAL_HOST`), или указав просто `https://dev.account.ely.by`. + +После того, как это будет сделано, запускаем dev-сервер (находясь в папке frontend): + +``` +npm start +``` + +dev-сервер поднимется на 8080 порту и будет доступен по адресу http://localhost:8080. ### Как влезть в работающий контейнер +Начиная с версии docker-compose 1.9.0, появилась команда `docker-compose exec`, которая позволяет выполнить +на работающем контейнере произвольную команду, основываясь на имени сервиса в compose файле. + +``` +docker-compose exec app bash +``` + +------------------------ + +_// Старый вариант_ + Сперва, с помощью команды `docker ps` мы увидим все запущенные контейнеры. Нас интересуют значения из первой колонки CONTAINER ID или NAMES. Узнать, чему они соответствуют можно прочитав название IMAGE из 2 колонки. Чтобы выполнить команду внутри работабщего контейнера, нужно выполнить: From 7c9e856453a35d15a5a814f64edd3cb724b52c81 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 23 Nov 2016 23:41:33 +0300 Subject: [PATCH 02/41] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=B8=20=D0=BE=D1=82=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20CleanupContr?= =?UTF-8?q?oller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console/controllers/CleanupController.php | 22 +++++++++++++ .../fixtures/OauthAccessTokenFixture.php | 17 ++++++++++ .../fixtures/data/oauth-access-tokens.php | 13 ++++++++ .../common/fixtures/data/oauth-clients.php | 4 +-- .../common/fixtures/data/oauth-sessions.php | 7 +++++ .../controllers/CleanupControllerTest.php | 31 +++++++++++++++++++ 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 console/controllers/CleanupController.php create mode 100644 tests/codeception/common/fixtures/OauthAccessTokenFixture.php create mode 100644 tests/codeception/common/fixtures/data/oauth-access-tokens.php create mode 100644 tests/codeception/console/unit/controllers/CleanupControllerTest.php diff --git a/console/controllers/CleanupController.php b/console/controllers/CleanupController.php new file mode 100644 index 0000000..f08985b --- /dev/null +++ b/console/controllers/CleanupController.php @@ -0,0 +1,22 @@ +andWhere(['<', 'expire_time', time()]) + ->each(1000); + + foreach($accessTokens as $token) { + /** @var OauthAccessToken $token */ + $token->delete(); + } + + return self::EXIT_CODE_NORMAL; + } + +} diff --git a/tests/codeception/common/fixtures/OauthAccessTokenFixture.php b/tests/codeception/common/fixtures/OauthAccessTokenFixture.php new file mode 100644 index 0000000..d51f14f --- /dev/null +++ b/tests/codeception/common/fixtures/OauthAccessTokenFixture.php @@ -0,0 +1,17 @@ + [ + 'access_token' => '07541285-831e-1e47-e314-b950309a6fca', + 'session_id' => 1, + 'expire_time' => time() + 3600, + ], + 'admin-ely-expired' => [ + 'access_token' => '2977ec21-3022-96f8-544db-2e1df936908', + 'session_id' => 1, + 'expire_time' => time() - 3600, + ], +]; diff --git a/tests/codeception/common/fixtures/data/oauth-clients.php b/tests/codeception/common/fixtures/data/oauth-clients.php index c7b11a9..2ca68b2 100644 --- a/tests/codeception/common/fixtures/data/oauth-clients.php +++ b/tests/codeception/common/fixtures/data/oauth-clients.php @@ -6,7 +6,7 @@ return [ 'name' => 'Ely.by', 'description' => 'Всем знакомое елуби', 'redirect_uri' => 'http://ely.by', - 'account_id' => NULL, + 'account_id' => null, 'is_trusted' => 0, 'created_at' => 1455309271, ], @@ -16,7 +16,7 @@ return [ 'name' => 'TLauncher', 'description' => 'Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.', 'redirect_uri' => '', - 'account_id' => NULL, + 'account_id' => null, 'is_trusted' => 0, 'created_at' => 1455318468, ], diff --git a/tests/codeception/common/fixtures/data/oauth-sessions.php b/tests/codeception/common/fixtures/data/oauth-sessions.php index d0b9c34..42d2ea2 100644 --- a/tests/codeception/common/fixtures/data/oauth-sessions.php +++ b/tests/codeception/common/fixtures/data/oauth-sessions.php @@ -1,3 +1,10 @@ [ + 'id' => 1, + 'owner_type' => 'user', + 'owner_id' => 1, + 'client_id' => 'ely', + 'client_redirect_uri' => 'http://ely.by/authorization/oauth', + ], ]; diff --git a/tests/codeception/console/unit/controllers/CleanupControllerTest.php b/tests/codeception/console/unit/controllers/CleanupControllerTest.php new file mode 100644 index 0000000..ddae0ee --- /dev/null +++ b/tests/codeception/console/unit/controllers/CleanupControllerTest.php @@ -0,0 +1,31 @@ + OauthAccessTokenFixture::class, + ]; + } + + public function testActionAccessTokens() { + /** @var OauthAccessToken $validAccessToken */ + $validAccessToken = $this->tester->grabFixture('accessTokens', 'admin-ely'); + /** @var OauthAccessToken $expiredAccessToken */ + $expiredAccessToken = $this->tester->grabFixture('accessTokens', 'admin-ely-expired'); + + $controller = new CleanupController('cleanup', Yii::$app); + $this->assertEquals(0, $controller->actionAccessTokens()); + + $this->tester->canSeeRecord(OauthAccessToken::class, ['access_token' => $validAccessToken->access_token]); + $this->tester->cantSeeRecord(OauthAccessToken::class, ['access_token' => $expiredAccessToken->access_token]); + } + +} From f54c8ad1ad16e584072896c335f0266c9200fc54 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 23 Nov 2016 23:43:09 +0300 Subject: [PATCH 03/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20php=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD=D0=B5=D1=80=D0=B0=20=D0=A0?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8?= =?UTF-8?q?=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20app=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20cron=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B8=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=88=D0=B8=D1=85=20access=5Ftoken'=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 +++- Dockerfile-dev | 6 +++- docker/cron/cleanup | 2 ++ docker/php/composer.sh | 8 ----- docker/php/entrypoint.sh | 38 --------------------- docker/php/php.ini | 2 -- docker/php/supervisord.conf | 36 ------------------- docker/supervisor/account-queue-worker.conf | 6 ++++ 8 files changed, 18 insertions(+), 86 deletions(-) create mode 100644 docker/cron/cleanup delete mode 100644 docker/php/composer.sh delete mode 100644 docker/php/entrypoint.sh delete mode 100644 docker/php/php.ini delete mode 100644 docker/php/supervisord.conf create mode 100644 docker/supervisor/account-queue-worker.conf diff --git a/Dockerfile b/Dockerfile index ef80df9..f750e08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,8 @@ -FROM registry.ely.by/elyby/accounts-php:1.0.0 +FROM registry.ely.by/elyby/accounts-php:1.1.0 + +# Вносим конфигурации для крона и воркеров +COPY docker/cron/* /etc/cron.d/ +COPY docker/supervisor/* /etc/supervisor/conf.d/ COPY id_rsa /root/.ssh/id_rsa diff --git a/Dockerfile-dev b/Dockerfile-dev index e3e5ebb..86bee41 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,8 @@ -FROM registry.ely.by/elyby/accounts-php:1.0.0-dev +FROM registry.ely.by/elyby/accounts-php:1.1.0-dev + +# Вносим конфигурации для крона и воркеров +COPY docker/cron/* /etc/cron.d/ +COPY docker/supervisor/* /etc/supervisor/conf.d/ COPY id_rsa /root/.ssh/id_rsa diff --git a/docker/cron/cleanup b/docker/cron/cleanup new file mode 100644 index 0000000..4c056e7 --- /dev/null +++ b/docker/cron/cleanup @@ -0,0 +1,2 @@ +# https://crontab.guru/every-hour +0 * * * * php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 diff --git a/docker/php/composer.sh b/docker/php/composer.sh deleted file mode 100644 index 517b5d0..0000000 --- a/docker/php/composer.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [ -n "$API_TOKEN" ] -then - php /usr/local/bin/composer.phar config -g github-oauth.github.com $API_TOKEN -fi - -exec php /usr/local/bin/composer.phar "$@" diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh deleted file mode 100644 index f6723cb..0000000 --- a/docker/php/entrypoint.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -cd /var/www/html - -if [ "$1" = "bash" ] || [ "$1" = "composer" ] -then - exec "$@" - exit 0 -fi - -# Переносим vendor, если его нету или он изменился (или затёрся силами volume) -if ! cmp -s ./../vendor/autoload.php ./vendor/autoload.php -then - echo "vendor have diffs..." - echo "removing exists vendor" - rm -rf ./vendor - echo "copying new one" - cp -r ./../vendor ./vendor -fi - -# Переносим dist, если его нету или он изменился (или затёрся силами volume) -if ! cmp -s ./../dist/index.html ./frontend/dist/index.html -then - echo "frontend dist have diffs..." - echo "removing exists dist" - rm -rf ./frontend/dist - echo "copying new one" - cp -r ./../dist ./frontend/dist -fi - -if [ "$YII_ENV" != "test" ] -then - wait-for-it db:3306 -s -- "php /var/www/html/yii migrate/up --interactive=0" -else - wait-for-it testdb:3306 -s -- "php /var/www/html/tests/codeception/bin/yii migrate/up --interactive=0" -fi - -exec "$@" diff --git a/docker/php/php.ini b/docker/php/php.ini deleted file mode 100644 index a9c3fab..0000000 --- a/docker/php/php.ini +++ /dev/null @@ -1,2 +0,0 @@ -error_reporting = E_ALL; -display_errors = On; diff --git a/docker/php/supervisord.conf b/docker/php/supervisord.conf deleted file mode 100644 index 22c385c..0000000 --- a/docker/php/supervisord.conf +++ /dev/null @@ -1,36 +0,0 @@ -[supervisord] -logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) -logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) -logfile_backups=10 ; (num of main logfile rotation backups;default 10) -loglevel=info ; (log level;default info; others: debug,warn,trace) -pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) -nodaemon=false ; (start in foreground if true;default false) -minfds=1024 ; (min. avail startup file descriptors;default 1024) -minprocs=200 ; (min. avail process descriptors;default 200) -user=root - -; the below section must remain in the config file for RPC -; (supervisorctl/web interface) to work, additional interfaces may be -; added by defining them in separate rpcinterface: sections -[rpcinterface:supervisor] -supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - -[supervisorctl] -serverurl=unix:///dev/shm/supervisor.sock ; use a unix:// URL for a unix socket - -[program:php-fpm] -command=php-fpm -autostart=true -autorestart=true -priority=5 -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 - -[program:account-queue-worker] -directory=/var/www/html -command=wait-for-it rabbitmq:5672 -- php yii account-queue -autostart=true -autorestart=true -priority=10 diff --git a/docker/supervisor/account-queue-worker.conf b/docker/supervisor/account-queue-worker.conf new file mode 100644 index 0000000..aed1af3 --- /dev/null +++ b/docker/supervisor/account-queue-worker.conf @@ -0,0 +1,6 @@ +[program:account-queue-worker] +directory=/var/www/html +command=wait-for-it rabbitmq:5672 -- php yii account-queue +autostart=true +autorestart=true +priority=10 From 8b86faa117a53da1c3c5fcf528c63601d3e40ba2 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 24 Nov 2016 00:59:44 +0300 Subject: [PATCH 04/41] =?UTF-8?q?=D0=98=D1=81=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BD=D0=B0=D0=B1=D0=BE=D1=80=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85,=20=D0=B4=D0=B0=D0=B1=D1=8B=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=84=D0=BB=D0=B8=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=20=D0=B4=D1=80=D1=83=D0=B3?= =?UTF-8?q?=D0=B8=D0=BC=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/codeception/api/functional/OauthAuthCodeCest.php | 2 +- .../common/fixtures/data/oauth-access-tokens.php | 4 ++-- .../codeception/common/fixtures/data/oauth-clients.php | 10 ++++++++++ .../common/fixtures/data/oauth-sessions.php | 6 +++--- .../console/unit/controllers/CleanupControllerTest.php | 4 ++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/codeception/api/functional/OauthAuthCodeCest.php b/tests/codeception/api/functional/OauthAuthCodeCest.php index 5f291db..2ed740f 100644 --- a/tests/codeception/api/functional/OauthAuthCodeCest.php +++ b/tests/codeception/api/functional/OauthAuthCodeCest.php @@ -81,7 +81,7 @@ class OauthAuthCodeCest { public function testCompleteActionOnWrongConditions(FunctionalTester $I) { $I->loggedInAsActiveAccount(); - $I->wantTo('get accept_required if I dom\'t require any scope, but this is first time request'); + $I->wantTo('get accept_required if I don\'t require any scope, but this is first time request'); $this->route->complete($this->buildQueryParams( 'ely', 'http://ely.by', diff --git a/tests/codeception/common/fixtures/data/oauth-access-tokens.php b/tests/codeception/common/fixtures/data/oauth-access-tokens.php index a5dd1db..3bcfe18 100644 --- a/tests/codeception/common/fixtures/data/oauth-access-tokens.php +++ b/tests/codeception/common/fixtures/data/oauth-access-tokens.php @@ -1,11 +1,11 @@ [ + 'admin-test1' => [ 'access_token' => '07541285-831e-1e47-e314-b950309a6fca', 'session_id' => 1, 'expire_time' => time() + 3600, ], - 'admin-ely-expired' => [ + 'admin-test1-expired' => [ 'access_token' => '2977ec21-3022-96f8-544db-2e1df936908', 'session_id' => 1, 'expire_time' => time() - 3600, diff --git a/tests/codeception/common/fixtures/data/oauth-clients.php b/tests/codeception/common/fixtures/data/oauth-clients.php index 2ca68b2..e7ddba5 100644 --- a/tests/codeception/common/fixtures/data/oauth-clients.php +++ b/tests/codeception/common/fixtures/data/oauth-clients.php @@ -20,4 +20,14 @@ return [ 'is_trusted' => 0, 'created_at' => 1455318468, ], + 'test1' => [ + 'id' => 'test1', + 'secret' => 'eEvrKHF47sqiaX94HsX-xXzdGiz3mcsq', + 'name' => 'Test1', + 'description' => 'Some description', + 'redirect_uri' => 'http://test1.net', + 'account_id' => null, + 'is_trusted' => 0, + 'created_at' => 1479937982, + ], ]; diff --git a/tests/codeception/common/fixtures/data/oauth-sessions.php b/tests/codeception/common/fixtures/data/oauth-sessions.php index 42d2ea2..ebbc2d2 100644 --- a/tests/codeception/common/fixtures/data/oauth-sessions.php +++ b/tests/codeception/common/fixtures/data/oauth-sessions.php @@ -1,10 +1,10 @@ [ + 'admin-test1' => [ 'id' => 1, 'owner_type' => 'user', 'owner_id' => 1, - 'client_id' => 'ely', - 'client_redirect_uri' => 'http://ely.by/authorization/oauth', + 'client_id' => 'test1', + 'client_redirect_uri' => 'http://test1.net/oauth', ], ]; diff --git a/tests/codeception/console/unit/controllers/CleanupControllerTest.php b/tests/codeception/console/unit/controllers/CleanupControllerTest.php index ddae0ee..bcb0cef 100644 --- a/tests/codeception/console/unit/controllers/CleanupControllerTest.php +++ b/tests/codeception/console/unit/controllers/CleanupControllerTest.php @@ -17,9 +17,9 @@ class CleanupControllerTest extends TestCase { public function testActionAccessTokens() { /** @var OauthAccessToken $validAccessToken */ - $validAccessToken = $this->tester->grabFixture('accessTokens', 'admin-ely'); + $validAccessToken = $this->tester->grabFixture('accessTokens', 'admin-test1'); /** @var OauthAccessToken $expiredAccessToken */ - $expiredAccessToken = $this->tester->grabFixture('accessTokens', 'admin-ely-expired'); + $expiredAccessToken = $this->tester->grabFixture('accessTokens', 'admin-test1-expired'); $controller = new CleanupController('cleanup', Yii::$app); $this->assertEquals(0, $controller->actionAccessTokens()); From 44cf77036e44e43438c814b0f201b6f698e8354a Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 24 Nov 2016 18:55:47 +0300 Subject: [PATCH 05/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20ima?= =?UTF-8?q?ge,=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20cron=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile-dev | 2 +- docker/cron/cleanup | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f750e08..4cbe416 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.0 +FROM registry.ely.by/elyby/accounts-php:1.1.1 # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ diff --git a/Dockerfile-dev b/Dockerfile-dev index 86bee41..51c0dd2 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.0-dev +FROM registry.ely.by/elyby/accounts-php:1.1.1-dev # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ diff --git a/docker/cron/cleanup b/docker/cron/cleanup index 4c056e7..75d517b 100644 --- a/docker/cron/cleanup +++ b/docker/cron/cleanup @@ -1,2 +1,2 @@ # https://crontab.guru/every-hour -0 * * * * php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 +0 * * * * root php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 From 999f8237e488bcecec6feb4f8745a197056407f8 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 26 Nov 2016 12:24:05 +0300 Subject: [PATCH 06/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=20php?= =?UTF-8?q?=20=D0=B2=20=D0=BA=D1=80=D0=BE=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile-dev | 2 +- docker/cron/cleanup | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4cbe416..24a74dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.1 +FROM registry.ely.by/elyby/accounts-php:1.1.2 # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ diff --git a/Dockerfile-dev b/Dockerfile-dev index 51c0dd2..67b7943 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.1-dev +FROM registry.ely.by/elyby/accounts-php:1.1.2-dev # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ diff --git a/docker/cron/cleanup b/docker/cron/cleanup index 75d517b..3401974 100644 --- a/docker/cron/cleanup +++ b/docker/cron/cleanup @@ -1,2 +1,2 @@ # https://crontab.guru/every-hour -0 * * * * root php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 +0 * * * * root /usr/local/bin/php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 From e6fa0fe6f3a14a3eb37ec3611c6a5174071f04ad Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 26 Nov 2016 14:09:15 +0300 Subject: [PATCH 07/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=BE=D0=B2=20AMQP=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=B1=D1=8B=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20...=20?= =?UTF-8?q?=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/components/RabbitMQ/Component.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/components/RabbitMQ/Component.php b/common/components/RabbitMQ/Component.php index 89da8f8..0ce0f91 100644 --- a/common/components/RabbitMQ/Component.php +++ b/common/components/RabbitMQ/Component.php @@ -111,8 +111,8 @@ class Component extends \yii\base\Component { public function sendToExchange($exchangeName, $routingKey, $message, $exchangeArgs = [], $publishArgs = []) { $message = $this->prepareMessage($message); $channel = $this->getChannel(); - call_user_func_array([$channel, 'exchange_declare'], $this->prepareExchangeArgs($exchangeName, $exchangeArgs)); - call_user_func_array([$channel, 'basic_publish'], $this->preparePublishArgs($message, $exchangeName, $routingKey, $publishArgs)); + $channel->exchange_declare(...$this->prepareExchangeArgs($exchangeName, $exchangeArgs)); + $channel->basic_publish(...$this->preparePublishArgs($message, $exchangeName, $routingKey, $publishArgs)); } /** From 20286f1744244967b8162fe776b5e111a64a94a7 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 27 Nov 2016 00:43:42 +0300 Subject: [PATCH 08/41] =?UTF-8?q?=D0=92=D1=81=D0=B5=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=D1=8B,=20=D0=BE=D1=82=D0=B2=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D0=B5=20=D0=B7=D0=B0=20oAuth=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B2=D0=B8=D0=BD=D1=83=D1=82=D1=8B=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B?= =?UTF-8?q?=20API,=20=D0=BE=D1=81=D0=B2=D0=B5=D0=B6=D1=91=D0=BD=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4,=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=B5=D0=B9=D0=BC=D1=81=D0=BF=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OAuth2}/Component.php | 34 ++++++------ .../OAuth2/Entities}/AccessTokenEntity.php | 4 +- .../OAuth2/Entities}/AuthCodeEntity.php | 4 +- .../OAuth2/Entities/ClientEntity.php | 22 ++++++++ .../OAuth2/Entities/ScopeEntity.php | 10 ++++ .../OAuth2/Entities}/SessionEntity.php | 6 ++- .../Exception/AcceptRequiredException.php | 2 +- .../Exception/AccessDeniedException.php | 2 +- .../OAuth2/Storage}/AccessTokenStorage.php | 18 ++++--- .../OAuth2/Storage}/AuthCodeStorage.php | 20 ++++--- .../OAuth2/Storage}/ClientStorage.php | 47 ++++++++-------- .../OAuth2/Storage}/RefreshTokenStorage.php | 12 +++-- .../OAuth2/Storage}/ScopeStorage.php | 9 ++-- .../OAuth2/Storage}/SessionStorage.php | 54 +++++++++---------- .../Utils/KeyAlgorithm/UuidAlgorithm.php | 16 ++++++ api/config/config.php | 2 +- api/controllers/OauthController.php | 6 +-- autocompletion.php | 6 +-- .../oauth/Util/KeyAlgorithm/UuidAlgorithm.php | 16 ------ 19 files changed, 163 insertions(+), 127 deletions(-) rename {common/components/oauth => api/components/OAuth2}/Component.php (57%) rename {common/components/oauth/Entity => api/components/OAuth2/Entities}/AccessTokenEntity.php (82%) rename {common/components/oauth/Entity => api/components/OAuth2/Entities}/AuthCodeEntity.php (81%) create mode 100644 api/components/OAuth2/Entities/ClientEntity.php create mode 100644 api/components/OAuth2/Entities/ScopeEntity.php rename {common/components/oauth/Entity => api/components/OAuth2/Entities}/SessionEntity.php (79%) rename {common/components/oauth => api/components/OAuth2}/Exception/AcceptRequiredException.php (89%) rename {common/components/oauth => api/components/OAuth2}/Exception/AccessDeniedException.php (84%) rename {common/components/oauth/Storage/Yii2 => api/components/OAuth2/Storage}/AccessTokenStorage.php (81%) rename {common/components/oauth/Storage/Redis => api/components/OAuth2/Storage}/AuthCodeStorage.php (80%) rename {common/components/oauth/Storage/Yii2 => api/components/OAuth2/Storage}/ClientStorage.php (67%) rename {common/components/oauth/Storage/Redis => api/components/OAuth2/Storage}/RefreshTokenStorage.php (78%) rename {common/components/oauth/Storage/Yii2 => api/components/OAuth2/Storage}/ScopeStorage.php (69%) rename {common/components/oauth/Storage/Yii2 => api/components/OAuth2/Storage}/SessionStorage.php (77%) create mode 100644 api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php delete mode 100644 common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php diff --git a/common/components/oauth/Component.php b/api/components/OAuth2/Component.php similarity index 57% rename from common/components/oauth/Component.php rename to api/components/OAuth2/Component.php index 6bd3091..940e29b 100644 --- a/common/components/oauth/Component.php +++ b/api/components/OAuth2/Component.php @@ -1,13 +1,13 @@ _authServer === null) { $authServer = new AuthorizationServer(); - $authServer - ->setAccessTokenStorage(new AccessTokenStorage()) - ->setClientStorage(new ClientStorage()) - ->setScopeStorage(new ScopeStorage()) - ->setSessionStorage(new SessionStorage()) - ->setAuthCodeStorage(new AuthCodeStorage()) - ->setRefreshTokenStorage(new RefreshTokenStorage()) - ->setScopeDelimiter(','); + $authServer->setAccessTokenStorage(new AccessTokenStorage()); + $authServer->setClientStorage(new ClientStorage()); + $authServer->setScopeStorage(new ScopeStorage()); + $authServer->setSessionStorage(new SessionStorage()); + $authServer->setAuthCodeStorage(new AuthCodeStorage()); + $authServer->setRefreshTokenStorage(new RefreshTokenStorage()); + $authServer->setScopeDelimiter(','); $this->_authServer = $authServer; foreach ($this->grantTypes as $grantType) { - if (!array_key_exists($grantType, $this->grantMap)) { + if (!isset($this->grantMap[$grantType])) { throw new InvalidConfigException('Invalid grant type'); } + /** @var Grant\GrantTypeInterface $grant */ $grant = new $this->grantMap[$grantType](); $this->_authServer->addGrantType($grant); } diff --git a/common/components/oauth/Entity/AccessTokenEntity.php b/api/components/OAuth2/Entities/AccessTokenEntity.php similarity index 82% rename from common/components/oauth/Entity/AccessTokenEntity.php rename to api/components/OAuth2/Entities/AccessTokenEntity.php index bd70930..3f92c5b 100644 --- a/common/components/oauth/Entity/AccessTokenEntity.php +++ b/api/components/OAuth2/Entities/AccessTokenEntity.php @@ -1,11 +1,9 @@ id = $id; + } + + public function setName(string $name) { + $this->name = $name; + } + + public function setSecret(string $secret) { + $this->secret = $secret; + } + + public function setRedirectUri(string $redirectUri) { + $this->redirectUri = $redirectUri; + } + +} diff --git a/api/components/OAuth2/Entities/ScopeEntity.php b/api/components/OAuth2/Entities/ScopeEntity.php new file mode 100644 index 0000000..7b9f3c0 --- /dev/null +++ b/api/components/OAuth2/Entities/ScopeEntity.php @@ -0,0 +1,10 @@ +id = $id; + } + +} diff --git a/common/components/oauth/Entity/SessionEntity.php b/api/components/OAuth2/Entities/SessionEntity.php similarity index 79% rename from common/components/oauth/Entity/SessionEntity.php rename to api/components/OAuth2/Entities/SessionEntity.php index 28fafb5..0d13361 100644 --- a/common/components/oauth/Entity/SessionEntity.php +++ b/api/components/OAuth2/Entities/SessionEntity.php @@ -1,5 +1,5 @@ clientId = $clientId; + } + } diff --git a/common/components/oauth/Exception/AcceptRequiredException.php b/api/components/OAuth2/Exception/AcceptRequiredException.php similarity index 89% rename from common/components/oauth/Exception/AcceptRequiredException.php rename to api/components/OAuth2/Exception/AcceptRequiredException.php index 36c5bf0..038be67 100644 --- a/common/components/oauth/Exception/AcceptRequiredException.php +++ b/api/components/OAuth2/Exception/AcceptRequiredException.php @@ -1,5 +1,5 @@ server))->hydrate([ - 'id' => $model->access_token, - 'expireTime' => $model->expire_time, - 'sessionId' => $model->session_id, - ]); + /** @var SessionStorage $sessionStorage */ + $sessionStorage = $this->server->getSessionStorage(); + + $token = new AccessTokenEntity($this->server); + $token->setId($model->access_token); + $token->setExpireTime($model->expire_time); + $token->setSession($sessionStorage->getById($model->session_id)); + + return $token; } /** diff --git a/common/components/oauth/Storage/Redis/AuthCodeStorage.php b/api/components/OAuth2/Storage/AuthCodeStorage.php similarity index 80% rename from common/components/oauth/Storage/Redis/AuthCodeStorage.php rename to api/components/OAuth2/Storage/AuthCodeStorage.php index f3bdbdc..e153260 100644 --- a/common/components/oauth/Storage/Redis/AuthCodeStorage.php +++ b/api/components/OAuth2/Storage/AuthCodeStorage.php @@ -1,7 +1,7 @@ server))->hydrate([ - 'id' => $result['id'], - 'redirectUri' => $result['client_redirect_uri'], - 'expireTime' => $result['expire_time'], - 'sessionId' => $result['session_id'], - ]); + /** @var SessionStorage $sessionStorage */ + $sessionStorage = $this->server->getSessionStorage(); + + $entity = new AuthCodeEntity($this->server); + $entity->setId($result['id']); + $entity->setRedirectUri($result['client_redirect_uri']); + $entity->setExpireTime($result['expire_time']); + $entity->setSession($sessionStorage->getById($result['session_id'])); + + return $entity; } /** diff --git a/common/components/oauth/Storage/Yii2/ClientStorage.php b/api/components/OAuth2/Storage/ClientStorage.php similarity index 67% rename from common/components/oauth/Storage/Yii2/ClientStorage.php rename to api/components/OAuth2/Storage/ClientStorage.php index 5e8808d..90d024b 100644 --- a/common/components/oauth/Storage/Yii2/ClientStorage.php +++ b/api/components/OAuth2/Storage/ClientStorage.php @@ -1,9 +1,9 @@ select(['id', 'name', 'secret', 'redirect_uri']) - ->where([OauthClient::tableName() . '.id' => $clientId]); - + $query = OauthClient::find()->andWhere(['id' => $clientId]); if ($clientSecret !== null) { $query->andWhere(['secret' => $clientSecret]); } - $model = $query->asArray()->one(); + /** @var OauthClient|null $model */ + $model = $query->one(); if ($model === null) { return null; } @@ -39,22 +37,17 @@ class ClientStorage extends AbstractStorage implements ClientInterface { * Короче это нужно учесть */ if ($redirectUri !== null) { - if ($redirectUri === self::REDIRECT_STATIC_PAGE || $redirectUri === self::REDIRECT_STATIC_PAGE_WITH_CODE) { + if (in_array($redirectUri, [self::REDIRECT_STATIC_PAGE, self::REDIRECT_STATIC_PAGE_WITH_CODE], true)) { // Тут, наверное, нужно проверить тип приложения } else { - if (!StringHelper::startsWith($redirectUri, $model['redirect_uri'], false)) { + if (!StringHelper::startsWith($redirectUri, $model->redirect_uri, false)) { return null; } } } - $entity = new ClientEntity($this->server); - $entity->hydrate([ - 'id' => $model['id'], - 'name' => $model['name'], - 'secret' => $model['secret'], - 'redirectUri' => $redirectUri, - ]); + $entity = $this->hydrate($model); + $entity->setRedirectUri($redirectUri); return $entity; } @@ -67,17 +60,23 @@ class ClientStorage extends AbstractStorage implements ClientInterface { throw new \ErrorException('This module assumes that $session typeof ' . SessionEntity::class); } - $model = OauthClient::find() - ->select(['id', 'name']) - ->andWhere(['id' => $session->getClientId()]) - ->asArray() - ->one(); - + /** @var OauthClient|null $model */ + $model = OauthClient::findOne($session->getClientId()); if ($model === null) { return null; } - return (new ClientEntity($this->server))->hydrate($model); + return $this->hydrate($model); + } + + private function hydrate(OauthClient $model) : ClientEntity { + $entity = new ClientEntity($this->server); + $entity->setId($model->id); + $entity->setName($model->name); + $entity->setSecret($model->secret); + $entity->setRedirectUri($model->redirect_uri); + + return $entity; } } diff --git a/common/components/oauth/Storage/Redis/RefreshTokenStorage.php b/api/components/OAuth2/Storage/RefreshTokenStorage.php similarity index 78% rename from common/components/oauth/Storage/Redis/RefreshTokenStorage.php rename to api/components/OAuth2/Storage/RefreshTokenStorage.php index f3ad9e0..037f252 100644 --- a/common/components/oauth/Storage/Redis/RefreshTokenStorage.php +++ b/api/components/OAuth2/Storage/RefreshTokenStorage.php @@ -1,5 +1,5 @@ server)) - ->setId($result['id']) - ->setExpireTime($result['expire_time']) - ->setAccessTokenId($result['access_token_id']); + $entity = new RefreshTokenEntity($this->server); + $entity->setId($result['id']); + $entity->setExpireTime($result['expire_time']); + $entity->setAccessTokenId($result['access_token_id']); + + return $entity; } /** diff --git a/common/components/oauth/Storage/Yii2/ScopeStorage.php b/api/components/OAuth2/Storage/ScopeStorage.php similarity index 69% rename from common/components/oauth/Storage/Yii2/ScopeStorage.php rename to api/components/OAuth2/Storage/ScopeStorage.php index 64fef0e..788f7e3 100644 --- a/common/components/oauth/Storage/Yii2/ScopeStorage.php +++ b/api/components/OAuth2/Storage/ScopeStorage.php @@ -1,8 +1,8 @@ andWhere(['id' => $scope])->asArray()->one(); + /** @var OauthScope|null $row */ + $row = OauthScope::findOne($scope); if ($row === null) { return null; } $entity = new ScopeEntity($this->server); - $entity->hydrate($row); + $entity->setId($row->id); return $entity; } diff --git a/common/components/oauth/Storage/Yii2/SessionStorage.php b/api/components/OAuth2/Storage/SessionStorage.php similarity index 77% rename from common/components/oauth/Storage/Yii2/SessionStorage.php rename to api/components/OAuth2/Storage/SessionStorage.php index 1542391..06e66ff 100644 --- a/common/components/oauth/Storage/Yii2/SessionStorage.php +++ b/api/components/OAuth2/Storage/SessionStorage.php @@ -1,8 +1,8 @@ cache[$sessionId])) { - $this->cache[$sessionId] = OauthSession::findOne($sessionId); - } - - return $this->cache[$sessionId]; - } - - private function hydrateEntity($sessionModel) { - if (!$sessionModel instanceof OauthSession) { - return null; - } - - return (new SessionEntity($this->server))->hydrate([ - 'id' => $sessionModel->id, - 'client_id' => $sessionModel->client_id, - ])->setOwner($sessionModel->owner_type, $sessionModel->owner_id); - } - /** * @param string $sessionId * @return SessionEntity|null */ - public function getSession($sessionId) { - return $this->hydrateEntity($this->getSessionModel($sessionId)); + public function getById($sessionId) { + return $this->hydrate($this->getSessionModel($sessionId)); } /** @@ -60,7 +37,7 @@ class SessionStorage extends AbstractStorage implements SessionInterface { }, ])->one(); - return $this->hydrateEntity($model); + return $this->hydrate($model); } /** @@ -71,7 +48,7 @@ class SessionStorage extends AbstractStorage implements SessionInterface { throw new ErrorException('This module assumes that $authCode typeof ' . AuthCodeEntity::class); } - return $this->getSession($authCode->getSessionId()); + return $this->getById($authCode->getSessionId()); } /** @@ -123,4 +100,21 @@ class SessionStorage extends AbstractStorage implements SessionInterface { $this->getSessionModel($session->getId())->getScopes()->add($scope->getId()); } + private function getSessionModel(string $sessionId) : OauthSession { + if (!isset($this->cache[$sessionId])) { + $this->cache[$sessionId] = OauthSession::findOne($sessionId); + } + + return $this->cache[$sessionId]; + } + + private function hydrate(OauthSession $sessionModel) { + $entity = new SessionEntity($this->server); + $entity->setId($sessionModel->id); + $entity->setClientId($sessionModel->client_id); + $entity->setOwner($sessionModel->owner_type, $sessionModel->owner_id); + + return $entity; + } + } diff --git a/api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php b/api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php new file mode 100644 index 0000000..54b4ba3 --- /dev/null +++ b/api/components/OAuth2/Utils/KeyAlgorithm/UuidAlgorithm.php @@ -0,0 +1,16 @@ +toString(); + } + +} diff --git a/api/config/config.php b/api/config/config.php index dfcd9c3..37bdd71 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -63,7 +63,7 @@ return [ 'format' => yii\web\Response::FORMAT_JSON, ], 'oauth' => [ - 'class' => common\components\oauth\Component::class, + 'class' => api\components\OAuth2\Component::class, 'grantTypes' => ['authorization_code'], ], 'errorHandler' => [ diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php index 4f5db86..bed3b4a 100644 --- a/api/controllers/OauthController.php +++ b/api/controllers/OauthController.php @@ -2,8 +2,8 @@ namespace api\controllers; use api\filters\ActiveUserRule; -use common\components\oauth\Exception\AcceptRequiredException; -use common\components\oauth\Exception\AccessDeniedException; +use api\components\OAuth2\Exceptions\AcceptRequiredException; +use api\components\OAuth2\Exceptions\AccessDeniedException; use common\models\Account; use common\models\OauthClient; use common\models\OauthScope; @@ -186,7 +186,7 @@ class OauthController extends Controller { } $scopes = $codeModel->getScopes(); - if (array_search(OauthScope::OFFLINE_ACCESS, array_keys($scopes)) === false) { + if (array_search(OauthScope::OFFLINE_ACCESS, array_keys($scopes), true) === false) { return; } } elseif ($grantType === 'refresh_token') { diff --git a/autocompletion.php b/autocompletion.php index 24c5140..42643fa 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -29,10 +29,10 @@ abstract class BaseApplication extends yii\base\Application { * Class WebApplication * Include only Web application related components here * - * @property \api\components\User\Component $user User component. - * @property \api\components\ApiUser\Component $apiUser Api User component. + * @property \api\components\User\Component $user User component. + * @property \api\components\ApiUser\Component $apiUser Api User component. * @property \api\components\ReCaptcha\Component $reCaptcha - * @property \common\components\oauth\Component $oauth + * @property \api\components\OAuth2\Component $oauth * * @method \api\components\User\Component getUser() */ diff --git a/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php b/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php deleted file mode 100644 index e75580d..0000000 --- a/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php +++ /dev/null @@ -1,16 +0,0 @@ -toString(); - } - -} From f5981f1cd17ef1cdd19c5fab717553a0f12e0376 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 27 Nov 2016 00:55:43 +0300 Subject: [PATCH 09/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20namespace=20=D0=B4=D0=BB=D1=8F=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/OAuth2/Exception/AcceptRequiredException.php | 2 +- api/components/OAuth2/Exception/AccessDeniedException.php | 2 +- api/controllers/OauthController.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/components/OAuth2/Exception/AcceptRequiredException.php b/api/components/OAuth2/Exception/AcceptRequiredException.php index 038be67..540650c 100644 --- a/api/components/OAuth2/Exception/AcceptRequiredException.php +++ b/api/components/OAuth2/Exception/AcceptRequiredException.php @@ -1,5 +1,5 @@ Date: Sun, 27 Nov 2016 00:56:13 +0300 Subject: [PATCH 10/41] =?UTF-8?q?redirectUri=20=D0=BC=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B1=D1=8B=D1=82=D1=8C=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/OAuth2/Entities/ClientEntity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/components/OAuth2/Entities/ClientEntity.php b/api/components/OAuth2/Entities/ClientEntity.php index 80ab62a..8636cf1 100644 --- a/api/components/OAuth2/Entities/ClientEntity.php +++ b/api/components/OAuth2/Entities/ClientEntity.php @@ -15,7 +15,7 @@ class ClientEntity extends \League\OAuth2\Server\Entity\ClientEntity { $this->secret = $secret; } - public function setRedirectUri(string $redirectUri) { + public function setRedirectUri($redirectUri) { $this->redirectUri = $redirectUri; } From 744ec9520abe35366f5976d887f9dd7882ecfb02 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 27 Nov 2016 17:41:39 +0300 Subject: [PATCH 11/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=20=D0=B2=20=D0=BA=D0=B0=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B8=D1=81=D0=BE=D0=BC=20=D0=B1=D0=B8=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D1=83=20Predis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2/Storage/AuthCodeStorage.php | 4 +- .../OAuth2/Storage/RefreshTokenStorage.php | 2 +- api/modules/session/filters/RateLimiter.php | 6 +- autocompletion.php | 2 +- common/components/Redis/Cache.php | 13 + common/components/Redis/Connection.php | 415 ++++++++++++++++++ .../components/Redis/ConnectionInterface.php | 19 + common/components/{redis => Redis}/Key.php | 2 +- common/components/{redis => Redis}/Set.php | 17 +- common/config/config.php | 4 +- common/models/OauthAccessToken.php | 2 +- common/models/OauthSession.php | 2 +- composer.json | 6 +- 13 files changed, 472 insertions(+), 22 deletions(-) create mode 100644 common/components/Redis/Cache.php create mode 100644 common/components/Redis/Connection.php create mode 100644 common/components/Redis/ConnectionInterface.php rename common/components/{redis => Redis}/Key.php (97%) rename common/components/{redis => Redis}/Set.php (53%) diff --git a/api/components/OAuth2/Storage/AuthCodeStorage.php b/api/components/OAuth2/Storage/AuthCodeStorage.php index e153260..a598b2b 100644 --- a/api/components/OAuth2/Storage/AuthCodeStorage.php +++ b/api/components/OAuth2/Storage/AuthCodeStorage.php @@ -2,8 +2,8 @@ namespace api\components\OAuth2\Storage; use api\components\OAuth2\Entities\AuthCodeEntity; -use common\components\redis\Key; -use common\components\redis\Set; +use common\components\Redis\Key; +use common\components\Redis\Set; use League\OAuth2\Server\Entity\AuthCodeEntity as OriginalAuthCodeEntity; use League\OAuth2\Server\Entity\ScopeEntity; use League\OAuth2\Server\Storage\AbstractStorage; diff --git a/api/components/OAuth2/Storage/RefreshTokenStorage.php b/api/components/OAuth2/Storage/RefreshTokenStorage.php index 037f252..68ea9c2 100644 --- a/api/components/OAuth2/Storage/RefreshTokenStorage.php +++ b/api/components/OAuth2/Storage/RefreshTokenStorage.php @@ -1,7 +1,7 @@ checkRateLimit( @@ -39,6 +40,7 @@ class RateLimiter extends \yii\filters\RateLimiter { /** * @inheritdoc + * @throws TooManyRequestsHttpException */ public function checkRateLimit($user, $request, $response, $action) { if (parse_url($request->getHostInfo(), PHP_URL_HOST) === $this->authserverDomain) { @@ -54,7 +56,7 @@ class RateLimiter extends \yii\filters\RateLimiter { $key = $this->buildKey($ip); $redis = $this->getRedis(); - $countRequests = intval($redis->executeCommand('INCR', [$key])); + $countRequests = (int)$redis->incr($key); if ($countRequests === 1) { $redis->executeCommand('EXPIRE', [$key, $this->limitTime]); } @@ -65,7 +67,7 @@ class RateLimiter extends \yii\filters\RateLimiter { } /** - * @return \yii\redis\Connection + * @return \common\components\Redis\Connection */ public function getRedis() { return Yii::$app->redis; diff --git a/autocompletion.php b/autocompletion.php index 42643fa..1d99674 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -17,7 +17,7 @@ class Yii extends \yii\BaseYii { * Used for properties that are identical for both WebApplication and ConsoleApplication * * @property \yii\swiftmailer\Mailer $mailer - * @property \yii\redis\Connection $redis + * @property \common\components\Redis\Connection $redis * @property \common\components\RabbitMQ\Component $amqp * @property \GuzzleHttp\Client $guzzle * @property \common\components\EmailRenderer $emailRenderer diff --git a/common/components/Redis/Cache.php b/common/components/Redis/Cache.php new file mode 100644 index 0000000..6a120a1 --- /dev/null +++ b/common/components/Redis/Cache.php @@ -0,0 +1,13 @@ +redis = Instance::ensure($this->redis, ConnectionInterface::class); + } + +} diff --git a/common/components/Redis/Connection.php b/common/components/Redis/Connection.php new file mode 100644 index 0000000..5bdc47d --- /dev/null +++ b/common/components/Redis/Connection.php @@ -0,0 +1,415 @@ +_client === null) { + $this->_client = new Client($this->prepareParams(), $this->options); + } + + return $this->_client; + } + + public function __call($name, $params) { + $redisCommand = mb_strtoupper($name); + if (in_array($redisCommand, self::REDIS_COMMANDS)) { + return $this->executeCommand($name, $params); + } + + return parent::__call($name, $params); + } + + public function executeCommand(string $name, array $params = []) { + return $this->getConnection()->$name(...$params); + } + + private function prepareParams() { + if ($this->parameters !== null) { + return $this->parameters; + } + + if ($this->unixSocket) { + $parameters = [ + 'scheme' => 'unix', + 'path' => $this->unixSocket, + ]; + } else { + $parameters = [ + 'scheme' => 'tcp', + 'host' => $this->hostname, + 'port' => $this->port, + ]; + } + + return array_merge($parameters, [ + 'database' => $this->database, + ]); + } + +} diff --git a/common/components/Redis/ConnectionInterface.php b/common/components/Redis/ConnectionInterface.php new file mode 100644 index 0000000..f4195fe --- /dev/null +++ b/common/components/Redis/ConnectionInterface.php @@ -0,0 +1,19 @@ +redis; } public function add($value) { - $this->getDb()->executeCommand('SADD', [$this->key, $value]); + static::getDb()->sadd($this->key, $value); return $this; } public function remove($value) { - $this->getDb()->executeCommand('SREM', [$this->key, $value]); + static::getDb()->srem($this->key, $value); return $this; } public function members() { - return $this->getDb()->executeCommand('SMEMBERS', [$this->key]); + return static::getDb()->smembers($this->key); } public function getValue() { @@ -32,18 +33,18 @@ class Set extends Key implements IteratorAggregate { } public function exists($value) { - return !!$this->getDb()->executeCommand('SISMEMBER', [$this->key, $value]); + return (bool)static::getDb()->sismember($this->key, $value); } public function diff(array $sets) { - return $this->getDb()->executeCommand('SDIFF', [$this->key, implode(' ', $sets)]); + return static::getDb()->sdiff([$this->key, implode(' ', $sets)]); } /** * @inheritdoc */ public function getIterator() { - return new \ArrayIterator($this->members()); + return new ArrayIterator($this->members()); } } diff --git a/common/config/config.php b/common/config/config.php index 31b0a83..262b2ec 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -3,7 +3,7 @@ return [ 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'components' => [ 'cache' => [ - 'class' => yii\redis\Cache::class, + 'class' => common\components\Redis\Cache::class, 'redis' => 'redis', ], 'db' => [ @@ -24,7 +24,7 @@ return [ 'passwordHashStrategy' => 'password_hash', ], 'redis' => [ - 'class' => yii\redis\Connection::class, + 'class' => common\components\Redis\Connection::class, 'hostname' => 'redis', 'password' => null, 'port' => 6379, diff --git a/common/models/OauthAccessToken.php b/common/models/OauthAccessToken.php index b253ef2..660ac7a 100644 --- a/common/models/OauthAccessToken.php +++ b/common/models/OauthAccessToken.php @@ -1,7 +1,7 @@ Date: Sun, 27 Nov 2016 19:19:13 +0300 Subject: [PATCH 12/41] =?UTF-8?q?=D0=98=D0=B7=20=D0=B1=D0=B0=D0=B7=D1=8B?= =?UTF-8?q?=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20oauth=5Fscopes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2/Storage/ScopeStorage.php | 6 ++--- common/models/OauthScope.php | 17 ++++++------- .../m161127_145211_remove_oauth_scopes.php | 25 +++++++++++++++++++ .../common/fixtures/OauthSessionFixture.php | 1 - 4 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 console/migrations/m161127_145211_remove_oauth_scopes.php diff --git a/api/components/OAuth2/Storage/ScopeStorage.php b/api/components/OAuth2/Storage/ScopeStorage.php index 788f7e3..be42d1e 100644 --- a/api/components/OAuth2/Storage/ScopeStorage.php +++ b/api/components/OAuth2/Storage/ScopeStorage.php @@ -12,14 +12,12 @@ class ScopeStorage extends AbstractStorage implements ScopeInterface { * @inheritdoc */ public function get($scope, $grantType = null, $clientId = null) { - /** @var OauthScope|null $row */ - $row = OauthScope::findOne($scope); - if ($row === null) { + if (!in_array($scope, OauthScope::getScopes(), true)) { return null; } $entity = new ScopeEntity($this->server); - $entity->setId($row->id); + $entity->setId($scope); return $entity; } diff --git a/common/models/OauthScope.php b/common/models/OauthScope.php index ab392bf..9fbf035 100644 --- a/common/models/OauthScope.php +++ b/common/models/OauthScope.php @@ -1,21 +1,20 @@ dropTable('{{%oauth_scopes}}'); + } + + public function safeDown() { + $this->createTable('{{%oauth_scopes}}', [ + 'id' => $this->string(64), + $this->primary('id'), + ]); + + $this->batchInsert('{{%oauth_scopes}}', ['id'], [ + ['offline_access'], + ['minecraft_server_session'], + ['account_info'], + ['account_email'], + ]); + } + +} diff --git a/tests/codeception/common/fixtures/OauthSessionFixture.php b/tests/codeception/common/fixtures/OauthSessionFixture.php index 0bbab87..fbeb903 100644 --- a/tests/codeception/common/fixtures/OauthSessionFixture.php +++ b/tests/codeception/common/fixtures/OauthSessionFixture.php @@ -1,7 +1,6 @@ Date: Tue, 29 Nov 2016 01:57:58 +0300 Subject: [PATCH 13/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B8=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B5=D0=B2=D1=88=D0=B5=D0=B3=D0=BE=20=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=D0=B5=D0=BD=D0=B0=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=85=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D1=8B=20=D1=81=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2/Entities/RefreshTokenEntity.php | 10 ++++ .../OAuth2/Storage/AuthCodeStorage.php | 2 +- .../OAuth2/Storage/RefreshTokenStorage.php | 9 ++-- api/controllers/OauthController.php | 5 +- common/components/Redis/Key.php | 30 +++++++----- common/components/Redis/Set.php | 24 ++++------ composer.json | 6 ++- .../api/functional/OauthRefreshTokenCest.php | 48 ++++++++++++------- 8 files changed, 83 insertions(+), 51 deletions(-) create mode 100644 api/components/OAuth2/Entities/RefreshTokenEntity.php diff --git a/api/components/OAuth2/Entities/RefreshTokenEntity.php b/api/components/OAuth2/Entities/RefreshTokenEntity.php new file mode 100644 index 0000000..bd4607f --- /dev/null +++ b/api/components/OAuth2/Entities/RefreshTokenEntity.php @@ -0,0 +1,10 @@ +dataTable, $code))->getValue(); + $result = json_decode((new Key($this->dataTable, $code))->getValue(), true); if (!$result) { return null; } diff --git a/api/components/OAuth2/Storage/RefreshTokenStorage.php b/api/components/OAuth2/Storage/RefreshTokenStorage.php index 68ea9c2..a1a31cf 100644 --- a/api/components/OAuth2/Storage/RefreshTokenStorage.php +++ b/api/components/OAuth2/Storage/RefreshTokenStorage.php @@ -1,8 +1,9 @@ dataTable, $token))->getValue(); + $result = json_decode((new Key($this->dataTable, $token))->getValue(), true); if (!$result) { return null; } $entity = new RefreshTokenEntity($this->server); $entity->setId($result['id']); - $entity->setExpireTime($result['expire_time']); $entity->setAccessTokenId($result['access_token_id']); return $entity; @@ -33,7 +33,6 @@ class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterfa public function create($token, $expireTime, $accessToken) { $payload = [ 'id' => $token, - 'expire_time' => $expireTime, 'access_token_id' => $accessToken, ]; @@ -43,7 +42,7 @@ class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterfa /** * @inheritdoc */ - public function delete(RefreshTokenEntity $token) { + public function delete(OriginalRefreshTokenEntity $token) { (new Key($this->dataTable, $token->getId()))->delete(); } diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php index b79c2a1..916a95f 100644 --- a/api/controllers/OauthController.php +++ b/api/controllers/OauthController.php @@ -195,7 +195,10 @@ class OauthController extends Controller { return; } - $this->getServer()->addGrantType(new RefreshTokenGrant()); + $grant = new RefreshTokenGrant(); + $grant->setRefreshTokenRotation(false); + + $this->getServer()->addGrantType($grant); } /** diff --git a/common/components/Redis/Key.php b/common/components/Redis/Key.php index 24a9fbf..dbecc5d 100644 --- a/common/components/Redis/Key.php +++ b/common/components/Redis/Key.php @@ -9,18 +9,18 @@ class Key { protected $key; /** - * @return \yii\redis\Connection + * @return Connection */ public function getRedis() { return Yii::$app->redis; } - public function getKey() { + public function getKey() : string { return $this->key; } public function getValue() { - return json_decode($this->getRedis()->get($this->key), true); + return $this->getRedis()->get($this->key); } public function setValue($value) { @@ -29,15 +29,27 @@ class Key { } public function delete() { - $this->getRedis()->executeCommand('DEL', [$this->key]); + $this->getRedis()->del($this->key); return $this; } + public function exists() : bool { + return (bool)$this->getRedis()->exists($this->key); + } + public function expire($ttl) { - $this->getRedis()->executeCommand('EXPIRE', [$this->key, $ttl]); + $this->getRedis()->expire($this->key, $ttl); return $this; } + public function __construct(...$key) { + if (empty($key)) { + throw new InvalidArgumentException('You must specify at least one key.'); + } + + $this->key = $this->buildKey($key); + } + private function buildKey(array $parts) { $keyParts = []; foreach($parts as $part) { @@ -47,12 +59,4 @@ class Key { return implode(':', $keyParts); } - public function __construct(...$key) { - if (empty($key)) { - throw new InvalidArgumentException('You must specify at least one key.'); - } - - $this->key = $this->buildKey($key); - } - } diff --git a/common/components/Redis/Set.php b/common/components/Redis/Set.php index 4d1782a..fe8302a 100644 --- a/common/components/Redis/Set.php +++ b/common/components/Redis/Set.php @@ -3,41 +3,37 @@ namespace common\components\Redis; use ArrayIterator; use IteratorAggregate; -use Yii; class Set extends Key implements IteratorAggregate { - /** - * @return Connection - */ - public static function getDb() { - return Yii::$app->redis; - } - public function add($value) { - static::getDb()->sadd($this->key, $value); + $this->getRedis()->sadd($this->key, $value); return $this; } public function remove($value) { - static::getDb()->srem($this->key, $value); + $this->getRedis()->srem($this->key, $value); return $this; } public function members() { - return static::getDb()->smembers($this->key); + return $this->getRedis()->smembers($this->key); } public function getValue() { return $this->members(); } - public function exists($value) { - return (bool)static::getDb()->sismember($this->key, $value); + public function exists(string $value = null) : bool { + if ($value === null) { + return parent::exists(); + } else { + return (bool)$this->getRedis()->sismember($this->key, $value); + } } public function diff(array $sets) { - return static::getDb()->sdiff([$this->key, implode(' ', $sets)]); + return $this->getRedis()->sdiff([$this->key, implode(' ', $sets)]); } /** diff --git a/composer.json b/composer.json index b663b89..e168e1b 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "yiisoft/yii2": "2.0.9", "yiisoft/yii2-swiftmailer": "*", "ramsey/uuid": "^3.5.0", - "league/oauth2-server": "~4.1.5", + "league/oauth2-server": "dev-improvements#546dbfe85ae7c049cf9266281d228afe8bdd3ef6", "yiisoft/yii2-redis": "~2.0.0", "guzzlehttp/guzzle": "^6.0.0", "php-amqplib/php-amqplib": "^2.6.2", @@ -53,6 +53,10 @@ { "type": "git", "url": "git@gitlab.com:elyby/email-renderer.git" + }, + { + "type": "git", + "url": "git@gitlab.ely.by:elyby/oauth2-server.git" } ], "scripts": { diff --git a/tests/codeception/api/functional/OauthRefreshTokenCest.php b/tests/codeception/api/functional/OauthRefreshTokenCest.php index dc6307a..47e5e47 100644 --- a/tests/codeception/api/functional/OauthRefreshTokenCest.php +++ b/tests/codeception/api/functional/OauthRefreshTokenCest.php @@ -23,14 +23,7 @@ class OauthRefreshTokenCest { 'ely', 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM' )); - $I->canSeeResponseCodeIs(200); - $I->canSeeResponseIsJson(); - $I->canSeeResponseContainsJson([ - 'token_type' => 'Bearer', - ]); - $I->canSeeResponseJsonMatchesJsonPath('$.access_token'); - $I->canSeeResponseJsonMatchesJsonPath('$.refresh_token'); - $I->canSeeResponseJsonMatchesJsonPath('$.expires_in'); + $this->canSeeRefreshTokenSuccess($I); } public function testRefreshTokenWithSameScopes(OauthSteps $I) { @@ -41,14 +34,26 @@ class OauthRefreshTokenCest { 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM', [S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS] )); - $I->canSeeResponseCodeIs(200); - $I->canSeeResponseIsJson(); - $I->canSeeResponseContainsJson([ - 'token_type' => 'Bearer', - ]); - $I->canSeeResponseJsonMatchesJsonPath('$.access_token'); - $I->canSeeResponseJsonMatchesJsonPath('$.refresh_token'); - $I->canSeeResponseJsonMatchesJsonPath('$.expires_in'); + $this->canSeeRefreshTokenSuccess($I); + } + + public function testRefreshTokenTwice(OauthSteps $I) { + $refreshToken = $I->getRefreshToken([S::MINECRAFT_SERVER_SESSION]); + $this->route->issueToken($this->buildParams( + $refreshToken, + 'ely', + 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM', + [S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS] + )); + $this->canSeeRefreshTokenSuccess($I); + + $this->route->issueToken($this->buildParams( + $refreshToken, + 'ely', + 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM', + [S::MINECRAFT_SERVER_SESSION, S::OFFLINE_ACCESS] + )); + $this->canSeeRefreshTokenSuccess($I); } public function testRefreshTokenWithNewScopes(OauthSteps $I) { @@ -91,4 +96,15 @@ class OauthRefreshTokenCest { return $params; } + private function canSeeRefreshTokenSuccess(OauthSteps $I) { + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'token_type' => 'Bearer', + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.access_token'); + $I->canSeeResponseJsonMatchesJsonPath('$.expires_in'); + $I->cantSeeResponseJsonMatchesJsonPath('$.refresh_token'); + } + } From 4f259a9dc75387e07e1938fe22e5c73b5df02e5e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 29 Nov 2016 23:15:56 +0300 Subject: [PATCH 14/41] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B2=D1=81=D0=B5=20=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=B2=D1=8B=D0=B5=20=D1=81=D1=83=D1=89=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D0=BD=D0=B0=20=D0=BD=D0=B0=D1=88=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2/Entities/AuthCodeEntity.php | 4 ++-- .../OAuth2/Entities/SessionEntity.php | 8 ++----- .../OAuth2/Grants/AuthCodeGrant.php | 20 +++++++++++++++++ .../OAuth2/Grants/RefreshTokenGrant.php | 22 +++++++++++++++++++ api/config/config.php | 8 +++++-- api/controllers/OauthController.php | 5 ++--- composer.json | 2 +- 7 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 api/components/OAuth2/Grants/AuthCodeGrant.php create mode 100644 api/components/OAuth2/Grants/RefreshTokenGrant.php diff --git a/api/components/OAuth2/Entities/AuthCodeEntity.php b/api/components/OAuth2/Entities/AuthCodeEntity.php index c216fca..892189e 100644 --- a/api/components/OAuth2/Entities/AuthCodeEntity.php +++ b/api/components/OAuth2/Entities/AuthCodeEntity.php @@ -1,7 +1,7 @@ sessionId = $session->getId(); diff --git a/api/components/OAuth2/Entities/SessionEntity.php b/api/components/OAuth2/Entities/SessionEntity.php index 0d13361..eea6fb3 100644 --- a/api/components/OAuth2/Entities/SessionEntity.php +++ b/api/components/OAuth2/Entities/SessionEntity.php @@ -1,7 +1,7 @@ clientId; } - /** - * @inheritdoc - * @return static - */ - public function associateClient(ClientEntity $client) { + public function associateClient(OriginalClientEntity $client) { parent::associateClient($client); $this->clientId = $client->getId(); diff --git a/api/components/OAuth2/Grants/AuthCodeGrant.php b/api/components/OAuth2/Grants/AuthCodeGrant.php new file mode 100644 index 0000000..cfadbe0 --- /dev/null +++ b/api/components/OAuth2/Grants/AuthCodeGrant.php @@ -0,0 +1,20 @@ +server); + } + + protected function createRefreshTokenEntity() { + return new Entities\RefreshTokenEntity($this->server); + } + + protected function createSessionEntity() { + return new Entities\SessionEntity($this->server); + } + +} diff --git a/api/components/OAuth2/Grants/RefreshTokenGrant.php b/api/components/OAuth2/Grants/RefreshTokenGrant.php new file mode 100644 index 0000000..e1fd3ce --- /dev/null +++ b/api/components/OAuth2/Grants/RefreshTokenGrant.php @@ -0,0 +1,22 @@ +server); + } + + protected function createRefreshTokenEntity() { + return new Entities\RefreshTokenEntity($this->server); + } + + protected function createSessionEntity() { + return new Entities\SessionEntity($this->server); + } + +} diff --git a/api/config/config.php b/api/config/config.php index 37bdd71..4e7ab1a 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -1,7 +1,7 @@ [ 'class' => api\components\OAuth2\Component::class, 'grantTypes' => ['authorization_code'], + 'grantMap' => [ + 'authorization_code' => api\components\OAuth2\Grants\AuthCodeGrant::class, + 'refresh_token' => api\components\OAuth2\Grants\RefreshTokenGrant::class, + ], ], 'errorHandler' => [ 'class' => api\components\ErrorHandler::class, diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php index 916a95f..5c19972 100644 --- a/api/controllers/OauthController.php +++ b/api/controllers/OauthController.php @@ -8,7 +8,6 @@ use common\models\Account; use common\models\OauthClient; use common\models\OauthScope; use League\OAuth2\Server\Exception\OAuthException; -use League\OAuth2\Server\Grant\RefreshTokenGrant; use Yii; use yii\filters\AccessControl; use yii\helpers\ArrayHelper; @@ -195,8 +194,8 @@ class OauthController extends Controller { return; } - $grant = new RefreshTokenGrant(); - $grant->setRefreshTokenRotation(false); + $grantClass = Yii::$app->oauth->grantMap['refresh_token']; + $grant = new $grantClass; $this->getServer()->addGrantType($grant); } diff --git a/composer.json b/composer.json index e168e1b..ae78d32 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "yiisoft/yii2": "2.0.9", "yiisoft/yii2-swiftmailer": "*", "ramsey/uuid": "^3.5.0", - "league/oauth2-server": "dev-improvements#546dbfe85ae7c049cf9266281d228afe8bdd3ef6", + "league/oauth2-server": "dev-improvements#b9277ccd664dcb80a766b73674d21de686cb9dda", "yiisoft/yii2-redis": "~2.0.0", "guzzlehttp/guzzle": "^6.0.0", "php-amqplib/php-amqplib": "^2.6.2", From 422d5c4fd48c8be5c3006bc7c99b783a8301eb20 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 30 Nov 2016 02:19:14 +0300 Subject: [PATCH 15/41] =?UTF-8?q?=D0=A5=D1=80=D0=B0=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B8=D1=89=D0=B5=20access=5Ftoken=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=B5=D0=BD=D0=BE=20=D0=B2=20redis=20=D0=9F=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B4=D0=BB=D1=8F=20oAu?= =?UTF-8?q?th=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/ApiUser/Identity.php | 6 +- .../OAuth2/Entities/AccessTokenEntity.php | 19 +++ .../OAuth2/Entities/AuthCodeEntity.php | 4 + .../OAuth2/Entities/RefreshTokenEntity.php | 34 +++++ .../OAuth2/Grants/RefreshTokenGrant.php | 128 ++++++++++++++++++ .../OAuth2/Storage/AccessTokenStorage.php | 81 ++++------- .../OAuth2/Storage/AuthCodeStorage.php | 68 ++++------ .../OAuth2/Storage/RefreshTokenStorage.php | 45 +++--- .../OAuth2/Storage/SessionStorage.php | 39 ++---- common/components/Redis/Key.php | 9 +- common/models/OauthAccessToken.php | 1 + common/models/OauthSession.php | 9 ++ 12 files changed, 297 insertions(+), 146 deletions(-) diff --git a/api/components/ApiUser/Identity.php b/api/components/ApiUser/Identity.php index 3000b93..d3e4fab 100644 --- a/api/components/ApiUser/Identity.php +++ b/api/components/ApiUser/Identity.php @@ -10,9 +10,9 @@ use yii\web\IdentityInterface; use yii\web\UnauthorizedHttpException; /** - * @property Account $account - * @property OauthClient $client - * @property OauthSession $session + * @property Account $account + * @property OauthClient $client + * @property OauthSession $session * @property OauthAccessToken $accessToken */ class Identity implements IdentityInterface { diff --git a/api/components/OAuth2/Entities/AccessTokenEntity.php b/api/components/OAuth2/Entities/AccessTokenEntity.php index 3f92c5b..183704d 100644 --- a/api/components/OAuth2/Entities/AccessTokenEntity.php +++ b/api/components/OAuth2/Entities/AccessTokenEntity.php @@ -1,6 +1,8 @@ sessionId; } + public function setSessionId($sessionId) { + $this->sessionId = $sessionId; + } + /** * @inheritdoc * @return static @@ -22,4 +28,17 @@ class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity { return $this; } + public function getSession() { + if ($this->session instanceof OriginalSessionEntity) { + return $this->session; + } + + $sessionStorage = $this->server->getSessionStorage(); + if (!$sessionStorage instanceof SessionStorage) { + throw new ErrorException('SessionStorage must be instance of ' . SessionStorage::class); + } + + return $sessionStorage->getById($this->sessionId); + } + } diff --git a/api/components/OAuth2/Entities/AuthCodeEntity.php b/api/components/OAuth2/Entities/AuthCodeEntity.php index 892189e..28bfc2b 100644 --- a/api/components/OAuth2/Entities/AuthCodeEntity.php +++ b/api/components/OAuth2/Entities/AuthCodeEntity.php @@ -22,4 +22,8 @@ class AuthCodeEntity extends \League\OAuth2\Server\Entity\AuthCodeEntity { return $this; } + public function setSessionId(string $sessionId) { + $this->sessionId = $sessionId; + } + } diff --git a/api/components/OAuth2/Entities/RefreshTokenEntity.php b/api/components/OAuth2/Entities/RefreshTokenEntity.php index bd4607f..2404fa7 100644 --- a/api/components/OAuth2/Entities/RefreshTokenEntity.php +++ b/api/components/OAuth2/Entities/RefreshTokenEntity.php @@ -1,10 +1,44 @@ session instanceof SessionEntity) { + return $this->session; + } + + $sessionStorage = $this->server->getSessionStorage(); + if (!$sessionStorage instanceof SessionStorage) { + throw new ErrorException('SessionStorage must be instance of ' . SessionStorage::class); + } + + return $sessionStorage->getById($this->sessionId); + } + + public function getSessionId() : int { + return $this->sessionId; + } + + public function setSession(OriginalSessionEntity $session) { + parent::setSession($session); + $this->setSessionId($session->getId()); + + return $this; + } + + public function setSessionId(int $sessionId) { + $this->sessionId = $sessionId; + } + } diff --git a/api/components/OAuth2/Grants/RefreshTokenGrant.php b/api/components/OAuth2/Grants/RefreshTokenGrant.php index e1fd3ce..d98b3d6 100644 --- a/api/components/OAuth2/Grants/RefreshTokenGrant.php +++ b/api/components/OAuth2/Grants/RefreshTokenGrant.php @@ -2,6 +2,12 @@ namespace api\components\OAuth2\Grants; use api\components\OAuth2\Entities; +use ErrorException; +use League\OAuth2\Server\Entity\ClientEntity as OriginalClientEntity; +use League\OAuth2\Server\Entity\RefreshTokenEntity as OriginalRefreshTokenEntity; +use League\OAuth2\Server\Event; +use League\OAuth2\Server\Exception; +use League\OAuth2\Server\Util\SecureKey; class RefreshTokenGrant extends \League\OAuth2\Server\Grant\RefreshTokenGrant { @@ -19,4 +25,126 @@ class RefreshTokenGrant extends \League\OAuth2\Server\Grant\RefreshTokenGrant { return new Entities\SessionEntity($this->server); } + /** + * Метод таки пришлось переписать по той причине, что нынче мы храним access_token в redis с expire значением, + * так что он может банально несуществовать на тот момент, когда к нему через refresh_token попытаются обратиться. + * Поэтому мы расширили логику RefreshTokenEntity и она теперь знает о сессии, в рамках которой была создана + * + * @inheritdoc + */ + public function completeFlow() { + $clientId = $this->server->getRequest()->request->get('client_id', $this->server->getRequest()->getUser()); + if (is_null($clientId)) { + throw new Exception\InvalidRequestException('client_id'); + } + + $clientSecret = $this->server->getRequest()->request->get( + 'client_secret', + $this->server->getRequest()->getPassword() + ); + if ($this->shouldRequireClientSecret() && is_null($clientSecret)) { + throw new Exception\InvalidRequestException('client_secret'); + } + + // Validate client ID and client secret + $client = $this->server->getClientStorage()->get( + $clientId, + $clientSecret, + null, + $this->getIdentifier() + ); + + if (($client instanceof OriginalClientEntity) === false) { + $this->server->getEventEmitter()->emit(new Event\ClientAuthenticationFailedEvent($this->server->getRequest())); + throw new Exception\InvalidClientException(); + } + + $oldRefreshTokenParam = $this->server->getRequest()->request->get('refresh_token', null); + if ($oldRefreshTokenParam === null) { + throw new Exception\InvalidRequestException('refresh_token'); + } + + // Validate refresh token + $oldRefreshToken = $this->server->getRefreshTokenStorage()->get($oldRefreshTokenParam); + if (($oldRefreshToken instanceof OriginalRefreshTokenEntity) === false) { + throw new Exception\InvalidRefreshException(); + } + + // Ensure the old refresh token hasn't expired + if ($oldRefreshToken->isExpired()) { + throw new Exception\InvalidRefreshException(); + } + + /** @var Entities\AccessTokenEntity|null $oldAccessToken */ + $oldAccessToken = $oldRefreshToken->getAccessToken(); + if ($oldAccessToken instanceof Entities\AccessTokenEntity) { + // Get the scopes for the original session + $session = $oldAccessToken->getSession(); + } else { + if (!$oldRefreshToken instanceof Entities\RefreshTokenEntity) { + throw new ErrorException('oldRefreshToken must be instance of ' . Entities\RefreshTokenEntity::class); + } + + $session = $oldRefreshToken->getSession(); + } + + $scopes = $this->formatScopes($session->getScopes()); + + // Get and validate any requested scopes + $requestedScopesString = $this->server->getRequest()->request->get('scope', ''); + $requestedScopes = $this->validateScopes($requestedScopesString, $client); + + // If no new scopes are requested then give the access token the original session scopes + if (count($requestedScopes) === 0) { + $newScopes = $scopes; + } else { + // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure + // the request doesn't include any new scopes + foreach ($requestedScopes as $requestedScope) { + if (!isset($scopes[$requestedScope->getId()])) { + throw new Exception\InvalidScopeException($requestedScope->getId()); + } + } + + $newScopes = $requestedScopes; + } + + // Generate a new access token and assign it the correct sessions + $newAccessToken = $this->createAccessTokenEntity(); + $newAccessToken->setId(SecureKey::generate()); + $newAccessToken->setExpireTime($this->getAccessTokenTTL() + time()); + $newAccessToken->setSession($session); + + foreach ($newScopes as $newScope) { + $newAccessToken->associateScope($newScope); + } + + // Expire the old token and save the new one + ($oldAccessToken instanceof Entities\AccessTokenEntity) && $oldAccessToken->expire(); + $newAccessToken->save(); + + $this->server->getTokenType()->setSession($session); + $this->server->getTokenType()->setParam('access_token', $newAccessToken->getId()); + $this->server->getTokenType()->setParam('expires_in', $this->getAccessTokenTTL()); + + if ($this->shouldRotateRefreshTokens()) { + // Expire the old refresh token + $oldRefreshToken->expire(); + + // Generate a new refresh token + $newRefreshToken = $this->createRefreshTokenEntity(); + $newRefreshToken->setId(SecureKey::generate()); + $newRefreshToken->setExpireTime($this->getRefreshTokenTTL() + time()); + $newRefreshToken->setAccessToken($newAccessToken); + $newRefreshToken->save(); + + $this->server->getTokenType()->setParam('refresh_token', $newRefreshToken->getId()); + } else { + $oldRefreshToken->setAccessToken($newAccessToken); + $oldRefreshToken->save(); + } + + return $this->server->getTokenType()->generateResponse(); + } + } diff --git a/api/components/OAuth2/Storage/AccessTokenStorage.php b/api/components/OAuth2/Storage/AccessTokenStorage.php index 513dcb1..fdeb14c 100644 --- a/api/components/OAuth2/Storage/AccessTokenStorage.php +++ b/api/components/OAuth2/Storage/AccessTokenStorage.php @@ -2,87 +2,66 @@ namespace api\components\OAuth2\Storage; use api\components\OAuth2\Entities\AccessTokenEntity; -use common\models\OauthAccessToken; +use common\components\Redis\Key; +use common\components\Redis\Set; use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity; use League\OAuth2\Server\Entity\ScopeEntity; use League\OAuth2\Server\Storage\AbstractStorage; use League\OAuth2\Server\Storage\AccessTokenInterface; -use yii\db\Exception; +use yii\helpers\Json; class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface { - private $cache = []; + public $dataTable = 'oauth_access_tokens'; - /** - * @param string $token - * @return OauthAccessToken|null - */ - private function getTokenModel($token) { - if (!isset($this->cache[$token])) { - $this->cache[$token] = OauthAccessToken::findOne($token); - } - - return $this->cache[$token]; - } - - /** - * @inheritdoc - */ public function get($token) { - $model = $this->getTokenModel($token); - if ($model === null) { - return null; - } - - /** @var SessionStorage $sessionStorage */ - $sessionStorage = $this->server->getSessionStorage(); + $result = Json::decode((new Key($this->dataTable, $token))->getValue()); $token = new AccessTokenEntity($this->server); - $token->setId($model->access_token); - $token->setExpireTime($model->expire_time); - $token->setSession($sessionStorage->getById($model->session_id)); + $token->setId($result['id']); + $token->setExpireTime($result['expire_time']); + $token->setSessionId($result['session_id']); return $token; } - /** - * @inheritdoc - */ public function getScopes(OriginalAccessTokenEntity $token) { + $scopes = $this->scopes($token->getId()); $entities = []; - foreach($this->getTokenModel($token->getId())->getScopes() as $scope) { - $entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + foreach($scopes as $scope) { + if ($this->server->getScopeStorage()->get($scope) !== null) { + $entities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + } } return $entities; } - /** - * @inheritdoc - */ public function create($token, $expireTime, $sessionId) { - $model = new OauthAccessToken(); - $model->access_token = $token; - $model->expire_time = $expireTime; - $model->session_id = $sessionId; + $payload = Json::encode([ + 'id' => $token, + 'expire_time' => $expireTime, + 'session_id' => $sessionId, + ]); - if (!$model->save()) { - throw new Exception('Cannot save ' . OauthAccessToken::class . ' model.'); - } + $this->key($token)->setValue($payload)->expireAt($expireTime); } - /** - * @inheritdoc - */ public function associateScope(OriginalAccessTokenEntity $token, ScopeEntity $scope) { - $this->getTokenModel($token->getId())->getScopes()->add($scope->getId()); + $this->scopes($token->getId())->add($scope->getId())->expireAt($token->getExpireTime()); } - /** - * @inheritdoc - */ public function delete(OriginalAccessTokenEntity $token) { - $this->getTokenModel($token->getId())->delete(); + $this->key($token->getId())->delete(); + $this->scopes($token->getId())->delete(); + } + + private function key(string $token) : Key { + return new Key($this->dataTable, $token); + } + + private function scopes(string $token) : Set { + return new Set($this->dataTable, $token, 'scopes'); } } diff --git a/api/components/OAuth2/Storage/AuthCodeStorage.php b/api/components/OAuth2/Storage/AuthCodeStorage.php index a6a888d..77d7f51 100644 --- a/api/components/OAuth2/Storage/AuthCodeStorage.php +++ b/api/components/OAuth2/Storage/AuthCodeStorage.php @@ -8,81 +8,65 @@ use League\OAuth2\Server\Entity\AuthCodeEntity as OriginalAuthCodeEntity; use League\OAuth2\Server\Entity\ScopeEntity; use League\OAuth2\Server\Storage\AbstractStorage; use League\OAuth2\Server\Storage\AuthCodeInterface; +use yii\helpers\Json; class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface { public $dataTable = 'oauth_auth_codes'; - public $ttl = 3600; // 1h - - /** - * @inheritdoc - */ public function get($code) { - $result = json_decode((new Key($this->dataTable, $code))->getValue(), true); - if (!$result) { + $result = Json::decode((new Key($this->dataTable, $code))->getValue()); + if ($result === null) { return null; } - if ($result['expire_time'] < time()) { - return null; - } - - /** @var SessionStorage $sessionStorage */ - $sessionStorage = $this->server->getSessionStorage(); - $entity = new AuthCodeEntity($this->server); $entity->setId($result['id']); - $entity->setRedirectUri($result['client_redirect_uri']); $entity->setExpireTime($result['expire_time']); - $entity->setSession($sessionStorage->getById($result['session_id'])); + $entity->setSessionId($result['session_id']); + $entity->setRedirectUri($result['client_redirect_uri']); return $entity; } - /** - * @inheritdoc - */ public function create($token, $expireTime, $sessionId, $redirectUri) { - $payload = [ + $payload = Json::encode([ 'id' => $token, 'expire_time' => $expireTime, 'session_id' => $sessionId, 'client_redirect_uri' => $redirectUri, - ]; + ]); - (new Key($this->dataTable, $token))->setValue($payload)->expire($this->ttl); + $this->key($token)->setValue($payload)->expireAt($expireTime); } - /** - * @inheritdoc - */ public function getScopes(OriginalAuthCodeEntity $token) { - $result = new Set($this->dataTable, $token->getId(), 'scopes'); - $response = []; - foreach ($result as $scope) { - // TODO: нужно проверить все выданные скоупы на их существование - $response[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + $scopes = $this->scopes($token->getId()); + $scopesEntities = []; + foreach ($scopes as $scope) { + if ($this->server->getScopeStorage()->get($scope) !== null) { + $scopesEntities[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + } } - return $response; + return $scopesEntities; } - /** - * @inheritdoc - */ public function associateScope(OriginalAuthCodeEntity $token, ScopeEntity $scope) { - (new Set($this->dataTable, $token->getId(), 'scopes'))->add($scope->getId())->expire($this->ttl); + $this->scopes($token->getId())->add($scope->getId())->expireAt($token->getExpireTime()); } - /** - * @inheritdoc - */ public function delete(OriginalAuthCodeEntity $token) { - // Удаляем ключ - (new Set($this->dataTable, $token->getId()))->delete(); - // Удаляем список скоупов для ключа - (new Set($this->dataTable, $token->getId(), 'scopes'))->delete(); + $this->key($token->getId())->delete(); + $this->scopes($token->getId())->delete(); + } + + private function key(string $token) : Key { + return new Key($this->dataTable, $token); + } + + private function scopes(string $token) : Set { + return new Set($this->dataTable, $token, 'scopes'); } } diff --git a/api/components/OAuth2/Storage/RefreshTokenStorage.php b/api/components/OAuth2/Storage/RefreshTokenStorage.php index a1a31cf..2321e76 100644 --- a/api/components/OAuth2/Storage/RefreshTokenStorage.php +++ b/api/components/OAuth2/Storage/RefreshTokenStorage.php @@ -3,47 +3,58 @@ namespace api\components\OAuth2\Storage; use api\components\OAuth2\Entities\RefreshTokenEntity; use common\components\Redis\Key; +use common\components\Redis\Set; +use common\models\OauthSession; +use ErrorException; use League\OAuth2\Server\Entity\RefreshTokenEntity as OriginalRefreshTokenEntity; use League\OAuth2\Server\Storage\AbstractStorage; use League\OAuth2\Server\Storage\RefreshTokenInterface; +use Yii; +use yii\helpers\Json; class RefreshTokenStorage extends AbstractStorage implements RefreshTokenInterface { public $dataTable = 'oauth_refresh_tokens'; - /** - * @inheritdoc - */ public function get($token) { - $result = json_decode((new Key($this->dataTable, $token))->getValue(), true); - if (!$result) { - return null; - } + $result = Json::decode((new Key($this->dataTable, $token))->getValue()); $entity = new RefreshTokenEntity($this->server); $entity->setId($result['id']); $entity->setAccessTokenId($result['access_token_id']); + $entity->setSessionId($result['session_id']); return $entity; } - /** - * @inheritdoc - */ public function create($token, $expireTime, $accessToken) { - $payload = [ + $sessionId = $this->server->getAccessTokenStorage()->get($accessToken)->getSession()->getId(); + $payload = Json::encode([ 'id' => $token, 'access_token_id' => $accessToken, - ]; + 'session_id' => $sessionId, + ]); - (new Key($this->dataTable, $token))->setValue($payload); + $this->key($token)->setValue($payload); + $this->sessionHash($sessionId)->add($token); } - /** - * @inheritdoc - */ public function delete(OriginalRefreshTokenEntity $token) { - (new Key($this->dataTable, $token->getId()))->delete(); + if (!$token instanceof RefreshTokenEntity) { + throw new ErrorException('Token must be instance of ' . RefreshTokenEntity::class); + } + + $this->key($token->getId())->delete(); + $this->sessionHash($token->getSessionId())->remove($token->getId()); + } + + public function sessionHash(string $sessionId) : Set { + $tableName = Yii::$app->db->getSchema()->getRawTableName(OauthSession::tableName()); + return new Set($tableName, $sessionId, 'refresh_tokens'); + } + + private function key(string $token) : Key { + return new Key($this->dataTable, $token); } } diff --git a/api/components/OAuth2/Storage/SessionStorage.php b/api/components/OAuth2/Storage/SessionStorage.php index 06e66ff..8777f96 100644 --- a/api/components/OAuth2/Storage/SessionStorage.php +++ b/api/components/OAuth2/Storage/SessionStorage.php @@ -11,13 +11,10 @@ use League\OAuth2\Server\Entity\ScopeEntity; use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity; use League\OAuth2\Server\Storage\AbstractStorage; use League\OAuth2\Server\Storage\SessionInterface; -use yii\db\ActiveQuery; use yii\db\Exception; class SessionStorage extends AbstractStorage implements SessionInterface { - private $cache = []; - /** * @param string $sessionId * @return SessionEntity|null @@ -26,23 +23,10 @@ class SessionStorage extends AbstractStorage implements SessionInterface { return $this->hydrate($this->getSessionModel($sessionId)); } - /** - * @inheritdoc - */ public function getByAccessToken(OriginalAccessTokenEntity $accessToken) { - /** @var OauthSession|null $model */ - $model = OauthSession::find()->innerJoinWith([ - 'accessTokens' => function(ActiveQuery $query) use ($accessToken) { - $query->andWhere(['access_token' => $accessToken->getId()]); - }, - ])->one(); - - return $this->hydrate($model); + throw new ErrorException('This method is not implemented and should not be used'); } - /** - * @inheritdoc - */ public function getByAuthCode(OriginalAuthCodeEntity $authCode) { if (!$authCode instanceof AuthCodeEntity) { throw new ErrorException('This module assumes that $authCode typeof ' . AuthCodeEntity::class); @@ -51,22 +35,17 @@ class SessionStorage extends AbstractStorage implements SessionInterface { return $this->getById($authCode->getSessionId()); } - /** - * {@inheritdoc} - */ public function getScopes(OriginalSessionEntity $session) { $result = []; foreach ($this->getSessionModel($session->getId())->getScopes() as $scope) { - // TODO: нужно проверить все выданные скоупы на их существование - $result[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + if ($this->server->getScopeStorage()->get($scope) !== null) { + $result[] = (new ScopeEntity($this->server))->hydrate(['id' => $scope]); + } } return $result; } - /** - * @inheritdoc - */ public function create($ownerType, $ownerId, $clientId, $clientRedirectUri = null) { $sessionId = OauthSession::find() ->select('id') @@ -93,19 +72,17 @@ class SessionStorage extends AbstractStorage implements SessionInterface { return $sessionId; } - /** - * @inheritdoc - */ public function associateScope(OriginalSessionEntity $session, ScopeEntity $scope) { $this->getSessionModel($session->getId())->getScopes()->add($scope->getId()); } private function getSessionModel(string $sessionId) : OauthSession { - if (!isset($this->cache[$sessionId])) { - $this->cache[$sessionId] = OauthSession::findOne($sessionId); + $session = OauthSession::findOne($sessionId); + if ($session === null) { + throw new ErrorException('Cannot find oauth session'); } - return $this->cache[$sessionId]; + return $session; } private function hydrate(OauthSession $sessionModel) { diff --git a/common/components/Redis/Key.php b/common/components/Redis/Key.php index dbecc5d..fb43152 100644 --- a/common/components/Redis/Key.php +++ b/common/components/Redis/Key.php @@ -24,7 +24,7 @@ class Key { } public function setValue($value) { - $this->getRedis()->set($this->key, json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->getRedis()->set($this->key, $value); return $this; } @@ -37,11 +37,16 @@ class Key { return (bool)$this->getRedis()->exists($this->key); } - public function expire($ttl) { + public function expire(int $ttl) { $this->getRedis()->expire($this->key, $ttl); return $this; } + public function expireAt(int $unixTimestamp) { + $this->getRedis()->expireat($this->key, $unixTimestamp); + return $this; + } + public function __construct(...$key) { if (empty($key)) { throw new InvalidArgumentException('You must specify at least one key.'); diff --git a/common/models/OauthAccessToken.php b/common/models/OauthAccessToken.php index 660ac7a..7364a79 100644 --- a/common/models/OauthAccessToken.php +++ b/common/models/OauthAccessToken.php @@ -15,6 +15,7 @@ use yii\db\ActiveRecord; * * Отношения: * @property OauthSession $session + * @deprecated */ class OauthAccessToken extends ActiveRecord { diff --git a/common/models/OauthSession.php b/common/models/OauthSession.php index 730c903..675cd31 100644 --- a/common/models/OauthSession.php +++ b/common/models/OauthSession.php @@ -2,6 +2,7 @@ namespace common\models; use common\components\Redis\Set; +use Yii; use yii\db\ActiveRecord; /** @@ -46,6 +47,14 @@ class OauthSession extends ActiveRecord { } $this->getScopes()->delete(); + /** @var \api\components\OAuth2\Storage\RefreshTokenStorage $refreshTokensStorage */ + $refreshTokensStorage = Yii::$app->oauth->getAuthServer()->getRefreshTokenStorage(); + $refreshTokensSet = $refreshTokensStorage->sessionHash($this->id); + foreach ($refreshTokensSet->members() as $refreshTokenId) { + $refreshTokensStorage->delete($refreshTokensStorage->get($refreshTokenId)); + } + + $refreshTokensSet->delete(); return true; } From 23d079346b6cdd316614b976567396f21937ae16 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 30 Nov 2016 12:19:10 +0300 Subject: [PATCH 16/41] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20oAuth=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20oauth=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=20=D0=B8=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B5=20=D0=B7=D0=B0=D0=B2=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BD=D0=B0=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=D0=BD=D1=83=D1=82=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/ApiUser/AuthChecker.php | 7 +++---- api/components/ApiUser/Identity.php | 26 ++++++++++++------------- api/modules/session/models/JoinForm.php | 2 +- common/models/OauthSession.php | 20 +++++++++---------- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/api/components/ApiUser/AuthChecker.php b/api/components/ApiUser/AuthChecker.php index ef3dae5..59f6fa9 100644 --- a/api/components/ApiUser/AuthChecker.php +++ b/api/components/ApiUser/AuthChecker.php @@ -1,7 +1,7 @@ oauth->getAuthServer()->getAccessTokenStorage()->get($token); if ($accessToken === null) { return false; } - return $accessToken->getScopes()->exists($permissionName); + return $accessToken->hasScope($permissionName); } } diff --git a/api/components/ApiUser/Identity.php b/api/components/ApiUser/Identity.php index d3e4fab..fb3510d 100644 --- a/api/components/ApiUser/Identity.php +++ b/api/components/ApiUser/Identity.php @@ -1,24 +1,25 @@ oauth->getAuthServer()->getAccessTokenStorage()->get($token); if ($model === null) { throw new UnauthorizedHttpException('Incorrect token'); } elseif ($model->isExpired()) { @@ -37,7 +37,7 @@ class Identity implements IdentityInterface { return new static($model); } - private function __construct(OauthAccessToken $accessToken) { + private function __construct(AccessTokenEntity $accessToken) { $this->_accessToken = $accessToken; } @@ -50,20 +50,20 @@ class Identity implements IdentityInterface { } public function getSession() : OauthSession { - return $this->_accessToken->session; + return OauthSession::findOne($this->_accessToken->getSessionId()); } - public function getAccessToken() : OauthAccessToken { + public function getAccessToken() : AccessTokenEntity { return $this->_accessToken; } /** - * Этот метод используется для получения пользователя, к которому привязаны права. + * Этот метод используется для получения токена, к которому привязаны права. * У нас права привязываются к токенам, так что возвращаем именно его id. * @inheritdoc */ public function getId() { - return $this->_accessToken->access_token; + return $this->_accessToken->getId(); } public function getAuthKey() { diff --git a/api/modules/session/models/JoinForm.php b/api/modules/session/models/JoinForm.php index 648338e..f5e1973 100644 --- a/api/modules/session/models/JoinForm.php +++ b/api/modules/session/models/JoinForm.php @@ -128,7 +128,7 @@ class JoinForm extends Model { $account = $accessModel->account; } - /** @var MinecraftAccessKey|\common\models\OauthAccessToken $accessModel */ + /** @var MinecraftAccessKey|\api\components\OAuth2\Entities\AccessTokenEntity $accessModel */ if ($accessModel->isExpired()) { Session::error("User with access_token = '{$accessToken}' failed join by expired access_token."); throw new ForbiddenOperationException('Expired access_token.'); diff --git a/common/models/OauthSession.php b/common/models/OauthSession.php index 675cd31..981983e 100644 --- a/common/models/OauthSession.php +++ b/common/models/OauthSession.php @@ -3,21 +3,21 @@ namespace common\models; use common\components\Redis\Set; use Yii; +use yii\base\ErrorException; use yii\db\ActiveRecord; /** * Поля: - * @property integer $id - * @property string $owner_type - * @property string $owner_id - * @property string $client_id - * @property string $client_redirect_uri + * @property integer $id + * @property string $owner_type + * @property string $owner_id + * @property string $client_id + * @property string $client_redirect_uri * * Отношения - * @property OauthAccessToken[] $accessTokens - * @property OauthClient $client - * @property Account $account - * @property Set $scopes + * @property OauthClient $client + * @property Account $account + * @property Set $scopes */ class OauthSession extends ActiveRecord { @@ -26,7 +26,7 @@ class OauthSession extends ActiveRecord { } public function getAccessTokens() { - return $this->hasMany(OauthAccessToken::class, ['session_id' => 'id']); + throw new ErrorException('This method is possible, but not implemented'); } public function getClient() { From 841673f7ef96f936e6af5d45eae6575696bbd0fc Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 30 Nov 2016 14:00:36 +0300 Subject: [PATCH 17/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20key-scan=20=D0=B4=D0=BB=D1=8F=20gitlab.ely.by?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile-dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 24a74dc..7877162 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN chmod 400 ~/.ssh/id_rsa \ && eval $(ssh-agent -s) \ && ssh-add /root/.ssh/id_rsa \ && touch /root/.ssh/known_hosts \ - && ssh-keyscan gitlab.com >> /root/.ssh/known_hosts + && ssh-keyscan gitlab.com gitlab.ely.by >> /root/.ssh/known_hosts # Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через # volume на dev окружении. В entrypoint эта папка будет скопирована обратно. diff --git a/Dockerfile-dev b/Dockerfile-dev index 67b7943..7c30fc4 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -11,7 +11,7 @@ RUN chmod 400 ~/.ssh/id_rsa \ && eval $(ssh-agent -s) \ && ssh-add /root/.ssh/id_rsa \ && touch /root/.ssh/known_hosts \ - && ssh-keyscan gitlab.com >> /root/.ssh/known_hosts + && ssh-keyscan gitlab.com gitlab.ely.by >> /root/.ssh/known_hosts # Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через # volume на dev окружении. В entrypoint эта папка будет скопирована обратно. From 5de6fa91c8b41a2d7eb03de2cd7ff1d230de49c6 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 30 Nov 2016 20:57:30 +0300 Subject: [PATCH 18/41] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=20ApiNormalizer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/AccountsController.php | 14 ++++---- api/controllers/AuthenticationController.php | 8 ++--- api/controllers/Controller.php | 11 +++--- api/controllers/FeedbackController.php | 2 +- api/controllers/SignupController.php | 6 ++-- api/traits/ApiNormalize.php | 26 -------------- .../api/unit/traits/ApiNormalizerTest.php | 35 ------------------- 7 files changed, 20 insertions(+), 82 deletions(-) delete mode 100644 api/traits/ApiNormalize.php delete mode 100644 tests/codeception/api/unit/traits/ApiNormalizerTest.php diff --git a/api/controllers/AccountsController.php b/api/controllers/AccountsController.php index 8fca1e6..cec7384 100644 --- a/api/controllers/AccountsController.php +++ b/api/controllers/AccountsController.php @@ -79,7 +79,7 @@ class AccountsController extends Controller { if (!$model->changePassword()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -94,7 +94,7 @@ class AccountsController extends Controller { if (!$model->change()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -110,7 +110,7 @@ class AccountsController extends Controller { if (!$model->sendCurrentEmailConfirmation()) { $data = [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; if (ArrayHelper::getValue($data['errors'], 'email') === E::RECENTLY_SENT_MESSAGE) { @@ -136,7 +136,7 @@ class AccountsController extends Controller { if (!$model->sendNewEmailConfirmation()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -152,7 +152,7 @@ class AccountsController extends Controller { if (!$model->changeEmail()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -171,7 +171,7 @@ class AccountsController extends Controller { if (!$model->applyLanguage()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -187,7 +187,7 @@ class AccountsController extends Controller { if (!$model->agreeWithLatestRules()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } diff --git a/api/controllers/AuthenticationController.php b/api/controllers/AuthenticationController.php index 051e30c..19556f8 100644 --- a/api/controllers/AuthenticationController.php +++ b/api/controllers/AuthenticationController.php @@ -53,7 +53,7 @@ class AuthenticationController extends Controller { if (($result = $model->login()) === false) { $data = [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; if (ArrayHelper::getValue($data['errors'], 'login') === E::ACCOUNT_NOT_ACTIVATED) { @@ -83,7 +83,7 @@ class AuthenticationController extends Controller { if ($model->forgotPassword() === false) { $data = [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; if (ArrayHelper::getValue($data['errors'], 'login') === E::RECENTLY_SENT_MESSAGE) { @@ -119,7 +119,7 @@ class AuthenticationController extends Controller { if (($result = $model->recoverPassword()) === false) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -134,7 +134,7 @@ class AuthenticationController extends Controller { if (($result = $model->renew()) === false) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } diff --git a/api/controllers/Controller.php b/api/controllers/Controller.php index 8c655d6..a4a981e 100644 --- a/api/controllers/Controller.php +++ b/api/controllers/Controller.php @@ -1,7 +1,6 @@ Yii::$app->getUser(), ]; - // xml нам не понадобится - unset($parentBehaviors['contentNegotiator']['formats']['application/xml']); - // rate limiter здесь не применяется - unset($parentBehaviors['rateLimiter']); + // xml и rate limiter нам не понадобятся + unset( + $parentBehaviors['contentNegotiator']['formats']['application/xml'], + $parentBehaviors['rateLimiter'] + ); return $parentBehaviors; } diff --git a/api/controllers/FeedbackController.php b/api/controllers/FeedbackController.php index e185c6d..5d4885a 100644 --- a/api/controllers/FeedbackController.php +++ b/api/controllers/FeedbackController.php @@ -27,7 +27,7 @@ class FeedbackController extends Controller { if (!$model->sendMessage()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } diff --git a/api/controllers/SignupController.php b/api/controllers/SignupController.php index 2518b5e..b1d29c0 100644 --- a/api/controllers/SignupController.php +++ b/api/controllers/SignupController.php @@ -43,7 +43,7 @@ class SignupController extends Controller { if (!$model->signup()) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } @@ -58,7 +58,7 @@ class SignupController extends Controller { if (!$model->sendRepeatMessage()) { $response = [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; if (ArrayHelper::getValue($response['errors'], 'email') === E::RECENTLY_SENT_MESSAGE) { @@ -83,7 +83,7 @@ class SignupController extends Controller { if (!($result = $model->confirm())) { return [ 'success' => false, - 'errors' => $this->normalizeModelErrors($model->getErrors()), + 'errors' => $model->getFirstErrors(), ]; } diff --git a/api/traits/ApiNormalize.php b/api/traits/ApiNormalize.php deleted file mode 100644 index 590680c..0000000 --- a/api/traits/ApiNormalize.php +++ /dev/null @@ -1,26 +0,0 @@ - 'first_error_of_field1', - * 'field2' => 'first_error_of_field2', - * ] - * - * @param array $errors - * @return array - */ - public function normalizeModelErrors(array $errors) { - $normalized = []; - foreach($errors as $attribute => $attrErrors) { - $normalized[$attribute] = $attrErrors[0]; - } - - return $normalized; - } - -} diff --git a/tests/codeception/api/unit/traits/ApiNormalizerTest.php b/tests/codeception/api/unit/traits/ApiNormalizerTest.php deleted file mode 100644 index 5641478..0000000 --- a/tests/codeception/api/unit/traits/ApiNormalizerTest.php +++ /dev/null @@ -1,35 +0,0 @@ -normalizeModelErrors([ - 'rulesAgreement' => [ - 'error.you_must_accept_rules', - ], - 'email' => [ - 'error.email_required', - ], - 'username' => [ - 'error.username_too_short', - 'error.username_not_unique', - ], - ]); - - $this->assertEquals([ - 'rulesAgreement' => 'error.you_must_accept_rules', - 'email' => 'error.email_required', - 'username' => 'error.username_too_short', - ], $normalized); - } - -} From 225f2fd3cf4f29c3fb6a5522c9dca3a9d10f0d64 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 22 Nov 2016 01:47:41 +0300 Subject: [PATCH 19/41] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B8=D1=87?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA?= =?UTF-8?q?=20=D0=B2=20sentry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/config/config.php | 11 +++++++++++ common/config/config.php | 9 +++++++++ composer.json | 3 ++- console/config/config.php | 4 ++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/api/config/config.php b/api/config/config.php index 4e7ab1a..5f41f0a 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -21,6 +21,17 @@ return [ 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => [ + [ + 'class' => mito\sentry\SentryTarget::class, + 'levels' => ['error', 'warning'], + 'except' => [ + 'legacy-authserver', + 'session', + 'yii\web\HttpException:*', + 'api\modules\session\exceptions\SessionServerException:*', + 'api\modules\authserver\exceptions\AuthserverException:*', + ], + ], [ 'class' => yii\log\FileTarget::class, 'levels' => ['error', 'warning'], diff --git a/common/config/config.php b/common/config/config.php index 262b2ec..418162a 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -20,6 +20,15 @@ return [ 'class' => yii\swiftmailer\Mailer::class, 'viewPath' => '@common/mail', ], + 'sentry' => [ + 'class' => mito\sentry\SentryComponent::class, + 'dsn' => 'https://9f37c63079e24f35b585ab61dd7ee068:b501bfc8d9fc49ccadbf16731705b222@sentry.ely.by/3', + 'environment' => YII_ENV_DEV ? 'development' : 'production', + 'jsNotifier' => false, + 'client' => [ + 'curl_method' => 'async', + ], + ], 'security' => [ 'passwordHashStrategy' => 'password_hash', ], diff --git a/composer.json b/composer.json index ae78d32..aeaa5d0 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "emarref/jwt": "~1.0.3", "ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec", "ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf", - "predis/predis": "^1.0" + "predis/predis": "^1.0", + "mito/yii2-sentry": "dev-1.0.0-dev#193b96880d30e9d3b616652a5abb3c4f28e14f2c" }, "require-dev": { "yiisoft/yii2-codeception": "*", diff --git a/console/config/config.php b/console/config/config.php index f7c04cf..2a8e775 100644 --- a/console/config/config.php +++ b/console/config/config.php @@ -13,6 +13,10 @@ return [ 'components' => [ 'log' => [ 'targets' => [ + [ + 'class' => mito\sentry\SentryTarget::class, + 'levels' => ['error', 'warning'], + ], [ 'class' => yii\log\FileTarget::class, 'levels' => ['error', 'warning'], From ef8bb894f0201f75b7fe2bd18493d97077fa34f5 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 2 Dec 2016 00:23:40 +0300 Subject: [PATCH 20/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20ENV=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=B0=D1=8F,=20=D0=BE=D1=82=D0=B2=D0=B5=D1=87?= =?UTF-8?q?=D0=B0=D1=8E=D1=89=D0=B0=D1=8F=20=D0=B7=D0=B0=20dsn=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=20sentry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-dist | 1 + common/config/config.php | 3 ++- tests/codeception/config/config.php | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env-dist b/.env-dist index 683a034..74035ec 100644 --- a/.env-dist +++ b/.env-dist @@ -4,6 +4,7 @@ YII_ENV=dev JWT_USER_SECRET= RECAPTCHA_PUBLIC= RECAPTCHA_SECRET= +SENTRY_DSN= # Web VIRTUAL_HOST=account.ely.by,authserver.ely.by diff --git a/common/config/config.php b/common/config/config.php index 418162a..fd19f21 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -22,7 +22,8 @@ return [ ], 'sentry' => [ 'class' => mito\sentry\SentryComponent::class, - 'dsn' => 'https://9f37c63079e24f35b585ab61dd7ee068:b501bfc8d9fc49ccadbf16731705b222@sentry.ely.by/3', + 'enabled' => !empty(getenv('SENTRY_DSN')), + 'dsn' => getenv('SENTRY_DSN'), 'environment' => YII_ENV_DEV ? 'development' : 'production', 'jsNotifier' => false, 'client' => [ diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index 3fbe89e..e91b77a 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -34,5 +34,8 @@ return [ // Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается 'passwordHashCost' => 4, ], + 'sentry' => [ + 'enabled' => false, + ], ], ]; From 6e4e2b26eef605e0737db7305f50ffdd2e15b3de Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 2 Dec 2016 11:38:14 +0300 Subject: [PATCH 21/41] =?UTF-8?q?Nginx=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8C=20=D1=81=D0=BE=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.dev.yml | 2 +- docker-compose.prod.yml | 2 +- docker/nginx/Dockerfile | 11 ---- docker/nginx/account.ely.by.conf.template | 78 ----------------------- docker/nginx/nginx.conf | 25 -------- docker/nginx/run.sh | 5 -- 6 files changed, 2 insertions(+), 121 deletions(-) delete mode 100644 docker/nginx/Dockerfile delete mode 100644 docker/nginx/account.ely.by.conf.template delete mode 100644 docker/nginx/nginx.conf delete mode 100644 docker/nginx/run.sh diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9dce11c..f93f1f7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -13,7 +13,7 @@ services: env_file: .env web: - build: ./docker/nginx + image: registry.ely.by/elyby/accounts-nginx:latest volumes_from: - app links: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 1d59c03..d311e0b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,7 +9,7 @@ services: env_file: .env web: - build: ./docker/nginx + image: registry.ely.by/elyby/accounts-nginx:1.0.2 volumes_from: - app links: diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile deleted file mode 100644 index 3c47e59..0000000 --- a/docker/nginx/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM nginx:1.11-alpine - -COPY nginx.conf /etc/nginx/nginx.conf -COPY account.ely.by.conf.template /etc/nginx/conf.d/account.ely.by.conf.template -COPY run.sh /run.sh - -RUN rm /etc/nginx/conf.d/default.conf \ - && chmod a+x /run.sh - -ENTRYPOINT ["/run.sh"] -CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/nginx/account.ely.by.conf.template b/docker/nginx/account.ely.by.conf.template deleted file mode 100644 index d5b2fbc..0000000 --- a/docker/nginx/account.ely.by.conf.template +++ /dev/null @@ -1,78 +0,0 @@ -server { - listen 80; - - root $root_path; - charset utf-8; - index index.html; - etag on; - - # Это можно раскоментить для целей отладки - # rewrite_log on; - # error_log /var/log/nginx/error.log debug; - - set $root_path '/var/www/html'; - set $frontend_path '${root_path}/frontend/dist'; - - set $request_url $request_uri; - set $host_with_uri '${host}${request_uri}'; - - if ($host_with_uri ~ '^${AUTHSERVER_HOST}/auth') { - set $request_url '/api/authserver${request_uri}'; - rewrite ^/auth /api/authserver$uri last; - } - - if ($host_with_uri ~ '^${AUTHSERVER_HOST}/session') { - set $request_url '/api/minecraft${request_uri}'; - rewrite ^/session /api/minecraft$uri last; - } - - if ($host_with_uri ~ '^${AUTHSERVER_HOST}/api/(user|profiles)') { - set $request_url '/api/mojang${request_uri}'; - rewrite ^/api/(user|profiles) /api/mojang$uri last; - } - - location / { - alias $frontend_path; - try_files $uri /index.html =404; - } - - location /api { - try_files $uri $uri /api/web/index.php$is_args$args; - } - - location ~* \.php$ { - fastcgi_pass php:9000; - include fastcgi_params; - - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param SERVER_NAME $host; - fastcgi_param REQUEST_URI $request_url; - fastcgi_param REMOTE_ADDR $http_x_real_ip; - - try_files $uri =404; - } - - # html файлы идут отдельно, для них будет применяться E-Tag кэширование - location ~* \.html$ { - root $frontend_path; - access_log off; - } - - # Раздача статики для frontend с указанием max-кэша. Сброс будет по #hash после ребилда webpackом - location ~* ^.+\.(jpg|jpeg|gif|png|svg|js|json|css|zip|rar|eot|ttf|woff|woff2|ico)$ { - root $frontend_path; - expires max; - etag off; - access_log off; - } - - # Запросы к статике для email, их нужно запустить внутрь vendor - location ^~ /images/emails/assets { - rewrite ^/images/emails/assets/(.+)$ /vendor/ely/emails-renderer/dist/assets/$1 last; - } - - location ^~ /vendor/ely/emails-renderer/dist/assets { - alias '${root_path}/vendor/ely/email-renderer/dist/assets'; - try_files $uri =404; - } -} diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf deleted file mode 100644 index 886547e..0000000 --- a/docker/nginx/nginx.conf +++ /dev/null @@ -1,25 +0,0 @@ -user nginx; -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - keepalive_timeout 10; - - include /etc/nginx/conf.d/*.conf; -} diff --git a/docker/nginx/run.sh b/docker/nginx/run.sh deleted file mode 100644 index 7365afe..0000000 --- a/docker/nginx/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env sh - -envsubst '$AUTHSERVER_HOST' < /etc/nginx/conf.d/account.ely.by.conf.template > /etc/nginx/conf.d/default.conf - -exec "$@" From 0a0cca0834636b5e1d7c034a34948c37abe1505e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 2 Dec 2016 11:38:35 +0300 Subject: [PATCH 22/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D1=8D=D1=88=D0=B8=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D0=B5=20?= =?UTF-8?q?nginx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/filters/NginxCache.php | 35 ++++++++++++ .../api/unit/filters/NginxCacheTest.php | 57 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 api/filters/NginxCache.php create mode 100644 tests/codeception/api/unit/filters/NginxCacheTest.php diff --git a/api/filters/NginxCache.php b/api/filters/NginxCache.php new file mode 100644 index 0000000..21669e1 --- /dev/null +++ b/api/filters/NginxCache.php @@ -0,0 +1,35 @@ + сколько кэшировать. + * + * Период можно задавать 2-умя путями: + * - если значение начинается с префикса @, оно задаёт абсолютное время в unix timestamp, + * до которого ответ может быть закэширован. + * - в ином случае значение интерпретируется как количество секунд, на которое необходимо + * закэшировать ответ + */ + public $rules; + + public function afterAction($action, $result) { + $rule = $this->rules[$action->id] ?? null; + if ($rule !== null) { + if (is_callable($rule)) { + $cacheTime = $rule($action); + } else { + $cacheTime = $rule; + } + + Yii::$app->response->headers->set('X-Accel-Expires', $cacheTime); + } + + return parent::afterAction($action, $result); + } + +} diff --git a/tests/codeception/api/unit/filters/NginxCacheTest.php b/tests/codeception/api/unit/filters/NginxCacheTest.php new file mode 100644 index 0000000..01f982d --- /dev/null +++ b/tests/codeception/api/unit/filters/NginxCacheTest.php @@ -0,0 +1,57 @@ +testAfterActionInternal(3600, 3600); + $this->testAfterActionInternal('@' . (time() + 30), '@' . (time() + 30)); + $this->testAfterActionInternal(function() { + return 3000; + }, 3000); + } + + private function testAfterActionInternal($ruleConfig, $expected) { + /** @var HeaderCollection|\PHPUnit_Framework_MockObject_MockObject $headers */ + $headers = $this->getMockBuilder(HeaderCollection::class) + ->setMethods(['set']) + ->getMock(); + + $headers->expects($this->once()) + ->method('set') + ->with('X-Accel-Expires', $expected); + + /** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */ + $request = $this->getMockBuilder(Request::class) + ->setMethods(['getHeaders']) + ->getMock(); + + $request->expects($this->any()) + ->method('getHeaders') + ->willReturn($headers); + + Yii::$app->set('response', $request); + + /** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ + $controller = $this->getMockBuilder(Controller::class) + ->setConstructorArgs(['mock', Yii::$app]) + ->getMock(); + + $component = new NginxCache([ + 'rules' => [ + 'index' => $ruleConfig, + ], + ]); + + $component->afterAction(new Action('index', $controller), ''); + } + +} From ffd4a9000f7e46f4c51f268a2394037ed66710a2 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 2 Dec 2016 11:38:53 +0300 Subject: [PATCH 23/41] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20/api/options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/OptionsController.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/controllers/OptionsController.php b/api/controllers/OptionsController.php index 4c230f9..c96762a 100644 --- a/api/controllers/OptionsController.php +++ b/api/controllers/OptionsController.php @@ -1,6 +1,7 @@ [ 'except' => ['index'], ], + 'nginxCache' => [ + 'class' => NginxCache::class, + 'rules' => [ + 'index' => 3600, // 1h + ], + ], ]); } From 6fc558c3c8dbda41605dc02b176400ab3a407419 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 3 Dec 2016 14:28:03 +0300 Subject: [PATCH 24/41] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20cron=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B8=20acc?= =?UTF-8?q?ess=5Ftokens=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/cron/.gitkeep | 0 docker/cron/cleanup | 2 -- 2 files changed, 2 deletions(-) create mode 100644 docker/cron/.gitkeep delete mode 100644 docker/cron/cleanup diff --git a/docker/cron/.gitkeep b/docker/cron/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/cron/cleanup b/docker/cron/cleanup deleted file mode 100644 index 3401974..0000000 --- a/docker/cron/cleanup +++ /dev/null @@ -1,2 +0,0 @@ -# https://crontab.guru/every-hour -0 * * * * root /usr/local/bin/php /var/www/html/yii cleanup/access-tokens >/dev/null 2>&1 From 9274155cfc0f6b467967c7901e6011a132547429 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 4 Dec 2016 15:34:32 +0300 Subject: [PATCH 25/41] =?UTF-8?q?=D0=9E=D1=82=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80?= =?UTF-8?q?=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=82=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D0=BC=D1=8B=D1=85=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-dist | 50 ++++++++++++++++++---- common/config/config-prod.php | 17 -------- common/config/config.php | 38 ++++++++++------ tests/codeception/api/functional.suite.yml | 2 +- tests/codeception/config/config.php | 17 -------- tests/docker-compose.yml | 25 +++++++---- 6 files changed, 85 insertions(+), 64 deletions(-) diff --git a/.env-dist b/.env-dist index 683a034..33bd18c 100644 --- a/.env-dist +++ b/.env-dist @@ -1,20 +1,56 @@ -# Основные параметры +# Параметры приложения +## Env приложения YII_DEBUG=true YII_ENV=dev + +## Параметры, отвечающие за безопасность JWT_USER_SECRET= + +## Внешние сервисы RECAPTCHA_PUBLIC= RECAPTCHA_SECRET= +## SMTP параметры +SMTP_USER= +SMTP_PASS= +SMTP_PORT= + +## Параметры подключения к базе данных +DB_HOST=db +DB_DATABASE=ely_accounts +DB_USER=ely_accounts_user +DB_PASSWORD=ely_accounts_password + +## Параметры подключения к redis +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_DATABASE=0 +REDIS_PASSWORD= + +## Параметры подключения к redis +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_DATABASE=0 +REDIS_PASS= + +## Параметры подключения к rabbitmq +RABBITMQ_HOST=rabbitmq +RABBITMQ_PORT=5672 +RABBITMQ_USER=ely-accounts-app +RABBITMQ_PASS=ely-accounts-app-password +RABBITMQ_VHOST=/ely.by + +## Конфигурация для Dev. +XDEBUG_CONFIG=remote_host=10.254.254.254 +PHP_IDE_CONFIG=serverName=docker + + # Web VIRTUAL_HOST=account.ely.by,authserver.ely.by AUTHSERVER_HOST=authserver.ely.by # LETSENCRYPT_HOST=account.ely.by # LETSENCRYPT_EMAIL=erickskrauch@ely.by -# SMTP (только для production) -SMTP_USER= -SMTP_PASS= - # MySQL MYSQL_ALLOW_EMPTY_PASSWORD=yes MYSQL_ROOT_PASSWORD= @@ -26,7 +62,3 @@ MYSQL_PASSWORD=ely_accounts_password RABBITMQ_DEFAULT_USER=ely-accounts-app RABBITMQ_DEFAULT_PASS=ely-accounts-app-password RABBITMQ_DEFAULT_VHOST=/ely.by - -# Конфигурация для Dev. -XDEBUG_CONFIG=remote_host=10.254.254.254 -PHP_IDE_CONFIG=serverName=docker diff --git a/common/config/config-prod.php b/common/config/config-prod.php index 373f6ca..0160435 100644 --- a/common/config/config-prod.php +++ b/common/config/config-prod.php @@ -6,22 +6,5 @@ return [ 'schemaCacheDuration' => 3600, 'schemaCache' => 'cache', ], - 'mailer' => [ - 'useFileTransport' => false, - 'transport' => [ - 'class' => Swift_SmtpTransport::class, - 'host' => 'ely.by', - 'username' => getenv('SMTP_USER'), - 'password' => getenv('SMTP_PASS'), - 'port' => 587, - 'encryption' => 'tls', - 'streamOptions' => [ - 'ssl' => [ - 'allow_self_signed' => true, - 'verify_peer' => false, - ], - ], - ], - ], ], ]; diff --git a/common/config/config.php b/common/config/config.php index 262b2ec..ed68090 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -8,9 +8,9 @@ return [ ], 'db' => [ 'class' => yii\db\Connection::class, - 'dsn' => 'mysql:host=db;dbname=' . getenv('MYSQL_DATABASE'), - 'username' => getenv('MYSQL_USER'), - 'password' => getenv('MYSQL_PASSWORD'), + 'dsn' => 'mysql:host=' . (getenv('DB_HOST') ?: 'db') . ';dbname=' . getenv('DB_DATABASE'), + 'username' => getenv('DB_USER'), + 'password' => getenv('DB_PASSWORD'), 'charset' => 'utf8', 'schemaMap' => [ 'mysql' => common\db\mysql\Schema::class, @@ -19,24 +19,38 @@ return [ 'mailer' => [ 'class' => yii\swiftmailer\Mailer::class, 'viewPath' => '@common/mail', + 'transport' => [ + 'class' => Swift_SmtpTransport::class, + 'host' => 'ely.by', + 'username' => getenv('SMTP_USER'), + 'password' => getenv('SMTP_PASS'), + 'port' => getenv('SMTP_PORT') ?: 587, + 'encryption' => 'tls', + 'streamOptions' => [ + 'ssl' => [ + 'allow_self_signed' => true, + 'verify_peer' => false, + ], + ], + ], ], 'security' => [ 'passwordHashStrategy' => 'password_hash', ], 'redis' => [ 'class' => common\components\Redis\Connection::class, - 'hostname' => 'redis', - 'password' => null, - 'port' => 6379, - 'database' => 0, + 'hostname' => getenv('REDIS_HOST') ?: 'redis', + 'password' => getenv('REDIS_PASS') ?: null, + 'port' => getenv('REDIS_PORT') ?: 6379, + 'database' => getenv('REDIS_DATABASE') ?: 0, ], 'amqp' => [ 'class' => common\components\RabbitMQ\Component::class, - 'host' => 'rabbitmq', - 'port' => 5672, - 'user' => getenv('RABBITMQ_DEFAULT_USER'), - 'password' => getenv('RABBITMQ_DEFAULT_PASS'), - 'vhost' => getenv('RABBITMQ_DEFAULT_VHOST'), + 'host' => getenv('RABBITMQ_HOST') ?: 'rabbitmq', + 'port' => getenv('RABBITMQ_PORT') ?: 5672, + 'user' => getenv('RABBITMQ_USER'), + 'password' => getenv('RABBITMQ_PASS'), + 'vhost' => getenv('RABBITMQ_VHOST'), ], 'guzzle' => [ 'class' => GuzzleHttp\Client::class, diff --git a/tests/codeception/api/functional.suite.yml b/tests/codeception/api/functional.suite.yml index 232c933..5d52ea1 100644 --- a/tests/codeception/api/functional.suite.yml +++ b/tests/codeception/api/functional.suite.yml @@ -23,4 +23,4 @@ modules: username: 'ely-accounts-tester' password: 'tester-password' vhost: '/account.ely.by/tests' - queues: ['account-operations'] + queues: [] diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index 3fbe89e..2904804 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -10,26 +10,9 @@ return [ ], ], 'components' => [ - 'db' => [ - 'dsn' => 'mysql:host=testdb;dbname=ely_accounts_test', - 'username' => 'ely_accounts_tester', - 'password' => 'ely_accounts_tester_password', - ], - 'mailer' => [ - 'useFileTransport' => true, - ], 'urlManager' => [ 'showScriptName' => true, ], - 'redis' => [ - 'hostname' => 'testredis', - ], - 'amqp' => [ - 'host' => 'testrabbit', - 'user' => 'ely-accounts-tester', - 'password' => 'tester-password', - 'vhost' => '/account.ely.by/tests', - ], 'security' => [ // Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается 'passwordHashCost' => 4, diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 4c2d96d..a9e8054 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -11,16 +11,25 @@ services: - testredis - testrabbit volumes: - - ./codeception/_output:/var/www/html/tests/codeception/_output - - ./codeception/api/_output:/var/www/html/tests/codeception/api/_output - - ./codeception/common/_output:/var/www/html/tests/codeception/common/_output - - ./codeception/console/_output:/var/www/html/tests/codeception/console/_output + - ./..:/var/www/html environment: - - YII_DEBUG=true - - YII_ENV=test + YII_DEBUG: "true" + YII_ENV: "test" + # DB config + DB_HOST: "testdb" + DB_DATABASE: "ely_accounts_test" + DB_USER: "ely_accounts_tester" + DB_PASSWORD: "ely_accounts_tester_password" + # Redis config + REDIS_HOST: "testredis" + # RabbitMQ config + RABBITMQ_HOST: "testrabbit" + RABBITMQ_USER: "ely-accounts-tester" + RABBITMQ_PASS: "tester-password" + RABBITMQ_VHOST: "/account.ely.by/tests" # Это я потом, когда-нибудь, уберу - - XDEBUG_CONFIG=remote_host=10.254.254.254 - - PHP_IDE_CONFIG=serverName=docker + XDEBUG_CONFIG: "remote_host=10.254.254.254" + PHP_IDE_CONFIG: "serverName=docker" testdb: container_name: accountelyby_testdb From 7a8f9950ab6ade2bb207fb245157656ae23f86be Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sun, 4 Dec 2016 15:12:36 +0200 Subject: [PATCH 26/41] #242: reduce npm verbosity in containers and in CI --- .gitlab-ci.yml | 4 ++-- Dockerfile | 4 ++-- Dockerfile-dev | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8f832ae..d5aa9ca 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,8 +28,8 @@ test:frontend: - frontend/node_modules script: - cd frontend - - npm i --silent - - npm run test + - npm i --silent > /dev/null + - npm run test --silent build:production: image: docker:latest diff --git a/Dockerfile b/Dockerfile index 24a74dc..fe82640 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils RUN cd ../frontend \ - && npm install \ + && npm install --quiet --depth -1 \ && cd - # Удаляем ключи из production контейнера на всякий случай @@ -46,7 +46,7 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \ # Билдим фронт && cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ - && npm run build \ + && npm run build:quite --quiet \ && rm node_modules \ # Копируем билд наружу, чтобы его не затёрло volume в dev режиме && cp -r ./dist /var/www/dist \ diff --git a/Dockerfile-dev b/Dockerfile-dev index 67b7943..d5a905e 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -32,7 +32,7 @@ COPY ./frontend/scripts /var/www/frontend/scripts COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils RUN cd ../frontend \ - && npm install \ + && npm install --quiet --depth -1 \ && cd - # Наконец переносим все сорцы внутрь контейнера @@ -43,7 +43,7 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \ # Билдим фронт && cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ - && npm run build \ + && npm run build:quite --quiet \ && rm node_modules \ # Копируем билд наружу, чтобы его не затёрло volume в dev режиме && cp -r ./dist /var/www/dist \ From 363b25e6da0b94d53b664cb05cdad390bc079c4f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 4 Dec 2016 15:49:45 +0300 Subject: [PATCH 27/41] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=20RabbitMQ=20=D0=B4=D0=BB=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=91=D0=BD=20=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83?= =?UTF-8?q?=D1=88=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/codeception/api/functional.suite.yml | 8 ----- .../common/_support/RabbitMQComponent.php | 33 +++++++++++++++++++ tests/codeception/config/config.php | 3 ++ tests/docker-compose.yml | 14 -------- 4 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 tests/codeception/common/_support/RabbitMQComponent.php diff --git a/tests/codeception/api/functional.suite.yml b/tests/codeception/api/functional.suite.yml index 5d52ea1..01d3dc2 100644 --- a/tests/codeception/api/functional.suite.yml +++ b/tests/codeception/api/functional.suite.yml @@ -5,7 +5,6 @@ modules: - Yii2 - tests\codeception\common\_support\FixtureHelper - Redis - - AMQP - Asserts - REST: depends: Yii2 @@ -17,10 +16,3 @@ modules: host: testredis port: 6379 database: 0 - AMQP: - host: testrabbit - port: 5672 - username: 'ely-accounts-tester' - password: 'tester-password' - vhost: '/account.ely.by/tests' - queues: [] diff --git a/tests/codeception/common/_support/RabbitMQComponent.php b/tests/codeception/common/_support/RabbitMQComponent.php new file mode 100644 index 0000000..4c8fc3a --- /dev/null +++ b/tests/codeception/common/_support/RabbitMQComponent.php @@ -0,0 +1,33 @@ + 4, ], + 'amqp' => [ + 'class' => tests\codeception\common\_support\RabbitMQComponent::class, + ], ], ]; diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index a9e8054..66ea17f 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -9,7 +9,6 @@ services: depends_on: - testdb - testredis - - testrabbit volumes: - ./..:/var/www/html environment: @@ -22,11 +21,6 @@ services: DB_PASSWORD: "ely_accounts_tester_password" # Redis config REDIS_HOST: "testredis" - # RabbitMQ config - RABBITMQ_HOST: "testrabbit" - RABBITMQ_USER: "ely-accounts-tester" - RABBITMQ_PASS: "tester-password" - RABBITMQ_VHOST: "/account.ely.by/tests" # Это я потом, когда-нибудь, уберу XDEBUG_CONFIG: "remote_host=10.254.254.254" PHP_IDE_CONFIG: "serverName=docker" @@ -45,11 +39,3 @@ services: testredis: container_name: accountelyby_testredis image: redis:3.0-alpine - - testrabbit: - container_name: accountelyby_testrabbit - image: rabbitmq:3.6 - environment: - RABBITMQ_DEFAULT_USER: "ely-accounts-tester" - RABBITMQ_DEFAULT_PASS: "tester-password" - RABBITMQ_DEFAULT_VHOST: "/account.ely.by/tests" From a8c7118e38116b5ec89e2d843124446c78d9af70 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 4 Dec 2016 19:56:49 +0300 Subject: [PATCH 28/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20Helper=20=D0=B4=D0=BB=D1=8F=20AMQP,=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9=20=D1=81=D0=BE=D0=B1=D0=B8?= =?UTF-8?q?=D1=80=D0=B0=D0=B5=D1=82=20=D0=B2=D1=81=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=83=D0=BF=D0=B0=D1=8E=D1=89=D0=B8=D0=B5=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B3=20=D0=B2=20=D1=84=D0=BE=D1=80=D0=BC=D0=B5=20Change?= =?UTF-8?q?UsernameForm=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B1=D0=B0=D0=B3=20=D1=81=20=D0=BA=D0=BE=D0=BD=D1=84?= =?UTF-8?q?=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2,=20=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B9=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B7=D0=B2?= =?UTF-8?q?=D0=BE=D0=BB=D1=8F=D0=BB=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=20=D0=BF=D1=80=D0=BE=D0=B2=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D1=81=D1=83=D1=89=D0=B5=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5\=D0=BD=D0=B5=D1=81=D1=83?= =?UTF-8?q?=D1=89=D0=B5=D1=81=D1=82=D0=B2=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D1=87=D0=B8=20=D1=85=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=20Redis=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20env=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B2=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/models/profile/ChangeUsernameForm.php | 10 +- tests/codeception/api/codeception.yml | 1 + tests/codeception/api/functional.suite.yml | 5 +- tests/codeception/api/unit.suite.yml | 2 + .../authentication/ConfirmEmailFormTest.php | 12 +- .../ChangeEmail/ConfirmNewEmailFormTest.php | 20 +++- .../models/profile/ChangeUsernameFormTest.php | 14 ++- .../common/_support/amqp/Helper.php | 106 ++++++++++++++++++ .../TestComponent.php} | 31 ++++- tests/codeception/common/codeception.yml | 1 + tests/codeception/common/unit.suite.yml | 1 + tests/codeception/config/config.php | 3 - tests/codeception/console/codeception.yml | 1 + tests/codeception/console/unit.suite.yml | 1 + 14 files changed, 188 insertions(+), 20 deletions(-) create mode 100644 tests/codeception/common/_support/amqp/Helper.php rename tests/codeception/common/_support/{RabbitMQComponent.php => amqp/TestComponent.php} (50%) diff --git a/api/models/profile/ChangeUsernameForm.php b/api/models/profile/ChangeUsernameForm.php index fb0a931..9e7dd53 100644 --- a/api/models/profile/ChangeUsernameForm.php +++ b/api/models/profile/ChangeUsernameForm.php @@ -28,15 +28,19 @@ class ChangeUsernameForm extends ApiForm { ]; } - public function change() { + public function change() : bool { if (!$this->validate()) { return false; } - $transaction = Yii::$app->db->beginTransaction(); $account = $this->getAccount(); - $oldNickname = $account->username; + if ($this->username === $account->username) { + return true; + } + + $transaction = Yii::$app->db->beginTransaction(); try { + $oldNickname = $account->username; $account->username = $this->username; if (!$account->save()) { throw new ErrorException('Cannot save account model with new username'); diff --git a/tests/codeception/api/codeception.yml b/tests/codeception/api/codeception.yml index 4318152..92cfc34 100644 --- a/tests/codeception/api/codeception.yml +++ b/tests/codeception/api/codeception.yml @@ -1,5 +1,6 @@ namespace: tests\codeception\api actor: Tester +params: [env] paths: tests: . log: _output diff --git a/tests/codeception/api/functional.suite.yml b/tests/codeception/api/functional.suite.yml index 01d3dc2..713312d 100644 --- a/tests/codeception/api/functional.suite.yml +++ b/tests/codeception/api/functional.suite.yml @@ -4,6 +4,7 @@ modules: - Filesystem - Yii2 - tests\codeception\common\_support\FixtureHelper + - tests\codeception\common\_support\amqp\Helper - Redis - Asserts - REST: @@ -11,8 +12,8 @@ modules: config: Yii2: configFile: '../config/api/functional.php' - cleanup: true + cleanup: false Redis: - host: testredis + host: "%REDIS_HOST%" port: 6379 database: 0 diff --git a/tests/codeception/api/unit.suite.yml b/tests/codeception/api/unit.suite.yml index 8ce2d31..3d31363 100644 --- a/tests/codeception/api/unit.suite.yml +++ b/tests/codeception/api/unit.suite.yml @@ -3,6 +3,8 @@ modules: enabled: - Yii2: part: [orm, email, fixtures] + - tests\codeception\common\_support\amqp\Helper config: Yii2: configFile: '../config/api/unit.php' + cleanup: false diff --git a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php index 1fd318b..2bbd5d7 100644 --- a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php @@ -25,9 +25,15 @@ class ConfirmEmailFormTest extends TestCase { $this->assertInstanceOf(AccountSession::class, $result->getSession(), 'session was generated'); $activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists(); $this->assertFalse($activationExists, 'email activation key is not exist'); - /** @var Account $user */ - $user = Account::findOne($fixture['account_id']); - $this->assertEquals(Account::STATUS_ACTIVE, $user->status, 'user status changed to active'); + /** @var Account $account */ + $account = Account::findOne($fixture['account_id']); + $this->assertEquals(Account::STATUS_ACTIVE, $account->status, 'user status changed to active'); + + $message = $this->tester->grabLastSentAmqpMessage('events'); + $body = json_decode($message->getBody(), true); + $this->assertEquals($account->id, $body['accountId']); + $this->assertEquals($account->username, $body['newUsername']); + $this->assertNull($body['oldUsername']); } private function createModel($key) { diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php index 68ab6c4..3ac9e26 100644 --- a/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php @@ -18,9 +18,8 @@ class ConfirmNewEmailFormTest extends TestCase { } public function testChangeEmail() { - $accountId = $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id']; /** @var Account $account */ - $account = Account::findOne($accountId); + $account = Account::findOne($this->getAccountId()); $newEmailConfirmationFixture = $this->tester->grabFixture('emailActivations', 'newEmailConfirmation'); $model = new ConfirmNewEmailForm($account, [ 'key' => $newEmailConfirmationFixture['key'], @@ -32,6 +31,23 @@ class ConfirmNewEmailFormTest extends TestCase { ])); $data = unserialize($newEmailConfirmationFixture['_data']); $this->assertEquals($data['newEmail'], $account->email); + $this->tester->canSeeAmqpMessageIsCreated('events'); + } + + public function testCreateTask() { + /** @var Account $account */ + $account = Account::findOne($this->getAccountId()); + $model = new ConfirmNewEmailForm($account); + $model->createTask(1, 'test1@ely.by', 'test@ely.by'); + $message = $this->tester->grabLastSentAmqpMessage('events'); + $body = json_decode($message->getBody(), true); + $this->assertEquals(1, $body['accountId']); + $this->assertEquals('test1@ely.by', $body['newEmail']); + $this->assertEquals('test@ely.by', $body['oldEmail']); + } + + private function getAccountId() { + return $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id']; } } diff --git a/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php b/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php index a41dd75..158b17e 100644 --- a/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php @@ -35,6 +35,7 @@ class ChangeUsernameFormTest extends TestCase { $this->assertTrue($model->change()); $this->assertEquals('my_new_nickname', Account::findOne($this->getAccountId())->username); $this->assertInstanceOf(UsernameHistory::class, UsernameHistory::findOne(['username' => 'my_new_nickname'])); + $this->tester->canSeeAmqpMessageIsCreated('events'); } public function testChangeWithoutChange() { @@ -49,7 +50,8 @@ class ChangeUsernameFormTest extends TestCase { 'AND', 'username' => $username, ['>=', 'applied_in', $callTime], - ]), 'no new UsernameHistory record, if we don\'t change nickname'); + ]), 'no new UsernameHistory record, if we don\'t change username'); + $this->tester->cantSeeAmqpMessageIsCreated('events'); } public function testChangeCase() { @@ -65,13 +67,17 @@ class ChangeUsernameFormTest extends TestCase { UsernameHistory::findOne(['username' => $newUsername]), 'username should change, if we change case of some letters' ); + $this->tester->canSeeAmqpMessageIsCreated('events'); } public function testCreateTask() { $model = new ChangeUsernameForm(); - $model->createEventTask('1', 'test1', 'test'); - // TODO: у меня пока нет идей о том, чтобы это как-то успешно протестировать, увы - // но по крайней мере можно убедиться, что оно не падает где-то на этом шаге + $model->createEventTask(1, 'test1', 'test'); + $message = $this->tester->grabLastSentAmqpMessage('events'); + $body = json_decode($message->getBody(), true); + $this->assertEquals(1, $body['accountId']); + $this->assertEquals('test1', $body['newUsername']); + $this->assertEquals('test', $body['oldUsername']); } private function getAccountId() { diff --git a/tests/codeception/common/_support/amqp/Helper.php b/tests/codeception/common/_support/amqp/Helper.php new file mode 100644 index 0000000..bea9e03 --- /dev/null +++ b/tests/codeception/common/_support/amqp/Helper.php @@ -0,0 +1,106 @@ +getYii2()->client; + $app = $connector->getApplication(); + $app->set('amqp', [ + 'class' => TestComponent::class, + ]); + + parent::_before($test); + } + + /** + * Checks that message is created. + * + * ```php + * seeAmqpMessageIsCreated(); + * + * // check that only 3 messages were created + * $I->seeAmqpMessageIsCreated(3); + * ``` + * + * @param string|null $exchange + * @param int|null $num + */ + public function seeAmqpMessageIsCreated($exchange = null, $num = null) { + if ($num === null) { + $this->assertNotEmpty($this->grabSentAmqpMessages($exchange), 'message were created'); + return; + } + + // TODO: заменить на assertCount() после релиза Codeception 2.2.7 + // https://github.com/Codeception/Codeception/pull/3802 + /** @noinspection PhpUnitTestsInspection */ + $this->assertEquals( + $num, + count($this->grabSentAmqpMessages($exchange)), + 'number of created messages is equal to ' . $num + ); + } + + /** + * Checks that no messages was created + * + * @param string|null $exchange + */ + public function dontSeeAmqpMessageIsCreated($exchange = null) { + $this->seeAmqpMessageIsCreated($exchange, 0); + } + + /** + * Returns last sent message + * + * @param string|null $exchange + * @return \PhpAmqpLib\Message\AMQPMessage + */ + public function grabLastSentAmqpMessage($exchange = null) { + $this->seeAmqpMessageIsCreated(); + $messages = $this->grabSentAmqpMessages($exchange); + + return end($messages); + } + + /** + * Returns array of all sent amqp messages. + * Each message is `\PhpAmqpLib\Message\AMQPMessage` instance. + * Useful to perform additional checks using `Asserts` module. + * + * @param string|null $exchange + * @return \PhpAmqpLib\Message\AMQPMessage[] + * @throws ModuleException + */ + public function grabSentAmqpMessages($exchange = null) { + $amqp = $this->grabComponent('amqp'); + if (!$amqp instanceof TestComponent) { + throw new ModuleException($this, 'AMQP module is not mocked, can\'t test messages'); + } + + return $amqp->getSentMessages($exchange); + } + + private function grabComponent(string $component) { + return $this->getYii2()->grabComponent($component); + } + + private function getYii2() : Yii2 { + $yii2 = $this->getModule('Yii2'); + if (!$yii2 instanceof Yii2) { + throw new ModuleException($this, 'Yii2 module must be configured'); + } + + return $yii2; + } + +} diff --git a/tests/codeception/common/_support/RabbitMQComponent.php b/tests/codeception/common/_support/amqp/TestComponent.php similarity index 50% rename from tests/codeception/common/_support/RabbitMQComponent.php rename to tests/codeception/common/_support/amqp/TestComponent.php index 4c8fc3a..9b8e159 100644 --- a/tests/codeception/common/_support/RabbitMQComponent.php +++ b/tests/codeception/common/_support/amqp/TestComponent.php @@ -1,10 +1,16 @@ sentMessages[$exchangeName][] = $this->prepareMessage($message); + } + + /** + * @param string|null $exchangeName + * @return \PhpAmqpLib\Message\AMQPMessage[] + */ + public function getSentMessages(string $exchangeName = null) : array { + if ($exchangeName !== null) { + return $this->sentMessages[$exchangeName] ?? []; + } else { + $messages = []; + foreach($this->sentMessages as $exchangeGroup) { + foreach ($exchangeGroup as $message) { + $messages[] = $message; + } + } + + return $messages; + } } } diff --git a/tests/codeception/common/codeception.yml b/tests/codeception/common/codeception.yml index f81585b..ff6249c 100644 --- a/tests/codeception/common/codeception.yml +++ b/tests/codeception/common/codeception.yml @@ -1,5 +1,6 @@ namespace: tests\codeception\common actor: Tester +params: [env] paths: tests: . log: _output diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml index d072b09..98fb59d 100644 --- a/tests/codeception/common/unit.suite.yml +++ b/tests/codeception/common/unit.suite.yml @@ -6,3 +6,4 @@ modules: config: Yii2: configFile: '../config/common/unit.php' + cleanup: false diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index fe8a64c..2904804 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -17,8 +17,5 @@ return [ // Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается 'passwordHashCost' => 4, ], - 'amqp' => [ - 'class' => tests\codeception\common\_support\RabbitMQComponent::class, - ], ], ]; diff --git a/tests/codeception/console/codeception.yml b/tests/codeception/console/codeception.yml index 14c972e..57e8496 100644 --- a/tests/codeception/console/codeception.yml +++ b/tests/codeception/console/codeception.yml @@ -1,5 +1,6 @@ namespace: tests\codeception\console actor: Tester +params: [env] paths: tests: . log: _output diff --git a/tests/codeception/console/unit.suite.yml b/tests/codeception/console/unit.suite.yml index 3ac6f10..bdcb10b 100644 --- a/tests/codeception/console/unit.suite.yml +++ b/tests/codeception/console/unit.suite.yml @@ -6,3 +6,4 @@ modules: config: Yii2: configFile: '../config/console/unit.php' + cleanup: false From 0d7ed89ca56f300262983fccc8e45c52039c7893 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 5 Dec 2016 19:22:24 +0300 Subject: [PATCH 29/41] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B1=D0=B0=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20gitlab-ci=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 32 +++++++++++++++++++++++++------- Dockerfile-dev | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d5aa9ca..f70e788 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,21 +4,39 @@ stages: - release variables: - CONTAINER_IMAGE: registry.ely.by/elyby/accounts + CONTAINER_IMAGE: "registry.ely.by/elyby/accounts" test:backend: - image: jonaskello/docker-and-compose:1.12.1-1.8.0 + image: docker:latest services: - - docker:1.12.1-dind + - mariadb:10.0 + - redis:3.0-alpine + variables: + # mariadb config + MYSQL_RANDOM_ROOT_PASSWORD: "true" + MYSQL_DATABASE: "ely_accounts_test" + MYSQL_USER: "ely_accounts_tester" + MYSQL_PASSWORD: "ely_accounts_tester_password" stage: test before_script: - docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by - echo "$SSH_PRIVATE_KEY" > id_rsa - - docker-compose -f tests/docker-compose.yml build --pull testphp - after_script: - - docker-compose -f tests/docker-compose.yml down -v script: - - docker-compose -f tests/docker-compose.yml run --rm testphp ./vendor/bin/codecept run -c tests + - export TEMP_DEV_IMAGE="${CONTAINER_IMAGE}:ci-${CI_BUILD_ID}" + - docker build --pull -f Dockerfile-dev -t $TEMP_DEV_IMAGE . + - > + docker run --rm + --add-host=mariadb:`getent hosts mariadb | awk '{ print $1 ; exit }'` + --add-host=redis:`getent hosts redis | awk '{ print $1 ; exit }'` + -e YII_DEBUG="true" + -e YII_ENV="test" + -e DB_HOST="mariadb" + -e DB_DATABASE="ely_accounts_test" + -e DB_USER="ely_accounts_tester" + -e DB_PASSWORD="ely_accounts_tester_password" + -e REDIS_HOST="redis" + $TEMP_DEV_IMAGE + php vendor/bin/codecept run -c tests test:frontend: image: node:5.12 diff --git a/Dockerfile-dev b/Dockerfile-dev index 133d64d..9d3ad4a 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.2-dev +FROM registry.ely.by/elyby/accounts-php:latest-dev # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ From c3fb2d7b34679e3a2e35f2bbb80ee22a4002fed2 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 00:53:38 +0300 Subject: [PATCH 30/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BC=D0=BE=D0=BA=20=D1=81=D0=BE=D0=B5?= =?UTF-8?q?=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=20=D1=80?= =?UTF-8?q?=D1=8D=D0=B1=D0=B8=D1=82=D0=BE=D0=BC=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/codeception/common/_support/amqp/Helper.php | 12 ------------ tests/codeception/config/config.php | 3 +++ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/codeception/common/_support/amqp/Helper.php b/tests/codeception/common/_support/amqp/Helper.php index bea9e03..7c6cd1b 100644 --- a/tests/codeception/common/_support/amqp/Helper.php +++ b/tests/codeception/common/_support/amqp/Helper.php @@ -4,21 +4,9 @@ namespace tests\codeception\common\_support\amqp; use Codeception\Exception\ModuleException; use Codeception\Module; use Codeception\Module\Yii2; -use Codeception\TestInterface; class Helper extends Module { - public function _before(TestInterface $test) { - /** @var \Codeception\Lib\Connector\Yii2 $connector */ - $connector = $this->getYii2()->client; - $app = $connector->getApplication(); - $app->set('amqp', [ - 'class' => TestComponent::class, - ]); - - parent::_before($test); - } - /** * Checks that message is created. * diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index 2904804..dedd16f 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -17,5 +17,8 @@ return [ // Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается 'passwordHashCost' => 4, ], + 'amqp' => [ + 'class' => tests\codeception\common\_support\amqp\TestComponent::class, + ], ], ]; From 49a6d4a37d2ae67b8ba474d79709cfa38f1d2871 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 01:34:51 +0300 Subject: [PATCH 31/41] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B1=D1=83=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20aufs=20=D0=BF=D1=80=D0=B8=20=D0=B1=D0=B8?= =?UTF-8?q?=D0=BB=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f70e788..f6b5827 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,7 @@ stages: - release variables: + DOCKER_DRIVER: aufs CONTAINER_IMAGE: "registry.ely.by/elyby/accounts" test:backend: From a1823db18955fbad542f9157c8568e193818549e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 12:37:44 +0300 Subject: [PATCH 32/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=20PHP=207.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Dockerfile-dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 946aa35..d76207d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.1.2 +FROM registry.ely.by/elyby/accounts-php:1.2.0 # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ diff --git a/Dockerfile-dev b/Dockerfile-dev index 9d3ad4a..ef963fb 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:latest-dev +FROM registry.ely.by/elyby/accounts-php:1.2.0-dev # Вносим конфигурации для крона и воркеров COPY docker/cron/* /etc/cron.d/ From 6484ec2fdc0582ab41dd0d64a0474febcb1974b7 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 12:38:21 +0300 Subject: [PATCH 33/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D1=83=D0=B5=D0=BC=20Yii2=202.0.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ae78d32..d897196 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "minimum-stability": "stable", "require": { "php": "^7.0.6", - "yiisoft/yii2": "2.0.9", + "yiisoft/yii2": "2.0.10", "yiisoft/yii2-swiftmailer": "*", "ramsey/uuid": "^3.5.0", "league/oauth2-server": "dev-improvements#b9277ccd664dcb80a766b73674d21de686cb9dda", From 8570cfcbfa8841cea6ae312f06e981908d370e4b Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 23:22:49 +0300 Subject: [PATCH 34/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2=20=D0=B2=20sentry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/config/config.php | 2 +- common/config/config.php | 2 +- composer.json | 2 +- console/config/config.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/config/config.php b/api/config/config.php index 5f41f0a..68e83c2 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -22,7 +22,7 @@ return [ 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => [ [ - 'class' => mito\sentry\SentryTarget::class, + 'class' => mito\sentry\Target::class, 'levels' => ['error', 'warning'], 'except' => [ 'legacy-authserver', diff --git a/common/config/config.php b/common/config/config.php index c06cd2a..2284c57 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -35,7 +35,7 @@ return [ ], ], 'sentry' => [ - 'class' => mito\sentry\SentryComponent::class, + 'class' => mito\sentry\Component::class, 'enabled' => !empty(getenv('SENTRY_DSN')), 'dsn' => getenv('SENTRY_DSN'), 'environment' => YII_ENV_DEV ? 'development' : 'production', diff --git a/composer.json b/composer.json index bfbef87..bc4c8b4 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec", "ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf", "predis/predis": "^1.0", - "mito/yii2-sentry": "dev-1.0.0-dev#193b96880d30e9d3b616652a5abb3c4f28e14f2c" + "mito/yii2-sentry": "dev-1.0.0-dev#e34bcdf5475310a66a6c94899ca62c9e9aa2bf8d" }, "require-dev": { "yiisoft/yii2-codeception": "*", diff --git a/console/config/config.php b/console/config/config.php index 2a8e775..748acc4 100644 --- a/console/config/config.php +++ b/console/config/config.php @@ -14,7 +14,7 @@ return [ 'log' => [ 'targets' => [ [ - 'class' => mito\sentry\SentryTarget::class, + 'class' => mito\sentry\Target::class, 'levels' => ['error', 'warning'], ], [ From c99d5c6b1c053c70d0ebfddd86c4896fb6471d5d Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 7 Dec 2016 23:51:01 +0300 Subject: [PATCH 35/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D1=80=D0=BE=D0=B1=D1=80=D0=BE=D1=81=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BB?= =?UTF-8?q?=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=84=D0=B8=D0=B3=20Raven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autocompletion.php | 1 + common/components/Sentry/Component.php | 18 ++++++++++++++++++ common/config/config.php | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 common/components/Sentry/Component.php diff --git a/autocompletion.php b/autocompletion.php index 1d99674..f91608c 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -21,6 +21,7 @@ class Yii extends \yii\BaseYii { * @property \common\components\RabbitMQ\Component $amqp * @property \GuzzleHttp\Client $guzzle * @property \common\components\EmailRenderer $emailRenderer + * @property \mito\sentry\Component $sentry */ abstract class BaseApplication extends yii\base\Application { } diff --git a/common/components/Sentry/Component.php b/common/components/Sentry/Component.php new file mode 100644 index 0000000..ce0b1c5 --- /dev/null +++ b/common/components/Sentry/Component.php @@ -0,0 +1,18 @@ +client) && !isset($this->client['release'])) { + $this->client['release'] = Yii::$app->version; + } + + parent::init(); + } + +} diff --git a/common/config/config.php b/common/config/config.php index 2284c57..2819864 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,5 +1,6 @@ '1.1.3-dev', 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'components' => [ 'cache' => [ @@ -35,11 +36,10 @@ return [ ], ], 'sentry' => [ - 'class' => mito\sentry\Component::class, + 'class' => common\components\Sentry\Component::class, 'enabled' => !empty(getenv('SENTRY_DSN')), 'dsn' => getenv('SENTRY_DSN'), 'environment' => YII_ENV_DEV ? 'development' : 'production', - 'jsNotifier' => false, 'client' => [ 'curl_method' => 'async', ], From 2cc058f7926fb444021b762d1ae61ddb8997be99 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 9 Dec 2016 11:03:40 +0300 Subject: [PATCH 36/41] =?UTF-8?q?=D0=9C=D0=B5=D0=BD=D1=8F=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8E=20=D0=B1=D0=B8=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BA,=20=D1=82.=D0=BA.=20=D0=B2=20=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B3=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BD=D0=B5=D1=82=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bc4c8b4..09c7d33 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec", "ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf", "predis/predis": "^1.0", - "mito/yii2-sentry": "dev-1.0.0-dev#e34bcdf5475310a66a6c94899ca62c9e9aa2bf8d" + "mito/yii2-sentry": "dev-fix_init#27f00805cb906f73b2c6f8181c1c655decb9be70" }, "require-dev": { "yiisoft/yii2-codeception": "*", @@ -58,6 +58,10 @@ { "type": "git", "url": "git@gitlab.ely.by:elyby/oauth2-server.git" + }, + { + "type": "git", + "url": "git@github.com:erickskrauch/yii2-sentry.git" } ], "scripts": { From 9fe42e46234aeae95b2a32a74cc7c88498d4d9ac Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 11 Dec 2016 15:05:01 +0300 Subject: [PATCH 37/41] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=20REDIS=20=D0=B2=20.env=20?= =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=D0=B5=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-dist | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.env-dist b/.env-dist index 647b78a..acc5b32 100644 --- a/.env-dist +++ b/.env-dist @@ -28,12 +28,6 @@ REDIS_PORT=6379 REDIS_DATABASE=0 REDIS_PASSWORD= -## Параметры подключения к redis -REDIS_HOST=redis -REDIS_PORT=6379 -REDIS_DATABASE=0 -REDIS_PASS= - ## Параметры подключения к rabbitmq RABBITMQ_HOST=rabbitmq RABBITMQ_PORT=5672 From b00c4ae4fc0a9a1464dea2cd3481812ba6c0d8d4 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 11 Dec 2016 17:58:08 +0300 Subject: [PATCH 38/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20User\Component::getIdentity(),=20=D0=B5?= =?UTF-8?q?=D1=81=D0=BB=D0=B8=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=B5=20=D0=BD=D0=B5=20=D0=B1?= =?UTF-8?q?=D1=8B=D0=BB=D0=BE=20accessFilter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/User/Component.php | 43 ++++++++-- .../unit/components/User/ComponentTest.php | 79 +++++++++++++------ 2 files changed, 89 insertions(+), 33 deletions(-) diff --git a/api/components/User/Component.php b/api/components/User/Component.php index a0685d5..646bd02 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -23,7 +23,7 @@ use yii\web\User as YiiUserComponent; * @property AccountSession|null $activeSession * @property AccountIdentity|null $identity * - * @method AccountIdentity|null getIdentity($autoRenew = true) + * @method AccountIdentity|null loginByAccessToken($token, $type = null) */ class Component extends YiiUserComponent { @@ -39,6 +39,8 @@ class Component extends YiiUserComponent { public $sessionTimeout = 'P7D'; + private $_identity; + public function init() { parent::init(); if (!$this->secret) { @@ -46,6 +48,24 @@ class Component extends YiiUserComponent { } } + /** + * @param bool $autoRenew + * @return null|AccountIdentity + */ + public function getIdentity($autoRenew = true) { + $result = parent::getIdentity($autoRenew); + if ($result === null && $this->_identity !== false) { + $bearer = $this->getBearerToken(); + if ($bearer !== null) { + $result = $this->loginByAccessToken($bearer); + } + + $this->_identity = $result ?: false; + } + + return $result; + } + /** * @param IdentityInterface $identity * @param bool $rememberMe @@ -149,14 +169,9 @@ class Component extends YiiUserComponent { return null; } - $authHeader = Yii::$app->request->getHeaders()->get('Authorization'); - if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) { - return null; - } - - $token = $matches[1]; + $bearer = $this->getBearerToken(); try { - $token = $this->parseToken($token); + $token = $this->parseToken($bearer); } catch (VerificationException $e) { return null; } @@ -203,4 +218,16 @@ class Component extends YiiUserComponent { ]; } + /** + * @return ?string + */ + private function getBearerToken() { + $authHeader = Yii::$app->request->getHeaders()->get('Authorization'); + if ($authHeader === null || !preg_match('/^Bearer\s+(.*?)$/', $authHeader, $matches)) { + return null; + } + + return $matches[1]; + } + } diff --git a/tests/codeception/api/unit/components/User/ComponentTest.php b/tests/codeception/api/unit/components/User/ComponentTest.php index 470a0b4..1bc2033 100644 --- a/tests/codeception/api/unit/components/User/ComponentTest.php +++ b/tests/codeception/api/unit/components/User/ComponentTest.php @@ -16,7 +16,6 @@ use tests\codeception\common\_support\ProtectedCaller; use tests\codeception\common\fixtures\AccountFixture; use tests\codeception\common\fixtures\AccountSessionFixture; use Yii; -use yii\web\HeaderCollection; use yii\web\Request; class ComponentTest extends TestCase { @@ -24,7 +23,7 @@ class ComponentTest extends TestCase { use ProtectedCaller; /** - * @var Component + * @var Component|\PHPUnit_Framework_MockObject_MockObject */ private $component; @@ -40,6 +39,46 @@ class ComponentTest extends TestCase { ]; } + public function testGetIdentity() { + $this->specify('getIdentity should return null, if not authorization header', function() { + $this->mockAuthorizationHeader(null); + $this->assertNull($this->component->getIdentity()); + }); + + $this->specify('getIdentity should return null, if passed bearer token don\'t return any account', function() { + $this->mockAuthorizationHeader('some-auth'); + /** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */ + $component = $this->getMockBuilder(Component::class) + ->setMethods(['loginByAccessToken']) + ->setConstructorArgs([$this->getComponentArguments()]) + ->getMock(); + + $component + ->expects($this->once()) + ->method('loginByAccessToken') + ->willReturn(null); + + $this->assertNull($component->getIdentity()); + }); + + $this->specify('getIdentity should return identity from loginByAccessToken method', function() { + $identity = new AccountIdentity(); + $this->mockAuthorizationHeader('some-auth'); + /** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */ + $component = $this->getMockBuilder(Component::class) + ->setMethods(['loginByAccessToken']) + ->setConstructorArgs([$this->getComponentArguments()]) + ->getMock(); + + $component + ->expects($this->once()) + ->method('loginByAccessToken') + ->willReturn($identity); + + $this->assertEquals($identity, $component->getIdentity()); + }); + } + public function testLogin() { $this->mockRequest(); $this->specify('success get LoginResult object without session value', function() { @@ -117,30 +156,9 @@ class ComponentTest extends TestCase { $component ->expects($this->any()) ->method('getIsGuest') - ->will($this->returnValue(false)); + ->willReturn(false); - /** @var HeaderCollection|\PHPUnit_Framework_MockObject_MockObject $headersCollection */ - $headersCollection = $this->getMockBuilder(HeaderCollection::class) - ->setMethods(['get']) - ->getMock(); - - $headersCollection - ->expects($this->any()) - ->method('get') - ->with($this->equalTo('Authorization')) - ->will($this->returnValue('Bearer ' . $result->getJwt())); - - /** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */ - $request = $this->getMockBuilder(Request::class) - ->setMethods(['getHeaders']) - ->getMock(); - - $request - ->expects($this->any()) - ->method('getHeaders') - ->will($this->returnValue($headersCollection)); - - Yii::$app->set('request', $request); + $this->mockAuthorizationHeader($result->getJwt()); $session = $component->getActiveSession(); expect($session)->isInstanceOf(AccountSession::class); @@ -203,6 +221,17 @@ class ComponentTest extends TestCase { return $request; } + /** + * @param string $bearerToken + */ + private function mockAuthorizationHeader($bearerToken = null) { + if ($bearerToken !== null) { + $bearerToken = 'Bearer ' . $bearerToken; + } + + Yii::$app->request->headers->set('Authorization', $bearerToken); + } + private function getComponentArguments() { return [ 'identityClass' => AccountIdentity::class, From f2e6df4022a0f18198ffbc14c5a96d0355138114 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 12 Dec 2016 00:07:39 +0300 Subject: [PATCH 39/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20OAuth=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/OauthController.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php index 5c19972..8df62a0 100644 --- a/api/controllers/OauthController.php +++ b/api/controllers/OauthController.php @@ -17,16 +17,12 @@ class OauthController extends Controller { public function behaviors() { return ArrayHelper::merge(parent::behaviors(), [ 'authenticator' => [ - 'except' => ['validate', 'token'], + 'only' => ['complete'], ], 'access' => [ 'class' => AccessControl::class, + 'only' => ['complete'], 'rules' => [ - [ - 'actions' => ['validate', 'token'], - 'allow' => true, - 'roles' => ['?'], - ], [ 'class' => ActiveUserRule::class, 'actions' => ['complete'], From 6d4bef0549f9127f078ed19f2068b46e9eca6ee1 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 13 Dec 2016 01:10:05 +0300 Subject: [PATCH 40/41] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B0=20=D0=BD=D0=B0=20refresh-token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/User/Component.php | 2 +- api/controllers/AuthenticationController.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/components/User/Component.php b/api/components/User/Component.php index 646bd02..aa996e6 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -110,7 +110,7 @@ class Component extends YiiUserComponent { return $result; } - public function renew(AccountSession $session) { + public function renew(AccountSession $session): RenewResult { $account = $session->account; $transaction = Yii::$app->db->beginTransaction(); try { diff --git a/api/controllers/AuthenticationController.php b/api/controllers/AuthenticationController.php index 19556f8..0046f7c 100644 --- a/api/controllers/AuthenticationController.php +++ b/api/controllers/AuthenticationController.php @@ -17,13 +17,14 @@ class AuthenticationController extends Controller { public function behaviors() { return ArrayHelper::merge(parent::behaviors(), [ 'authenticator' => [ - 'except' => ['login', 'forgot-password', 'recover-password', 'refresh-token'], + 'only' => ['logout'], ], 'access' => [ 'class' => AccessControl::class, + 'except' => ['refresh-token'], 'rules' => [ [ - 'actions' => ['login', 'forgot-password', 'recover-password', 'refresh-token'], + 'actions' => ['login', 'forgot-password', 'recover-password'], 'allow' => true, 'roles' => ['?'], ], From d492ca5c81c15aee7ac618f76979a9ad3e433c8c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 14 Dec 2016 00:35:39 +0300 Subject: [PATCH 41/41] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BA=20=D1=80=D0=B5=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D1=83=201.1.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/config/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/config/config.php b/common/config/config.php index 2819864..d1be897 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,6 +1,6 @@ '1.1.3-dev', + 'version' => '1.1.3', 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'components' => [ 'cache' => [