From 4bebf6c581cb9644e669c67ddd9f83e99a9ef725 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 3 Nov 2017 16:16:07 +0300 Subject: [PATCH 01/41] 1.1.22-dev [skip ci] --- 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 521e185..b5d7a68 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,6 +1,6 @@ '1.1.21', + 'version' => '1.1.22-dev', 'vendorPath' => dirname(__DIR__, 2) . '/vendor', 'components' => [ 'cache' => [ From e6fe2f375570deb5b1ab3a78d5d8769afddac289 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 4 Nov 2017 15:52:57 +0300 Subject: [PATCH 02/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=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=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20AMQP=20=D0=B2=D0=BE=D1=80=D0=BA=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console/controllers/AmqpController.php | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/console/controllers/AmqpController.php b/console/controllers/AmqpController.php index e7c3bc0..e06c765 100644 --- a/console/controllers/AmqpController.php +++ b/console/controllers/AmqpController.php @@ -2,19 +2,21 @@ namespace console\controllers; use Ely\Amqp\ControllerTrait; +use Exception; use PhpAmqpLib\Message\AMQPMessage; use Yii; use yii\console\Controller; use yii\db\Exception as YiiDbException; use yii\helpers\ArrayHelper; use yii\helpers\Inflector; -use yii\helpers\StringHelper; abstract class AmqpController extends Controller { use ControllerTrait { callback as _callback; } + private $reconnected = false; + public final function actionIndex() { $this->start(); } @@ -35,12 +37,17 @@ abstract class AmqpController extends Controller { try { $this->_callback($msg); } catch (YiiDbException $e) { - if (StringHelper::startsWith($e->getMessage(), 'Error while sending QUERY packet')) { - exit(self::EXIT_CODE_ERROR); + if ($this->reconnected || !$this->isRestorableException($e)) { + throw $e; } - throw $e; + $this->reconnected = true; + Yii::$app->db->close(); + Yii::$app->db->open(); + $this->callback($msg); } + + $this->reconnected = false; } /** @@ -57,4 +64,9 @@ abstract class AmqpController extends Controller { return ArrayHelper::getValue($this->getRoutesMap(), $route, 'route' . Inflector::camelize($route)); } + private function isRestorableException(Exception $e): bool { + return strpos($e->getMessage(), 'MySQL server has gone away') !== false + || strcmp($e->getMessage(), 'Error while sending QUERY packet') !== false; + } + } From 867c1a2b3be14cb942b0a9e0289bf1a67b45a708 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 13 Nov 2017 17:14:58 +0300 Subject: [PATCH 03/41] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D1=80=D0=B3?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=84=D1=80=D0=B5?= =?UTF-8?q?=D0=B9=D0=BC=D0=B2=D0=BE=D1=80=D0=BA=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=B0?= =?UTF-8?q?=D1=81=D0=BF=D0=B5=D0=BA=D1=82=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/aop/AspectKernel.php | 13 +++++++++++++ api/web/index.php | 23 ++++++++++++++++++++--- composer.json | 9 ++++++++- 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 api/aop/AspectKernel.php diff --git a/api/aop/AspectKernel.php b/api/aop/AspectKernel.php new file mode 100644 index 0000000..c003331 --- /dev/null +++ b/api/aop/AspectKernel.php @@ -0,0 +1,13 @@ +init([ + 'debug' => YII_DEBUG, + 'appDir' => __DIR__ . '/../../', + 'cacheDir' => __DIR__ . '/../runtime/aspect', + 'excludePaths' => [ + __DIR__ . '/../runtime/aspect', + __DIR__ . '/../../vendor', + ], +]); + require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php'; +spl_autoload_unregister(['Yii', 'autoload']); require __DIR__ . '/../../common/config/bootstrap.php'; require __DIR__ . '/../config/bootstrap.php'; -$config = \common\config\ConfigLoader::load('api'); +$config = ConfigLoader::load('api'); -$application = new yii\web\Application($config); +$application = new Application($config); $application->run(); diff --git a/composer.json b/composer.json index fb033f1..975a12c 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "spomky-labs/otphp": "^9.0.2", "bacon/bacon-qr-code": "^1.0", "paragonie/constant_time_encoding": "^2.0", - "webmozart/assert": "^1.2.0" + "webmozart/assert": "^1.2.0", + "goaop/framework": "~2.1.2", + "domnikl/statsd": "^2.6" }, "require-dev": { "yiisoft/yii2-debug": "*", @@ -51,6 +53,11 @@ } ], "autoload": { + "psr-4": { + "api\\": "api", + "common\\": "common", + "console\\": "console" + }, "files": [ "common/consts.php" ] From e9208d8f2179c11e8b5da33d0644362eb2e1db88 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 14 Nov 2017 19:49:51 +0300 Subject: [PATCH 04/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=D1=81=D0=BE=D0=B1=D1=81=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=83=D1=8E=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20each=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=20=D0=BD=D0=B0=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=20cleanup=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console/controllers/CleanupController.php | 46 +++++++++++++++++------ 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/console/controllers/CleanupController.php b/console/controllers/CleanupController.php index 60caf85..73e3f27 100644 --- a/console/controllers/CleanupController.php +++ b/console/controllers/CleanupController.php @@ -4,24 +4,24 @@ namespace console\controllers; use common\models\AccountSession; use common\models\EmailActivation; use common\models\MinecraftAccessKey; +use Generator; use yii\console\Controller; +use yii\db\ActiveQueryInterface; class CleanupController extends Controller { public function actionEmailKeys() { $query = EmailActivation::find(); - $conditions = ['OR']; foreach ($this->getEmailActivationsDurationsMap() as $typeId => $expiration) { - $conditions[] = [ + $query->orWhere([ 'AND', ['type' => $typeId], ['<', 'created_at', time() - $expiration], - ]; + ]); } - /** @var \yii\db\BatchQueryResult|EmailActivation[] $expiredEmails */ - $expiredEmails = $query->andWhere($conditions)->each(); - foreach ($expiredEmails as $email) { + foreach ($this->each($query) as $email) { + /** @var EmailActivation $email */ $email->delete(); } @@ -29,12 +29,11 @@ class CleanupController extends Controller { } public function actionMinecraftSessions() { - /** @var \yii\db\BatchQueryResult|MinecraftAccessKey[] $expiredMinecraftSessions */ - $expiredMinecraftSessions = MinecraftAccessKey::find() - ->andWhere(['<', 'updated_at', time() - 1209600]) // 2 weeks - ->each(); + $expiredMinecraftSessionsQuery = MinecraftAccessKey::find() + ->andWhere(['<', 'updated_at', time() - 1209600]); // 2 weeks - foreach ($expiredMinecraftSessions as $minecraftSession) { + foreach ($this->each($expiredMinecraftSessionsQuery) as $minecraftSession) { + /** @var MinecraftAccessKey $minecraftSession */ $minecraftSession->delete(); } @@ -63,6 +62,31 @@ class CleanupController extends Controller { return self::EXIT_CODE_NORMAL; } + /** + * Each function implementation, that allows you to iterate over values, + * when in each iteration row removing from database. If you do not remove + * value in iteration, then this will cause infinite loop. + * + * @param ActiveQueryInterface $query + * @param int $size + * + * @return Generator + */ + private function each(ActiveQueryInterface $query, int $size = 100): Generator { + $query = clone $query; + $query->limit($size); + while (true) { + $rows = $query->all(); + if (empty($rows)) { + break; + } + + foreach ($rows as $row) { + yield $row; + } + } + } + private function getEmailActivationsDurationsMap(): array { $durationsMap = []; foreach (EmailActivation::getClassMap() as $typeId => $className) { From dab0ab4cbae223bda4b341ab9aa66c5f286ba2d7 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 14 Nov 2017 20:34:05 +0300 Subject: [PATCH 05/41] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D1=83=D1=8E=D1=82=D1=81=D1=8F=20=D1=87=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=20=D0=B0=D1=81=D0=BF=D0=B5=D0=BA=D1=82=D0=BD?= =?UTF-8?q?=D1=83=D1=8E=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/aop/AspectKernel.php | 1 + api/aop/aspects/MockDataAspect.php | 177 ++++++++++++++++++ api/components/TestData.php | 174 ----------------- api/config/config.php | 1 - .../accounts/actions/BaseAccountAction.php | 3 +- 5 files changed, 180 insertions(+), 176 deletions(-) create mode 100644 api/aop/aspects/MockDataAspect.php delete mode 100644 api/components/TestData.php diff --git a/api/aop/AspectKernel.php b/api/aop/AspectKernel.php index c003331..79cc8ed 100644 --- a/api/aop/AspectKernel.php +++ b/api/aop/AspectKernel.php @@ -8,6 +8,7 @@ use Go\Core\AspectKernel as BaseAspectKernel; class AspectKernel extends BaseAspectKernel { protected function configureAop(AspectContainer $container): void { + $container->registerAspect(new aspects\MockDataAspect()); } } diff --git a/api/aop/aspects/MockDataAspect.php b/api/aop/aspects/MockDataAspect.php new file mode 100644 index 0000000..976de32 --- /dev/null +++ b/api/aop/aspects/MockDataAspect.php @@ -0,0 +1,177 @@ +actionIndex(*))") + */ + public function beforeSignup(MethodInvocation $invocation) { + $email = $this->getRequest()->post('email'); + if ($email === 'let-me-register@ely.by') { + return ['success' => true]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\controllers\SignupController->actionRepeatMessage(*))") + */ + public function beforeRepeatMessage(MethodInvocation $invocation) { + $email = $this->getRequest()->post('email'); + if ($email === 'let-me-register@ely.by' || $email === 'let-me-repeat@ely.by') { + return ['success' => true]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\controllers\SignupController->actionConfirm(*))") + */ + public function beforeSignupConfirm(MethodInvocation $invocation) { + $email = $this->getRequest()->post('key'); + if ($email === 'LETMEIN') { + return [ + 'success' => true, + 'access_token' => 'dummy_token', + 'expires_in' => time() + 60, + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\controllers\AuthenticationController->actionForgotPassword(*))") + */ + public function beforeForgotPassword(MethodInvocation $invocation) { + $login = $this->getRequest()->post('login'); + if ($login === 'let-me-recover@ely.by') { + return [ + 'success' => true, + 'data' => [ + 'canRepeatIn' => time() + 60, + 'repeatFrequency' => 60, + ], + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\controllers\AuthenticationController->actionRecoverPassword(*))") + */ + public function beforeRecoverPassword(MethodInvocation $invocation) { + $key = $this->getRequest()->post('key'); + if ($key === 'LETMEIN') { + return [ + 'success' => true, + 'access_token' => 'dummy_token', + 'expires_in' => time() + 60, + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\modules\accounts\controllers\DefaultController->actionGet(*))") + */ + public function beforeAccountGet(MethodInvocation $invocation) { + $httpAuth = $this->getRequest()->getHeaders()->get('authorization'); + if ($httpAuth === 'Bearer dummy_token') { + return [ + 'id' => 1, + 'uuid' => 'f63cd5e1-680f-4c2d-baa2-cc7bb174b71a', + 'username' => 'dummy', + 'isOtpEnabled' => false, + 'registeredAt' => time(), + 'lang' => 'en', + 'elyProfileLink' => 'http://ely.by/u1', + 'email' => 'let-me-register@ely.by', + 'isActive' => true, + 'passwordChangedAt' => time(), + 'hasMojangUsernameCollision' => false, + 'shouldAcceptRules' => false, + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\modules\accounts\actions\EmailVerificationAction->run(*))") + */ + public function beforeAccountEmailVerification(MethodInvocation $invocation) { + $httpAuth = $this->getRequest()->getHeaders()->get('authorization'); + if ($httpAuth === 'Bearer dummy_token') { + $password = $this->getRequest()->post('password'); + if (empty($password)) { + return [ + 'success' => false, + 'errors' => [ + 'password' => 'error.password_required', + ], + ]; + } + + return [ + 'success' => true, + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\modules\accounts\actions\NewEmailVerificationAction->run(*))") + */ + public function beforeAccountNewEmailVerification(MethodInvocation $invocation) { + $key = $this->getRequest()->post('key'); + if ($key === 'LETMEIN') { + return [ + 'success' => true, + ]; + } + + return $invocation->proceed(); + } + + /** + * @param MethodInvocation $invocation + * @Around("execution(public api\modules\accounts\actions\ChangeEmailAction->run(*))") + */ + public function beforeAccountChangeEmail(MethodInvocation $invocation) { + $key = $this->getRequest()->post('key'); + if ($key === 'LETMEIN') { + return [ + 'success' => true, + 'email' => 'brand-new-email@ely.by', + ]; + } + + return $invocation->proceed(); + } + + private function getRequest(): Request { + return Yii::$app->getRequest(); + } + +} diff --git a/api/components/TestData.php b/api/components/TestData.php deleted file mode 100644 index ac4b417..0000000 --- a/api/components/TestData.php +++ /dev/null @@ -1,174 +0,0 @@ - 'beforeSignup', - 'signup/repeat-message' => 'beforeRepeatMessage', - 'signup/confirm' => 'beforeSignupConfirm', - 'authentication/forgot-password' => 'beforeForgotPassword', - 'authentication/recover-password' => 'beforeRecoverPassword', - 'default/get' => 'beforeAccountGet', - 'default/email-verification' => 'beforeAccountEmailVerification', - 'default/new-email-verification' => 'beforeAccountNewEmailVerification', - 'default/email' => 'beforeAccountChangeEmail', - ]; - - public static function getInstance(): callable { - return Closure::fromCallable([new static(), 'beforeAction']); - } - - public function beforeAction(ActionEvent $event): void { - $id = $event->action->controller->id . '/' . $event->action->id; - if (!isset(self::MAP[$id])) { - return; - } - - $handler = self::MAP[$id]; - $request = Yii::$app->request; - $response = Yii::$app->response; - $result = $this->$handler($request, $response); - if ($result === null) { - return; - } - - $response->content = Json::encode($result); - - // Prevent request execution - $event->isValid = false; - $event->handled = true; - } - - public function beforeSignup(Request $request): ?array { - $email = $request->post('email'); - if ($email === 'let-me-register@ely.by') { - return ['success' => true]; - } - - return null; - } - - public function beforeRepeatMessage(Request $request): ?array { - $email = $request->post('email'); - if ($email === 'let-me-register@ely.by' || $email === 'let-me-repeat@ely.by') { - return ['success' => true]; - } - - return null; - } - - public function beforeSignupConfirm(Request $request): ?array { - $email = $request->post('key'); - if ($email === 'LETMEIN') { - return [ - 'success' => true, - 'access_token' => 'dummy_token', - 'expires_in' => time() + 60, - ]; - } - - return null; - } - - public function beforeForgotPassword(Request $request): ?array { - $login = $request->post('login'); - if ($login === 'let-me-recover@ely.by') { - return [ - 'success' => true, - 'data' => [ - 'canRepeatIn' => time() + 60, - 'repeatFrequency' => 60, - ], - ]; - } - - return null; - } - - public function beforeRecoverPassword(Request $request): ?array { - $key = $request->post('key'); - if ($key === 'LETMEIN') { - return [ - 'success' => true, - 'access_token' => 'dummy_token', - 'expires_in' => time() + 60, - ]; - } - - return null; - } - - public function beforeAccountGet(Request $request): ?array { - $httpAuth = $request->getHeaders()->get('authorization'); - if ($httpAuth === 'Bearer dummy_token') { - return [ - 'id' => 1, - 'uuid' => 'f63cd5e1-680f-4c2d-baa2-cc7bb174b71a', - 'username' => 'dummy', - 'isOtpEnabled' => false, - 'registeredAt' => time(), - 'lang' => 'en', - 'elyProfileLink' => 'http://ely.by/u1', - 'email' => 'let-me-register@ely.by', - 'isActive' => true, - 'passwordChangedAt' => time(), - 'hasMojangUsernameCollision' => false, - 'shouldAcceptRules' => false, - ]; - } - - return null; - } - - public function beforeAccountEmailVerification(Request $request): ?array { - $httpAuth = $request->getHeaders()->get('authorization'); - if ($httpAuth === 'Bearer dummy_token') { - $password = $request->post('password'); - if (empty($password)) { - return [ - 'success' => false, - 'errors' => [ - 'password' => 'error.password_required', - ], - ]; - } - - return [ - 'success' => true, - ]; - } - - return null; - } - - public function beforeAccountNewEmailVerification(Request $request): ?array { - $key = $request->post('key'); - if ($key === 'LETMEIN') { - return [ - 'success' => true, - ]; - } - - return null; - } - - public function beforeAccountChangeEmail(Request $request): ?array { - $key = $request->post('key'); - if ($key === 'LETMEIN') { - return [ - 'success' => true, - 'email' => 'brand-new-email@ely.by', - ]; - } - - return null; - } - -} diff --git a/api/config/config.php b/api/config/config.php index c38ea0c..8969ef5 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -87,5 +87,4 @@ return [ 'internal' => api\modules\internal\Module::class, 'accounts' => api\modules\accounts\Module::class, ], - 'on beforeAction' => api\components\TestData::getInstance(), ]; diff --git a/api/modules/accounts/actions/BaseAccountAction.php b/api/modules/accounts/actions/BaseAccountAction.php index b91ca2b..874602d 100644 --- a/api/modules/accounts/actions/BaseAccountAction.php +++ b/api/modules/accounts/actions/BaseAccountAction.php @@ -9,7 +9,8 @@ use yii\web\NotFoundHttpException; abstract class BaseAccountAction extends Action { - final public function run(int $id): array { + // TODO: вернуть final модификатор метода после того, как в GoAOP добавят поддержку аспектов для final методов + public function run(int $id): array { $className = $this->getFormClassName(); /** @var AccountActionForm $model */ $model = new $className($this->findAccount($id)); From d175dcdaed5df93ecf50228566f5e24aa8d2e01c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 15 Nov 2017 00:03:38 +0300 Subject: [PATCH 06/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B5=D0=B1=D1=83=D1=84=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D0=B7=D0=B8=D1=80=D1=83=D0=B5=D0=BC=D0=BE=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=20=D0=B1=D0=B0=D0=B7=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D1=8B=20=D1=81=20each=20=D0=B8=20batch=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autocompletion.php | 1 + common/config/config.php | 13 +++++++++ console/controllers/CleanupController.php | 33 +++-------------------- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/autocompletion.php b/autocompletion.php index e0dce4d..3890cc7 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -16,6 +16,7 @@ class Yii extends \yii\BaseYii { * Class BaseApplication * Used for properties that are identical for both WebApplication and ConsoleApplication * + * @property \yii\db\Connection $unbufferedDb * @property \yii\swiftmailer\Mailer $mailer * @property \common\components\Redis\Connection $redis * @property \common\components\RabbitMQ\Component $amqp diff --git a/common/config/config.php b/common/config/config.php index b5d7a68..d334a5c 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -17,6 +17,19 @@ return [ 'mysql' => common\db\mysql\Schema::class, ], ], + 'unbufferedDb' => [ + 'class' => yii\db\Connection::class, + 'dsn' => 'mysql:host=' . (getenv('DB_HOST') ?: 'db') . ';dbname=' . getenv('DB_DATABASE'), + 'username' => getenv('DB_USER'), + 'password' => getenv('DB_PASSWORD'), + 'charset' => 'utf8', + 'attributes' => [ + PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false, + ], + 'schemaMap' => [ + 'mysql' => common\db\mysql\Schema::class, + ], + ], 'mailer' => [ 'class' => yii\swiftmailer\Mailer::class, 'viewPath' => '@common/mail', diff --git a/console/controllers/CleanupController.php b/console/controllers/CleanupController.php index 73e3f27..e2276e8 100644 --- a/console/controllers/CleanupController.php +++ b/console/controllers/CleanupController.php @@ -4,9 +4,8 @@ namespace console\controllers; use common\models\AccountSession; use common\models\EmailActivation; use common\models\MinecraftAccessKey; -use Generator; +use Yii; use yii\console\Controller; -use yii\db\ActiveQueryInterface; class CleanupController extends Controller { @@ -20,7 +19,7 @@ class CleanupController extends Controller { ]); } - foreach ($this->each($query) as $email) { + foreach ($query->each(100, Yii::$app->unbufferedDb) as $email) { /** @var EmailActivation $email */ $email->delete(); } @@ -32,7 +31,7 @@ class CleanupController extends Controller { $expiredMinecraftSessionsQuery = MinecraftAccessKey::find() ->andWhere(['<', 'updated_at', time() - 1209600]); // 2 weeks - foreach ($this->each($expiredMinecraftSessionsQuery) as $minecraftSession) { + foreach ($expiredMinecraftSessionsQuery->each(100, Yii::$app->unbufferedDb) as $minecraftSession) { /** @var MinecraftAccessKey $minecraftSession */ $minecraftSession->delete(); } @@ -62,31 +61,6 @@ class CleanupController extends Controller { return self::EXIT_CODE_NORMAL; } - /** - * Each function implementation, that allows you to iterate over values, - * when in each iteration row removing from database. If you do not remove - * value in iteration, then this will cause infinite loop. - * - * @param ActiveQueryInterface $query - * @param int $size - * - * @return Generator - */ - private function each(ActiveQueryInterface $query, int $size = 100): Generator { - $query = clone $query; - $query->limit($size); - while (true) { - $rows = $query->all(); - if (empty($rows)) { - break; - } - - foreach ($rows as $row) { - yield $row; - } - } - } - private function getEmailActivationsDurationsMap(): array { $durationsMap = []; foreach (EmailActivation::getClassMap() as $typeId => $className) { @@ -94,6 +68,7 @@ class CleanupController extends Controller { $object = new $className; /** @var \common\behaviors\EmailActivationExpirationBehavior $behavior */ $behavior = $object->getBehavior('expirationBehavior'); + /** @noinspection NullPointerExceptionInspection */ $expiration = $behavior->expirationTimeout ?? 1123200; // 13d по умолчанию // Приращаем 1 день, чтобы пользователи ещё могли получать сообщения об истечении кода активации /** @noinspection SummerTimeUnsafeTimeManipulationInspection */ From 47b6761f78b5df03d6f972e63b896759fee72972 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 20 Nov 2017 20:48:43 +0300 Subject: [PATCH 07/41] =?UTF-8?q?=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=8D=D1=82=D0=B0=D0=BF=D0=B5=20=D0=B2=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20TOTP=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BC=D0=BE=D0=B6=D0=B5=D1=82=20=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B5=D1=87=D1=8C=20=D0=B2=20=D1=82=D0=B5=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=202=20=D0=BF=D0=B5=D1=80=D0=B8=D0=BE=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../accounts/models/EnableTwoFactorAuthForm.php | 2 +- api/validators/TotpValidator.php | 2 +- .../functional/accounts/EnableTwoFactorAuthCest.php | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/api/modules/accounts/models/EnableTwoFactorAuthForm.php b/api/modules/accounts/models/EnableTwoFactorAuthForm.php index 0216c19..be86125 100644 --- a/api/modules/accounts/models/EnableTwoFactorAuthForm.php +++ b/api/modules/accounts/models/EnableTwoFactorAuthForm.php @@ -18,7 +18,7 @@ class EnableTwoFactorAuthForm extends AccountActionForm { return [ ['account', 'validateOtpDisabled'], ['totp', 'required', 'message' => E::TOTP_REQUIRED], - ['totp', TotpValidator::class, 'account' => $this->getAccount()], + ['totp', TotpValidator::class, 'account' => $this->getAccount(), 'window' => 2], ['password', PasswordRequiredValidator::class, 'account' => $this->getAccount()], ]; } diff --git a/api/validators/TotpValidator.php b/api/validators/TotpValidator.php index da84468..ce8f6e8 100644 --- a/api/validators/TotpValidator.php +++ b/api/validators/TotpValidator.php @@ -20,7 +20,7 @@ class TotpValidator extends Validator { * @var int|null Задаёт окно, в промежуток которого будет проверяться код. * Позволяет избежать ситуации, когда пользователь ввёл код в последнюю секунду * его существования и пока шёл запрос, тот протух. - * Значение задаётся в +- кодах, а не секундах. + * Значение задаётся в +- периодах, а не секундах. */ public $window; diff --git a/tests/codeception/api/functional/accounts/EnableTwoFactorAuthCest.php b/tests/codeception/api/functional/accounts/EnableTwoFactorAuthCest.php index aff4324..841dffe 100644 --- a/tests/codeception/api/functional/accounts/EnableTwoFactorAuthCest.php +++ b/tests/codeception/api/functional/accounts/EnableTwoFactorAuthCest.php @@ -58,4 +58,15 @@ class EnableTwoFactorAuthCest { ]); } + public function testSuccessEnableWithNotSoExpiredCode(FunctionalTester $I) { + $accountId = $I->amAuthenticated('AccountWithOtpSecret'); + $totp = TOTP::create('AAAA'); + $this->route->enableTwoFactorAuth($accountId, $totp->at(time() - 35), 'password_0'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => true, + ]); + } + } From c8c6401e143602a4173a2de8aeb4a177886311cc Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 21 Nov 2017 15:43:48 +0300 Subject: [PATCH 08/41] =?UTF-8?q?=D0=9E=D1=82=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D1=91=D0=BD=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=87=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20autoloader=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=D0=BA=D1=88=D0=B5=D0=BD=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b15dad6..17c3985 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ COPY ./composer.json /var/www/composer.json # Устанавливаем зависимости PHP RUN cd .. \ - && composer install --no-interaction --no-suggest --no-dev --classmap-authoritative \ + && composer install --no-interaction --no-suggest --no-dev --optimize-autoloader \ && cd - # Устанавливаем зависимости для Node.js From 42b6bc561ee0011198be7a3514a12ab53c80b77d Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 16 Nov 2017 19:55:00 +0300 Subject: [PATCH 09/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20Yii2=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B2=20statsd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-dist | 6 +++ autocompletion.php | 1 + common/components/StatsD.php | 89 ++++++++++++++++++++++++++++++++++++ common/config/config.php | 6 +++ 4 files changed, 102 insertions(+) create mode 100644 common/components/StatsD.php diff --git a/.env-dist b/.env-dist index acc5b32..b9dd499 100644 --- a/.env-dist +++ b/.env-dist @@ -35,6 +35,12 @@ RABBITMQ_USER=ely-accounts-app RABBITMQ_PASS=ely-accounts-app-password RABBITMQ_VHOST=/ely.by +## Параметры Statsd +STATSD_HOST=statsd.ely.by +STATSD_PORT=8125 +# This value can be blank +STATSD_NAMESPACE= + ## Конфигурация для Dev. XDEBUG_CONFIG=remote_host=10.254.254.254 PHP_IDE_CONFIG=serverName=docker diff --git a/autocompletion.php b/autocompletion.php index 3890cc7..1d5fd81 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -24,6 +24,7 @@ class Yii extends \yii\BaseYii { * @property \common\components\EmailRenderer $emailRenderer * @property \mito\sentry\Component $sentry * @property \api\components\OAuth2\Component $oauth + * @property \common\components\StatsD $statsd */ abstract class BaseApplication extends yii\base\Application { } diff --git a/common/components/StatsD.php b/common/components/StatsD.php new file mode 100644 index 0000000..172de0f --- /dev/null +++ b/common/components/StatsD.php @@ -0,0 +1,89 @@ +getClient()->increment($key); + } + + public function dec(string $key): void { + $this->getClient()->decrement($key); + } + + public function count(string $key, int $value): void { + $this->getClient()->count($key, $value); + } + + public function time(string $key, float $time): void { + $this->getClient()->timing($key, floor($time)); + } + + public function startTiming(string $key): void { + $this->getClient()->startTiming($key); + } + + public function endTiming(string $key): void { + $this->getClient()->endTiming($key); + } + + public function peakMemoryUsage(string $key): void { + $this->getClient()->memory($key); + } + + /** + * Pass delta values as a string. + * Accepts both positive (+11) and negative (-4) delta values. + * $statsd->gauge('foobar', 3); + * $statsd->gauge('foobar', '+11'); + * + * @param string $key + * @param string|int $value + */ + public function gauge(string $key, $value): void { + $this->getClient()->gauge($key, $value); + } + + public function set(string $key, int $value): void { + $this->getClient()->set($key, $value); + } + + public function getClient(): Client { + if ($this->client === null) { + $connection = $this->createConnection(); + $this->client = new Client($connection, $this->namespace); + } + + return $this->client; + } + + protected function createConnection(): Connection { + if (!empty($this->host) && !empty($this->port)) { + return new Connection\UdpSocket($this->host, $this->port); + } + + return new Connection\Blackhole(); + } + +} diff --git a/common/config/config.php b/common/config/config.php index d334a5c..c1e886b 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -90,6 +90,12 @@ return [ 'itemFile' => '@common/rbac/.generated/items.php', 'ruleFile' => '@common/rbac/.generated/rules.php', ], + 'statsd' => [ + 'class' => common\components\StatsD::class, + 'host' => getenv('STATSD_HOST'), + 'port' => getenv('STATSD_PORT') ?: 8125, + 'namespace' => getenv('STATSD_NAMESPACE') ?: 'ely.accounts.' . gethostname() . '.app', + ], ], 'container' => [ 'definitions' => [ From 72f546c827da04d4515b560db04ed4ac88c89177 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 17 Nov 2017 03:04:14 +0300 Subject: [PATCH 10/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=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BF=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/web/index.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/web/index.php b/api/web/index.php index 09a6f8b..00517a7 100644 --- a/api/web/index.php +++ b/api/web/index.php @@ -4,6 +4,8 @@ use api\aop\AspectKernel; use common\config\ConfigLoader; use yii\web\Application; +$time = microtime(true); + require __DIR__ . '/../../vendor/autoload.php'; defined('YII_DEBUG') or define('YII_DEBUG', in_array(getenv('YII_DEBUG'), ['true', '1'])); @@ -29,3 +31,7 @@ $config = ConfigLoader::load('api'); $application = new Application($config); $application->run(); + +$timeDifference = (microtime(true) - $time) * 1000; +fastcgi_finish_request(); +Yii::$app->statsd->time('request.time', $timeDifference); From 236f0e7d50b44e162ea06ce7738519133e5c1a69 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 19 Nov 2017 15:36:51 +0300 Subject: [PATCH 11/41] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=20=D0=B2=20sessionserve?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/modules/session/models/HasJoinedForm.php | 12 +++++------ api/modules/session/models/JoinForm.php | 22 +++++++++----------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/api/modules/session/models/HasJoinedForm.php b/api/modules/session/models/HasJoinedForm.php index 131c1f4..da5a2ee 100644 --- a/api/modules/session/models/HasJoinedForm.php +++ b/api/modules/session/models/HasJoinedForm.php @@ -6,6 +6,7 @@ use api\modules\session\exceptions\IllegalArgumentException; use api\modules\session\models\protocols\HasJoinedInterface; use api\modules\session\Module as Session; use common\models\Account; +use Yii; use yii\base\ErrorException; use yii\base\Model; @@ -19,6 +20,7 @@ class HasJoinedForm extends Model { } public function hasJoined(): Account { + Yii::$app->statsd->inc('sessionserver.hasJoined.attempt'); if (!$this->protocol->validate()) { throw new IllegalArgumentException(); } @@ -26,13 +28,12 @@ class HasJoinedForm extends Model { $serverId = $this->protocol->getServerId(); $username = $this->protocol->getUsername(); - Session::info( - "Server with server_id = '{$serverId}' trying to verify has joined user with username = '{$username}'." - ); + Session::info("Server with server_id = '{$serverId}' trying to verify has joined user with username = '{$username}'."); $joinModel = SessionModel::find($username, $serverId); if ($joinModel === null) { Session::error("Not found join operation for username = '{$username}'."); + Yii::$app->statsd->inc('sessionserver.hasJoined.fail_no_join'); throw new ForbiddenOperationException('Invalid token.'); } @@ -42,9 +43,8 @@ class HasJoinedForm extends Model { throw new ErrorException('Account must exists'); } - Session::info( - "User with username = '{$username}' successfully verified by server with server_id = '{$serverId}'." - ); + Session::info("User with username = '{$username}' successfully verified by server with server_id = '{$serverId}'."); + Yii::$app->statsd->inc('sessionserver.hasJoined.success'); return $account; } diff --git a/api/modules/session/models/JoinForm.php b/api/modules/session/models/JoinForm.php index 2676fb2..c3ec952 100644 --- a/api/modules/session/models/JoinForm.php +++ b/api/modules/session/models/JoinForm.php @@ -53,6 +53,7 @@ class JoinForm extends Model { $serverId = $this->serverId; $accessToken = $this->accessToken; Session::info("User with access_token = '{$accessToken}' trying join to server with server_id = '{$serverId}'."); + Yii::$app->statsd->inc('sessionserver.join.attempts'); if (!$this->validate()) { return false; } @@ -63,10 +64,8 @@ class JoinForm extends Model { throw new ErrorException('Cannot save join session model'); } - Session::info( - "User with access_token = '{$accessToken}' and nickname = '{$account->username}' successfully joined to " . - "server_id = '{$serverId}'." - ); + Session::info("User with access_token = '{$accessToken}' and nickname = '{$account->username}' successfully joined to server_id = '{$serverId}'."); + Yii::$app->statsd->inc('sessionserver.join.success'); return true; } @@ -100,6 +99,7 @@ class JoinForm extends Model { /** @var MinecraftAccessKey|\api\components\OAuth2\Entities\AccessTokenEntity $accessModel */ if ($accessModel->isExpired()) { Session::error("User with access_token = '{$accessToken}' failed join by expired access_token."); + Yii::$app->statsd->inc('sessionserver.join.fail_token_expired'); throw new ForbiddenOperationException('Expired access_token.'); } @@ -113,11 +113,13 @@ class JoinForm extends Model { if ($identity === null) { Session::error("User with access_token = '{$accessToken}' failed join by wrong access_token."); + Yii::$app->statsd->inc('sessionserver.join.fail_wrong_token'); throw new ForbiddenOperationException('Invalid access_token.'); } if (!Yii::$app->user->can(P::MINECRAFT_SERVER_SESSION)) { Session::error("User with access_token = '{$accessToken}' doesn't have enough scopes to make join."); + Yii::$app->statsd->inc('sessionserver.join.fail_not_enough_scopes'); throw new ForbiddenOperationException('The token does not have required scope.'); } @@ -127,18 +129,14 @@ class JoinForm extends Model { $selectedProfile = $this->selectedProfile; $isUuid = StringHelper::isUuid($selectedProfile); if ($isUuid && $account->uuid !== $this->normalizeUUID($selectedProfile)) { - Session::error( - "User with access_token = '{$accessToken}' trying to join with identity = '{$selectedProfile}'," . - " but access_token issued to account with id = '{$account->uuid}'." - ); + Session::error("User with access_token = '{$accessToken}' trying to join with identity = '{$selectedProfile}', but access_token issued to account with id = '{$account->uuid}'."); + Yii::$app->statsd->inc('sessionserver.join.fail_uuid_mismatch'); throw new ForbiddenOperationException('Wrong selected_profile.'); } if (!$isUuid && mb_strtolower($account->username) !== mb_strtolower($selectedProfile)) { - Session::error( - "User with access_token = '{$accessToken}' trying to join with identity = '{$selectedProfile}'," . - " but access_token issued to account with username = '{$account->username}'." - ); + Session::error("User with access_token = '{$accessToken}' trying to join with identity = '{$selectedProfile}', but access_token issued to account with username = '{$account->username}'."); + Yii::$app->statsd->inc('sessionserver.join.fail_username_mismatch'); throw new ForbiddenOperationException('Invalid credentials'); } From 63db3adca9ead65f1c85590d8a939b5bc4d1d152 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 19 Nov 2017 18:32:51 +0300 Subject: [PATCH 12/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=20=D0=B0=D0=BD=D0=BD=D0=BE=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20Yii2=20=D0=B8=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B0=20=D0=B2=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/aop/AspectKernel.php | 1 + api/aop/annotations/CollectModelMetrics.php | 19 +++++++++ api/aop/aspects/CollectMetricsAspect.php | 41 +++++++++++++++++++ .../authentication/ConfirmEmailForm.php | 2 + .../authentication/ForgotPasswordForm.php | 6 +++ api/models/authentication/LoginForm.php | 2 + api/models/authentication/LogoutForm.php | 8 +++- .../authentication/RecoverPasswordForm.php | 2 + .../authentication/RefreshTokenForm.php | 2 + .../authentication/RegistrationForm.php | 2 + .../RepeatAccountActivationForm.php | 6 +++ 11 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 api/aop/annotations/CollectModelMetrics.php create mode 100644 api/aop/aspects/CollectMetricsAspect.php diff --git a/api/aop/AspectKernel.php b/api/aop/AspectKernel.php index 79cc8ed..fc30542 100644 --- a/api/aop/AspectKernel.php +++ b/api/aop/AspectKernel.php @@ -9,6 +9,7 @@ class AspectKernel extends BaseAspectKernel { protected function configureAop(AspectContainer $container): void { $container->registerAspect(new aspects\MockDataAspect()); + $container->registerAspect(new aspects\CollectMetricsAspect()); } } diff --git a/api/aop/annotations/CollectModelMetrics.php b/api/aop/annotations/CollectModelMetrics.php new file mode 100644 index 0000000..4614c91 --- /dev/null +++ b/api/aop/annotations/CollectModelMetrics.php @@ -0,0 +1,19 @@ +getMethod()->getAnnotation(CollectModelMetrics::class); + $prefix = trim($annotation->prefix, '.'); + + Yii::$app->statsd->inc($prefix . '.attempt'); + $result = $invocation->proceed(); + if ($result !== false) { + Yii::$app->statsd->inc($prefix . '.success'); + return $result; + } + + /** @var \yii\base\Model $model */ + $model = $invocation->getThis(); + $errors = array_values($model->getFirstErrors()); + if (!isset($errors[0])) { + Yii::error('Unsuccess result with empty errors list'); + return false; + } + + Yii::$app->statsd->inc($prefix . '.' . $errors[0]); + + return false; + } + +} diff --git a/api/models/authentication/ConfirmEmailForm.php b/api/models/authentication/ConfirmEmailForm.php index ac5f78f..c813ac0 100644 --- a/api/models/authentication/ConfirmEmailForm.php +++ b/api/models/authentication/ConfirmEmailForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/models/authentication/LoginForm.php b/api/models/authentication/LoginForm.php index 1e51eed..c5a2948 100644 --- a/api/models/authentication/LoginForm.php +++ b/api/models/authentication/LoginForm.php @@ -1,6 +1,7 @@ user; + $component = Yii::$app->user; $session = $component->getActiveSession(); if ($session === null) { return true; diff --git a/api/models/authentication/RecoverPasswordForm.php b/api/models/authentication/RecoverPasswordForm.php index ce21498..358fec0 100644 --- a/api/models/authentication/RecoverPasswordForm.php +++ b/api/models/authentication/RecoverPasswordForm.php @@ -1,6 +1,7 @@ validate()) { return false; From 6ee40f3fcc4b51fbaea6b8d4f638c1e36b560b5c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 21 Nov 2017 19:58:55 +0300 Subject: [PATCH 13/41] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=20=D0=B2=20oauth2=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/models/OauthProcess.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/models/OauthProcess.php b/api/models/OauthProcess.php index c666922..5d50582 100644 --- a/api/models/OauthProcess.php +++ b/api/models/OauthProcess.php @@ -87,6 +87,7 @@ class OauthProcess { */ public function complete(): array { try { + Yii::$app->statsd->inc('oauth.complete.attempt'); $grant = $this->getAuthorizationCodeGrant(); $authParams = $grant->checkAuthorizeParams(); $account = Yii::$app->user->identity->getAccount(); @@ -94,6 +95,7 @@ class OauthProcess { $clientModel = OauthClient::findOne($authParams->getClient()->getId()); if (!$this->canAutoApprove($account, $clientModel, $authParams)) { + Yii::$app->statsd->inc('oauth.complete.approve_required'); $isAccept = Yii::$app->request->post('accept'); if ($isAccept === null) { throw new AcceptRequiredException(); @@ -109,7 +111,12 @@ class OauthProcess { 'success' => true, 'redirectUri' => $redirectUri, ]; + Yii::$app->statsd->inc('oauth.complete.success'); } catch (OAuthException $e) { + if (!$e instanceof AcceptRequiredException) { + Yii::$app->statsd->inc('oauth.complete.fail'); + } + $response = $this->buildErrorResponse($e); } @@ -139,8 +146,11 @@ class OauthProcess { */ public function getToken(): array { try { + Yii::$app->statsd->inc('oauth.issueToken.attempt'); $response = $this->server->issueAccessToken(); + Yii::$app->statsd->inc('oauth.issueToken.success'); } catch (OAuthException $e) { + Yii::$app->statsd->inc('oauth.issueToken.fail'); Yii::$app->response->statusCode = $e->httpStatusCode; $response = [ 'error' => $e->errorType, From a94e7095c88c4567202ff387b173944dbf2ea04b Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 21 Nov 2017 20:06:26 +0300 Subject: [PATCH 14/41] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=81=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA=20=D0=B2=20=D0=B4=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D1=81=20=D0=B0=D0=BA?= =?UTF-8?q?=D0=BA=D0=B0=D1=83=D0=BD=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/modules/accounts/models/AcceptRulesForm.php | 4 ++++ api/modules/accounts/models/ChangeEmailForm.php | 4 ++++ api/modules/accounts/models/ChangeLanguageForm.php | 4 ++++ api/modules/accounts/models/ChangePasswordForm.php | 4 ++++ api/modules/accounts/models/ChangeUsernameForm.php | 4 ++++ api/modules/accounts/models/DisableTwoFactorAuthForm.php | 4 ++++ api/modules/accounts/models/EnableTwoFactorAuthForm.php | 4 ++++ api/modules/accounts/models/SendEmailVerificationForm.php | 4 ++++ api/modules/accounts/models/SendNewEmailVerificationForm.php | 4 ++++ 9 files changed, 36 insertions(+) diff --git a/api/modules/accounts/models/AcceptRulesForm.php b/api/modules/accounts/models/AcceptRulesForm.php index 83e207a..49ec85a 100644 --- a/api/modules/accounts/models/AcceptRulesForm.php +++ b/api/modules/accounts/models/AcceptRulesForm.php @@ -1,11 +1,15 @@ getAccount(); $account->rules_agreement_version = LATEST_RULES_VERSION; diff --git a/api/modules/accounts/models/ChangeEmailForm.php b/api/modules/accounts/models/ChangeEmailForm.php index d08c848..c1c92c4 100644 --- a/api/modules/accounts/models/ChangeEmailForm.php +++ b/api/modules/accounts/models/ChangeEmailForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/ChangeLanguageForm.php b/api/modules/accounts/models/ChangeLanguageForm.php index e1061de..b9502e3 100644 --- a/api/modules/accounts/models/ChangeLanguageForm.php +++ b/api/modules/accounts/models/ChangeLanguageForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/ChangePasswordForm.php b/api/modules/accounts/models/ChangePasswordForm.php index 48824ff..1782ef2 100644 --- a/api/modules/accounts/models/ChangePasswordForm.php +++ b/api/modules/accounts/models/ChangePasswordForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/ChangeUsernameForm.php b/api/modules/accounts/models/ChangeUsernameForm.php index 193712e..86fa961 100644 --- a/api/modules/accounts/models/ChangeUsernameForm.php +++ b/api/modules/accounts/models/ChangeUsernameForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/DisableTwoFactorAuthForm.php b/api/modules/accounts/models/DisableTwoFactorAuthForm.php index 7a98c44..ce8a429 100644 --- a/api/modules/accounts/models/DisableTwoFactorAuthForm.php +++ b/api/modules/accounts/models/DisableTwoFactorAuthForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/EnableTwoFactorAuthForm.php b/api/modules/accounts/models/EnableTwoFactorAuthForm.php index be86125..0e19639 100644 --- a/api/modules/accounts/models/EnableTwoFactorAuthForm.php +++ b/api/modules/accounts/models/EnableTwoFactorAuthForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/SendEmailVerificationForm.php b/api/modules/accounts/models/SendEmailVerificationForm.php index 47f596f..593215f 100644 --- a/api/modules/accounts/models/SendEmailVerificationForm.php +++ b/api/modules/accounts/models/SendEmailVerificationForm.php @@ -1,6 +1,7 @@ validate()) { return false; diff --git a/api/modules/accounts/models/SendNewEmailVerificationForm.php b/api/modules/accounts/models/SendNewEmailVerificationForm.php index 1c2823e..100c09b 100644 --- a/api/modules/accounts/models/SendNewEmailVerificationForm.php +++ b/api/modules/accounts/models/SendNewEmailVerificationForm.php @@ -1,6 +1,7 @@ validate()) { return false; From 6f7fcf9e44c14ff5c1e67a6c5e2ed499872ce076 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 21 Nov 2017 20:11:28 +0300 Subject: [PATCH 15/41] =?UTF-8?q?=D0=9B=D0=BE=D0=B3=D0=B3=D0=B8=D1=80?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=BE=20=D1=82=D0=B8=D0=BF=D0=B5=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D0=BC=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=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=BD=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/modules/session/models/JoinForm.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/modules/session/models/JoinForm.php b/api/modules/session/models/JoinForm.php index c3ec952..f53d7f5 100644 --- a/api/modules/session/models/JoinForm.php +++ b/api/modules/session/models/JoinForm.php @@ -96,10 +96,11 @@ class JoinForm extends Model { /** @var MinecraftAccessKey|null $accessModel */ $accessModel = MinecraftAccessKey::findOne($accessToken); if ($accessModel !== null) { + Yii::$app->statsd->inc('sessionserver.authentication.legacy_minecraft_protocol'); /** @var MinecraftAccessKey|\api\components\OAuth2\Entities\AccessTokenEntity $accessModel */ if ($accessModel->isExpired()) { Session::error("User with access_token = '{$accessToken}' failed join by expired access_token."); - Yii::$app->statsd->inc('sessionserver.join.fail_token_expired'); + Yii::$app->statsd->inc('sessionserver.authentication.legacy_minecraft_protocol_token_expired'); throw new ForbiddenOperationException('Expired access_token.'); } @@ -117,9 +118,10 @@ class JoinForm extends Model { throw new ForbiddenOperationException('Invalid access_token.'); } + Yii::$app->statsd->inc('sessionserver.authentication.oauth2'); if (!Yii::$app->user->can(P::MINECRAFT_SERVER_SESSION)) { Session::error("User with access_token = '{$accessToken}' doesn't have enough scopes to make join."); - Yii::$app->statsd->inc('sessionserver.join.fail_not_enough_scopes'); + Yii::$app->statsd->inc('sessionserver.authentication.oauth2_not_enough_scopes'); throw new ForbiddenOperationException('The token does not have required scope.'); } From 0fee23ac86633c7bc2f76696248f8893f769c571 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 22 Nov 2017 22:45:21 +0300 Subject: [PATCH 16/41] =?UTF-8?q?=D0=A1=D0=B1=D0=BE=D1=80=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BA=20=D0=BE=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B5=20=D0=B2=D0=BE=D1=80=D0=BA=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console/controllers/AccountQueueController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/console/controllers/AccountQueueController.php b/console/controllers/AccountQueueController.php index be77b59..04ee523 100644 --- a/console/controllers/AccountQueueController.php +++ b/console/controllers/AccountQueueController.php @@ -32,11 +32,14 @@ class AccountQueueController extends AmqpController { } public function routeUsernameChanged(UsernameChanged $body): bool { + Yii::$app->statsd->inc('worker.account.usernameChanged.attempt'); $mojangApi = $this->createMojangApi(); try { $response = $mojangApi->usernameToUUID($body->newUsername); + Yii::$app->statsd->inc('worker.account.usernameChanged.found'); } catch (NoContentException $e) { $response = false; + Yii::$app->statsd->inc('worker.account.usernameChanged.not_found'); } catch (RequestException $e) { return true; } From a0423d9b13c41dd27f34de2cf21b91423572ff88 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 25 Nov 2017 23:21:37 +0300 Subject: [PATCH 17/41] =?UTF-8?q?Yii2=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=91=D0=BD=20=D0=B4=D0=BE=202.0.13.1?= 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 975a12c..4e7debf 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "require": { "php": "^7.1", "roave/security-advisories": "dev-master", - "yiisoft/yii2": "2.0.12", + "yiisoft/yii2": "2.0.13.1", "yiisoft/yii2-swiftmailer": "~2.1.0", "ramsey/uuid": "^3.5", "league/oauth2-server": "^4.1", From 8c8116038e9c56e6adbd0fe33d4e0100553d8d0c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 28 Nov 2017 14:23:06 +0300 Subject: [PATCH 18/41] =?UTF-8?q?=D0=A4=D0=BE=D1=80=D0=BA=20Codeception=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=B1=D1=80=D0=BE=D1=81=D0=B0?= =?UTF-8?q?=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=B8=20=D0=B2=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=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 --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4e7debf..e266c82 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "yiisoft/yii2-faker": "*", "flow/jsonpath": "^0.3.1", "phpunit/phpunit": "^6.0", - "codeception/codeception": "2.3.6", + "codeception/codeception": "dev-reset_yii2_app#6045eed00f7b163226d04fe40333f076b0f132e3", "codeception/specify": "*", "codeception/verify": "*", "mockery/mockery": "^1.0.0", @@ -50,6 +50,10 @@ { "type": "git", "url": "git@gitlab.ely.by:elyby/email-renderer.git" + }, + { + "type": "git", + "url": "git@github.com:erickskrauch/Codeception.git" } ], "autoload": { From 4d48b38e47486b3b6759eef26888eba8e22cc62f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 28 Nov 2017 14:24:28 +0300 Subject: [PATCH 19/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=D1=81=D0=BE=D0=B2=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=20codecepti?= =?UTF-8?q?on/specify=201.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- tests/codeception/api/_bootstrap.php | 4 +--- tests/codeception/common/_bootstrap.php | 3 +-- tests/codeception/console/_bootstrap.php | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e266c82..af98768 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "flow/jsonpath": "^0.3.1", "phpunit/phpunit": "^6.0", "codeception/codeception": "dev-reset_yii2_app#6045eed00f7b163226d04fe40333f076b0f132e3", - "codeception/specify": "*", + "codeception/specify": "^1.0.0", "codeception/verify": "*", "mockery/mockery": "^1.0.0", "php-mock/php-mock-mockery": "^1.2.0" diff --git a/tests/codeception/api/_bootstrap.php b/tests/codeception/api/_bootstrap.php index 03865c3..fe5358e 100644 --- a/tests/codeception/api/_bootstrap.php +++ b/tests/codeception/api/_bootstrap.php @@ -1,6 +1,5 @@ Date: Sun, 26 Nov 2017 04:44:41 +0300 Subject: [PATCH 20/41] =?UTF-8?q?=D0=9E=D1=82=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=D1=81=D0=B5=D1=85=20email=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=B0=20=D0=B2=20=D0=BE=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env-dist | 1 + .../authentication/ForgotPasswordForm.php | 5 +- .../authentication/RegistrationForm.php | 4 +- .../RepeatAccountActivationForm.php | 35 +++++------- .../models/SendEmailVerificationForm.php | 4 +- .../models/SendNewEmailVerificationForm.php | 4 +- autocompletion.php | 1 + common/components/EmailRenderer.php | 8 +-- common/config/config.php | 10 ++++ common/emails/EmailHelper.php | 50 +---------------- common/tasks/SendCurrentEmailConfirmation.php | 43 +++++++++++++++ common/tasks/SendNewEmailConfirmation.php | 43 +++++++++++++++ common/tasks/SendPasswordRecoveryEmail.php | 55 +++++++++++++++++++ common/tasks/SendRegistrationEmail.php | 55 +++++++++++++++++++ composer.json | 3 +- console/config/config.php | 11 ++-- docker/supervisor/worker-queue.conf | 6 ++ 17 files changed, 253 insertions(+), 85 deletions(-) create mode 100644 common/tasks/SendCurrentEmailConfirmation.php create mode 100644 common/tasks/SendNewEmailConfirmation.php create mode 100644 common/tasks/SendPasswordRecoveryEmail.php create mode 100644 common/tasks/SendRegistrationEmail.php create mode 100644 docker/supervisor/worker-queue.conf diff --git a/.env-dist b/.env-dist index b9dd499..a4fcd1c 100644 --- a/.env-dist +++ b/.env-dist @@ -2,6 +2,7 @@ ## Env приложения YII_DEBUG=true YII_ENV=dev +DOMAIN=https://account.ely.by ## Параметры, отвечающие за безопасность JWT_USER_SECRET= diff --git a/api/models/authentication/ForgotPasswordForm.php b/api/models/authentication/ForgotPasswordForm.php index 8b2f52b..45cf5cc 100644 --- a/api/models/authentication/ForgotPasswordForm.php +++ b/api/models/authentication/ForgotPasswordForm.php @@ -4,13 +4,14 @@ namespace api\models\authentication; use api\aop\annotations\CollectModelMetrics; use api\components\ReCaptcha\Validator as ReCaptchaValidator; use api\models\base\ApiForm; -use common\emails\EmailHelper; use common\helpers\Error as E; use api\traits\AccountFinder; use common\components\UserFriendlyRandomKey; use common\models\Account; use common\models\confirmations\ForgotPassword; use common\models\EmailActivation; +use common\tasks\SendPasswordRecoveryEmail; +use Yii; use yii\base\ErrorException; class ForgotPasswordForm extends ApiForm { @@ -80,7 +81,7 @@ class ForgotPasswordForm extends ApiForm { throw new ErrorException('Cannot create email activation for forgot password form'); } - EmailHelper::forgotPassword($emailActivation); + Yii::$app->queue->push(SendPasswordRecoveryEmail::createFromConfirmation($emailActivation)); return true; } diff --git a/api/models/authentication/RegistrationForm.php b/api/models/authentication/RegistrationForm.php index 743b1e4..2382fd5 100644 --- a/api/models/authentication/RegistrationForm.php +++ b/api/models/authentication/RegistrationForm.php @@ -3,13 +3,13 @@ namespace api\models\authentication; use api\aop\annotations\CollectModelMetrics; use api\components\ReCaptcha\Validator as ReCaptchaValidator; -use common\emails\EmailHelper; use api\models\base\ApiForm; use common\helpers\Error as E; use common\components\UserFriendlyRandomKey; use common\models\Account; use common\models\confirmations\RegistrationConfirmation; use common\models\UsernameHistory; +use common\tasks\SendRegistrationEmail; use common\validators\EmailValidator; use common\validators\LanguageValidator; use common\validators\PasswordValidator; @@ -104,7 +104,7 @@ class RegistrationForm extends ApiForm { throw new ErrorException('Cannot save username history record'); } - EmailHelper::registration($emailActivation); + Yii::$app->queue->push(SendRegistrationEmail::createFromConfirmation($emailActivation)); $transaction->commit(); } catch (Exception $e) { diff --git a/api/models/authentication/RepeatAccountActivationForm.php b/api/models/authentication/RepeatAccountActivationForm.php index 58d6c35..a56d226 100644 --- a/api/models/authentication/RepeatAccountActivationForm.php +++ b/api/models/authentication/RepeatAccountActivationForm.php @@ -3,15 +3,15 @@ namespace api\models\authentication; use api\aop\annotations\CollectModelMetrics; use api\components\ReCaptcha\Validator as ReCaptchaValidator; -use common\emails\EmailHelper; +use api\exceptions\ThisShouldNotHappenException; use api\models\base\ApiForm; use common\helpers\Error as E; use common\components\UserFriendlyRandomKey; use common\models\Account; use common\models\confirmations\RegistrationConfirmation; use common\models\EmailActivation; +use common\tasks\SendRegistrationEmail; use Yii; -use yii\base\ErrorException; class RepeatAccountActivationForm extends ApiForm { @@ -57,7 +57,6 @@ class RepeatAccountActivationForm extends ApiForm { /** * @CollectModelMetrics(prefix="signup.repeatEmail") * @return bool - * @throws ErrorException */ public function sendRepeatMessage() { if (!$this->validate()) { @@ -66,27 +65,23 @@ class RepeatAccountActivationForm extends ApiForm { $account = $this->getAccount(); $transaction = Yii::$app->db->beginTransaction(); - try { - EmailActivation::deleteAll([ - 'account_id' => $account->id, - 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, - ]); - $activation = new RegistrationConfirmation(); - $activation->account_id = $account->id; - $activation->key = UserFriendlyRandomKey::make(); - if (!$activation->save()) { - throw new ErrorException('Unable save email-activation model.'); - } + EmailActivation::deleteAll([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + ]); - EmailHelper::registration($activation); - - $transaction->commit(); - } catch (ErrorException $e) { - $transaction->rollBack(); - throw $e; + $activation = new RegistrationConfirmation(); + $activation->account_id = $account->id; + $activation->key = UserFriendlyRandomKey::make(); + if (!$activation->save()) { + throw new ThisShouldNotHappenException('Unable save email-activation model.'); } + Yii::$app->queue->push(SendRegistrationEmail::createFromConfirmation($activation)); + + $transaction->commit(); + return true; } diff --git a/api/modules/accounts/models/SendEmailVerificationForm.php b/api/modules/accounts/models/SendEmailVerificationForm.php index 593215f..cd680da 100644 --- a/api/modules/accounts/models/SendEmailVerificationForm.php +++ b/api/modules/accounts/models/SendEmailVerificationForm.php @@ -3,11 +3,11 @@ namespace api\modules\accounts\models; use api\aop\annotations\CollectModelMetrics; use api\exceptions\ThisShouldNotHappenException; -use common\emails\EmailHelper; use api\validators\PasswordRequiredValidator; use common\helpers\Error as E; use common\models\confirmations\CurrentEmailConfirmation; use common\models\EmailActivation; +use common\tasks\SendCurrentEmailConfirmation; use Yii; class SendEmailVerificationForm extends AccountActionForm { @@ -48,7 +48,7 @@ class SendEmailVerificationForm extends AccountActionForm { $this->removeOldCode(); $activation = $this->createCode(); - EmailHelper::changeEmailConfirmCurrent($activation); + Yii::$app->queue->push(SendCurrentEmailConfirmation::createFromConfirmation($activation)); $transaction->commit(); diff --git a/api/modules/accounts/models/SendNewEmailVerificationForm.php b/api/modules/accounts/models/SendNewEmailVerificationForm.php index 100c09b..1c9470a 100644 --- a/api/modules/accounts/models/SendNewEmailVerificationForm.php +++ b/api/modules/accounts/models/SendNewEmailVerificationForm.php @@ -3,10 +3,10 @@ namespace api\modules\accounts\models; use api\aop\annotations\CollectModelMetrics; use api\exceptions\ThisShouldNotHappenException; -use common\emails\EmailHelper; use api\validators\EmailActivationKeyValidator; use common\models\confirmations\NewEmailConfirmation; use common\models\EmailActivation; +use common\tasks\SendNewEmailConfirmation; use common\validators\EmailValidator; use Yii; @@ -39,7 +39,7 @@ class SendNewEmailVerificationForm extends AccountActionForm { $activation = $this->createCode(); - EmailHelper::changeEmailConfirmNew($activation); + Yii::$app->queue->push(SendNewEmailConfirmation::createFromConfirmation($activation)); $transaction->commit(); diff --git a/autocompletion.php b/autocompletion.php index 1d5fd81..07b6667 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -25,6 +25,7 @@ class Yii extends \yii\BaseYii { * @property \mito\sentry\Component $sentry * @property \api\components\OAuth2\Component $oauth * @property \common\components\StatsD $statsd + * @property \yii\queue\Queue $queue */ abstract class BaseApplication extends yii\base\Application { } diff --git a/common/components/EmailRenderer.php b/common/components/EmailRenderer.php index 3c493ca..ce0e3e7 100644 --- a/common/components/EmailRenderer.php +++ b/common/components/EmailRenderer.php @@ -29,7 +29,7 @@ class EmailRenderer extends Component { parent::__construct($config); if ($this->_baseDomain === null) { - $this->_baseDomain = Yii::$app->request->getHostInfo(); + $this->_baseDomain = Yii::$app->urlManager->getHostInfo(); if ($this->_baseDomain === null) { throw new InvalidConfigException('Cannot automatically obtain base domain'); } @@ -51,7 +51,7 @@ class EmailRenderer extends Component { * @param string $templateName * @return TemplateBuilder */ - public function getTemplate(string $templateName) : TemplateBuilder { + public function getTemplate(string $templateName): TemplateBuilder { return $this->renderer->getTemplate($templateName); } @@ -60,11 +60,11 @@ class EmailRenderer extends Component { * @throws \Ely\Email\RendererException * @return string */ - public function render(TemplateBuilder $template) : string { + public function render(TemplateBuilder $template): string { return $this->renderer->render($template); } - private function buildBasePath() : string { + private function buildBasePath(): string { return $this->_baseDomain . $this->basePath; } diff --git a/common/config/config.php b/common/config/config.php index c1e886b..692fbed 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -96,6 +96,16 @@ return [ 'port' => getenv('STATSD_PORT') ?: 8125, 'namespace' => getenv('STATSD_NAMESPACE') ?: 'ely.accounts.' . gethostname() . '.app', ], + 'queue' => [ + 'class' => yii\queue\amqp\Queue::class, + 'host' => getenv('RABBITMQ_HOST') ?: 'rabbitmq', + 'port' => getenv('RABBITMQ_PORT') ?: 5672, + 'user' => getenv('RABBITMQ_USER'), + 'password' => getenv('RABBITMQ_PASS'), + 'vhost' => getenv('RABBITMQ_VHOST'), + 'queueName' => 'worker', + 'exchangeName' => 'tasks', + ], ], 'container' => [ 'definitions' => [ diff --git a/common/emails/EmailHelper.php b/common/emails/EmailHelper.php index 2d40cb3..3b0d5e3 100644 --- a/common/emails/EmailHelper.php +++ b/common/emails/EmailHelper.php @@ -1,56 +1,10 @@ account; - $locale = $account->lang; - $params = new RegistrationEmailParams( - $account->username, - $emailActivation->key, - Yii::$app->request->getHostInfo() . '/activation/' . $emailActivation->key - ); - - (new RegistrationEmail(self::buildTo($account), $locale, $params))->send(); - } - - public static function forgotPassword(ForgotPassword $emailActivation): void { - $account = $emailActivation->account; - $locale = $account->lang; - $params = new ForgotPasswordParams( - $account->username, - $emailActivation->key, - Yii::$app->request->getHostInfo() . '/recover-password/' . $emailActivation->key - ); - - (new ForgotPasswordEmail(self::buildTo($account), $locale, $params))->send(); - } - - public static function changeEmailConfirmCurrent(CurrentEmailConfirmation $emailActivation): void { - (new ChangeEmailConfirmCurrentEmail(self::buildTo($emailActivation->account), $emailActivation->key))->send(); - } - - public static function changeEmailConfirmNew(NewEmailConfirmation $emailActivation): void { - $account = $emailActivation->account; - (new ChangeEmailConfirmNewEmail(self::buildTo($account), $account->username, $emailActivation->key))->send(); - } - - public static function buildTo(Account $account): array { - return [$account->email => $account->username]; + public static function buildTo(string $username, string $email): array { + return [$email => $username]; } } diff --git a/common/tasks/SendCurrentEmailConfirmation.php b/common/tasks/SendCurrentEmailConfirmation.php new file mode 100644 index 0000000..3f5362a --- /dev/null +++ b/common/tasks/SendCurrentEmailConfirmation.php @@ -0,0 +1,43 @@ +email = $confirmation->account->email; + $result->username = $confirmation->account->username; + $result->code = $confirmation->key; + + return $result; + } + + public function getTtr() { + return 30; + } + + public function canRetry($attempt, $error) { + return true; + } + + /** + * @param \yii\queue\Queue $queue + */ + public function execute($queue) { + $to = EmailHelper::buildTo($this->username, $this->email); + $template = new ChangeEmailConfirmCurrentEmail($to, $this->code); + $template->send(); + } + +} diff --git a/common/tasks/SendNewEmailConfirmation.php b/common/tasks/SendNewEmailConfirmation.php new file mode 100644 index 0000000..1d09f42 --- /dev/null +++ b/common/tasks/SendNewEmailConfirmation.php @@ -0,0 +1,43 @@ +email = $confirmation->getNewEmail(); + $result->username = $confirmation->account->username; + $result->code = $confirmation->key; + + return $result; + } + + public function getTtr() { + return 30; + } + + public function canRetry($attempt, $error) { + return true; + } + + /** + * @param \yii\queue\Queue $queue + */ + public function execute($queue) { + $to = EmailHelper::buildTo($this->username, $this->email); + $template = new ChangeEmailConfirmNewEmail($to, $this->username, $this->code); + $template->send(); + } + +} diff --git a/common/tasks/SendPasswordRecoveryEmail.php b/common/tasks/SendPasswordRecoveryEmail.php new file mode 100644 index 0000000..fe39b9a --- /dev/null +++ b/common/tasks/SendPasswordRecoveryEmail.php @@ -0,0 +1,55 @@ +account; + + $result = new self(); + $result->username = $account->username; + $result->email = $account->email; + $result->code = $confirmation->key; + $result->link = Yii::$app->request->getHostInfo() . '/recover-password/' . $confirmation->key; + $result->locale = $account->lang; + + return $result; + } + + public function getTtr() { + return 30; + } + + public function canRetry($attempt, $error) { + return true; + } + + /** + * @param \yii\queue\Queue $queue + * @throws \common\emails\exceptions\CannotSendEmailException + */ + public function execute($queue) { + $params = new ForgotPasswordParams($this->username, $this->code, $this->link); + $to = EmailHelper::buildTo($this->username, $this->email); + $template = new ForgotPasswordEmail($to, $this->locale, $params); + $template->send(); + } + +} diff --git a/common/tasks/SendRegistrationEmail.php b/common/tasks/SendRegistrationEmail.php new file mode 100644 index 0000000..ec80050 --- /dev/null +++ b/common/tasks/SendRegistrationEmail.php @@ -0,0 +1,55 @@ +account; + + $result = new self(); + $result->username = $account->username; + $result->email = $account->email; + $result->code = $confirmation->key; + $result->link = Yii::$app->request->getHostInfo() . '/activation/' . $confirmation->key; + $result->locale = $account->lang; + + return $result; + } + + public function getTtr() { + return 30; + } + + public function canRetry($attempt, $error) { + return true; + } + + /** + * @param \yii\queue\Queue $queue + * @throws \common\emails\exceptions\CannotSendEmailException + */ + public function execute($queue) { + $params = new RegistrationEmailParams($this->username, $this->code, $this->link); + $to = EmailHelper::buildTo($this->username, $this->email); + $template = new RegistrationEmail($to, $this->locale, $params); + $template->send(); + } + +} diff --git a/composer.json b/composer.json index af98768..f37adb5 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ "paragonie/constant_time_encoding": "^2.0", "webmozart/assert": "^1.2.0", "goaop/framework": "~2.1.2", - "domnikl/statsd": "^2.6" + "domnikl/statsd": "^2.6", + "yiisoft/yii2-queue": "~2.0.1" }, "require-dev": { "yiisoft/yii2-debug": "*", diff --git a/console/config/config.php b/console/config/config.php index 748acc4..7b5d94d 100644 --- a/console/config/config.php +++ b/console/config/config.php @@ -1,13 +1,13 @@ 'accounts-console', 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], + 'bootstrap' => ['log', 'queue'], 'controllerNamespace' => 'console\controllers', 'params' => $params, 'components' => [ @@ -23,10 +23,13 @@ return [ ], ], ], + 'urlManager' => [ + 'hostInfo' => getenv('DOMAIN') ?: 'https://account.ely.by', + ], ], 'controllerMap' => [ 'migrate' => [ - 'class' => yii\console\controllers\MigrateController::class, + 'class' => yii\console\controllers\MigrateController::class, 'templateFile' => '@console/views/migration.php', ], ], diff --git a/docker/supervisor/worker-queue.conf b/docker/supervisor/worker-queue.conf new file mode 100644 index 0000000..b7d715f --- /dev/null +++ b/docker/supervisor/worker-queue.conf @@ -0,0 +1,6 @@ +[program:account-queue-worker] +directory=/var/www/html +command=wait-for-it rabbitmq:5672 -- php yii queue/listen +autostart=true +autorestart=true +priority=10 From b8049e88996efdbe35e25056e83743cad7bfb1f5 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 27 Nov 2017 02:29:15 +0300 Subject: [PATCH 21/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC,=20=D1=87=D1=82=D0=BE=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=BB=D0=B8=20=D0=BF=D0=B8=D1=81=D1=8C=D0=BC?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RepeatAccountActivationForm.php | 2 + common/tasks/SendCurrentEmailConfirmation.php | 1 + common/tasks/SendNewEmailConfirmation.php | 1 + common/tasks/SendPasswordRecoveryEmail.php | 1 + common/tasks/SendRegistrationEmail.php | 1 + tests/codeception/api/unit.suite.yml | 1 + .../authentication/ForgotPasswordFormTest.php | 38 ++++++++----- .../authentication/RegistrationFormTest.php | 44 +++++++-------- .../RepeatAccountActivationFormTest.php | 30 ++++++----- .../models/SendEmailVerificationFormTest.php | 15 ++++-- .../SendNewEmailVerificationFormTest.php | 15 ++++-- .../_support/queue/CodeceptionQueueHelper.php | 51 ++++++++++++++++++ .../common/_support/queue/Queue.php | 32 +++++++++++ .../common/unit/emails/EmailHelperTest.php | 7 +-- .../SendCurrentEmailConfirmationTest.php | 47 ++++++++++++++++ .../tasks/SendNewEmailConfirmationTest.php | 47 ++++++++++++++++ .../tasks/SendPasswordRecoveryEmailTest.php | 53 +++++++++++++++++++ .../unit/tasks/SendRegistrationEmailTest.php | 53 +++++++++++++++++++ tests/codeception/config/config.php | 6 +++ 19 files changed, 387 insertions(+), 58 deletions(-) create mode 100644 tests/codeception/common/_support/queue/CodeceptionQueueHelper.php create mode 100644 tests/codeception/common/_support/queue/Queue.php create mode 100644 tests/codeception/common/unit/tasks/SendCurrentEmailConfirmationTest.php create mode 100644 tests/codeception/common/unit/tasks/SendNewEmailConfirmationTest.php create mode 100644 tests/codeception/common/unit/tasks/SendPasswordRecoveryEmailTest.php create mode 100644 tests/codeception/common/unit/tasks/SendRegistrationEmailTest.php diff --git a/api/models/authentication/RepeatAccountActivationForm.php b/api/models/authentication/RepeatAccountActivationForm.php index a56d226..9cbec46 100644 --- a/api/models/authentication/RepeatAccountActivationForm.php +++ b/api/models/authentication/RepeatAccountActivationForm.php @@ -78,6 +78,8 @@ class RepeatAccountActivationForm extends ApiForm { throw new ThisShouldNotHappenException('Unable save email-activation model.'); } + $this->emailActivation = $activation; + Yii::$app->queue->push(SendRegistrationEmail::createFromConfirmation($activation)); $transaction->commit(); diff --git a/common/tasks/SendCurrentEmailConfirmation.php b/common/tasks/SendCurrentEmailConfirmation.php index 3f5362a..55e96ef 100644 --- a/common/tasks/SendCurrentEmailConfirmation.php +++ b/common/tasks/SendCurrentEmailConfirmation.php @@ -1,4 +1,5 @@ $this->tester->grabFixture('accounts', 'admin')['username']]); + /** @var Account $account */ + $account = $this->tester->grabFixture('accounts', 'admin'); + $model = new ForgotPasswordForm(['login' => $account->username]); $this->assertTrue($model->forgotPassword(), 'form should be successfully processed'); $activation = $model->getEmailActivation(); $this->assertInstanceOf(EmailActivation::class, $activation, 'getEmailActivation should return valid object instance'); - $this->tester->canSeeEmailIsSent(1); - /** @var \yii\swiftmailer\Message $email */ - $email = $this->tester->grabSentEmails()[0]; - $body = $email->getSwiftMessage()->getBody(); - $this->assertContains($activation->key, $body); - $this->assertContains('/recover-password/' . $activation->key, $body); + + $this->assertTaskCreated($this->tester->grabLastQueuedJob(), $account, $activation); } public function testForgotPasswordResend() { - $fixture = $this->tester->grabFixture('accounts', 'account-with-expired-forgot-password-message'); - $model = new ForgotPasswordForm([ - 'login' => $fixture['username'], - ]); + /** @var Account $account */ + $account = $this->tester->grabFixture('accounts', 'account-with-expired-forgot-password-message'); + $model = new ForgotPasswordForm(['login' => $account->username]); $callTime = time(); $this->assertTrue($model->forgotPassword(), 'form should be successfully processed'); $emailActivation = $model->getEmailActivation(); $this->assertInstanceOf(EmailActivation::class, $emailActivation); $this->assertGreaterThanOrEqual($callTime, $emailActivation->created_at); - $this->tester->canSeeEmailIsSent(1); + + $this->assertTaskCreated($this->tester->grabLastQueuedJob(), $account, $emailActivation); + } + + /** + * @param SendPasswordRecoveryEmail $job + * @param Account $account + * @param EmailActivation $activation + */ + private function assertTaskCreated($job, Account $account, EmailActivation $activation) { + $this->assertInstanceOf(SendPasswordRecoveryEmail::class, $job); + $this->assertSame($account->username, $job->username); + $this->assertSame($account->email, $job->email); + $this->assertSame($account->lang, $job->locale); + $this->assertSame($activation->key, $job->code); + $this->assertSame('http://localhost/recover-password/' . $activation->key, $job->link); } /** diff --git a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php index e957e74..bd76a40 100644 --- a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php @@ -7,6 +7,7 @@ use Codeception\Specify; use common\models\Account; use common\models\EmailActivation; use common\models\UsernameHistory; +use common\tasks\SendRegistrationEmail; use GuzzleHttp\ClientInterface; use tests\codeception\api\unit\TestCase; use tests\codeception\common\fixtures\AccountFixture; @@ -40,23 +41,19 @@ class RegistrationFormTest extends TestCase { } public function testValidatePasswordAndRePasswordMatch() { - $this->specify('error.rePassword_does_not_match if password and rePassword not match', function() { - $model = new RegistrationForm([ - 'password' => 'enough-length', - 'rePassword' => 'password', - ]); - expect($model->validate(['rePassword']))->false(); - expect($model->getErrors('rePassword'))->equals(['error.rePassword_does_not_match']); - }); + $model = new RegistrationForm([ + 'password' => 'enough-length', + 'rePassword' => 'but-mismatch', + ]); + $this->assertFalse($model->validate(['rePassword'])); + $this->assertSame(['error.rePassword_does_not_match'], $model->getErrors('rePassword')); - $this->specify('no errors if password and rePassword match', function() { - $model = new RegistrationForm([ - 'password' => 'enough-length', - 'rePassword' => 'enough-length', - ]); - expect($model->validate(['rePassword']))->true(); - expect($model->getErrors('rePassword'))->isEmpty(); - }); + $model = new RegistrationForm([ + 'password' => 'enough-length', + 'rePassword' => 'enough-length', + ]); + $this->assertTrue($model->validate(['rePassword'])); + $this->assertEmpty($model->getErrors('rePassword')); } public function testSignup() { @@ -118,12 +115,15 @@ class RegistrationFormTest extends TestCase { 'account_id' => $account->id, 'applied_in' => $account->created_at, ])->exists(), 'username history record exists in database'); - $this->tester->canSeeEmailIsSent(1); - /** @var \yii\swiftmailer\Message $email */ - $email = $this->tester->grabSentEmails()[0]; - $body = $email->getSwiftMessage()->getBody(); - $this->assertContains($activation->key, $body); - $this->assertContains('/activation/' . $activation->key, $body); + + /** @var SendRegistrationEmail $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(SendRegistrationEmail::class, $job); + $this->assertSame($account->username, $job->username); + $this->assertSame($account->email, $job->email); + $this->assertSame($account->lang, $job->locale); + $this->assertSame($activation->key, $job->code); + $this->assertSame('http://localhost/activation/' . $activation->key, $job->link); } private function mockRequest($ip = '88.225.20.236') { diff --git a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php index 23acf22..6dbebb1 100644 --- a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php @@ -5,6 +5,7 @@ use api\components\ReCaptcha\Validator as ReCaptchaValidator; use api\models\authentication\RepeatAccountActivationForm; use Codeception\Specify; use common\models\EmailActivation; +use common\tasks\SendRegistrationEmail; use GuzzleHttp\ClientInterface; use tests\codeception\api\unit\TestCase; use tests\codeception\common\fixtures\AccountFixture; @@ -69,19 +70,24 @@ class RepeatAccountActivationFormTest extends TestCase { } public function testSendRepeatMessage() { - $this->specify('no magic if we don\'t pass validation', function() { - $model = new RepeatAccountActivationForm(); - expect($model->sendRepeatMessage())->false(); - $this->tester->cantSeeEmailIsSent(); - }); + $model = new RepeatAccountActivationForm(); + $this->assertFalse($model->sendRepeatMessage(), 'no magic if we don\'t pass validation'); + $this->assertEmpty($this->tester->grabQueueJobs()); - $this->specify('successfully send new message if previous message has expired', function() { - $email = $this->tester->grabFixture('accounts', 'not-activated-account-with-expired-message')['email']; - $model = new RepeatAccountActivationForm(['email' => $email]); - expect($model->sendRepeatMessage())->true(); - expect($model->getActivation())->notNull(); - $this->tester->canSeeEmailIsSent(1); - }); + /** @var \common\models\Account $account */ + $account = $this->tester->grabFixture('accounts', 'not-activated-account-with-expired-message'); + $model = new RepeatAccountActivationForm(['email' => $account->email]); + $this->assertTrue($model->sendRepeatMessage()); + $activation = $model->getActivation(); + $this->assertNotNull($activation); + /** @var SendRegistrationEmail $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(SendRegistrationEmail::class, $job); + $this->assertSame($account->username, $job->username); + $this->assertSame($account->email, $job->email); + $this->assertSame($account->lang, $job->locale); + $this->assertSame($activation->key, $job->code); + $this->assertSame('http://localhost/activation/' . $activation->key, $job->link); } /** diff --git a/tests/codeception/api/unit/modules/accounts/models/SendEmailVerificationFormTest.php b/tests/codeception/api/unit/modules/accounts/models/SendEmailVerificationFormTest.php index 4a34e36..dbee640 100644 --- a/tests/codeception/api/unit/modules/accounts/models/SendEmailVerificationFormTest.php +++ b/tests/codeception/api/unit/modules/accounts/models/SendEmailVerificationFormTest.php @@ -5,6 +5,7 @@ use api\modules\accounts\models\SendEmailVerificationForm; use common\models\Account; use common\models\confirmations\CurrentEmailConfirmation; use common\models\EmailActivation; +use common\tasks\SendCurrentEmailConfirmation; use tests\codeception\api\unit\TestCase; use tests\codeception\common\fixtures\AccountFixture; use tests\codeception\common\fixtures\EmailActivationFixture; @@ -35,11 +36,19 @@ class SendEmailVerificationFormTest extends TestCase { 'password' => 'password_0', ]); $this->assertTrue($model->performAction()); - $this->assertTrue(EmailActivation::find()->andWhere([ + /** @var EmailActivation $activation */ + $activation = EmailActivation::findOne([ 'account_id' => $account->id, 'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION, - ])->exists()); - $this->tester->canSeeEmailIsSent(); + ]); + $this->assertInstanceOf(EmailActivation::class, $activation); + + /** @var SendCurrentEmailConfirmation $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(SendCurrentEmailConfirmation::class, $job); + $this->assertSame($account->username, $job->username); + $this->assertSame($account->email, $job->email); + $this->assertSame($activation->key, $job->code); } } diff --git a/tests/codeception/api/unit/modules/accounts/models/SendNewEmailVerificationFormTest.php b/tests/codeception/api/unit/modules/accounts/models/SendNewEmailVerificationFormTest.php index 0802019..addee2c 100644 --- a/tests/codeception/api/unit/modules/accounts/models/SendNewEmailVerificationFormTest.php +++ b/tests/codeception/api/unit/modules/accounts/models/SendNewEmailVerificationFormTest.php @@ -5,6 +5,7 @@ use api\modules\accounts\models\SendNewEmailVerificationForm; use common\models\Account; use common\models\confirmations\NewEmailConfirmation; use common\models\EmailActivation; +use common\tasks\SendNewEmailConfirmation; use tests\codeception\api\unit\TestCase; use tests\codeception\common\fixtures\AccountFixture; use tests\codeception\common\fixtures\EmailActivationFixture; @@ -44,11 +45,19 @@ class SendNewEmailVerificationFormTest extends TestCase { Mock::func(EmailValidator::class, 'checkdnsrr')->andReturn(true); $this->assertTrue($model->performAction()); $this->assertNull(EmailActivation::findOne($key)); - $this->assertNotNull(EmailActivation::findOne([ + /** @var EmailActivation $activation */ + $activation = EmailActivation::findOne([ 'account_id' => $account->id, 'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION, - ])); - $this->tester->canSeeEmailIsSent(); + ]); + $this->assertNotNull(EmailActivation::class, $activation); + + /** @var SendNewEmailConfirmation $job */ + $job = $this->tester->grabLastQueuedJob(); + $this->assertInstanceOf(SendNewEmailConfirmation::class, $job); + $this->assertSame($account->username, $job->username); + $this->assertSame('my-new-email@ely.by', $job->email); + $this->assertSame($activation->key, $job->code); } } diff --git a/tests/codeception/common/_support/queue/CodeceptionQueueHelper.php b/tests/codeception/common/_support/queue/CodeceptionQueueHelper.php new file mode 100644 index 0000000..8878ea3 --- /dev/null +++ b/tests/codeception/common/_support/queue/CodeceptionQueueHelper.php @@ -0,0 +1,51 @@ +grabQueueJobs(); + 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 \yii\queue\JobInterface[] + * @throws ModuleException + */ + public function grabQueueJobs() { + $amqp = $this->grabComponent('queue'); + if (!$amqp instanceof Queue) { + throw new ModuleException($this, 'AMQP module is not mocked, can\'t test messages'); + } + + return $amqp->getMessages(); + } + + 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/queue/Queue.php b/tests/codeception/common/_support/queue/Queue.php new file mode 100644 index 0000000..169da26 --- /dev/null +++ b/tests/codeception/common/_support/queue/Queue.php @@ -0,0 +1,32 @@ +messages[] = $job; + } + + public function status($id) { + throw new NotSupportedException('Status is not supported in the driver.'); + } + + public function getMessages() { + return $this->messages; + } + + protected function pushMessage($message, $ttr, $delay, $priority) { + // This function is abstract, but will be not called + } + + public function __set($name, $value) { + // Yii2 components may contains some configuration + // But we just ignore it for this mock component + } + +} diff --git a/tests/codeception/common/unit/emails/EmailHelperTest.php b/tests/codeception/common/unit/emails/EmailHelperTest.php index 9b9d011..0f1f62d 100644 --- a/tests/codeception/common/unit/emails/EmailHelperTest.php +++ b/tests/codeception/common/unit/emails/EmailHelperTest.php @@ -2,17 +2,12 @@ namespace tests\codeception\common\unit\emails; use common\emails\EmailHelper; -use common\models\Account; use tests\codeception\common\unit\TestCase; class EmailHelperTest extends TestCase { public function testBuildTo() { - /** @var Account|\Mockery\MockInterface $account */ - $account = mock(Account::class)->makePartial(); - $account->username = 'mock-username'; - $account->email = 'mock@ely.by'; - $this->assertEquals(['mock@ely.by' => 'mock-username'], EmailHelper::buildTo($account)); + $this->assertSame(['mock@ely.by' => 'username'], EmailHelper::buildTo('username', 'mock@ely.by')); } } diff --git a/tests/codeception/common/unit/tasks/SendCurrentEmailConfirmationTest.php b/tests/codeception/common/unit/tasks/SendCurrentEmailConfirmationTest.php new file mode 100644 index 0000000..eb81b0a --- /dev/null +++ b/tests/codeception/common/unit/tasks/SendCurrentEmailConfirmationTest.php @@ -0,0 +1,47 @@ +username = 'mock-username'; + $account->email = 'mock@ely.by'; + $account->lang = 'id'; + + /** @var \Mockery\Mock|CurrentEmailConfirmation $confirmation */ + $confirmation = mock(CurrentEmailConfirmation::class)->makePartial(); + $confirmation->key = 'ABCDEFG'; + $confirmation->shouldReceive('getAccount')->andReturn($account); + + $result = SendCurrentEmailConfirmation::createFromConfirmation($confirmation); + $this->assertInstanceOf(SendCurrentEmailConfirmation::class, $result); + $this->assertSame('mock-username', $result->username); + $this->assertSame('mock@ely.by', $result->email); + $this->assertSame('ABCDEFG', $result->code); + } + + public function testExecute() { + $task = new SendCurrentEmailConfirmation(); + $task->username = 'mock-username'; + $task->email = 'mock@ely.by'; + $task->code = 'GFEDCBA'; + + $task->execute(mock(Queue::class)); + + $this->tester->canSeeEmailIsSent(1); + /** @var \yii\swiftmailer\Message $email */ + $email = $this->tester->grabSentEmails()[0]; + $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); + $this->assertSame('Ely.by Account change E-mail confirmation', $email->getSubject()); + $children = $email->getSwiftMessage()->getChildren()[0]; + $this->assertContains('GFEDCBA', $children->getBody()); + } + +} diff --git a/tests/codeception/common/unit/tasks/SendNewEmailConfirmationTest.php b/tests/codeception/common/unit/tasks/SendNewEmailConfirmationTest.php new file mode 100644 index 0000000..a0fcb12 --- /dev/null +++ b/tests/codeception/common/unit/tasks/SendNewEmailConfirmationTest.php @@ -0,0 +1,47 @@ +username = 'mock-username'; + $account->lang = 'id'; + + /** @var \Mockery\Mock|NewEmailConfirmation $confirmation */ + $confirmation = mock(NewEmailConfirmation::class)->makePartial(); + $confirmation->key = 'ABCDEFG'; + $confirmation->shouldReceive('getAccount')->andReturn($account); + $confirmation->shouldReceive('getNewEmail')->andReturn('new-email@ely.by'); + + $result = SendNewEmailConfirmation::createFromConfirmation($confirmation); + $this->assertInstanceOf(SendNewEmailConfirmation::class, $result); + $this->assertSame('mock-username', $result->username); + $this->assertSame('new-email@ely.by', $result->email); + $this->assertSame('ABCDEFG', $result->code); + } + + public function testExecute() { + $task = new SendNewEmailConfirmation(); + $task->username = 'mock-username'; + $task->email = 'mock@ely.by'; + $task->code = 'GFEDCBA'; + + $task->execute(mock(Queue::class)); + + $this->tester->canSeeEmailIsSent(1); + /** @var \yii\swiftmailer\Message $email */ + $email = $this->tester->grabSentEmails()[0]; + $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); + $this->assertSame('Ely.by Account new E-mail confirmation', $email->getSubject()); + $children = $email->getSwiftMessage()->getChildren()[0]; + $this->assertContains('GFEDCBA', $children->getBody()); + } + +} diff --git a/tests/codeception/common/unit/tasks/SendPasswordRecoveryEmailTest.php b/tests/codeception/common/unit/tasks/SendPasswordRecoveryEmailTest.php new file mode 100644 index 0000000..b7c887b --- /dev/null +++ b/tests/codeception/common/unit/tasks/SendPasswordRecoveryEmailTest.php @@ -0,0 +1,53 @@ +username = 'mock-username'; + $account->email = 'mock@ely.by'; + $account->lang = 'id'; + + /** @var \Mockery\Mock|ForgotPassword $confirmation */ + $confirmation = mock(ForgotPassword::class)->makePartial(); + $confirmation->key = 'ABCDEFG'; + $confirmation->shouldReceive('getAccount')->andReturn($account); + + $result = SendPasswordRecoveryEmail::createFromConfirmation($confirmation); + $this->assertInstanceOf(SendPasswordRecoveryEmail::class, $result); + $this->assertSame('mock-username', $result->username); + $this->assertSame('mock@ely.by', $result->email); + $this->assertSame('ABCDEFG', $result->code); + $this->assertSame('http://localhost/recover-password/ABCDEFG', $result->link); + $this->assertSame('id', $result->locale); + } + + public function testExecute() { + $task = new SendPasswordRecoveryEmail(); + $task->username = 'mock-username'; + $task->email = 'mock@ely.by'; + $task->code = 'GFEDCBA'; + $task->link = 'https://account.ely.by/recover-password/ABCDEFG'; + $task->locale = 'ru'; + + $task->execute(mock(Queue::class)); + + $this->tester->canSeeEmailIsSent(1); + /** @var \yii\swiftmailer\Message $email */ + $email = $this->tester->grabSentEmails()[0]; + $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); + $this->assertSame('Ely.by Account forgot password', $email->getSubject()); + $body = $email->getSwiftMessage()->getBody(); + $this->assertContains('Привет, mock-username', $body); + $this->assertContains('GFEDCBA', $body); + $this->assertContains('https://account.ely.by/recover-password/ABCDEFG', $body); + } + +} diff --git a/tests/codeception/common/unit/tasks/SendRegistrationEmailTest.php b/tests/codeception/common/unit/tasks/SendRegistrationEmailTest.php new file mode 100644 index 0000000..41ace9b --- /dev/null +++ b/tests/codeception/common/unit/tasks/SendRegistrationEmailTest.php @@ -0,0 +1,53 @@ +username = 'mock-username'; + $account->email = 'mock@ely.by'; + $account->lang = 'ru'; + + /** @var \Mockery\Mock|RegistrationConfirmation $confirmation */ + $confirmation = mock(RegistrationConfirmation::class)->makePartial(); + $confirmation->key = 'ABCDEFG'; + $confirmation->shouldReceive('getAccount')->andReturn($account); + + $result = SendRegistrationEmail::createFromConfirmation($confirmation); + $this->assertInstanceOf(SendRegistrationEmail::class, $result); + $this->assertSame('mock-username', $result->username); + $this->assertSame('mock@ely.by', $result->email); + $this->assertSame('ABCDEFG', $result->code); + $this->assertSame('http://localhost/activation/ABCDEFG', $result->link); + $this->assertSame('ru', $result->locale); + } + + public function testExecute() { + $task = new SendRegistrationEmail(); + $task->username = 'mock-username'; + $task->email = 'mock@ely.by'; + $task->code = 'GFEDCBA'; + $task->link = 'https://account.ely.by/activation/ABCDEFG'; + $task->locale = 'ru'; + + $task->execute(mock(Queue::class)); + + $this->tester->canSeeEmailIsSent(1); + /** @var \yii\swiftmailer\Message $email */ + $email = $this->tester->grabSentEmails()[0]; + $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); + $this->assertSame('Ely.by Account registration', $email->getSubject()); + $body = $email->getSwiftMessage()->getBody(); + $this->assertContains('Привет, mock-username', $body); + $this->assertContains('GFEDCBA', $body); + $this->assertContains('https://account.ely.by/activation/ABCDEFG', $body); + } + +} diff --git a/tests/codeception/config/config.php b/tests/codeception/config/config.php index edd34d4..78b097c 100644 --- a/tests/codeception/config/config.php +++ b/tests/codeception/config/config.php @@ -9,6 +9,9 @@ return [ 'namespace' => 'tests\codeception\common\fixtures', ], ], + 'params' => [ + 'fromEmail' => 'ely@ely.by', + ], 'components' => [ 'urlManager' => [ 'showScriptName' => true, @@ -20,6 +23,9 @@ return [ 'amqp' => [ 'class' => tests\codeception\common\_support\amqp\TestComponent::class, ], + 'queue' => [ + 'class' => tests\codeception\common\_support\queue\Queue::class, + ], 'sentry' => [ 'enabled' => false, ], From 58e09e0964f94f952615175f6c51f5b29330e74d Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 28 Nov 2017 15:33:33 +0300 Subject: [PATCH 22/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=20id=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B5=D1=80=D0=B0=20supervisor,=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=20=D0=B2=D0=BE=D1=80=D0=BA=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20verbose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/supervisor/worker-queue.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/supervisor/worker-queue.conf b/docker/supervisor/worker-queue.conf index b7d715f..397c104 100644 --- a/docker/supervisor/worker-queue.conf +++ b/docker/supervisor/worker-queue.conf @@ -1,6 +1,6 @@ -[program:account-queue-worker] +[program:queue-worker] directory=/var/www/html -command=wait-for-it rabbitmq:5672 -- php yii queue/listen +command=wait-for-it rabbitmq:5672 -- php yii queue/listen -v autostart=true autorestart=true priority=10 From 5269f9b25aee498717077c4b7bc144fd0afbec3f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 28 Nov 2017 19:12:50 +0300 Subject: [PATCH 23/41] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20runtime=20=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D0=BE=20=D0=B2=20boo?= =?UTF-8?q?tstrap.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 ++---- Dockerfile-dev | 6 ++---- docker/php/bootstrap.sh | 3 +++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 17c3985..3b5968d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,10 +43,8 @@ RUN rm -rf /root/.ssh # Наконец переносим все сорцы внутрь контейнера COPY . /var/www/html -RUN mkdir -p api/runtime api/web/assets console/runtime \ - && chown www-data:www-data api/runtime api/web/assets console/runtime \ - # Билдим фронт - && cd frontend \ +# Билдим фронт +RUN cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ && npm run build:quiet \ && rm node_modules \ diff --git a/Dockerfile-dev b/Dockerfile-dev index b2ea2a5..f32b2e7 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -40,10 +40,8 @@ RUN cd ../frontend \ # Наконец переносим все сорцы внутрь контейнера COPY . /var/www/html -RUN mkdir -p api/runtime api/web/assets console/runtime \ - && chown www-data:www-data api/runtime api/web/assets console/runtime \ - # Билдим фронт - && cd frontend \ +# Билдим фронт +RUN cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ && npm run build:quiet \ && rm node_modules \ diff --git a/docker/php/bootstrap.sh b/docker/php/bootstrap.sh index 81be543..65617dd 100755 --- a/docker/php/bootstrap.sh +++ b/docker/php/bootstrap.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +mkdir -p api/runtime api/web/assets console/runtime +chown www-data:www-data api/runtime api/web/assets console/runtime + if [ "$YII_ENV" = "test" ] then YII_EXEC="/var/www/html/tests/codeception/bin/yii" From ea033f70bcdbf7a4d3099cc4969f714153543111 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 29 Nov 2017 02:04:13 +0300 Subject: [PATCH 24/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=D1=84=D0=BE=D1=80=D0=BA=20yii2-queu?= =?UTF-8?q?e=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=B2=D0=BE?= =?UTF-8?q?=D0=B7=D0=BD=D0=B8=D0=BA=D0=BD=D0=BE=D0=B2=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B8?= 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 f37adb5..7ec9c12 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "webmozart/assert": "^1.2.0", "goaop/framework": "~2.1.2", "domnikl/statsd": "^2.6", - "yiisoft/yii2-queue": "~2.0.1" + "yiisoft/yii2-queue": "dev-fix_amqp_error_behavior#385e1cc0afcce0d6712080beb11922735e72f59d" }, "require-dev": { "yiisoft/yii2-debug": "*", @@ -55,6 +55,10 @@ { "type": "git", "url": "git@github.com:erickskrauch/Codeception.git" + }, + { + "type": "git", + "url": "git@github.com:erickskrauch/yii2-queue.git" } ], "autoload": { From 868ee1c61cd70ab123b974bba70c2cc8614c96f3 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 29 Nov 2017 02:38:11 +0300 Subject: [PATCH 25/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=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BE=D0=BA=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- console/components/ErrorHandler.php | 20 ++++++++++++++++++++ console/config/config.php | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 console/components/ErrorHandler.php diff --git a/console/components/ErrorHandler.php b/console/components/ErrorHandler.php new file mode 100644 index 0000000..042dc2f --- /dev/null +++ b/console/components/ErrorHandler.php @@ -0,0 +1,20 @@ +error; + if ($exception instanceof Swift_TransportException) { + Yii::warning($exception); + return; + } + + Yii::error($exception); + } + +} diff --git a/console/config/config.php b/console/config/config.php index 7b5d94d..be4d94e 100644 --- a/console/config/config.php +++ b/console/config/config.php @@ -26,6 +26,9 @@ return [ 'urlManager' => [ 'hostInfo' => getenv('DOMAIN') ?: 'https://account.ely.by', ], + 'queue' => [ + 'on afterError' => [new console\components\ErrorHandler(), 'handleQueueError'], + ], ], 'controllerMap' => [ 'migrate' => [ From 9356ad24b3542eec6d64b9a804f8453768b0d8ae Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 2 Dec 2017 21:04:48 +0300 Subject: [PATCH 26/41] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B8=D0=B3=D0=BD=D0=BE=D1=80=D0=B8=D1=80=D1=83?= =?UTF-8?q?=D0=B5=D0=BC=20JWT=20InvalidSubjectException=20UnauthorizedHttp?= =?UTF-8?q?Exception=20=D0=B2=20User/Component=20=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B5=20=D0=BD=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B8?= =?UTF-8?q?=D1=80=D1=83=D0=B5=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/User/Component.php | 18 +++++++++++------- api/components/User/IdentityInterface.php | 7 +++++++ api/components/User/JwtIdentity.php | 4 +--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/api/components/User/Component.php b/api/components/User/Component.php index 7374842..a75c7e8 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -17,6 +17,7 @@ use Emarref\Jwt\Verification\Context as VerificationContext; use Exception; use Yii; use yii\base\InvalidConfigException; +use yii\web\UnauthorizedHttpException; use yii\web\User as YiiUserComponent; /** @@ -28,11 +29,11 @@ use yii\web\User as YiiUserComponent; */ class Component extends YiiUserComponent { - const KEEP_MINECRAFT_SESSIONS = 1; - const KEEP_SITE_SESSIONS = 2; - const KEEP_CURRENT_SESSION = 4; + public const KEEP_MINECRAFT_SESSIONS = 1; + public const KEEP_SITE_SESSIONS = 2; + public const KEEP_CURRENT_SESSION = 4; - const JWT_SUBJECT_PREFIX = 'ely|'; + public const JWT_SUBJECT_PREFIX = 'ely|'; public $enableSession = false; @@ -59,7 +60,7 @@ class Component extends YiiUserComponent { } public function findIdentityByAccessToken($accessToken): ?IdentityInterface { - if ($accessToken === null) { + if (empty($accessToken)) { return null; } @@ -67,10 +68,13 @@ class Component extends YiiUserComponent { $identityClass = $this->identityClass; try { return $identityClass::findIdentityByAccessToken($accessToken); + } catch (UnauthorizedHttpException $e) { + // Do nothing. It's okay to catch this. } catch (Exception $e) { Yii::error($e); - return null; } + + return null; } public function createJwtAuthenticationToken(Account $account, bool $rememberMe): AuthenticationResult { @@ -223,7 +227,7 @@ class Component extends YiiUserComponent { */ protected function getClaims(Account $account): array { $currentTime = new DateTime(); - $hostInfo = Yii::$app->request->hostInfo; + $hostInfo = Yii::$app->request->hostIHttpExceptionnfo; return [ new ScopesClaim([R::ACCOUNTS_WEB_USER]), diff --git a/api/components/User/IdentityInterface.php b/api/components/User/IdentityInterface.php index 723dde1..f9f589f 100644 --- a/api/components/User/IdentityInterface.php +++ b/api/components/User/IdentityInterface.php @@ -5,6 +5,13 @@ use common\models\Account; interface IdentityInterface extends \yii\web\IdentityInterface { + /** + * @param string $token + * @param string $type + * + * @throws \yii\web\UnauthorizedHttpException + * @return IdentityInterface + */ public static function findIdentityByAccessToken($token, $type = null): IdentityInterface; /** diff --git a/api/components/User/JwtIdentity.php b/api/components/User/JwtIdentity.php index a168013..78a8491 100644 --- a/api/components/User/JwtIdentity.php +++ b/api/components/User/JwtIdentity.php @@ -4,7 +4,6 @@ namespace api\components\User; use common\models\Account; use Emarref\Jwt\Claim\Subject; use Emarref\Jwt\Exception\ExpiredException; -use Emarref\Jwt\Exception\InvalidSubjectException; use Emarref\Jwt\Token; use Exception; use Yii; @@ -29,8 +28,7 @@ class JwtIdentity implements IdentityInterface { $component = Yii::$app->user; try { $token = $component->parseToken($rawToken); - } catch (ExpiredException | InvalidSubjectException $e) { - // InvalidSubjectException is temporary solution and should be removed in the next release + } catch (ExpiredException $e) { throw new UnauthorizedHttpException('Token expired'); } catch (Exception $e) { Yii::error($e); From 40625dbef9ab8677d13310e941bc2932b7485514 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 2 Dec 2017 22:07:38 +0300 Subject: [PATCH 27/41] =?UTF-8?q?=D0=A3=D0=BF=D1=80=D0=B0=D0=B7=D0=B4?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=BE=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20aud=20=D0=B8=20iss?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=B4=D0=BB=D1=8F=20JWT=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/User/Component.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/api/components/User/Component.php b/api/components/User/Component.php index a75c7e8..4049771 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -131,14 +131,10 @@ class Component extends YiiUserComponent { public function parseToken(string $jwtString): Token { $token = &self::$parsedTokensCache[$jwtString]; if ($token === null) { - $hostInfo = Yii::$app->request->hostInfo; - $jwt = new Jwt(); $notVerifiedToken = $jwt->deserialize($jwtString); $context = new VerificationContext(EncryptionFactory::create($this->getAlgorithm())); - $context->setAudience($hostInfo); - $context->setIssuer($hostInfo); $context->setSubject(self::JWT_SUBJECT_PREFIX); $jwt->verify($notVerifiedToken, $context); @@ -227,12 +223,9 @@ class Component extends YiiUserComponent { */ protected function getClaims(Account $account): array { $currentTime = new DateTime(); - $hostInfo = Yii::$app->request->hostIHttpExceptionnfo; return [ new ScopesClaim([R::ACCOUNTS_WEB_USER]), - new Claim\Audience($hostInfo), - new Claim\Issuer($hostInfo), new Claim\IssuedAt($currentTime), new Claim\Expiration($currentTime->add(new DateInterval($this->expirationTimeout))), new Claim\Subject(self::JWT_SUBJECT_PREFIX . $account->id), From ee68e373f4659eab04e7454fb8f583f82a76c4b1 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 2 Dec 2017 22:15:00 +0300 Subject: [PATCH 28/41] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=81=D0=BC?= =?UTF-8?q?=D0=BE=D1=82=D1=80=D0=B5=D0=BD=D1=8B=20=D0=B2=D0=B8=D1=81=D1=8F?= =?UTF-8?q?=D1=89=D0=B8=D0=B5=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/config/ConfigLoader.php | 5 +---- tests/codeception/config/functional.php | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/common/config/ConfigLoader.php b/common/config/ConfigLoader.php index d4683ec..eb88158 100644 --- a/common/config/ConfigLoader.php +++ b/common/config/ConfigLoader.php @@ -5,10 +5,7 @@ use yii\helpers\ArrayHelper; class ConfigLoader { - /* - * TODO: В PHP 7.1 следует сделать её protected - */ - const ROOT_PATH = __DIR__ . '/../..'; + private const ROOT_PATH = __DIR__ . '/../..'; private $application; diff --git a/tests/codeception/config/functional.php b/tests/codeception/config/functional.php index a53b592..6583b9c 100644 --- a/tests/codeception/config/functional.php +++ b/tests/codeception/config/functional.php @@ -3,7 +3,6 @@ return [ 'components' => [ 'request' => [ // it's not recommended to run functional tests with CSRF validation enabled - // TODO: у нас вроде и без того нет проверки csrf 'enableCsrfValidation' => false, 'enableCookieValidation' => false, // but if you absolutely need it set cookie domain to localhost From 6c52899583cbee43fa6e1090f0f526f64ffc131e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 4 Dec 2017 15:02:58 +0300 Subject: [PATCH 29/41] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D1=91=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D1=81=D1=82=D1=91?= =?UTF-8?q?=D0=BA=D1=88=D0=B5=D0=B3=D0=BE=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=20=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 --- tests/codeception/api/functional/accounts/GetCest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/codeception/api/functional/accounts/GetCest.php b/tests/codeception/api/functional/accounts/GetCest.php index b7e3f8f..4dfaaeb 100644 --- a/tests/codeception/api/functional/accounts/GetCest.php +++ b/tests/codeception/api/functional/accounts/GetCest.php @@ -60,9 +60,8 @@ class GetCest { public function testGetInfoWithExpiredToken(FunctionalTester $I) { // Устанавливаем заведомо истёкший токен $I->amBearerAuthenticated( - // TODO: обновить токен - 'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJpYXQiO' . - 'jE0NjQ2Mjc1NDUsImV4cCI6MTQ2NDYzMTE0NSwianRpIjoxfQ.9c1mm0BK-cuW1qh15F12s2Fh37IN43YeeZeU4DFtlrE' + 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0NjQ2Mjc1NDUsImV4cCI6MTQ2NDYzMTE0NSwic3ViIjoiZWx5fDEiLCJlbHktc' . + '2NvcGVzIjoiYWNjb3VudHNfd2ViX3VzZXIifQ.v1u8V5wk2RkWmnZtH3jZvM3zO1Gpgbp2DQFfLfy8jHY' ); $this->route->get(1); From 62782d3ced6035252646bde8961ed7a7177f6f5b Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 4 Dec 2017 15:56:26 +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=20JwtIdentityTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/codeception/api/unit/models/JwtIdentityTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/codeception/api/unit/models/JwtIdentityTest.php b/tests/codeception/api/unit/models/JwtIdentityTest.php index 6d6ba72..bd45721 100644 --- a/tests/codeception/api/unit/models/JwtIdentityTest.php +++ b/tests/codeception/api/unit/models/JwtIdentityTest.php @@ -37,8 +37,6 @@ class JwtIdentityTest extends TestCase { */ public function testFindIdentityByAccessTokenWithExpiredToken() { $token = new Token(); - $token->addClaim(new Claim\Audience('http://localhost')); - $token->addClaim(new Claim\Issuer('http://localhost')); $token->addClaim(new Claim\IssuedAt(1464593193)); $token->addClaim(new Claim\Expiration(1464596793)); $token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id'])); From c7b6d1961d8fefbc5aa5bd2cb77a6fefb8345133 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Wed, 13 Dec 2017 00:13:36 +0200 Subject: [PATCH 31/41] Try to begin with migration to yarn --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ce9d083..567a928 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,17 +39,17 @@ test:backend: php vendor/bin/codecept run -c tests test:frontend: - image: node:8.2.1 + image: node:8.9.3-alpine stage: test cache: paths: - frontend/node_modules script: - cd frontend - - npm run build:install --silent - - npm run lint --silent - # - npm run flow --silent # disabled due to missing libelf.so.1 in docker container - - npm run test --silent + - yarn run build:install + - yarn run lint + # - yarn flow # disabled due to missing libelf.so.1 in docker container + - yarn test build:production: image: docker:latest From 58780f3ee101df43fbd390a4b6b4980da85911c3 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Wed, 13 Dec 2017 16:05:30 +0300 Subject: [PATCH 32/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=20PHP?= =?UTF-8?q?,=20Node.js=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=88=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B0=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20yarn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 11 +++++++++-- Dockerfile | 9 +++++---- Dockerfile-dev | 9 +++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 567a928..ccc8488 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,16 +39,23 @@ test:backend: php vendor/bin/codecept run -c tests test:frontend: - image: node:8.9.3-alpine + image: node:9.2.1-alpine stage: test cache: paths: - frontend/node_modules + before_script: + # Enable SSL support for wget + - apk add --update openssl + # https://github.com/facebook/flow/issues/3649#issuecomment-308070179 + - wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub + - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk + - apk add glibc-2.25-r0.apk script: - cd frontend - yarn run build:install - yarn run lint - # - yarn flow # disabled due to missing libelf.so.1 in docker container + - yarn flow - yarn test build:production: diff --git a/Dockerfile b/Dockerfile index 3b5968d..69ea7c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.5.1 +FROM registry.ely.by/elyby/accounts-php:1.6.0 # bootstrap скрипт для проекта COPY docker/php/bootstrap.sh /bootstrap.sh @@ -30,11 +30,12 @@ RUN cd .. \ RUN mkdir -p /var/www/frontend COPY ./frontend/package.json /var/www/frontend/ +COPY ./frontend/yarn.lock /var/www/frontend/ COPY ./frontend/scripts /var/www/frontend/scripts COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils -RUN cd ../frontend \ - && npm run build:install \ +RUN cd /var/www/frontend \ + && yarn run build:install \ && cd - # Удаляем ключи из production контейнера на всякий случай @@ -46,7 +47,7 @@ COPY . /var/www/html # Билдим фронт RUN cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ - && npm run build:quiet \ + && yarn run build:quiet \ && rm node_modules \ # Копируем билд наружу, чтобы его не затёрло volume в dev режиме && cp -r ./dist /var/www/dist \ diff --git a/Dockerfile-dev b/Dockerfile-dev index f32b2e7..c53dab6 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,4 +1,4 @@ -FROM registry.ely.by/elyby/accounts-php:1.5.1-dev +FROM registry.ely.by/elyby/accounts-php:1.6.0-dev # bootstrap скрипт для проекта COPY docker/php/bootstrap.sh /bootstrap.sh @@ -30,11 +30,12 @@ RUN cd .. \ RUN mkdir -p /var/www/frontend COPY ./frontend/package.json /var/www/frontend/ +COPY ./frontend/yarn.lock /var/www/frontend/ COPY ./frontend/scripts /var/www/frontend/scripts COPY ./frontend/webpack-utils /var/www/frontend/webpack-utils -RUN cd ../frontend \ - && npm run build:install \ +RUN cd /var/www/frontend \ + && yarn run build:install \ && cd - # Наконец переносим все сорцы внутрь контейнера @@ -43,7 +44,7 @@ COPY . /var/www/html # Билдим фронт RUN cd frontend \ && ln -s /var/www/frontend/node_modules $PWD/node_modules \ - && npm run build:quiet \ + && yarn run build:quiet \ && rm node_modules \ # Копируем билд наружу, чтобы его не затёрло volume в dev режиме && cp -r ./dist /var/www/dist \ From 43b69aed0ba9fece2501a19523f2bc0f7faed9dd Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 17 Dec 2017 17:18:25 +0300 Subject: [PATCH 33/41] =?UTF-8?q?=D0=A1=D0=BD=D0=BE=D0=B2=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=B5=D0=BC=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ccc8488..aa07dcc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,17 +45,19 @@ test:frontend: paths: - frontend/node_modules before_script: + # Uncomment this steps to enable install required for flow libraries + # Enable SSL support for wget - - apk add --update openssl + # - apk add --update openssl # https://github.com/facebook/flow/issues/3649#issuecomment-308070179 - - wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub - - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk - - apk add glibc-2.25-r0.apk + # - wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub + # - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk + # - apk add glibc-2.25-r0.apk script: - cd frontend - yarn run build:install - yarn run lint - - yarn flow + # - yarn flow - yarn test build:production: From d2f3a05b315bf8318d5584dd3415f781c432cd82 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 23 Dec 2017 00:32:36 +0300 Subject: [PATCH 34/41] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D1=83=20utf8md4=5Funicode=5Fci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- console/db/Migration.php | 2 +- ...2_200114_migrate_to_utf8md4_unicode_ci.php | 37 +++++++++++++++++++ docker/mariadb/Dockerfile | 2 +- docker/mariadb/custom.cnf | 8 ++-- 5 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 console/migrations/m171222_200114_migrate_to_utf8md4_unicode_ci.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa07dcc..6b6b653 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ variables: test:backend: image: docker:latest services: - - mariadb:10.0 + - mariadb:10.2.11 - redis:3.0-alpine variables: # mariadb config diff --git a/console/db/Migration.php b/console/db/Migration.php index dd1cc3c..9633643 100644 --- a/console/db/Migration.php +++ b/console/db/Migration.php @@ -11,7 +11,7 @@ class Migration extends YiiMigration { public function getTableOptions($engine = 'InnoDB') { $tableOptions = null; if ($this->db->driverName === 'mysql') { - $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=' . $engine; + $tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=' . $engine; } return $tableOptions; diff --git a/console/migrations/m171222_200114_migrate_to_utf8md4_unicode_ci.php b/console/migrations/m171222_200114_migrate_to_utf8md4_unicode_ci.php new file mode 100644 index 0000000..0fef2b7 --- /dev/null +++ b/console/migrations/m171222_200114_migrate_to_utf8md4_unicode_ci.php @@ -0,0 +1,37 @@ +execute('SET FOREIGN_KEY_CHECKS=0'); + + $dbName = $this->db->createCommand('SELECT DATABASE()')->queryScalar(); + $this->execute("ALTER DATABASE {{%$dbName}} CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"); + $tables = $this->db->createCommand('SHOW TABLES')->queryColumn(); + foreach ($tables as $table) { + $this->execute("ALTER TABLE {{%$table}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + } + + $this->execute('ALTER TABLE {{%usernames_history}} MODIFY username VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL'); + + $this->execute('SET FOREIGN_KEY_CHECKS=1'); + } + + public function safeDown() { + $this->execute('SET FOREIGN_KEY_CHECKS=0'); + + $dbName = $this->db->createCommand('SELECT DATABASE()')->queryScalar(); + $this->execute("ALTER DATABASE {{%$dbName}} CHARACTER SET = utf8 COLLATE = utf8_general_ci"); + $tables = $this->db->createCommand('SHOW TABLES')->queryColumn(); + foreach ($tables as $table) { + $this->execute("ALTER TABLE {{%$table}} CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci"); + } + + $this->execute('ALTER TABLE {{%usernames_history}} MODIFY username VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL'); + + $this->execute('SET FOREIGN_KEY_CHECKS=1'); + } + +} diff --git a/docker/mariadb/Dockerfile b/docker/mariadb/Dockerfile index 68358b6..2dabddb 100644 --- a/docker/mariadb/Dockerfile +++ b/docker/mariadb/Dockerfile @@ -1,4 +1,4 @@ -FROM mariadb:10.0 +FROM mariadb:10.2.11 COPY custom.cnf /etc/mysql/conf.d/ diff --git a/docker/mariadb/custom.cnf b/docker/mariadb/custom.cnf index 0cf5418..66a1832 100644 --- a/docker/mariadb/custom.cnf +++ b/docker/mariadb/custom.cnf @@ -1,9 +1,9 @@ [mysql] -default-character-set = utf8 +default-character-set = utf8mb4 [mysqld] -character-set-server = utf8 -collation-server = utf8_general_ci +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci [client] -default-character-set = utf8 +default-character-set = utf8mb4 From bd3e8cf2bdc75943b481a3f8784f6de910bd8029 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 23 Dec 2017 00:57:43 +0300 Subject: [PATCH 35/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=D1=8B=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=84=D0=B8=D0=BA=D1=81=D1=82=D1=83=D1=80=20(=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=87=D0=B5=D0=BC=D1=83=20=D0=BE=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D1=8C=D1=88=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D0=BE=3F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/codeception/common/fixtures/data/accounts.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index fe15830..382db97 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -54,6 +54,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1457890086, 'updated_at' => 1457890086, + 'password_changed_at' => 1457890086, ], 'account-with-fresh-forgot-password-message' => [ 'id' => 5, @@ -67,6 +68,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1462891432, 'updated_at' => 1462891432, + 'password_changed_at' => 1462891432, ], 'account-with-expired-forgot-password-message' => [ 'id' => 6, @@ -80,6 +82,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1462891612, 'updated_at' => 1462891612, + 'password_changed_at' => 1462891612, ], 'account-with-change-email-init-state' => [ 'id' => 7, @@ -93,6 +96,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1463427287, 'updated_at' => 1463427287, + 'password_changed_at' => 1463427287, ], 'account-with-change-email-finish-state' => [ 'id' => 8, @@ -106,6 +110,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1463349615, 'updated_at' => 1463349615, + 'password_changed_at' => 1463349615, ], 'account-with-old-rules-version' => [ 'id' => 9, @@ -119,6 +124,7 @@ return [ 'rules_agreement_version' => null, 'created_at' => 1470499952, 'updated_at' => 1470499952, + 'password_changed_at' => 1470499952, ], 'banned-account' => [ 'id' => 10, @@ -132,6 +138,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1472682343, 'updated_at' => 1472682343, + 'password_changed_at' => 1472682343, ], 'account-with-usernames-history' => [ 'id' => 11, @@ -145,6 +152,7 @@ return [ 'rules_agreement_version' => \common\LATEST_RULES_VERSION, 'created_at' => 1474404139, 'updated_at' => 1474404149, + 'password_changed_at' => 1474404149, ], 'account-with-otp-secret' => [ 'id' => 12, @@ -160,6 +168,7 @@ return [ 'is_otp_enabled' => false, 'created_at' => 1485124615, 'updated_at' => 1485124615, + 'password_changed_at' => 1485124615, ], 'account-with-enabled-otp' => [ 'id' => 13, @@ -175,5 +184,6 @@ return [ 'is_otp_enabled' => true, 'created_at' => 1485124685, 'updated_at' => 1485124685, + 'password_changed_at' => 1485124685, ], ]; From 3138a74a7f212c6ff4be3fdab5e84295c35d7ea1 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 23 Dec 2017 01:10:54 +0300 Subject: [PATCH 36/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=20NPE=20=D0=B2=20AccountOwner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/rbac/rules/AccountOwner.php | 5 ++++- .../common/unit/rbac/rules/AccountOwnerTest.php | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/common/rbac/rules/AccountOwner.php b/common/rbac/rules/AccountOwner.php index 2127556..d5be749 100644 --- a/common/rbac/rules/AccountOwner.php +++ b/common/rbac/rules/AccountOwner.php @@ -30,7 +30,10 @@ class AccountOwner extends Rule { } $identity = Yii::$app->user->findIdentityByAccessToken($accessToken); - /** @noinspection NullPointerExceptionInspection это исключено, т.к. уже сработал authManager */ + if ($identity === null) { + return false; + } + $account = $identity->getAccount(); if ($account === null) { return false; diff --git a/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php b/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php index 2d8bb6d..0113ea6 100644 --- a/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php +++ b/tests/codeception/common/unit/rbac/rules/AccountOwnerTest.php @@ -12,6 +12,16 @@ use const common\LATEST_RULES_VERSION; class AccountOwnerTest extends TestCase { + public function testIdentityIsNull() { + $component = mock(Component::class . '[findIdentityByAccessToken]', [['secret' => 'secret']]); + $component->shouldDeferMissing(); + $component->shouldReceive('findIdentityByAccessToken')->andReturn(null); + + Yii::$app->set('user', $component); + + $this->assertFalse((new AccountOwner())->execute('some token', new Item(), ['accountId' => 123])); + } + public function testExecute() { $rule = new AccountOwner(); $item = new Item(); From 6b4a21ef30e8814117441304560ded8a9cdd6580 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sat, 30 Dec 2017 21:09:48 +0200 Subject: [PATCH 37/41] =?UTF-8?q?Revert=20"=D0=A1=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=B5=D0=BC=20flo?= =?UTF-8?q?w"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 31a164b779c328cc6db827d09b5db28f1e6232eb. --- .gitlab-ci.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b6b653..5d8220e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,19 +45,17 @@ test:frontend: paths: - frontend/node_modules before_script: - # Uncomment this steps to enable install required for flow libraries - # Enable SSL support for wget - # - apk add --update openssl + - apk add --update openssl # https://github.com/facebook/flow/issues/3649#issuecomment-308070179 - # - wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub - # - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk - # - apk add glibc-2.25-r0.apk + - wget -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub + - wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk + - apk add glibc-2.25-r0.apk script: - cd frontend - yarn run build:install - yarn run lint - # - yarn flow + - yarn flow - yarn test build:production: From 22c29a3331d62630a75e054a6cf0e110bc40ed7c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 31 Dec 2017 15:28:31 +0300 Subject: [PATCH 38/41] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B1=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d311e0b..47b6e78 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,7 +9,7 @@ services: env_file: .env web: - image: registry.ely.by/elyby/accounts-nginx:1.0.2 + image: registry.ely.by/elyby/accounts-nginx:1.0.3 volumes_from: - app links: From 33091aaefaa1d944b92c44cef02ec36e5b757338 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 1 Jan 2018 16:53:53 +0300 Subject: [PATCH 39/41] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20yii2-queue=20=D1=81=20amqp-interop=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B4=D0=BB=D1=8F=20RabbitMQ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/config/config.php | 3 ++- composer.json | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/config/config.php b/common/config/config.php index 692fbed..7ab2f96 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -97,7 +97,8 @@ return [ 'namespace' => getenv('STATSD_NAMESPACE') ?: 'ely.accounts.' . gethostname() . '.app', ], 'queue' => [ - 'class' => yii\queue\amqp\Queue::class, + 'class' => yii\queue\amqp_interop\Queue::class, + 'driver' => yii\queue\amqp_interop\Queue::ENQUEUE_AMQP_LIB, 'host' => getenv('RABBITMQ_HOST') ?: 'rabbitmq', 'port' => getenv('RABBITMQ_PORT') ?: 5672, 'user' => getenv('RABBITMQ_USER'), diff --git a/composer.json b/composer.json index 7ec9c12..75f759f 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "webmozart/assert": "^1.2.0", "goaop/framework": "~2.1.2", "domnikl/statsd": "^2.6", - "yiisoft/yii2-queue": "dev-fix_amqp_error_behavior#385e1cc0afcce0d6712080beb11922735e72f59d" + "yiisoft/yii2-queue": "~2.0.2", + "enqueue/amqp-lib": "^0.8.11" }, "require-dev": { "yiisoft/yii2-debug": "*", @@ -55,10 +56,6 @@ { "type": "git", "url": "git@github.com:erickskrauch/Codeception.git" - }, - { - "type": "git", - "url": "git@github.com:erickskrauch/yii2-queue.git" } ], "autoload": { From 5649c49a575d27c021d069129a39028f49ecd35f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 1 Jan 2018 17:12:15 +0300 Subject: [PATCH 40/41] Fixes ACCOUNTS-319 --- api/components/User/Component.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/components/User/Component.php b/api/components/User/Component.php index 4049771..95b52fd 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -132,7 +132,11 @@ class Component extends YiiUserComponent { $token = &self::$parsedTokensCache[$jwtString]; if ($token === null) { $jwt = new Jwt(); - $notVerifiedToken = $jwt->deserialize($jwtString); + try { + $notVerifiedToken = $jwt->deserialize($jwtString); + } catch (Exception $e) { + throw new VerificationException('Incorrect token encoding', 0, $e); + } $context = new VerificationContext(EncryptionFactory::create($this->getAlgorithm())); $context->setSubject(self::JWT_SUBJECT_PREFIX); From f328cb47e1407a6ade95b97a93c236459a30641e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 1 Jan 2018 23:30:24 +0300 Subject: [PATCH 41/41] 1.1.22 [skip ci] --- 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 7ab2f96..b258bb4 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,6 +1,6 @@ '1.1.22-dev', + 'version' => '1.1.22', 'vendorPath' => dirname(__DIR__, 2) . '/vendor', 'components' => [ 'cache' => [