mirror of
				https://github.com/elyby/accounts.git
				synced 2025-05-31 14:11:46 +05:30 
			
		
		
		
	Merge pull request #21 from elyby/iss_20_minecraftservices_profile
MinecraftServices Profile info API endpoint
This commit is contained in:
		| @@ -171,9 +171,15 @@ Docker: | ||||
|     - docker push $WEB_LATEST_IMAGE_NAME | ||||
|     - docker push $DB_VERSIONED_IMAGE_NAME | ||||
|     - docker push $DB_LATEST_IMAGE_NAME | ||||
|   only: | ||||
|     - master | ||||
|     - tags | ||||
|   rules: | ||||
|     - if: '$CI_COMMIT_TAG' | ||||
|       when: on_success | ||||
|     - if: '$CI_COMMIT_BRANCH == "master"' | ||||
|       when: on_success | ||||
|     - if: '$CI_COMMIT_MESSAGE =~ /\[deploy.*\]/' | ||||
|       when: on_success | ||||
|       # Default: | ||||
|     - when: never | ||||
|  | ||||
| ########## | ||||
| # Deploy # | ||||
|   | ||||
| @@ -54,7 +54,7 @@ class TokensFactory extends Component { | ||||
|  | ||||
|     public function createForMinecraftAccount(Account $account, string $clientToken): Token { | ||||
|         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), | ||||
|             'sub' => $this->buildSub($account->id), | ||||
|             'exp' => Carbon::now()->addDays(2)->getTimestamp(), | ||||
|   | ||||
| @@ -46,6 +46,7 @@ return [ | ||||
|     '/mojang/profiles/<username>' => 'mojang/api/uuid-by-username', | ||||
|     '/mojang/profiles/<uuid>/names' => 'mojang/api/usernames-by-uuid', | ||||
|     'POST /mojang/profiles' => 'mojang/api/uuids-by-usernames', | ||||
|     'GET /mojang/services/minecraft/profile' => 'mojang/services/profile', | ||||
|  | ||||
|     // authlib-injector | ||||
|     '/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); | ||||
|         $this->assertEqualsWithDelta(time(), $token->getClaim('iat'), 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->assertSame('ely|1', $token->getClaim('sub')); | ||||
|     } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ use const common\LATEST_RULES_VERSION; | ||||
| /** | ||||
|  * Fields: | ||||
|  * @property int         $id | ||||
|  * @property string      $uuid | ||||
|  * @property string      $uuid UUID with dashes | ||||
|  * @property string      $username | ||||
|  * @property string      $email | ||||
|  * @property string      $password_hash | ||||
|   | ||||
		Reference in New Issue
	
	Block a user