mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-05 01:16:15 +00:00
Introduce support for Translatable disconnection messages
this allows localizing disconnection screens (at least, once #4512 has been addressed) and the disconnect reasons shown on the console. We already had disconnect messages implicitly localized in a few places, so this is just formalizing it. This does break BC with any code that previously passed translation keys as the disconnect screen message, because they'll no longer be translated (only Translatables will be translatated now).
This commit is contained in:
@ -23,7 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\auth;
|
||||
|
||||
use pocketmine\lang\KnownTranslationKeys;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\network\mcpe\JwtException;
|
||||
use pocketmine\network\mcpe\JwtUtils;
|
||||
use pocketmine\network\mcpe\protocol\types\login\JwtChainLinkBody;
|
||||
@ -49,7 +50,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
* keychain is invalid for whatever reason (bad signature, not in nbf-exp window, etc). If this is non-null, the
|
||||
* keychain might have been tampered with. The player will always be disconnected if this is non-null.
|
||||
*/
|
||||
private ?string $error = "Unknown";
|
||||
private Translatable|string|null $error = "Unknown";
|
||||
/**
|
||||
* Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang
|
||||
* root public key.
|
||||
@ -59,7 +60,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
|
||||
/**
|
||||
* @param string[] $chainJwts
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPublicKey) : void $onCompletion
|
||||
* @phpstan-param \Closure(bool $isAuthenticated, bool $authRequired, Translatable|string|null $error, ?string $clientPublicKey) : void $onCompletion
|
||||
*/
|
||||
public function __construct(
|
||||
array $chainJwts,
|
||||
@ -76,7 +77,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
$this->clientPublicKey = $this->validateChain();
|
||||
$this->error = null;
|
||||
}catch(VerifyLoginException $e){
|
||||
$this->error = $e->getMessage();
|
||||
$this->error = $e->getDisconnectMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +110,8 @@ class ProcessLoginTask extends AsyncTask{
|
||||
try{
|
||||
[$headersArray, $claimsArray, ] = JwtUtils::parse($jwt);
|
||||
}catch(JwtException $e){
|
||||
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), 0, $e);
|
||||
//TODO: we shouldn't be showing internal information like this to the client
|
||||
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e);
|
||||
}
|
||||
|
||||
$mapper = new \JsonMapper();
|
||||
@ -121,21 +123,23 @@ class ProcessLoginTask extends AsyncTask{
|
||||
/** @var JwtHeader $headers */
|
||||
$headers = $mapper->map($headersArray, new JwtHeader());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), 0, $e);
|
||||
//TODO: we shouldn't be showing internal information like this to the client
|
||||
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e);
|
||||
}
|
||||
|
||||
$headerDerKey = base64_decode($headers->x5u, true);
|
||||
if($headerDerKey === false){
|
||||
//TODO: we shouldn't be showing internal information like this to the client
|
||||
throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u");
|
||||
}
|
||||
|
||||
if($currentPublicKey === null){
|
||||
if(!$first){
|
||||
throw new VerifyLoginException(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_MISSINGKEY);
|
||||
throw new VerifyLoginException("Missing JWT public key", KnownTranslationFactory::pocketmine_disconnect_invalidSession_missingKey());
|
||||
}
|
||||
}elseif($headerDerKey !== $currentPublicKey){
|
||||
//Fast path: if the header key doesn't match what we expected, the signature isn't going to validate anyway
|
||||
throw new VerifyLoginException(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE);
|
||||
throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
|
||||
}
|
||||
|
||||
try{
|
||||
@ -145,10 +149,10 @@ class ProcessLoginTask extends AsyncTask{
|
||||
}
|
||||
try{
|
||||
if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){
|
||||
throw new VerifyLoginException(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_BADSIGNATURE);
|
||||
throw new VerifyLoginException("Invalid JWT signature", KnownTranslationFactory::pocketmine_disconnect_invalidSession_badSignature());
|
||||
}
|
||||
}catch(JwtException $e){
|
||||
throw new VerifyLoginException($e->getMessage(), 0, $e);
|
||||
throw new VerifyLoginException($e->getMessage(), null, 0, $e);
|
||||
}
|
||||
|
||||
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){
|
||||
@ -164,16 +168,16 @@ class ProcessLoginTask extends AsyncTask{
|
||||
/** @var JwtChainLinkBody $claims */
|
||||
$claims = $mapper->map($claimsArray, new JwtChainLinkBody());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), 0, $e);
|
||||
throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e);
|
||||
}
|
||||
|
||||
$time = time();
|
||||
if(isset($claims->nbf) && $claims->nbf > $time + self::CLOCK_DRIFT_MAX){
|
||||
throw new VerifyLoginException(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_TOOEARLY);
|
||||
throw new VerifyLoginException("JWT not yet valid", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooEarly());
|
||||
}
|
||||
|
||||
if(isset($claims->exp) && $claims->exp < $time - self::CLOCK_DRIFT_MAX){
|
||||
throw new VerifyLoginException(KnownTranslationKeys::POCKETMINE_DISCONNECT_INVALIDSESSION_TOOLATE);
|
||||
throw new VerifyLoginException("JWT expired", KnownTranslationFactory::pocketmine_disconnect_invalidSession_tooLate());
|
||||
}
|
||||
|
||||
if(isset($claims->identityPublicKey)){
|
||||
@ -188,7 +192,7 @@ class ProcessLoginTask extends AsyncTask{
|
||||
public function onCompletion() : void{
|
||||
/**
|
||||
* @var \Closure $callback
|
||||
* @phpstan-var \Closure(bool, bool, ?string, ?string) : void $callback
|
||||
* @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback
|
||||
*/
|
||||
$callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION);
|
||||
$callback($this->authenticated, $this->authRequired, $this->error, $this->clientPublicKey);
|
||||
|
@ -23,6 +23,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\auth;
|
||||
|
||||
use pocketmine\lang\Translatable;
|
||||
|
||||
class VerifyLoginException extends \RuntimeException{
|
||||
|
||||
private Translatable|string $disconnectMessage;
|
||||
|
||||
public function __construct(string $message, Translatable|string|null $disconnectMessage = null, int $code = 0, ?\Throwable $previous = null){
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->disconnectMessage = $disconnectMessage ?? $message;
|
||||
}
|
||||
|
||||
public function getDisconnectMessage() : Translatable|string{ return $this->disconnectMessage; }
|
||||
}
|
||||
|
Reference in New Issue
Block a user