diff --git a/api/assets/AppAsset.php b/api/assets/AppAsset.php
deleted file mode 100644
index e7e722f..0000000
--- a/api/assets/AppAsset.php
+++ /dev/null
@@ -1,29 +0,0 @@
-
- * @since 2.0
- */
-class AppAsset extends AssetBundle
-{
- public $basePath = '@webroot';
- public $baseUrl = '@web';
- public $css = [
- 'css/site.css',
- ];
- public $js = [
- ];
- public $depends = [
- 'yii\web\YiiAsset',
- 'yii\bootstrap\BootstrapAsset',
- ];
-}
diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php
index 3aa937c..c730cf1 100644
--- a/api/controllers/OauthController.php
+++ b/api/controllers/OauthController.php
@@ -4,7 +4,9 @@ namespace api\controllers;
use common\components\oauth\Exception\AcceptRequiredException;
use common\components\oauth\Exception\AccessDeniedException;
use common\models\OauthClient;
+use common\models\OauthScope;
use League\OAuth2\Server\Exception\OAuthException;
+use League\OAuth2\Server\Grant\RefreshTokenGrant;
use Yii;
use yii\filters\AccessControl;
use yii\helpers\ArrayHelper;
@@ -17,7 +19,7 @@ class OauthController extends Controller {
'class' => AccessControl::class,
'rules' => [
[
- 'actions' => ['validate'],
+ 'actions' => ['validate', 'issue-token'],
'allow' => true,
],
[
@@ -32,8 +34,9 @@ class OauthController extends Controller {
public function verbs() {
return [
- 'validate' => ['GET'],
- 'complete' => ['POST'],
+ 'validate' => ['GET'],
+ 'complete' => ['POST'],
+ 'issue-token' => ['POST'],
];
}
@@ -141,20 +144,28 @@ class OauthController extends Controller {
}
/**
- * Метод выполняется сервером приложения, которому был выдан auth_token.
+ * Метод выполняется сервером приложения, которому был выдан auth_token иди refresh_token.
*
* Входными данными является стандартный список GET параметров по стандарту oAuth:
* $_GET = [
* client_id,
* client_secret,
* redirect_uri,
- * code|refresh_token,
+ * code,
+ * grant_type,
+ * ]
+ * для запроса grant_type = authentication_code.
+ * $_GET = [
+ * client_id,
+ * client_secret,
+ * refresh_token,
* grant_type,
* ]
*
* @return array
*/
public function actionIssueToken() {
+ $this->attachRefreshTokenGrantIfNeedle();
try {
$response = $this->getServer()->issueAccessToken();
} catch (OAuthException $e) {
@@ -168,6 +179,28 @@ class OauthController extends Controller {
return $response;
}
+ private function attachRefreshTokenGrantIfNeedle() {
+ $grantType = Yii::$app->request->post('grant_type');
+ if ($grantType === 'authorization_code' && Yii::$app->request->post('code')) {
+ $authCode = Yii::$app->request->post('code');
+ $codeModel = $this->getServer()->getAuthCodeStorage()->get($authCode);
+ if ($codeModel === null) {
+ return;
+ }
+
+ $scopes = $codeModel->getScopes();
+ if (array_search(OauthScope::OFFLINE_ACCESS, array_keys($scopes)) === false) {
+ return;
+ }
+ } elseif ($grantType === 'refresh_token') {
+ // Это валидный кейс
+ } else {
+ return;
+ }
+
+ $this->getServer()->addGrantType(new RefreshTokenGrant());
+ }
+
/**
* @param array $queryParams
* @param OauthClient $clientModel
diff --git a/api/messages/.gitkeep b/api/messages/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/api/views/layouts/main.php b/api/views/layouts/main.php
deleted file mode 100644
index a4da544..0000000
--- a/api/views/layouts/main.php
+++ /dev/null
@@ -1,77 +0,0 @@
-
-beginPage() ?>
-
-
-
-
-
- = Html::csrfMetaTags() ?>
- = Html::encode($this->title) ?>
- head() ?>
-
-
-beginBody() ?>
-
-
- 'My Company',
- 'brandUrl' => Yii::$app->homeUrl,
- 'options' => [
- 'class' => 'navbar-inverse navbar-fixed-top',
- ],
- ]);
- $menuItems = [
- ['label' => 'Home', 'url' => ['site/index']],
- ['label' => 'About', 'url' => ['site/about']],
- ['label' => 'Contact', 'url' => ['site/contact']],
- ];
- if (Yii::$app->user->isGuest) {
- $menuItems[] = ['label' => 'Signup', 'url' => ['site/signup']];
- $menuItems[] = ['label' => 'Login', 'url' => ['site/login']];
- } else {
- $menuItems[] = [
- 'label' => 'Logout (' . Yii::$app->user->identity->username . ')',
- 'url' => ['site/logout'],
- 'linkOptions' => ['data-method' => 'post']
- ];
- }
- echo Nav::widget([
- 'options' => ['class' => 'navbar-nav navbar-right'],
- 'items' => $menuItems,
- ]);
- NavBar::end();
- ?>
-
-
- = Breadcrumbs::widget([
- 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
- ]) ?>
- = $content ?>
-
-
-
-
-
-endBody() ?>
-
-
-endPage() ?>
diff --git a/api/views/site/about.php b/api/views/site/about.php
deleted file mode 100644
index 8eb0764..0000000
--- a/api/views/site/about.php
+++ /dev/null
@@ -1,16 +0,0 @@
-title = 'About';
-$this->params['breadcrumbs'][] = $this->title;
-?>
-
-
= Html::encode($this->title) ?>
-
-
This is the About page. You may modify the following file to customize its content:
-
-
= __FILE__ ?>
-
diff --git a/api/views/site/contact.php b/api/views/site/contact.php
deleted file mode 100644
index be11983..0000000
--- a/api/views/site/contact.php
+++ /dev/null
@@ -1,45 +0,0 @@
-title = 'Contact';
-$this->params['breadcrumbs'][] = $this->title;
-?>
-
diff --git a/api/views/site/error.php b/api/views/site/error.php
deleted file mode 100644
index 0ba2574..0000000
--- a/api/views/site/error.php
+++ /dev/null
@@ -1,27 +0,0 @@
-title = $name;
-?>
-
-
-
= Html::encode($this->title) ?>
-
-
- = nl2br(Html::encode($message)) ?>
-
-
-
- The above error occurred while the Web server was processing your request.
-
-
- Please contact us if you think this is a server error. Thank you.
-
-
-
diff --git a/api/views/site/index.php b/api/views/site/index.php
deleted file mode 100644
index f780610..0000000
--- a/api/views/site/index.php
+++ /dev/null
@@ -1,53 +0,0 @@
-title = 'My Yii Application';
-?>
-
-
-
-
Congratulations!
-
-
You have successfully created your Yii-powered application.
-
-
Get started with Yii
-
-
-
-
-
-
-
Heading
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
-
-
Yii Documentation »
-
-
-
Heading
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
-
-
Yii Forum »
-
-
-
Heading
-
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et
- dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
- ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
- fugiat nulla pariatur.
-
-
Yii Extensions »
-
-
-
-
-
diff --git a/api/views/site/requestPasswordResetToken.php b/api/views/site/requestPasswordResetToken.php
deleted file mode 100644
index a687530..0000000
--- a/api/views/site/requestPasswordResetToken.php
+++ /dev/null
@@ -1,31 +0,0 @@
-title = 'Request password reset';
-$this->params['breadcrumbs'][] = $this->title;
-?>
-
-
= Html::encode($this->title) ?>
-
-
Please fill out your email. A link to reset password will be sent there.
-
-
-
- 'request-password-reset-form']); ?>
-
- = $form->field($model, 'email') ?>
-
-
- = Html::submitButton('Send', ['class' => 'btn btn-primary']) ?>
-
-
-
-
-
-
diff --git a/api/views/site/resetPassword.php b/api/views/site/resetPassword.php
deleted file mode 100644
index 6818ca9..0000000
--- a/api/views/site/resetPassword.php
+++ /dev/null
@@ -1,31 +0,0 @@
-title = 'Reset password';
-$this->params['breadcrumbs'][] = $this->title;
-?>
-
-
= Html::encode($this->title) ?>
-
-
Please choose your new password:
-
-
-
- 'reset-password-form']); ?>
-
- = $form->field($model, 'password')->passwordInput() ?>
-
-
- = Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
-
-
-
-
-
-
diff --git a/api/views/site/signup.php b/api/views/site/signup.php
deleted file mode 100644
index bbaffc5..0000000
--- a/api/views/site/signup.php
+++ /dev/null
@@ -1,35 +0,0 @@
-title = 'Signup';
-$this->params['breadcrumbs'][] = $this->title;
-?>
-
-
= Html::encode($this->title) ?>
-
-
Please fill out the following fields to signup:
-
-
-
- 'form-signup']); ?>
-
- = $form->field($model, 'username') ?>
-
- = $form->field($model, 'email') ?>
-
- = $form->field($model, 'password')->passwordInput() ?>
-
-
- = Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
-
-
-
-
-
-
diff --git a/common/components/oauth/Component.php b/common/components/oauth/Component.php
index 9fe54a3..9844817 100644
--- a/common/components/oauth/Component.php
+++ b/common/components/oauth/Component.php
@@ -2,6 +2,7 @@
namespace common\components\oauth;
use common\components\oauth\Storage\Redis\AuthCodeStorage;
+use common\components\oauth\Storage\Redis\RefreshTokenStorage;
use common\components\oauth\Storage\Yii2\AccessTokenStorage;
use common\components\oauth\Storage\Yii2\ClientStorage;
use common\components\oauth\Storage\Yii2\ScopeStorage;
@@ -43,6 +44,7 @@ class Component extends \yii\base\Component {
->setScopeStorage(new ScopeStorage())
->setSessionStorage(new SessionStorage())
->setAuthCodeStorage(new AuthCodeStorage())
+ ->setRefreshTokenStorage(new RefreshTokenStorage())
->setScopeDelimiter(',');
$this->_authServer = $authServer;
diff --git a/common/components/oauth/Entity/AccessTokenEntity.php b/common/components/oauth/Entity/AccessTokenEntity.php
index 3501d82..bd70930 100644
--- a/common/components/oauth/Entity/AccessTokenEntity.php
+++ b/common/components/oauth/Entity/AccessTokenEntity.php
@@ -2,7 +2,7 @@
namespace common\components\oauth\Entity;
use League\OAuth2\Server\Entity\EntityTrait;
-use League\OAuth2\Server\Entity\SessionEntity;
+use League\OAuth2\Server\Entity\SessionEntity as OriginalSessionEntity;
class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
use EntityTrait;
@@ -17,7 +17,7 @@ class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
* @inheritdoc
* @return static
*/
- public function setSession(SessionEntity $session) {
+ public function setSession(OriginalSessionEntity $session) {
parent::setSession($session);
$this->sessionId = $session->getId();
diff --git a/common/components/oauth/Storage/Redis/AuthCodeStorage.php b/common/components/oauth/Storage/Redis/AuthCodeStorage.php
index 8e75e7f..204027d 100644
--- a/common/components/oauth/Storage/Redis/AuthCodeStorage.php
+++ b/common/components/oauth/Storage/Redis/AuthCodeStorage.php
@@ -32,7 +32,7 @@ class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
'id' => $result['id'],
'redirectUri' => $result['client_redirect_uri'],
'expireTime' => $result['expire_time'],
- 'sessionId' => $result['sessionId'],
+ 'sessionId' => $result['session_id'],
]);
}
diff --git a/common/components/oauth/Storage/Redis/RefreshTokenStorage.php b/common/components/oauth/Storage/Redis/RefreshTokenStorage.php
index 2736f78..f3ad9e0 100644
--- a/common/components/oauth/Storage/Redis/RefreshTokenStorage.php
+++ b/common/components/oauth/Storage/Redis/RefreshTokenStorage.php
@@ -1,5 +1,5 @@
cache[$token])) {
+ if (!isset($this->cache[$token])) {
$this->cache[$token] = OauthAccessToken::findOne($token);
}
diff --git a/common/components/redis/Key.php b/common/components/redis/Key.php
index 1b1f951..aaea4ff 100644
--- a/common/components/redis/Key.php
+++ b/common/components/redis/Key.php
@@ -20,7 +20,7 @@ class Key {
}
public function getValue() {
- return $this->getRedis()->get(json_decode($this->key));
+ return json_decode($this->getRedis()->get($this->key), true);
}
public function setValue($value) {
diff --git a/common/models/OauthScope.php b/common/models/OauthScope.php
index cfa3acc..74d3901 100644
--- a/common/models/OauthScope.php
+++ b/common/models/OauthScope.php
@@ -10,6 +10,9 @@ use yii\db\ActiveRecord;
*/
class OauthScope extends ActiveRecord {
+ const OFFLINE_ACCESS = 'offline_access';
+ const MINECRAFT_SERVER_SESSION = 'minecraft_server_session';
+
public static function tableName() {
return '{{%oauth_scopes}}';
}
diff --git a/common/models/OauthSession.php b/common/models/OauthSession.php
index e438287..a74032c 100644
--- a/common/models/OauthSession.php
+++ b/common/models/OauthSession.php
@@ -25,7 +25,7 @@ class OauthSession extends ActiveRecord {
return '{{%oauth_sessions}}';
}
- public function getOauthAccessTokens() {
+ public function getAccessTokens() {
return $this->hasMany(OauthAccessToken::class, ['session_id' => 'id']);
}
diff --git a/console/migrations/m160222_204006_add_init_scopes.php b/console/migrations/m160222_204006_add_init_scopes.php
new file mode 100644
index 0000000..8201196
--- /dev/null
+++ b/console/migrations/m160222_204006_add_init_scopes.php
@@ -0,0 +1,18 @@
+batchInsert('{{%oauth_scopes}}', ['id'], [
+ ['offline_access'],
+ ['minecraft_server_session'],
+ ]);
+ }
+
+ public function safeDown() {
+ $this->delete('{{%oauth_scopes}}');
+ }
+
+}
diff --git a/tests/codeception/api/_pages/OauthRoute.php b/tests/codeception/api/_pages/OauthRoute.php
index 4356928..0a4118a 100644
--- a/tests/codeception/api/_pages/OauthRoute.php
+++ b/tests/codeception/api/_pages/OauthRoute.php
@@ -18,4 +18,9 @@ class OauthRoute extends BasePage {
$this->actor->sendPOST($this->getUrl($queryParams), $postParams);
}
+ public function issueToken($postParams = []) {
+ $this->route = ['oauth/issue-token'];
+ $this->actor->sendPOST($this->getUrl(), $postParams);
+ }
+
}
diff --git a/tests/codeception/api/functional/OauthAccessTokenCest.php b/tests/codeception/api/functional/OauthAccessTokenCest.php
new file mode 100644
index 0000000..3d92717
--- /dev/null
+++ b/tests/codeception/api/functional/OauthAccessTokenCest.php
@@ -0,0 +1,99 @@
+route = new OauthRoute($I);
+ }
+
+ public function testIssueTokenWithWrongArgs(FunctionalTester $I) {
+ $I->wantTo('check behavior on on request without any credentials');
+ $this->route->issueToken();
+ $I->canSeeResponseCodeIs(400);
+ $I->canSeeResponseContainsJson([
+ 'error' => 'invalid_request',
+ ]);
+
+ $I->wantTo('check behavior on passing invalid auth code');
+ $this->route->issueToken($this->buildParams(
+ 'wrong-auth-code',
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ 'http://ely.by'
+ ));
+ $I->canSeeResponseCodeIs(400);
+ $I->canSeeResponseContainsJson([
+ 'error' => 'invalid_request',
+ ]);
+ }
+
+ public function testIssueToken(FunctionalTester $I, Scenario $scenario) {
+ $I = new OauthSteps($scenario);
+ $authCode = $I->getAuthCode();
+ $this->route->issueToken($this->buildParams(
+ $authCode,
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ 'http://ely.by'
+ ));
+ $I->canSeeResponseCodeIs(200);
+ $I->canSeeResponseIsJson();
+ $I->canSeeResponseContainsJson([
+ 'token_type' => 'Bearer',
+ ]);
+ $I->canSeeResponseJsonMatchesJsonPath('$.access_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
+ }
+
+ public function testIssueTokenWithRefreshToken(FunctionalTester $I, Scenario $scenario) {
+ $I = new OauthSteps($scenario);
+ $authCode = $I->getAuthCode(false);
+ $this->route->issueToken($this->buildParams(
+ $authCode,
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ 'http://ely.by'
+ ));
+ $I->canSeeResponseCodeIs(200);
+ $I->canSeeResponseIsJson();
+ $I->canSeeResponseContainsJson([
+ 'token_type' => 'Bearer',
+ ]);
+ $I->canSeeResponseJsonMatchesJsonPath('$.access_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.refresh_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
+ }
+
+ private function buildParams($code = null, $clientId = null, $clientSecret = null, $redirectUri = null) {
+ $params = ['grant_type' => 'authorization_code'];
+ if ($code !== null) {
+ $params['code'] = $code;
+ }
+
+ if ($clientId !== null) {
+ $params['client_id'] = $clientId;
+ }
+
+ if ($clientSecret !== null) {
+ $params['client_secret'] = $clientSecret;
+ }
+
+ if ($redirectUri !== null) {
+ $params['redirect_uri'] = $redirectUri;
+ }
+
+ return $params;
+ }
+
+}
diff --git a/tests/codeception/api/functional/OauthCest.php b/tests/codeception/api/functional/OauthAuthCodeCest.php
similarity index 99%
rename from tests/codeception/api/functional/OauthCest.php
rename to tests/codeception/api/functional/OauthAuthCodeCest.php
index bd84ef1..34d0001 100644
--- a/tests/codeception/api/functional/OauthCest.php
+++ b/tests/codeception/api/functional/OauthAuthCodeCest.php
@@ -3,8 +3,9 @@ namespace tests\codeception\api;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\AccountSteps;
+use Yii;
-class OauthCest {
+class OauthAuthCodeCest {
/**
* @var OauthRoute
diff --git a/tests/codeception/api/functional/OauthRefreshTokenCest.php b/tests/codeception/api/functional/OauthRefreshTokenCest.php
new file mode 100644
index 0000000..7068587
--- /dev/null
+++ b/tests/codeception/api/functional/OauthRefreshTokenCest.php
@@ -0,0 +1,99 @@
+route = new OauthRoute($I);
+ }
+
+ public function testRefreshToken(FunctionalTester $I, Scenario $scenario) {
+ $I = new OauthSteps($scenario);
+ $refreshToken = $I->getRefreshToken();
+ $this->route->issueToken($this->buildParams(
+ $refreshToken,
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM'
+ ));
+ $I->canSeeResponseCodeIs(200);
+ $I->canSeeResponseIsJson();
+ $I->canSeeResponseContainsJson([
+ 'token_type' => 'Bearer',
+ ]);
+ $I->canSeeResponseJsonMatchesJsonPath('$.access_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.refresh_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
+ }
+
+ public function testRefreshTokenWithSameScopes(FunctionalTester $I, Scenario $scenario) {
+ $I = new OauthSteps($scenario);
+ $refreshToken = $I->getRefreshToken();
+ $this->route->issueToken($this->buildParams(
+ $refreshToken,
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ [OauthScope::MINECRAFT_SERVER_SESSION, OauthScope::OFFLINE_ACCESS]
+ ));
+ $I->canSeeResponseCodeIs(200);
+ $I->canSeeResponseIsJson();
+ $I->canSeeResponseContainsJson([
+ 'token_type' => 'Bearer',
+ ]);
+ $I->canSeeResponseJsonMatchesJsonPath('$.access_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.refresh_token');
+ $I->canSeeResponseJsonMatchesJsonPath('$.expires_in');
+ }
+
+ public function testRefreshTokenWithNewScopes(FunctionalTester $I, Scenario $scenario) {
+ $I = new OauthSteps($scenario);
+ $refreshToken = $I->getRefreshToken();
+ $this->route->issueToken($this->buildParams(
+ $refreshToken,
+ 'ely',
+ 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ [OauthScope::MINECRAFT_SERVER_SESSION, OauthScope::OFFLINE_ACCESS, 'change_skin']
+ ));
+ $I->canSeeResponseCodeIs(400);
+ $I->canSeeResponseIsJson();
+ $I->canSeeResponseContainsJson([
+ 'error' => 'invalid_scope',
+ ]);
+ }
+
+ private function buildParams($refreshToken = null, $clientId = null, $clientSecret = null, $scopes = []) {
+ $params = ['grant_type' => 'refresh_token'];
+ if ($refreshToken !== null) {
+ $params['refresh_token'] = $refreshToken;
+ }
+
+ if ($clientId !== null) {
+ $params['client_id'] = $clientId;
+ }
+
+ if ($clientSecret !== null) {
+ $params['client_secret'] = $clientSecret;
+ }
+
+ if (!empty($scopes)) {
+ if (is_array($scopes)) {
+ $scopes = implode(',', $scopes);
+ }
+
+ $params['scope'] = $scopes;
+ }
+
+ return $params;
+ }
+
+}
diff --git a/tests/codeception/api/functional/_steps/OauthSteps.php b/tests/codeception/api/functional/_steps/OauthSteps.php
new file mode 100644
index 0000000..d303c63
--- /dev/null
+++ b/tests/codeception/api/functional/_steps/OauthSteps.php
@@ -0,0 +1,42 @@
+loggedInAsActiveAccount();
+ $route = new OauthRoute($this);
+ $route->complete([
+ 'client_id' => 'ely',
+ 'redirect_uri' => 'http://ely.by',
+ 'response_type' => 'code',
+ 'scope' => 'minecraft_server_session' . ($online ? '' : ',offline_access'),
+ ], ['accept' => true]);
+ $this->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
+ $response = json_decode($this->grabResponse(), true);
+ preg_match('/code=(\w+)/', $response['redirectUri'], $matches);
+
+ return $matches[1];
+ }
+
+ public function getRefreshToken() {
+ // TODO: по идее можно напрямую сделать зпись в базу, что ускорит процесс тестирования
+ $authCode = $this->getAuthCode(false);
+ $route = new OauthRoute($this);
+ $route->issueToken([
+ 'code' => $authCode,
+ 'client_id' => 'ely',
+ 'client_secret' => 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
+ 'redirect_uri' => 'http://ely.by',
+ 'grant_type' => 'authorization_code',
+ ]);
+
+ $response = json_decode($this->grabResponse(), true);
+
+ return $response['refresh_token'];
+ }
+
+}
diff --git a/tests/codeception/common/fixtures/data/oauth-scopes.php b/tests/codeception/common/fixtures/data/oauth-scopes.php
index dc2ef34..6d16ab5 100644
--- a/tests/codeception/common/fixtures/data/oauth-scopes.php
+++ b/tests/codeception/common/fixtures/data/oauth-scopes.php
@@ -6,4 +6,7 @@ return [
'change_skin' => [
'id' => 'change_skin',
],
+ 'offline_access' => [
+ 'id' => 'offline_access',
+ ],
];