Drop usage of goaop, replace implementation with events

This commit is contained in:
ErickSkrauch
2023-11-20 04:39:13 +01:00
parent 2bc83f39cf
commit 16877d502d
27 changed files with 365 additions and 778 deletions

View File

@@ -1,17 +0,0 @@
<?php
namespace api\aop;
use Doctrine\Common\Annotations\AnnotationReader;
use Go\Core\AspectContainer;
use Go\Core\AspectKernel as BaseAspectKernel;
class AspectKernel extends BaseAspectKernel {
protected function configureAop(AspectContainer $container): void {
AnnotationReader::addGlobalIgnoredName('url');
$container->registerAspect(new aspects\MockDataAspect());
$container->registerAspect(new aspects\CollectMetricsAspect());
}
}

View File

@@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace api\aop\annotations;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Annotation\Required;
/**
* @Annotation
* @Target("METHOD")
*/
class CollectModelMetrics {
/**
* @Required()
* @var string sets the prefix for collected metrics. Should be specified without trailing dots
*/
public $prefix = '';
}

View File

@@ -1,41 +0,0 @@
<?php
namespace api\aop\aspects;
use api\aop\annotations\CollectModelMetrics;
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;
use Yii;
class CollectMetricsAspect implements Aspect {
/**
* @param MethodInvocation $invocation Invocation
* @Around("@execution(api\aop\annotations\CollectModelMetrics)")
*/
public function sendMetrics(MethodInvocation $invocation) {
/** @var CollectModelMetrics $annotation */
$annotation = $invocation->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;
}
}

View File

