diff --git a/api/components/OAuth2/Storage/ScopeStorage.php b/api/components/OAuth2/Storage/ScopeStorage.php index 3e08a07..263060f 100644 --- a/api/components/OAuth2/Storage/ScopeStorage.php +++ b/api/components/OAuth2/Storage/ScopeStorage.php @@ -32,6 +32,7 @@ class ScopeStorage extends AbstractStorage implements ScopeInterface { private const CLIENT_CREDENTIALS_PERMISSIONS_INTERNAL = [ P::BLOCK_ACCOUNT, P::OBTAIN_EXTENDED_ACCOUNT_INFO, + P::ESCAPE_IDENTITY_VERIFICATION, ]; /** @@ -55,6 +56,7 @@ class ScopeStorage extends AbstractStorage implements ScopeInterface { $client = $this->server->getClientStorage()->get($clientId); Assert::that($client)->isInstanceOf(ClientEntity::class); + /** @noinspection NullPointerExceptionInspection */ $isTrusted = $client->isTrusted(); } diff --git a/api/components/User/Component.php b/api/components/User/Component.php index aa0e37d..7374842 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -58,7 +58,11 @@ class Component extends YiiUserComponent { } } - public function findIdentityByAccessToken(string $accessToken): ?IdentityInterface { + public function findIdentityByAccessToken($accessToken): ?IdentityInterface { + if ($accessToken === null) { + return null; + } + /** @var \api\components\User\IdentityInterface|string $identityClass */ $identityClass = $this->identityClass; try { diff --git a/api/validators/PasswordRequiredValidator.php b/api/validators/PasswordRequiredValidator.php index ce10ca6..0a1e687 100644 --- a/api/validators/PasswordRequiredValidator.php +++ b/api/validators/PasswordRequiredValidator.php @@ -3,8 +3,11 @@ namespace api\validators; use common\helpers\Error as E; use common\models\Account; +use common\rbac\Permissions as P; use yii\base\InvalidConfigException; +use yii\di\Instance; use yii\validators\Validator; +use yii\web\User; class PasswordRequiredValidator extends Validator { @@ -18,14 +21,25 @@ class PasswordRequiredValidator extends Validator { */ public $skipOnEmpty = false; + /** + * @var User|string + */ + public $user = 'user'; + public function init() { parent::init(); if (!$this->account instanceof Account) { throw new InvalidConfigException('account should be instance of ' . Account::class); } + + $this->user = Instance::ensure($this->user, User::class); } protected function validateValue($value) { + if ($this->user->can(P::ESCAPE_IDENTITY_VERIFICATION)) { + return null; + } + if (empty($value)) { return [E::PASSWORD_REQUIRED, []]; } diff --git a/common/rbac/Manager.php b/common/rbac/Manager.php index 038d2d5..07bc91a 100644 --- a/common/rbac/Manager.php +++ b/common/rbac/Manager.php @@ -19,6 +19,10 @@ class Manager extends PhpManager { */ public function getAssignments($accessToken): array { $identity = Yii::$app->user->findIdentityByAccessToken($accessToken); + if ($identity === null) { + return []; + } + /** @noinspection NullPointerExceptionInspection */ $permissions = $identity->getAssignedPermissions(); if (empty($permissions)) { diff --git a/common/rbac/Permissions.php b/common/rbac/Permissions.php index 3a49076..e86c1e0 100644 --- a/common/rbac/Permissions.php +++ b/common/rbac/Permissions.php @@ -28,4 +28,7 @@ final class Permissions { public const OBTAIN_ACCOUNT_EMAIL = 'obtain_account_email'; public const OBTAIN_EXTENDED_ACCOUNT_INFO = 'obtain_account_extended_info'; + // Service permissions + public const ESCAPE_IDENTITY_VERIFICATION = 'escape_identity_verification'; + } diff --git a/console/controllers/RbacController.php b/console/controllers/RbacController.php index b6ee484..7dbc07b 100644 --- a/console/controllers/RbacController.php +++ b/console/controllers/RbacController.php @@ -45,6 +45,8 @@ class RbacController extends Controller { $permManageOwnTwoFactorAuth = $this->createPermission(P::MANAGE_OWN_TWO_FACTOR_AUTH, AccountOwner::class); $permMinecraftServerSession = $this->createPermission(P::MINECRAFT_SERVER_SESSION); + $permEscapeIdentityVerification = $this->createPermission(P::ESCAPE_IDENTITY_VERIFICATION); + $roleAccountsWebUser = $this->createRole(R::ACCOUNTS_WEB_USER); $authManager->addChild($permObtainOwnAccountInfo, $permObtainAccountInfo); diff --git a/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php b/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php index f036f14..e6fdc38 100644 --- a/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php +++ b/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php @@ -2,32 +2,35 @@ namespace codeception\api\unit\validators; use api\validators\PasswordRequiredValidator; -use Codeception\Specify; use common\models\Account; +use common\rbac\Permissions as P; use tests\codeception\api\unit\TestCase; use tests\codeception\common\_support\ProtectedCaller; use common\helpers\Error as E; +use yii\web\User; class PasswordRequiredValidatorTest extends TestCase { - use Specify; use ProtectedCaller; public function testValidateValue() { $account = new Account(['password' => '12345678']); - $this->specify('get error.password_required if password is empty', function () use ($account) { - $model = new PasswordRequiredValidator(['account' => $account]); - expect($this->callProtected($model, 'validateValue', ''))->equals([E::PASSWORD_REQUIRED, []]); - }); + $model = new PasswordRequiredValidator(['account' => $account]); - $this->specify('get error.password_incorrect if password is incorrect', function () use ($account) { - $model = new PasswordRequiredValidator(['account' => $account]); - expect($this->callProtected($model, 'validateValue', '87654321'))->equals([E::PASSWORD_INCORRECT, []]); - }); + // Get error.password_required if password is empty + $this->assertEquals([E::PASSWORD_REQUIRED, []], $this->callProtected($model, 'validateValue', '')); - $this->specify('no errors, if password is correct for provided account', function () use ($account) { - $model = new PasswordRequiredValidator(['account' => $account]); - expect($this->callProtected($model, 'validateValue', '12345678'))->null(); - }); + // Get error.password_incorrect if password is incorrect + $this->assertEquals([E::PASSWORD_INCORRECT, []], $this->callProtected($model, 'validateValue', '87654321')); + + // No errors, if password is correct for provided account + $this->assertNull($this->callProtected($model, 'validateValue', '12345678')); + + // Skip validation if user can skip identity verification + /** @var User|\Mockery\MockInterface $component */ + $component = mock(User::class . '[can]', [['identityClass' => '']]); + $component->shouldReceive('can')->withArgs([P::ESCAPE_IDENTITY_VERIFICATION])->andReturn(true); + $model->user = $component; + $this->assertNull($this->callProtected($model, 'validateValue', '')); } }