mirror of
https://github.com/elyby/accounts.git
synced 2024-11-26 16:52:02 +05:30
Merge pull request #21 from elyby/iss_20_minecraftservices_profile
MinecraftServices Profile info API endpoint
This commit is contained in:
commit
9c39e97640
@ -171,9 +171,15 @@ Docker:
|
|||||||
- docker push $WEB_LATEST_IMAGE_NAME
|
- docker push $WEB_LATEST_IMAGE_NAME
|
||||||
- docker push $DB_VERSIONED_IMAGE_NAME
|
- docker push $DB_VERSIONED_IMAGE_NAME
|
||||||
- docker push $DB_LATEST_IMAGE_NAME
|
- docker push $DB_LATEST_IMAGE_NAME
|
||||||
only:
|
rules:
|
||||||
- master
|
- if: '$CI_COMMIT_TAG'
|
||||||
- tags
|
when: on_success
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||||
|
when: on_success
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /\[deploy.*\]/'
|
||||||
|
when: on_success
|
||||||
|
# Default:
|
||||||
|
- when: never
|
||||||
|
|
||||||
##########
|
##########
|
||||||
# Deploy #
|
# Deploy #
|
||||||
|
@ -54,7 +54,7 @@ class TokensFactory extends Component {
|
|||||||
|
|
||||||
public function createForMinecraftAccount(Account $account, string $clientToken): Token {
|
public function createForMinecraftAccount(Account $account, string $clientToken): Token {
|
||||||
return Yii::$app->tokens->create([
|
return Yii::$app->tokens->create([
|
||||||
'scope' => $this->prepareScopes([P::MINECRAFT_SERVER_SESSION]),
|
'scope' => $this->prepareScopes([P::OBTAIN_OWN_ACCOUNT_INFO, P::MINECRAFT_SERVER_SESSION]),
|
||||||
'ely-client-token' => new EncryptedValue($clientToken),
|
'ely-client-token' => new EncryptedValue($clientToken),
|
||||||
'sub' => $this->buildSub($account->id),
|
'sub' => $this->buildSub($account->id),
|
||||||
'exp' => Carbon::now()->addDays(2)->getTimestamp(),
|
'exp' => Carbon::now()->addDays(2)->getTimestamp(),
|
||||||
|
@ -46,6 +46,7 @@ return [
|
|||||||
'/mojang/profiles/<username>' => 'mojang/api/uuid-by-username',
|
'/mojang/profiles/<username>' => 'mojang/api/uuid-by-username',
|
||||||
'/mojang/profiles/<uuid>/names' => 'mojang/api/usernames-by-uuid',
|
'/mojang/profiles/<uuid>/names' => 'mojang/api/usernames-by-uuid',
|
||||||
'POST /mojang/profiles' => 'mojang/api/uuids-by-usernames',
|
'POST /mojang/profiles' => 'mojang/api/uuids-by-usernames',
|
||||||
|
'GET /mojang/services/minecraft/profile' => 'mojang/services/profile',
|
||||||
|
|
||||||
// authlib-injector
|
// authlib-injector
|
||||||
'/authlib-injector/authserver/<action>' => 'authserver/authentication/<action>',
|
'/authlib-injector/authserver/<action>' => 'authserver/authentication/<action>',
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace api\modules\mojang\behaviors;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Yii;
|
||||||
|
use yii\base\Behavior;
|
||||||
|
use yii\base\Event;
|
||||||
|
use yii\web\NotFoundHttpException;
|
||||||
|
use yii\web\Response;
|
||||||
|
use yii\web\UnauthorizedHttpException;
|
||||||
|
|
||||||
|
final class ServiceErrorConverterBehavior extends Behavior {
|
||||||
|
|
||||||
|
public function events(): array {
|
||||||
|
return [
|
||||||
|
Response::EVENT_BEFORE_SEND => Closure::fromCallable([$this, 'beforeSend']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function beforeSend(Event $event): void {
|
||||||
|
/** @var Response $response */
|
||||||
|
$response = $event->sender;
|
||||||
|
$data = $response->data;
|
||||||
|
if ($data === null || !isset($data['status'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = Yii::$app->request;
|
||||||
|
$type = $data['type'];
|
||||||
|
switch ($type) {
|
||||||
|
case UnauthorizedHttpException::class:
|
||||||
|
$response->data = [
|
||||||
|
'path' => '/' . $request->getPathInfo(),
|
||||||
|
'errorType' => 'UnauthorizedOperationException',
|
||||||
|
'error' => 'UnauthorizedOperationException',
|
||||||
|
'errorMessage' => 'Unauthorized',
|
||||||
|
'developerMessage' => 'Unauthorized',
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case NotFoundHttpException::class:
|
||||||
|
$response->data = [
|
||||||
|
'path' => '/' . $request->getPathInfo(),
|
||||||
|
'errorType' => 'NOT_FOUND',
|
||||||
|
'error' => 'NOT_FOUND',
|
||||||
|
'errorMessage' => 'The server has not found anything matching the request URI',
|
||||||
|
'developerMessage' => 'The server has not found anything matching the request URI',
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
96
api/modules/mojang/controllers/ServicesController.php
Normal file
96
api/modules/mojang/controllers/ServicesController.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace api\modules\mojang\controllers;
|
||||||
|
|
||||||
|
use api\controllers\Controller;
|
||||||
|
use api\modules\mojang\behaviors\ServiceErrorConverterBehavior;
|
||||||
|
use api\rbac\Permissions;
|
||||||
|
use common\components\SkinsSystemApi;
|
||||||
|
use Exception;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
|
use Yii;
|
||||||
|
use yii\filters\AccessControl;
|
||||||
|
use yii\filters\VerbFilter;
|
||||||
|
use yii\helpers\ArrayHelper;
|
||||||
|
use yii\web\NotFoundHttpException;
|
||||||
|
use function Ramsey\Uuid\v3;
|
||||||
|
|
||||||
|
final class ServicesController extends Controller {
|
||||||
|
|
||||||
|
public function behaviors(): array {
|
||||||
|
return ArrayHelper::merge(parent::behaviors(), [
|
||||||
|
'access' => [
|
||||||
|
'class' => AccessControl::class,
|
||||||
|
'rules' => [
|
||||||
|
[
|
||||||
|
'allow' => true,
|
||||||
|
'actions' => ['profile'],
|
||||||
|
'roles' => [Permissions::OBTAIN_ACCOUNT_INFO],
|
||||||
|
'roleParams' => function(): array {
|
||||||
|
$account = Yii::$app->user->identity->getAccount();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'accountId' => $account ? $account->id : -1,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'verbs' => [
|
||||||
|
'class' => VerbFilter::class,
|
||||||
|
'actions' => [
|
||||||
|
'profile' => ['GET'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init(): void {
|
||||||
|
parent::init();
|
||||||
|
$this->response->attachBehavior('errorFormatter', ServiceErrorConverterBehavior::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actionProfile(SkinsSystemApi $skinsSystemApi): array {
|
||||||
|
$account = Yii::$app->user->identity->getAccount();
|
||||||
|
if ($account === null) {
|
||||||
|
throw new NotFoundHttpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$textures = $skinsSystemApi->textures($account->username);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Yii::warning('Cannot get textures from skinsystem.ely.by. Exception message is ' . $e->getMessage());
|
||||||
|
$textures = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'id' => str_replace('-', '', $account->uuid),
|
||||||
|
'name' => $account->username,
|
||||||
|
'skins' => [],
|
||||||
|
'capes' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($textures['SKIN'])) {
|
||||||
|
$response['skins'][] = [
|
||||||
|
'id' => v3(Uuid::NAMESPACE_URL, $textures['SKIN']['url']),
|
||||||
|
'state' => 'ACTIVE',
|
||||||
|
'url' => $textures['SKIN']['url'],
|
||||||
|
'variant' => isset($textures['SKIN']['metadata']['model']) ? 'SLIM' : 'CLASSIC',
|
||||||
|
'alias' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($textures['CAPE'])) {
|
||||||
|
$response['capes'][] = [
|
||||||
|
'id' => v3(Uuid::NAMESPACE_URL, $textures['CAPE']['url']),
|
||||||
|
'state' => 'ACTIVE',
|
||||||
|
'url' => $textures['CAPE']['url'],
|
||||||
|
'alias' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
api/tests/functional/mojang/ProfileCest.php
Normal file
58
api/tests/functional/mojang/ProfileCest.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace api\tests\functional\mojang;
|
||||||
|
|
||||||
|
use api\tests\functional\_steps\OauthSteps;
|
||||||
|
use api\tests\FunctionalTester;
|
||||||
|
|
||||||
|
final class ProfileCest {
|
||||||
|
|
||||||
|
public function getProfile(FunctionalTester $I): void {
|
||||||
|
$I->amAuthenticated();
|
||||||
|
$I->sendGet('/api/mojang/services/minecraft/profile');
|
||||||
|
$I->canSeeResponseCodeIs(200);
|
||||||
|
$I->canSeeResponseContainsJson([
|
||||||
|
'id' => 'df936908b2e1544d96f82977ec213022',
|
||||||
|
'name' => 'Admin',
|
||||||
|
'skins' => [
|
||||||
|
[
|
||||||
|
'id' => '1794a784-2d87-32f0-b233-0b2fd5682444',
|
||||||
|
'state' => 'ACTIVE',
|
||||||
|
'url' => 'http://localhost/skin.png',
|
||||||
|
'variant' => 'CLASSIC',
|
||||||
|
'alias' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'capes' => [],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfileAsServiceAccount(OauthSteps $I): void {
|
||||||
|
$accessToken = $I->getAccessTokenByClientCredentialsGrant(['internal_account_info']);
|
||||||
|
$I->amBearerAuthenticated($accessToken);
|
||||||
|
|
||||||
|
$I->sendGet('/api/mojang/services/minecraft/profile');
|
||||||
|
$I->canSeeResponseCodeIs(404);
|
||||||
|
$I->canSeeResponseContainsJson([
|
||||||
|
'path' => '/mojang/services/minecraft/profile',
|
||||||
|
'errorType' => 'NOT_FOUND',
|
||||||
|
'error' => 'NOT_FOUND',
|
||||||
|
'errorMessage' => 'The server has not found anything matching the request URI',
|
||||||
|
'developerMessage' => 'The server has not found anything matching the request URI',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfileWithoutAuthentication(FunctionalTester $I): void {
|
||||||
|
$I->sendGet('/api/mojang/services/minecraft/profile');
|
||||||
|
$I->canSeeResponseCodeIs(401);
|
||||||
|
$I->canSeeResponseContainsJson([
|
||||||
|
'path' => '/mojang/services/minecraft/profile',
|
||||||
|
'errorType' => 'UnauthorizedOperationException',
|
||||||
|
'error' => 'UnauthorizedOperationException',
|
||||||
|
'errorMessage' => 'Unauthorized',
|
||||||
|
'developerMessage' => 'Unauthorized',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -93,7 +93,7 @@ class TokensFactoryTest extends TestCase {
|
|||||||
$token = $factory->createForMinecraftAccount($account, $clientToken);
|
$token = $factory->createForMinecraftAccount($account, $clientToken);
|
||||||
$this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 5);
|
$this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 5);
|
||||||
$this->assertEqualsWithDelta(time() + 60 * 60 * 24 * 2, $token->getClaim('exp'), 5);
|
$this->assertEqualsWithDelta(time() + 60 * 60 * 24 * 2, $token->getClaim('exp'), 5);
|
||||||
$this->assertSame('minecraft_server_session', $token->getClaim('scope'));
|
$this->assertSame('obtain_own_account_info minecraft_server_session', $token->getClaim('scope'));
|
||||||
$this->assertNotSame('e44fae79-f80e-4975-952e-47e8a9ed9472', $token->getClaim('ely-client-token'));
|
$this->assertNotSame('e44fae79-f80e-4975-952e-47e8a9ed9472', $token->getClaim('ely-client-token'));
|
||||||
$this->assertSame('ely|1', $token->getClaim('sub'));
|
$this->assertSame('ely|1', $token->getClaim('sub'));
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ use const common\LATEST_RULES_VERSION;
|
|||||||
/**
|
/**
|
||||||
* Fields:
|
* Fields:
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string $uuid
|
* @property string $uuid UUID with dashes
|
||||||
* @property string $username
|
* @property string $username
|
||||||
* @property string $email
|
* @property string $email
|
||||||
* @property string $password_hash
|
* @property string $password_hash
|
||||||
|
Loading…
Reference in New Issue
Block a user