Протестирована логика подписи access_token и refresh_token, добавлены базовые скоупы, подчищен проект

This commit is contained in:
ErickSkrauch 2016-02-23 00:49:46 +03:00
parent f5f93ddef1
commit 1c6ba30abf
26 changed files with 318 additions and 357 deletions

View File

@ -1,29 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\assets;
use yii\web\AssetBundle;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @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',
];
}

View File

@ -4,7 +4,9 @@ namespace api\controllers;
use common\components\oauth\Exception\AcceptRequiredException; use common\components\oauth\Exception\AcceptRequiredException;
use common\components\oauth\Exception\AccessDeniedException; use common\components\oauth\Exception\AccessDeniedException;
use common\models\OauthClient; use common\models\OauthClient;
use common\models\OauthScope;
use League\OAuth2\Server\Exception\OAuthException; use League\OAuth2\Server\Exception\OAuthException;
use League\OAuth2\Server\Grant\RefreshTokenGrant;
use Yii; use Yii;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
@ -17,7 +19,7 @@ class OauthController extends Controller {
'class' => AccessControl::class, 'class' => AccessControl::class,
'rules' => [ 'rules' => [
[ [
'actions' => ['validate'], 'actions' => ['validate', 'issue-token'],
'allow' => true, 'allow' => true,
], ],
[ [
@ -32,8 +34,9 @@ class OauthController extends Controller {
public function verbs() { public function verbs() {
return [ return [
'validate' => ['GET'], 'validate' => ['GET'],
'complete' => ['POST'], 'complete' => ['POST'],
'issue-token' => ['POST'],
]; ];
} }
@ -141,20 +144,28 @@ class OauthController extends Controller {
} }
/** /**
* Метод выполняется сервером приложения, которому был выдан auth_token. * Метод выполняется сервером приложения, которому был выдан auth_token иди refresh_token.
* *
* Входными данными является стандартный список GET параметров по стандарту oAuth: * Входными данными является стандартный список GET параметров по стандарту oAuth:
* $_GET = [ * $_GET = [
* client_id, * client_id,
* client_secret, * client_secret,
* redirect_uri, * redirect_uri,
* code|refresh_token, * code,
* grant_type,
* ]
* для запроса grant_type = authentication_code.
* $_GET = [
* client_id,
* client_secret,
* refresh_token,
* grant_type, * grant_type,
* ] * ]
* *
* @return array * @return array
*/ */
public function actionIssueToken() { public function actionIssueToken() {
$this->attachRefreshTokenGrantIfNeedle();
try { try {
$response = $this->getServer()->issueAccessToken(); $response = $this->getServer()->issueAccessToken();
} catch (OAuthException $e) { } catch (OAuthException $e) {
@ -168,6 +179,28 @@ class OauthController extends Controller {
return $response; 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 array $queryParams
* @param OauthClient $clientModel * @param OauthClient $clientModel

View File

View File

@ -1,77 +0,0 @@
<?php
/* @var $this \yii\web\View */
/* @var $content string */
use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
use api\assets\AppAsset;
AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<div class="wrap">
<?php
NavBar::begin([
'brandLabel' => '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();
?>
<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= $content ?>
</div>
</div>
<footer class="footer">
<div class="container">
<p class="pull-left">&copy; My Company <?= date('Y') ?></p>
<p class="pull-right"><?= Yii::powered() ?></p>
</div>
</footer>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

View File

@ -1,16 +0,0 @@
<?php
/* @var $this yii\web\View */
use yii\helpers\Html;
$this->title = 'About';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-about">
<h1><?= Html::encode($this->title) ?></h1>
<p>This is the About page. You may modify the following file to customize its content:</p>
<code><?= __FILE__ ?></code>
</div>

View File

@ -1,45 +0,0 @@
<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \api\models\ContactForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
use yii\captcha\Captcha;
$this->title = 'Contact';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-contact">
<h1><?= Html::encode($this->title) ?></h1>
<p>
If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
</p>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'contact-form']); ?>
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'email') ?>
<?= $form->field($model, 'subject') ?>
<?= $form->field($model, 'body')->textArea(['rows' => 6]) ?>
<?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
]) ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary', 'name' => 'contact-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>

View File

@ -1,27 +0,0 @@
<?php
/* @var $this yii\web\View */
/* @var $name string */
/* @var $message string */
/* @var $exception Exception */
use yii\helpers\Html;
$this->title = $name;
?>
<div class="site-error">
<h1><?= Html::encode($this->title) ?></h1>
<div class="alert alert-danger">
<?= nl2br(Html::encode($message)) ?>
</div>
<p>
The above error occurred while the Web server was processing your request.
</p>
<p>
Please contact us if you think this is a server error. Thank you.
</p>
</div>

View File

@ -1,53 +0,0 @@
<?php
/* @var $this yii\web\View */
$this->title = 'My Yii Application';
?>
<div class="site-index">
<div class="jumbotron">
<h1>Congratulations!</h1>
<p class="lead">You have successfully created your Yii-powered application.</p>
<p><a class="btn btn-lg btn-success" href="http://www.yiiframework.com">Get started with Yii</a></p>
</div>
<div class="body-content">
<div class="row">
<div class="col-lg-4">
<h2>Heading</h2>
<p>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.</p>
<p><a class="btn btn-default" href="http://www.yiiframework.com/doc/">Yii Documentation &raquo;</a></p>
</div>
<div class="col-lg-4">
<h2>Heading</h2>
<p>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.</p>
<p><a class="btn btn-default" href="http://www.yiiframework.com/forum/">Yii Forum &raquo;</a></p>
</div>
<div class="col-lg-4">
<h2>Heading</h2>
<p>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.</p>
<p><a class="btn btn-default" href="http://www.yiiframework.com/extensions/">Yii Extensions &raquo;</a></p>
</div>
</div>
</div>
</div>

View File

@ -1,31 +0,0 @@
<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \api\models\PasswordResetRequestForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
$this->title = 'Request password reset';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-request-password-reset">
<h1><?= Html::encode($this->title) ?></h1>
<p>Please fill out your email. A link to reset password will be sent there.</p>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'request-password-reset-form']); ?>
<?= $form->field($model, 'email') ?>
<div class="form-group">
<?= Html::submitButton('Send', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>

View File

@ -1,31 +0,0 @@
<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \api\models\ResetPasswordForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
$this->title = 'Reset password';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-reset-password">
<h1><?= Html::encode($this->title) ?></h1>
<p>Please choose your new password:</p>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'reset-password-form']); ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>

View File

@ -1,35 +0,0 @@
<?php
/* @var $this yii\web\View */
/* @var $form yii\bootstrap\ActiveForm */
/* @var $model \api\models\SignupForm */
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
$this->title = 'Signup';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="site-signup">
<h1><?= Html::encode($this->title) ?></h1>
<p>Please fill out the following fields to signup:</p>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'form-signup']); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'email') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@
namespace common\components\oauth; namespace common\components\oauth;
use common\components\oauth\Storage\Redis\AuthCodeStorage; 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\AccessTokenStorage;
use common\components\oauth\Storage\Yii2\ClientStorage; use common\components\oauth\Storage\Yii2\ClientStorage;
use common\components\oauth\Storage\Yii2\ScopeStorage; use common\components\oauth\Storage\Yii2\ScopeStorage;
@ -43,6 +44,7 @@ class Component extends \yii\base\Component {
->setScopeStorage(new ScopeStorage()) ->setScopeStorage(new ScopeStorage())
->setSessionStorage(new SessionStorage()) ->setSessionStorage(new SessionStorage())
->setAuthCodeStorage(new AuthCodeStorage()) ->setAuthCodeStorage(new AuthCodeStorage())
->setRefreshTokenStorage(new RefreshTokenStorage())
->setScopeDelimiter(','); ->setScopeDelimiter(',');
$this->_authServer = $authServer; $this->_authServer = $authServer;

View File

@ -2,7 +2,7 @@
namespace common\components\oauth\Entity; namespace common\components\oauth\Entity;
use League\OAuth2\Server\Entity\EntityTrait; 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 { class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
use EntityTrait; use EntityTrait;
@ -17,7 +17,7 @@ class AccessTokenEntity extends \League\OAuth2\Server\Entity\AccessTokenEntity {
* @inheritdoc * @inheritdoc
* @return static * @return static
*/ */
public function setSession(SessionEntity $session) { public function setSession(OriginalSessionEntity $session) {
parent::setSession($session); parent::setSession($session);
$this->sessionId = $session->getId(); $this->sessionId = $session->getId();

View File

@ -32,7 +32,7 @@ class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
'id' => $result['id'], 'id' => $result['id'],
'redirectUri' => $result['client_redirect_uri'], 'redirectUri' => $result['client_redirect_uri'],
'expireTime' => $result['expire_time'], 'expireTime' => $result['expire_time'],
'sessionId' => $result['sessionId'], 'sessionId' => $result['session_id'],
]); ]);
} }

