PocketMine-MP 5.30.2 git-98f04176111e5ecab5e8814ffc69d992bfb64939
Loading...
Searching...
No Matches
LoginPacketHandler.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\network\mcpe\handler;
25
37use pocketmine\network\mcpe\protocol\types\login\AuthenticationType;
46use Ramsey\Uuid\Uuid;
47use function gettype;
48use function is_array;
49use function is_object;
50use function json_decode;
51use const JSON_THROW_ON_ERROR;
52
61 public function __construct(
62 private Server $server,
63 private NetworkSession $session,
64 private \Closure $playerInfoConsumer,
65 private \Closure $authCallback
66 ){}
67
68 public function handleLogin(LoginPacket $packet) : bool{
69 $authInfo = $this->parseAuthInfo($packet->authInfoJson);
70 $jwtChain = $this->parseJwtChain($authInfo->Certificate);
71 $extraData = $this->fetchAuthData($jwtChain);
72
73 if(!Player::isValidUserName($extraData->displayName)){
74 $this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidName());
75
76 return true;
77 }
78
79 $clientData = $this->parseClientData($packet->clientDataJwt);
80
81 try{
82 $skin = $this->session->getTypeConverter()->getSkinAdapter()->fromSkinData(ClientDataToSkinDataHelper::fromClientData($clientData));
83 }catch(\InvalidArgumentException | InvalidSkinException $e){
84 $this->session->disconnectWithError(
85 reason: "Invalid skin: " . $e->getMessage(),
86 disconnectScreenMessage: KnownTranslationFactory::disconnectionScreen_invalidSkin()
87 );
88
89 return true;
90 }
91
92 if(!Uuid::isValid($extraData->identity)){
93 throw new PacketHandlingException("Invalid login UUID");
94 }
95 $uuid = Uuid::fromString($extraData->identity);
96 $arrClientData = (array) $clientData;
97 $arrClientData["TitleID"] = $extraData->titleId;
98
99 if($extraData->XUID !== ""){
100 $playerInfo = new XboxLivePlayerInfo(
101 $extraData->XUID,
102 $extraData->displayName,
103 $uuid,
104 $skin,
105 $clientData->LanguageCode,
106 $arrClientData
107 );
108 }else{
109 $playerInfo = new PlayerInfo(
110 $extraData->displayName,
111 $uuid,
112 $skin,
113 $clientData->LanguageCode,
114 $arrClientData
115 );
116 }
117 ($this->playerInfoConsumer)($playerInfo);
118
119 $ev = new PlayerPreLoginEvent(
120 $playerInfo,
121 $this->session->getIp(),
122 $this->session->getPort(),
123 $this->server->requiresAuthentication()
124 );
125 if($this->server->getNetwork()->getValidConnectionCount() > $this->server->getMaxPlayers()){
126 $ev->setKickFlag(PlayerPreLoginEvent::KICK_FLAG_SERVER_FULL, KnownTranslationFactory::disconnectionScreen_serverFull());
127 }
128 if(!$this->server->isWhitelisted($playerInfo->getUsername())){
129 $ev->setKickFlag(PlayerPreLoginEvent::KICK_FLAG_SERVER_WHITELISTED, KnownTranslationFactory::pocketmine_disconnect_whitelisted());
130 }
131
132 $banMessage = null;
133 if(($banEntry = $this->server->getNameBans()->getEntry($playerInfo->getUsername())) !== null){
134 $banReason = $banEntry->getReason();
135 $banMessage = $banReason === "" ? KnownTranslationFactory::pocketmine_disconnect_ban_noReason() : KnownTranslationFactory::pocketmine_disconnect_ban($banReason);
136 }elseif(($banEntry = $this->server->getIPBans()->getEntry($this->session->getIp())) !== null){
137 $banReason = $banEntry->getReason();
138 $banMessage = KnownTranslationFactory::pocketmine_disconnect_ban($banReason !== "" ? $banReason : KnownTranslationFactory::pocketmine_disconnect_ban_ip());
139 }
140 if($banMessage !== null){
141 $ev->setKickFlag(PlayerPreLoginEvent::KICK_FLAG_BANNED, $banMessage);
142 }
143
144 $ev->call();
145 if(!$ev->isAllowed()){
146 $this->session->disconnect($ev->getFinalDisconnectReason(), $ev->getFinalDisconnectScreenMessage());
147 return true;
148 }
149
150 $this->processLogin($authInfo->Token, AuthenticationType::from($authInfo->AuthenticationType), $jwtChain->chain, $packet->clientDataJwt, $ev->isAuthRequired());
151
152 return true;
153 }
154
158 protected function parseAuthInfo(string $authInfo) : AuthenticationInfo{
159 try{
160 $authInfoJson = json_decode($authInfo, associative: false, flags: JSON_THROW_ON_ERROR);
161 }catch(\JsonException $e){
162 throw PacketHandlingException::wrap($e);
163 }
164 if(!is_object($authInfoJson)){
165 throw new \RuntimeException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object");
166 }
167
168 $mapper = new \JsonMapper();
169 $mapper->bExceptionOnMissingData = true;
170 $mapper->bExceptionOnUndefinedProperty = true;
171 $mapper->bStrictObjectTypeChecking = true;
172 try{
173 $clientData = $mapper->map($authInfoJson, new AuthenticationInfo());
174 }catch(\JsonMapper_Exception $e){
175 throw PacketHandlingException::wrap($e);
176 }
177 return $clientData;
178 }
179
183 protected function parseJwtChain(string $chainDataJwt) : JwtChain{
184 try{
185 $jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR);
186 }catch(\JsonException $e){
187 throw PacketHandlingException::wrap($e);
188 }
189 if(!is_object($jwtChainJson)){
190 throw new \RuntimeException("Unexpected type for JWT chain data: " . gettype($jwtChainJson) . ", expected object");
191 }
192
193 $mapper = new \JsonMapper();
194 $mapper->bExceptionOnMissingData = true;
195 $mapper->bExceptionOnUndefinedProperty = true;
196 $mapper->bStrictObjectTypeChecking = true;
197 try{
198 $clientData = $mapper->map($jwtChainJson, new JwtChain());
199 }catch(\JsonMapper_Exception $e){
200 throw PacketHandlingException::wrap($e);
201 }
202 return $clientData;
203 }
204
208 protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
210 $extraData = null;
211 foreach($chain->chain as $jwt){
212 //validate every chain element
213 try{
214 [, $claims, ] = JwtUtils::parse($jwt);
215 }catch(JwtException $e){
216 throw PacketHandlingException::wrap($e);
217 }
218 if(isset($claims["extraData"])){
219 if($extraData !== null){
220 throw new PacketHandlingException("Found 'extraData' more than once in chainData");
221 }
222
223 if(!is_array($claims["extraData"])){
224 throw new PacketHandlingException("'extraData' key should be an array");
225 }
226 $mapper = new \JsonMapper();
227 $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
228 $mapper->bExceptionOnMissingData = true;
229 $mapper->bExceptionOnUndefinedProperty = true;
230 $mapper->bStrictObjectTypeChecking = true;
231 try{
233 $extraData = $mapper->map($claims["extraData"], new AuthenticationData());
234 }catch(\JsonMapper_Exception $e){
235 throw PacketHandlingException::wrap($e);
236 }
237 }
238 }
239 if($extraData === null){
240 throw new PacketHandlingException("'extraData' not found in chain data");
241 }
242 return $extraData;
243 }
244
248 protected function parseClientData(string $clientDataJwt) : ClientData{
249 try{
250 [, $clientDataClaims, ] = JwtUtils::parse($clientDataJwt);
251 }catch(JwtException $e){
252 throw PacketHandlingException::wrap($e);
253 }
254
255 $mapper = new \JsonMapper();
256 $mapper->bEnforceMapType = false; //TODO: we don't really need this as an array, but right now we don't have enough models
257 $mapper->bExceptionOnMissingData = true;
258 $mapper->bExceptionOnUndefinedProperty = true;
259 $mapper->bStrictObjectTypeChecking = true;
260 try{
261 $clientData = $mapper->map($clientDataClaims, new ClientData());
262 }catch(\JsonMapper_Exception $e){
263 throw PacketHandlingException::wrap($e);
264 }
265 return $clientData;
266 }
267
276 protected function processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired) : void{
277 if($legacyCertificate === null){
278 throw new PacketHandlingException("Legacy certificate cannot be null");
279 }
280 $this->server->getAsyncPool()->submitTask(new ProcessLoginTask($legacyCertificate, $clientData, $authRequired, $this->authCallback));
281 $this->session->setHandler(null); //drop packets received during login verification
282 }
283}
processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired)
__construct(private Server $server, private NetworkSession $session, private \Closure $playerInfoConsumer, private \Closure $authCallback)