Объединены сущности для авторизации посредством JWT токенов и токенов, выданных через oAuth2.

Все действия, связанные с аккаунтами, теперь вызываются через url `/api/v1/accounts/<id>/<action>`.
Добавлена вменяемая система разграничения прав на основе RBAC.
Теперь oAuth2 токены генерируются как случайная строка в 40 символов длинной, а не UUID.
Исправлен баг с неправильным временем жизни токена в ответе успешного запроса аутентификации.
Теперь все unit тесты можно успешно прогнать без наличия интернета.
This commit is contained in:
ErickSkrauch
2017-09-19 20:06:16 +03:00
parent 928b3aa7fc
commit dd2c4bc413
173 changed files with 2719 additions and 2748 deletions

View File

@ -1,201 +0,0 @@
<?php
namespace api\controllers;
use api\filters\ActiveUserRule;
use api\models\profile\AcceptRulesForm;
use api\models\profile\ChangeEmail\ConfirmNewEmailForm;
use api\models\profile\ChangeEmail\InitStateForm;
use api\models\profile\ChangeEmail\NewEmailForm;
use api\models\profile\ChangeLanguageForm;
use api\models\profile\ChangePasswordForm;
use api\models\profile\ChangeUsernameForm;
use common\helpers\Error as E;
use common\models\Account;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
class AccountsController extends Controller {
public function behaviors() {
return ArrayHelper::merge(parent::behaviors(), [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'actions' => ['current', 'accept-rules'],
'allow' => true,
'roles' => ['@'],
],
[
'class' => ActiveUserRule::class,
'actions' => [
'change-password',
'change-username',
'change-email-initialize',
'change-email-submit-new-email',
'change-email-confirm-new-email',
'change-lang',
],
],
],
],
]);
}
public function verbs() {
return [
'current' => ['GET'],
'change-password' => ['POST'],
'change-username' => ['POST'],
'change-email-initialize' => ['POST'],
'change-email-submit-new-email' => ['POST'],
'change-email-confirm-new-email' => ['POST'],
'change-lang' => ['POST'],
'accept-rules' => ['POST'],
];
}
public function actionCurrent() {
$account = Yii::$app->user->identity;
return [
'id' => $account->id,
'uuid' => $account->uuid,
'username' => $account->username,
'email' => $account->email,
'lang' => $account->lang,
'isActive' => $account->status === Account::STATUS_ACTIVE,
'passwordChangedAt' => $account->password_changed_at,
'hasMojangUsernameCollision' => $account->hasMojangUsernameCollision(),
'shouldAcceptRules' => !$account->isAgreedWithActualRules(),
'isOtpEnabled' => (bool)$account->is_otp_enabled,
];
}
public function actionChangePassword() {
$account = Yii::$app->user->identity;
$model = new ChangePasswordForm($account);
$model->load(Yii::$app->request->post());
if (!$model->changePassword()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
public function actionChangeUsername() {
$account = Yii::$app->user->identity;
$model = new ChangeUsernameForm($account);
$model->load(Yii::$app->request->post());
if (!$model->change()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
public function actionChangeEmailInitialize() {
$account = Yii::$app->user->identity;
$model = new InitStateForm($account);
$model->load(Yii::$app->request->post());
if (!$model->sendCurrentEmailConfirmation()) {
$data = [
'success' => false,
'errors' => $model->getFirstErrors(),
];
if (ArrayHelper::getValue($data['errors'], 'email') === E::RECENTLY_SENT_MESSAGE) {
$emailActivation = $model->getEmailActivation();
$data['data'] = [
'canRepeatIn' => $emailActivation->canRepeatIn(),
'repeatFrequency' => $emailActivation->repeatTimeout,
];
}
return $data;
}
return [
'success' => true,
];
}
public function actionChangeEmailSubmitNewEmail() {
$account = Yii::$app->user->identity;
$model = new NewEmailForm($account);
$model->load(Yii::$app->request->post());
if (!$model->sendNewEmailConfirmation()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
public function actionChangeEmailConfirmNewEmail() {
$account = Yii::$app->user->identity;
$model = new ConfirmNewEmailForm($account);
$model->load(Yii::$app->request->post());
if (!$model->changeEmail()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
'data' => [
'email' => $account->email,
],
];
}
public function actionChangeLang() {
$account = Yii::$app->user->identity;
$model = new ChangeLanguageForm($account);
$model->load(Yii::$app->request->post());
if (!$model->applyLanguage()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
public function actionAcceptRules() {
$account = Yii::$app->user->identity;
$model = new AcceptRulesForm($account);
$model->load(Yii::$app->request->post());
if (!$model->agreeWithLatestRules()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace api\controllers;
use Yii;
use yii\filters\auth\HttpBearerAuth;
/**
* Поведения:
* @mixin \yii\filters\ContentNegotiator
* @mixin \yii\filters\VerbFilter
* @mixin HttpBearerAuth
*/
class ApiController extends \yii\rest\Controller {
public function behaviors() {
$parentBehaviors = parent::behaviors();
// Добавляем авторизатор для входа по Bearer токенам
$parentBehaviors['authenticator'] = [
'class' => HttpBearerAuth::class,
'user' => Yii::$app->apiUser,
];
// xml нам не понадобится
unset($parentBehaviors['contentNegotiator']['formats']['application/xml']);
// rate limiter здесь не применяется
unset($parentBehaviors['rateLimiter']);
return $parentBehaviors;
}
}

View File

@ -14,7 +14,7 @@ use yii\helpers\ArrayHelper;
class AuthenticationController extends Controller {
public function behaviors() {
public function behaviors(): array {
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'only' => ['logout'],
@ -139,9 +139,12 @@ class AuthenticationController extends Controller {
];
}
$response = $result->getAsResponse();
unset($response['refresh_token']);
return array_merge([
'success' => true,
], $result->getAsResponse());
], $response);
}
}

View File

@ -12,7 +12,7 @@ use yii\filters\auth\HttpBearerAuth;
*/
class Controller extends \yii\rest\Controller {
public function behaviors() {
public function behaviors(): array {
$parentBehaviors = parent::behaviors();
// Добавляем авторизатор для входа по jwt токенам
$parentBehaviors['authenticator'] = [

View File

@ -7,7 +7,7 @@ use yii\helpers\ArrayHelper;
class FeedbackController extends Controller {
public function behaviors() {
public function behaviors(): array {
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'optional' => ['index'],

View File

@ -1,14 +1,15 @@
<?php
namespace api\controllers;
use api\components\ApiUser\AccessControl;
use common\models\OauthScope as S;
use api\models\OauthAccountInfo;
use common\rbac\Permissions as P;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
class IdentityInfoController extends ApiController {
class IdentityInfoController extends Controller {
public function behaviors() {
public function behaviors(): array {
return ArrayHelper::merge(parent::behaviors(), [
'access' => [
'class' => AccessControl::class,
@ -16,29 +17,22 @@ class IdentityInfoController extends ApiController {
[
'actions' => ['index'],
'allow' => true,
'roles' => [S::ACCOUNT_INFO],
'roles' => [P::OBTAIN_ACCOUNT_INFO],
'roleParams' => function() {
/** @noinspection NullPointerExceptionInspection */
return [
'accountId' => Yii::$app->user->getIdentity()->getAccount()->id,
];
},
],
],
],
]);
}
public function actionIndex() {
$account = Yii::$app->apiUser->getIdentity()->getAccount();
$response = [
'id' => $account->id,
'uuid' => $account->uuid,
'username' => $account->username,
'registeredAt' => $account->created_at,
'profileLink' => $account->getProfileLink(),
'preferredLanguage' => $account->lang,
];
if (Yii::$app->apiUser->can(S::ACCOUNT_EMAIL)) {
$response['email'] = $account->email;
}
return $response;
public function actionIndex(): array {
/** @noinspection NullPointerExceptionInspection */
return (new OauthAccountInfo(Yii::$app->user->getIdentity()->getAccount()))->info();
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace api\controllers;
use api\filters\ActiveUserRule;
use api\models\OauthProcess;
use common\rbac\Permissions as P;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
@ -19,8 +19,14 @@ class OauthController extends Controller {
'only' => ['complete'],
'rules' => [
[
'class' => ActiveUserRule::class,
'allow' => true,
'actions' => ['complete'],
'roles' => [P::COMPLETE_OAUTH_FLOW],
'roleParams' => function() {
return [
'accountId' => Yii::$app->user->identity->getAccount()->id,
];
},
],
],
],

View File

@ -7,7 +7,7 @@ use yii\helpers\ArrayHelper;
class OptionsController extends Controller {
public function behaviors() {
public function behaviors(): array {
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'except' => ['index'],

View File

@ -11,7 +11,7 @@ use yii\helpers\ArrayHelper;
class SignupController extends Controller {
public function behaviors() {
public function behaviors(): array {
return ArrayHelper::merge(parent::behaviors(), [
'authenticator' => [
'except' => ['index', 'repeat-message', 'confirm'],

View File

@ -1,75 +0,0 @@
<?php
namespace api\controllers;
use api\filters\ActiveUserRule;
use api\models\profile\TwoFactorAuthForm;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
class TwoFactorAuthController extends Controller {
public $defaultAction = 'credentials';
public function behaviors() {
return ArrayHelper::merge(parent::behaviors(), [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'class' => ActiveUserRule::class,
],
],
],
]);
}
public function verbs() {
return [
'credentials' => ['GET'],
'activate' => ['POST'],
'disable' => ['DELETE'],
];
}
public function actionCredentials() {
$account = Yii::$app->user->identity;
$model = new TwoFactorAuthForm($account);
return $model->getCredentials();
}
public function actionActivate() {
$account = Yii::$app->user->identity;
$model = new TwoFactorAuthForm($account, ['scenario' => TwoFactorAuthForm::SCENARIO_ACTIVATE]);
$model->load(Yii::$app->request->post());
if (!$model->activate()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
public function actionDisable() {
$account = Yii::$app->user->identity;
$model = new TwoFactorAuthForm($account, ['scenario' => TwoFactorAuthForm::SCENARIO_DISABLE]);
$model->load(Yii::$app->request->getBodyParams());
if (!$model->disable()) {
return [
'success' => false,
'errors' => $model->getFirstErrors(),
];
}
return [
'success' => true,
];
}
}