@@ -1,177 +0,0 @@
<?php
namespace api\aop\aspects;
use Go\Aop\Aspect;
use Go\Aop\Intercept\MethodInvocation;
use Go\Lang\Annotation\Around;
use Yii;
use yii\web\Request;
class MockDataAspect implements Aspect {
/**
* @param MethodInvocation $invocation Invocation
* @Around("execution(public api\controllers\SignupController->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();
}
}

View File

@@ -2,7 +2,14 @@
return [
'id' => 'accounts-site-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log', 'authserver', 'internal', 'mojang'],
'bootstrap' => [
'log',
'authserver',
'internal',
'mojang',
api\eventListeners\MockDataResponse::class,
api\eventListeners\LogMetricsToStatsd::class,
],
'controllerNamespace' => 'api\controllers',
'params' => [
'authserverHost' => getenv('AUTHSERVER_HOST') ?: 'authserver.ely.by',

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace api\eventListeners;
use api\controllers\AuthenticationController;
use api\controllers\SignupController;
use api\modules\accounts\actions;
use Closure;
use Yii;
use yii\base\ActionEvent;
use yii\base\BootstrapInterface;
use yii\base\Controller;
use yii\base\Event;
final class LogMetricsToStatsd implements BootstrapInterface {
public function bootstrap($app): void {
Event::on(Controller::class, Controller::EVENT_BEFORE_ACTION, Closure::fromCallable([$this, 'beforeAction']));
Event::on(Controller::class, Controller::EVENT_AFTER_ACTION, Closure::fromCallable([$this, 'afterAction']));
}
private function beforeAction(ActionEvent $event): void {
$prefix = $this->getPrefix($event);
if ($prefix === null) {
return;
}
Yii::$app->statsd->inc($prefix . '.attempt');
}
private function afterAction(ActionEvent $event): void {
$prefix = $this->getPrefix($event);
if ($prefix === null) {
return;
}
$result = $event->result;
if (isset($result['success'])) {
if ($result['success']) {
Yii::$app->statsd->inc($prefix . '.success');
} else {
$errors = $result['errors'];
Yii::$app->statsd->inc($prefix . '.' . $errors[array_key_first($errors)]);
}
}
}
private function getPrefix(ActionEvent $event): ?string {
$action = $event->action;
switch (get_class($action)) {
case actions\AcceptRulesAction::class: return 'accounts.acceptRules';
case actions\ChangeEmailAction::class: return 'accounts.changeEmail';
case actions\ChangeLanguageAction::class: return 'accounts.switchLanguage';
case actions\ChangePasswordAction::class: return 'accounts.changePassword';
case actions\ChangeUsernameAction::class: return 'accounts.changeUsername';
case actions\EnableTwoFactorAuthAction::class: return 'accounts.enableTwoFactorAuth';
case actions\DisableTwoFactorAuthAction::class: return 'accounts.disableTwoFactorAuth';
case actions\EmailVerificationAction::class: return 'accounts.sendEmailVerification';
case actions\NewEmailVerificationAction::class: return 'accounts.sendNewEmailVerification';
}
$controller = $action->controller;
if ($controller instanceof SignupController) {
switch ($action->id) {
case 'index': return 'signup.register';
case 'repeatMessage': return 'signup.repeatEmail';
case 'confirm': return 'signup.confirmEmail';
}
}
if ($controller instanceof AuthenticationController) {
switch ($action->id) {
case 'login': return 'authentication.login';
case 'logout': return 'authentication.logout';
case 'forgotPassword': return 'authentication.forgotPassword';
case 'recoverPassword': return 'authentication.recoverPassword';
case 'refreshToken': return 'authentication.renew';
}
}
return null;
}
}

View File

@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace api\eventListeners;
use api\controllers\AuthenticationController;
use api\controllers\SignupController;
use api\modules\accounts\actions\ChangeEmailAction;
use api\modules\accounts\actions\EmailVerificationAction;
use api\modules\accounts\actions\NewEmailVerificationAction;
use api\modules\accounts\controllers\DefaultController;
use Closure;
use yii\base\ActionEvent;
use yii\base\BootstrapInterface;
use yii\base\Controller;
use yii\base\Event;
use yii\web\Response;
final class MockDataResponse implements BootstrapInterface {
public function bootstrap($app): void {
Event::on(Controller::class, Controller::EVENT_BEFORE_ACTION, Closure::fromCallable([$this, 'beforeAction']));
}
private function beforeAction(ActionEvent $event): void {
$result = $this->getResponse($event);
if ($result === null) {
return;
}
$response = $event->action->controller->response;
$response->format = Response::FORMAT_JSON;
$response->data = $result;
$event->handled = true;
$event->isValid = false;
}
private function getResponse(ActionEvent $event): ?array {
$action = $event->action;
$controller = $action->controller;
$request = $controller->request;
if ($controller instanceof SignupController && $action->id === 'index') {
$email = $request->post('email');
if ($email === 'let-me-register@ely.by') {
return ['success' => true];
}
}
if ($controller instanceof SignupController && $action->id === 'repeatMessage') {
$email = $request->post('email');
if ($email === 'let-me-register@ely.by' || $email === 'let-me-repeat@ely.by') {
return ['success' => true];
}
}
if ($controller instanceof SignupController && $action->id === 'confirm') {
$key = $request->post('key');
if ($key === 'LETMEIN') {
return [
'success' => true,
'access_token' => 'dummy_token',
'expires_in' => time() + 60,
];
}
}
if ($controller instanceof AuthenticationController && $action->id === 'forgotPassword') {
$login = $request->post('login');
if ($login === 'let-me-recover@ely.by') {
return [
'success' => true,
'data' => [
'canRepeatIn' => time() + 60,
'repeatFrequency' => 60,
],
];
}
}
if ($controller instanceof AuthenticationController && $action->id === 'recoverPassword') {
$key = $request->post('key');
if ($key === 'LETMEIN') {
return [
'success' => true,
'access_token' => 'dummy_token',
'expires_in' => time() + 60,
];
}
}
if ($controller instanceof DefaultController && $action->id === 'get') {
$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,
];
}
}
if ($action instanceof EmailVerificationAction) {
$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,
];
}
}
if ($action instanceof NewEmailVerificationAction) {
$key = $request->post('key');
if ($key === 'LETMEIN') {
return [
'success' => true,
];
}
}
if ($action instanceof ChangeEmailAction) {
$key = $request->post('key');
if ($key === 'LETMEIN') {
return [
'success' => true,
'email' => 'brand-new-email@ely.by',
];
}
}
return null;
}
}

View File

@@ -1,6 +1,5 @@
<?php
use api\aop\AspectKernel;
use common\config\ConfigLoader;
use yii\web\Application;
@@ -11,17 +10,6 @@ require __DIR__ . '/../vendor/autoload.php';
defined('YII_DEBUG') || define('YII_DEBUG', in_array(getenv('YII_DEBUG'), ['true', '1'], false));
defined('YII_ENV') || define('YII_ENV', getenv('YII_ENV'));
// Initialize an application aspect container
AspectKernel::getInstance()->init([
'debug' => YII_DEBUG,
'appDir' => dirname(__DIR__),
'cacheDir' => __DIR__ . '/runtime/aspect',
'excludePaths' => [
__DIR__ . '/runtime/aspect',
__DIR__ . '/../vendor',
],
]);
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/../common/config/bootstrap.php';
require __DIR__ . '/config/bootstrap.php';

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\models\base\ApiForm;
use api\validators\EmailActivationKeyValidator;
use common\models\Account;
@@ -22,9 +21,6 @@ class ConfirmEmailForm extends ApiForm {
];
}
/**
* @CollectModelMetrics(prefix="signup.confirmEmail")
*/
public function confirm(): ?AuthenticationResult {
if (!$this->validate()) {
return null;

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\base\ApiForm;
use common\components\UserFriendlyRandomKey;
@@ -61,10 +60,6 @@ class ForgotPasswordForm extends ApiForm {
return Account::find()->andWhereLogin($this->login)->one();
}
/**
* @CollectModelMetrics(prefix="authentication.forgotPassword")
* @return bool
*/
public function forgotPassword(): bool {
if (!$this->validate()) {
return false;

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\models\base\ApiForm;
use api\validators\TotpValidator;
use common\helpers\Error as E;
@@ -104,9 +103,6 @@ class LoginForm extends ApiForm {
return Account::find()->andWhereLogin($this->login)->one();
}
/**
* @CollectModelMetrics(prefix="authentication.login")
*/
public function login(): ?AuthenticationResult {
if (!$this->validate()) {
return null;

View File

@@ -3,16 +3,11 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\models\base\ApiForm;
use Yii;
class LogoutForm extends ApiForm {
/**
* @CollectModelMetrics(prefix="authentication.logout")
* @return bool
*/
public function logout(): bool {
$component = Yii::$app->user;
$session = $component->getActiveSession();

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\models\base\ApiForm;
use api\validators\EmailActivationKeyValidator;
use common\helpers\Error as E;
@@ -36,9 +35,6 @@ class RecoverPasswordForm extends ApiForm {
}
}
/**
* @CollectModelMetrics(prefix="authentication.recoverPassword")
*/
public function recoverPassword(): ?AuthenticationResult {
if (!$this->validate()) {
return null;

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\models\base\ApiForm;
use common\helpers\Error as E;
use common\models\AccountSession;
@@ -32,9 +31,6 @@ class RefreshTokenForm extends ApiForm {
}
}
/**
* @CollectModelMetrics(prefix="authentication.renew")
*/
public function renew(): ?AuthenticationResult {
if (!$this->validate()) {
return null;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\base\ApiForm;
use common\components\UserFriendlyRandomKey;
@@ -63,11 +62,6 @@ class RegistrationForm extends ApiForm {
}
}
/**
* @CollectModelMetrics(prefix="signup.register")
* @return Account|null the saved model or null if saving fails
* @throws Exception
*/
public function signup() {
if (!$this->validate() && !$this->canContinue($this->getFirstErrors())) {
return null;

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\models\authentication;
use api\aop\annotations\CollectModelMetrics;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\base\ApiForm;
use common\components\UserFriendlyRandomKey;
@@ -56,10 +55,6 @@ class RepeatAccountActivationForm extends ApiForm {
}
}
/**
* @CollectModelMetrics(prefix="signup.repeatEmail")
* @return bool
*/
public function sendRepeatMessage(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,15 +1,11 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use yii\base\ErrorException;
use const common\LATEST_RULES_VERSION;
class AcceptRulesForm extends AccountActionForm {
/**
* @CollectModelMetrics(prefix="accounts.acceptRules")
*/
public function performAction(): bool {
$account = $this->getAccount();
$account->rules_agreement_version = LATEST_RULES_VERSION;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\validators\EmailActivationKeyValidator;
use common\models\EmailActivation;
use Webmozart\Assert\Assert;
@@ -17,9 +16,6 @@ class ChangeEmailForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.changeEmail")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use common\validators\LanguageValidator;
use Webmozart\Assert\Assert;
@@ -16,9 +15,6 @@ class ChangeLanguageForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.switchLanguage")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\components\User\Component;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error as E;
@@ -44,9 +43,6 @@ class ChangePasswordForm extends AccountActionForm {
}
}
/**
* @CollectModelMetrics(prefix="accounts.changePassword")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\validators\PasswordRequiredValidator;
use common\models\UsernameHistory;
use common\tasks\PullMojangUsername;
@@ -24,9 +23,6 @@ class ChangeUsernameForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.changeUsername")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\validators\PasswordRequiredValidator;
use api\validators\TotpValidator;
use common\helpers\Error as E;
@@ -22,9 +21,6 @@ class DisableTwoFactorAuthForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.disableTwoFactorAuth")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\components\User\Component;
use api\validators\PasswordRequiredValidator;
use api\validators\TotpValidator;
@@ -24,9 +23,6 @@ class EnableTwoFactorAuthForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.enableTwoFactorAuth")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error as E;
use common\models\confirmations\CurrentEmailConfirmation;
@@ -37,9 +36,6 @@ class SendEmailVerificationForm extends AccountActionForm {
}
}
/**
* @CollectModelMetrics(prefix="accounts.sendEmailVerification")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;

View File

@@ -1,7 +1,6 @@
<?php
namespace api\modules\accounts\models;
use api\aop\annotations\CollectModelMetrics;
use api\validators\EmailActivationKeyValidator;
use common\models\confirmations\NewEmailConfirmation;
use common\models\EmailActivation;
@@ -23,9 +22,6 @@ class SendNewEmailVerificationForm extends AccountActionForm {
];
}
/**
* @CollectModelMetrics(prefix="accounts.sendNewEmailVerification")
*/
public function performAction(): bool {
if (!$this->validate()) {
return false;