View File

@ -1,5 +1,5 @@
<?php <?php
namespace Fahmiardi\OAuth2\Server\Storage\Redis; namespace common\components\oauth\Storage\Redis;
use common\components\redis\Key; use common\components\redis\Key;
use League\OAuth2\Server\Entity\RefreshTokenEntity; use League\OAuth2\Server\Entity\RefreshTokenEntity;

View File

@ -18,7 +18,7 @@ class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface
* @return OauthAccessToken|null * @return OauthAccessToken|null
*/ */
private function getTokenModel($token) { private function getTokenModel($token) {
if (isset($this->cache[$token])) { if (!isset($this->cache[$token])) {
$this->cache[$token] = OauthAccessToken::findOne($token); $this->cache[$token] = OauthAccessToken::findOne($token);
} }

View File

@ -20,7 +20,7 @@ class Key {
} }
public function getValue() { public function getValue() {
return $this->getRedis()->get(json_decode($this->key)); return json_decode($this->getRedis()->get($this->key), true);
} }
public function setValue($value) { public function setValue($value) {

View File

@ -10,6 +10,9 @@ use yii\db\ActiveRecord;
*/ */
class OauthScope extends ActiveRecord { class OauthScope extends ActiveRecord {
const OFFLINE_ACCESS = 'offline_access';
const MINECRAFT_SERVER_SESSION = 'minecraft_server_session';
public static function tableName() { public static function tableName() {
return '{{%oauth_scopes}}'; return '{{%oauth_scopes}}';
} }

View File

@ -25,7 +25,7 @@ class OauthSession extends ActiveRecord {
return '{{%oauth_sessions}}'; return '{{%oauth_sessions}}';
} }
public function getOauthAccessTokens() { public function getAccessTokens() {
return $this->hasMany(OauthAccessToken::class, ['session_id' => 'id']); return $this->hasMany(OauthAccessToken::class, ['session_id' => 'id']);
} }

View File

@ -0,0 +1,18 @@
<?php
use console\db\Migration;
class m160222_204006_add_init_scopes extends Migration {
public function safeUp() {
$this->batchInsert('{{%oauth_scopes}}', ['id'], [
['offline_access'],
['minecraft_server_session'],
]);
}
public function safeDown() {
$this->delete('{{%oauth_scopes}}');
}
}

View File

@ -18,4 +18,9 @@ class OauthRoute extends BasePage {
$this->actor->sendPOST($this->getUrl($queryParams), $postParams); $this->actor->sendPOST($this->getUrl($queryParams), $postParams);
} }
public function issueToken($postParams = []) {
$this->route = ['oauth/issue-token'];
$this->actor->sendPOST($this->getUrl(), $postParams);
}
} }

View File

@ -0,0 +1,99 @@
<?php
namespace tests\codeception\api;
use Codeception\Scenario;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use Yii;
class OauthAccessTokenCest {
/**
* @var OauthRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->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;
}
}

View File

@ -3,8 +3,9 @@ namespace tests\codeception\api;
use tests\codeception\api\_pages\OauthRoute; use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\AccountSteps; use tests\codeception\api\functional\_steps\AccountSteps;
use Yii;
class OauthCest { class OauthAuthCodeCest {
/** /**
* @var OauthRoute * @var OauthRoute

View File

@ -0,0 +1,99 @@
<?php
namespace tests\codeception\api;
use Codeception\Scenario;
use common\models\OauthScope;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\OauthSteps;
use Yii;
class OauthRefreshTokenCest {
/**
* @var OauthRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->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;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace tests\codeception\api\functional\_steps;
use tests\codeception\api\_pages\OauthRoute;
class OauthSteps extends AccountSteps {
public function getAuthCode($online = true) {
// TODO: по идее можно напрямую сделать зпись в базу, что ускорит процесс тестирования
$this->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'];
}
}

View File

@ -6,4 +6,7 @@ return [
'change_skin' => [ 'change_skin' => [
'id' => 'change_skin', 'id' => 'change_skin',
], ],
'offline_access' => [
'id' => 'offline_access',
],
]; ];