mirror of
https://github.com/elyby/accounts.git
synced 2024-11-30 02:32:26 +05:30
Добавлена логика HasJoined для сервера авторизации Minecraft
Исправлена ошибка в JoinForm Добавлено базовое API для общения с сервером системы скинов
This commit is contained in:
parent
198e440b8d
commit
68ce8b3fb6
@ -4,9 +4,12 @@ namespace api\modules\session\controllers;
|
||||
use api\controllers\ApiController;
|
||||
use api\modules\session\exceptions\ForbiddenOperationException;
|
||||
use api\modules\session\exceptions\SessionServerException;
|
||||
use api\modules\session\models\HasJoinedForm;
|
||||
use api\modules\session\models\JoinForm;
|
||||
use api\modules\session\models\protocols\LegacyJoin;
|
||||
use api\modules\session\models\protocols\ModernHasJoined;
|
||||
use api\modules\session\models\protocols\ModernJoin;
|
||||
use common\models\Textures;
|
||||
use Yii;
|
||||
use yii\web\Response;
|
||||
|
||||
@ -57,4 +60,38 @@ class SessionController extends ApiController {
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
public function actionHasJoined() {
|
||||
Yii::$app->response->format = Response::FORMAT_JSON;
|
||||
|
||||
$data = Yii::$app->request->get();
|
||||
$protocol = new ModernHasJoined($data['username'] ?? '', $data['serverId'] ?? '');
|
||||
$hasJoinedForm = new HasJoinedForm($protocol);
|
||||
$account = $hasJoinedForm->hasJoined();
|
||||
$textures = new Textures($account);
|
||||
|
||||
return $textures->getMinecraftResponse();
|
||||
}
|
||||
|
||||
public function actionHasJoinedLegacy() {
|
||||
Yii::$app->response->format = Response::FORMAT_RAW;
|
||||
|
||||
$data = Yii::$app->request->get();
|
||||
$protocol = new ModernHasJoined($data['user'] ?? '', $data['serverId'] ?? '');
|
||||
$hasJoinedForm = new HasJoinedForm($protocol);
|
||||
try {
|
||||
$hasJoinedForm->hasJoined();
|
||||
} catch (SessionServerException $e) {
|
||||
Yii::$app->response->statusCode = $e->statusCode;
|
||||
if ($e instanceof ForbiddenOperationException) {
|
||||
$message = 'NO';
|
||||
} else {
|
||||
$message = $e->getMessage();
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
return 'YES';
|
||||
}
|
||||
|
||||
}
|
||||
|
52
api/modules/session/models/HasJoinedForm.php
Normal file
52
api/modules/session/models/HasJoinedForm.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace api\modules\session\models;
|
||||
|
||||
use api\modules\session\exceptions\ForbiddenOperationException;
|
||||
use api\modules\session\exceptions\IllegalArgumentException;
|
||||
use api\modules\session\models\protocols\HasJoinedInterface;
|
||||
use api\modules\session\Module as Session;
|
||||
use common\models\Account;
|
||||
use yii\base\ErrorException;
|
||||
use yii\base\Model;
|
||||
|
||||
class HasJoinedForm extends Model {
|
||||
|
||||
private $protocol;
|
||||
|
||||
public function __construct(HasJoinedInterface $protocol, array $config = []) {
|
||||
$this->protocol = $protocol;
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
public function hasJoined() : Account {
|
||||
if (!$this->protocol->validate()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
$serverId = $this->protocol->getServerId();
|
||||
$username = $this->protocol->getUsername();
|
||||
|
||||
Session::info(
|
||||
"Server with server_id = '{$serverId}' trying to verify has joined user with username = '{$username}'."
|
||||
);
|
||||
|
||||
$joinModel = SessionModel::find($username, $serverId);
|
||||
if ($joinModel === null) {
|
||||
Session::error("Not found join operation for username = '{$username}'.");
|
||||
throw new ForbiddenOperationException('Invalid token.');
|
||||
}
|
||||
|
||||
$joinModel->delete();
|
||||
$account = $joinModel->getAccount();
|
||||
if ($account === null) {
|
||||
throw new ErrorException('Account must exists');
|
||||
}
|
||||
|
||||
Session::info(
|
||||
"User with username = '{$username}' successfully verified by server with server_id = '{$serverId}'."
|
||||
);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
}
|
@ -18,9 +18,9 @@ use yii\web\UnauthorizedHttpException;
|
||||
|
||||
class JoinForm extends Model {
|
||||
|
||||
private $accessToken;
|
||||
private $selectedProfile;
|
||||
private $serverId;
|
||||
public $accessToken;
|
||||
public $selectedProfile;
|
||||
public $serverId;
|
||||
|
||||
/**
|
||||
* @var Account|null
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace api\modules\session\models;
|
||||
|
||||
use common\models\Account;
|
||||
use Yii;
|
||||
|
||||
class SessionModel {
|
||||
@ -50,7 +51,15 @@ class SessionModel {
|
||||
return Yii::$app->redis->executeCommand('DEL', [static::buildKey($this->username, $this->serverId)]);
|
||||
}
|
||||
|
||||
protected static function buildKey($username, $serverId) {
|
||||
/**
|
||||
* @return Account|null
|
||||
* TODO: после перехода на PHP 7.1 установить тип как ?Account
|
||||
*/
|
||||
public function getAccount() {
|
||||
return Account::findOne(['username' => $this->username]);
|
||||
}
|
||||
|
||||
protected static function buildKey($username, $serverId) : string {
|
||||
return md5('minecraft:join-server:' . mb_strtolower($username) . ':' . $serverId);
|
||||
}
|
||||
|
||||
|
31
api/modules/session/models/protocols/BaseHasJoined.php
Normal file
31
api/modules/session/models/protocols/BaseHasJoined.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace api\modules\session\models\protocols;
|
||||
|
||||
use yii\validators\RequiredValidator;
|
||||
|
||||
abstract class BaseHasJoined implements HasJoinedInterface {
|
||||
|
||||
private $username;
|
||||
private $serverId;
|
||||
|
||||
public function __construct(string $username, string $serverId) {
|
||||
$this->username = $username;
|
||||
$this->serverId = $serverId;
|
||||
}
|
||||
|
||||
public function getUsername() : string {
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getServerId() : string {
|
||||
return $this->serverId;
|
||||
}
|
||||
|
||||
public function validate() : bool {
|
||||
$validator = new RequiredValidator();
|
||||
|
||||
return $validator->validate($this->username)
|
||||
&& $validator->validate($this->serverId);
|
||||
}
|
||||
|
||||
}
|
12
api/modules/session/models/protocols/HasJoinedInterface.php
Normal file
12
api/modules/session/models/protocols/HasJoinedInterface.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace api\modules\session\models\protocols;
|
||||
|
||||
interface HasJoinedInterface {
|
||||
|
||||
public function getUsername() : string;
|
||||
|
||||
public function getServerId() : string;
|
||||
|
||||
public function validate() : bool;
|
||||
|
||||
}
|
6
api/modules/session/models/protocols/LegacyHasJoined.php
Normal file
6
api/modules/session/models/protocols/LegacyHasJoined.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace api\modules\session\models\protocols;
|
||||
|
||||
class LegacyHasJoined extends BaseHasJoined {
|
||||
|
||||
}
|
6
api/modules/session/models/protocols/ModernHasJoined.php
Normal file
6
api/modules/session/models/protocols/ModernHasJoined.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace api\modules\session\models\protocols;
|
||||
|
||||
class ModernHasJoined extends BaseHasJoined {
|
||||
|
||||
}
|
@ -19,6 +19,7 @@ class Yii extends \yii\BaseYii {
|
||||
* @property \yii\swiftmailer\Mailer $mailer
|
||||
* @property \yii\redis\Connection $redis
|
||||
* @property \common\components\RabbitMQ\Component $amqp
|
||||
* @property \GuzzleHttp\Client $guzzle
|
||||
*/
|
||||
abstract class BaseApplication extends yii\base\Application {
|
||||
}
|
||||
|
29
common/components/SkinSystem/Api.php
Normal file
29
common/components/SkinSystem/Api.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace common\components\SkinSystem;
|
||||
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Yii;
|
||||
|
||||
class Api {
|
||||
|
||||
const BASE_DOMAIN = 'http://skinsystem.ely.by';
|
||||
|
||||
public function textures($username) : array {
|
||||
$response = $this->getClient()->get($this->getBuildUrl('/textures/' . $username));
|
||||
$textures = json_decode($response->getBody(), true);
|
||||
|
||||
return $textures;
|
||||
}
|
||||
|
||||
protected function getBuildUrl(string $url) : string {
|
||||
return self::BASE_DOMAIN . $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GuzzleClient
|
||||
*/
|
||||
protected function getClient() : GuzzleClient {
|
||||
return Yii::$app->guzzle;
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,9 @@ return [
|
||||
'amqp' => [
|
||||
'class' => \common\components\RabbitMQ\Component::class,
|
||||
],
|
||||
'guzzle' => [
|
||||
'class' => \GuzzleHttp\Client::class,
|
||||
],
|
||||
],
|
||||
'aliases' => [
|
||||
'@bower' => '@vendor/bower-asset',
|
||||
|
71
common/models/Textures.php
Normal file
71
common/models/Textures.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace common\models;
|
||||
|
||||
use common\components\SkinSystem\Api as SkinSystemApi;
|
||||
|
||||
class Textures {
|
||||
|
||||
public $displayElyMark = true;
|
||||
|
||||
/**
|
||||
* @var Account
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
public function __construct(Account $account) {
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
public function getMinecraftResponse() {
|
||||
$response = [
|
||||
'name' => $this->account->username,
|
||||
'id' => str_replace('-', '', $this->account->uuid),
|
||||
'properties' => [
|
||||
[
|
||||
'name' => 'textures',
|
||||
'signature' => 'Cg==',
|
||||
'value' => $this->getTexturesValue(),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->displayElyMark) {
|
||||
$response['ely'] = true;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getTexturesValue($encrypted = true) {
|
||||
$array = [
|
||||
'timestamp' => time() + 60 * 60 * 24 * 2,
|
||||
'profileId' => str_replace('-', '', $this->account->uuid),
|
||||
'profileName' => $this->account->username,
|
||||
'textures' => $this->getTextures(),
|
||||
];
|
||||
|
||||
if ($this->displayElyMark) {
|
||||
$array['ely'] = true;
|
||||
}
|
||||
|
||||
if (!$encrypted) {
|
||||
return $array;
|
||||
} else {
|
||||
return $this->encrypt($array);
|
||||
}
|
||||
}
|
||||
|
||||
public function getTextures() {
|
||||
$api = new SkinSystemApi();
|
||||
return $api->textures($this->account->username);
|
||||
}
|
||||
|
||||
public static function encrypt(array $data) {
|
||||
return base64_encode(stripcslashes(json_encode($data)));
|
||||
}
|
||||
|
||||
public static function decrypt($string, $assoc = true) {
|
||||
return json_decode(base64_decode($string), $assoc);
|
||||
}
|
||||
|
||||
}
|
@ -18,4 +18,14 @@ class SessionServerRoute extends BasePage {
|
||||
$this->actor->sendGET($this->getUrl(), $params);
|
||||
}
|
||||
|
||||
public function hasJoined(array $params) {
|
||||
$this->route = ['sessionserver/session/has-joined'];
|
||||
$this->actor->sendGET($this->getUrl(), $params);
|
||||
}
|
||||
|
||||
public function hasJoinedLegacy(array $params) {
|
||||
$this->route = ['sessionserver/session/has-joined-legacy'];
|
||||
$this->actor->sendGET($this->getUrl(), $params);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ modules:
|
||||
- tests\codeception\common\_support\FixtureHelper
|
||||
- Redis
|
||||
- AMQP
|
||||
- Asserts
|
||||
- REST:
|
||||
depends: Yii2
|
||||
config:
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\functional\_steps;
|
||||
|
||||
use common\models\OauthScope as S;
|
||||
use Faker\Provider\Uuid;
|
||||
use tests\codeception\api\_pages\SessionServerRoute;
|
||||
|
||||
class SessionServerSteps extends \tests\codeception\api\FunctionalTester {
|
||||
|
||||
public function amJoined($byLegacy = false) {
|
||||
$oauthSteps = new OauthSteps($this->scenario);
|
||||
$accessToken = $oauthSteps->getAccessToken([S::MINECRAFT_SERVER_SESSION]);
|
||||
$route = new SessionServerRoute($this);
|
||||
$serverId = Uuid::uuid();
|
||||
$username = 'Admin';
|
||||
|
||||
if ($byLegacy) {
|
||||
$route->joinLegacy([
|
||||
'sessionId' => 'token:' . $accessToken . ':' . 'df936908-b2e1-544d-96f8-2977ec213022',
|
||||
'user' => $username,
|
||||
'serverId' => $serverId,
|
||||
]);
|
||||
|
||||
$this->canSeeResponseEquals('OK');
|
||||
} else {
|
||||
$route->join([
|
||||
'accessToken' => $accessToken,
|
||||
'selectedProfile' => 'df936908-b2e1-544d-96f8-2977ec213022',
|
||||
'serverId' => $serverId,
|
||||
]);
|
||||
|
||||
$this->canSeeResponseContainsJson(['id' => 'OK']);
|
||||
}
|
||||
|
||||
return [$username, $serverId];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\functional\sessionserver;
|
||||
|
||||
use Faker\Provider\Uuid;
|
||||
use tests\codeception\api\_pages\SessionServerRoute;
|
||||
use tests\codeception\api\functional\_steps\SessionServerSteps;
|
||||
use tests\codeception\api\FunctionalTester;
|
||||
|
||||
class HasJoinedCest {
|
||||
|
||||
/**
|
||||
* @var SessionServerRoute
|
||||
*/
|
||||
private $route;
|
||||
|
||||
public function _before(FunctionalTester $I) {
|
||||
$this->route = new SessionServerRoute($I);
|
||||
}
|
||||
|
||||
public function hasJoined(SessionServerSteps $I) {
|
||||
$I->wantTo('check hasJoined user to some server');
|
||||
list($username, $serverId) = $I->amJoined();
|
||||
|
||||
$this->route->hasJoined([
|
||||
'username' => $username,
|
||||
'serverId' => $serverId,
|
||||
]);
|
||||
$I->seeResponseCodeIs(200);
|
||||
$I->seeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'name' => $username,
|
||||
'id' => 'df936908b2e1544d96f82977ec213022',
|
||||
'ely' => true,
|
||||
'properties' => [
|
||||
[
|
||||
'name' => 'textures',
|
||||
'signature' => 'Cg==',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$I->canSeeResponseJsonMatchesJsonPath('$.properties[0].value');
|
||||
$value = json_decode($I->grabResponse(), true)['properties'][0]['value'];
|
||||
$decoded = json_decode(base64_decode($value), true);
|
||||
$I->assertArrayHasKey('timestamp', $decoded);
|
||||
$I->assertArrayHasKey('textures', $decoded);
|
||||
$I->assertEquals('df936908b2e1544d96f82977ec213022', $decoded['profileId']);
|
||||
$I->assertEquals('Admin', $decoded['profileName']);
|
||||
$I->assertTrue($decoded['ely']);
|
||||
$textures = $decoded['textures'];
|
||||
$I->assertArrayHasKey('SKIN', $textures);
|
||||
$skinTextures = $textures['SKIN'];
|
||||
$I->assertArrayHasKey('url', $skinTextures);
|
||||
$I->assertArrayHasKey('hash', $skinTextures);
|
||||
}
|
||||
|
||||
public function wrongArguments(FunctionalTester $I) {
|
||||
$I->wantTo('get error on wrong amount of arguments');
|
||||
$this->route->hasJoined([
|
||||
'wrong' => 'argument',
|
||||
]);
|
||||
$I->canSeeResponseCodeIs(400);
|
||||
$I->canSeeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'error' => 'IllegalArgumentException',
|
||||
'errorMessage' => 'credentials can not be null.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasJoinedWithNoJoinOperation(FunctionalTester $I) {
|
||||
$I->wantTo('hasJoined to some server without join call');
|
||||
$this->route->hasJoined([
|
||||
'username' => 'some-username',
|
||||
'serverId' => Uuid::uuid(),
|
||||
]);
|
||||
$I->seeResponseCodeIs(401);
|
||||
$I->seeResponseIsJson();
|
||||
$I->canSeeResponseContainsJson([
|
||||
'error' => 'ForbiddenOperationException',
|
||||
'errorMessage' => 'Invalid token.',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace tests\codeception\api\functional\sessionserver;
|
||||
|
||||
use Faker\Provider\Uuid;
|
||||
use tests\codeception\api\_pages\SessionServerRoute;
|
||||
use tests\codeception\api\functional\_steps\SessionServerSteps;
|
||||
use tests\codeception\api\FunctionalTester;
|
||||
|
||||
class HasJoinedLegacyCest {
|
||||
|
||||
/**
|
||||
* @var SessionServerRoute
|
||||
*/
|
||||
private $route;
|
||||
|
||||
public function _before(FunctionalTester $I) {
|
||||
$this->route = new SessionServerRoute($I);
|
||||
}
|
||||
|
||||
public function hasJoined(SessionServerSteps $I) {
|
||||
$I->wantTo('test hasJoined user to some server by legacy version');
|
||||
list($username, $serverId) = $I->amJoined(true);
|
||||
|
||||
$this->route->hasJoinedLegacy([
|
||||
'user' => $username,
|
||||
'serverId' => $serverId,
|
||||
]);
|
||||
$I->seeResponseCodeIs(200);
|
||||
$I->canSeeResponseEquals('YES');
|
||||
}
|
||||
|
||||
public function wrongArguments(FunctionalTester $I) {
|
||||
$I->wantTo('get error on wrong amount of arguments');
|
||||
$this->route->hasJoinedLegacy([
|
||||
'wrong' => 'argument',
|
||||
]);
|
||||
$I->canSeeResponseCodeIs(400);
|
||||
$I->canSeeResponseEquals('credentials can not be null.');
|
||||
}
|
||||
|
||||
public function hasJoinedWithNoJoinOperation(FunctionalTester $I) {
|
||||
$I->wantTo('hasJoined by legacy version to some server without join call');
|
||||
$this->route->hasJoinedLegacy([
|
||||
'user' => 'random-username',
|
||||
'serverId' => Uuid::uuid(),
|
||||
]);
|
||||
$I->seeResponseCodeIs(401);
|
||||
$I->canSeeResponseEquals('NO');
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user