Cuberite
A lightweight, fast and extensible game server for Minecraft
Authenticator.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
5 
6 #include "ClientHandle.h"
7 #include "HTTP/UrlClient.h"
8 #include "HTTP/UrlParser.h"
9 #include "IniFile.h"
10 #include "JsonUtils.h"
11 #include "json/json.h"
12 #include "Protocol/MojangAPI.h"
13 #include "Root.h"
14 #include "Server.h"
15 #include "UUID.h"
16 
17 
18 
19 
20 
21 constexpr char DEFAULT_AUTH_SERVER[] = "sessionserver.mojang.com";
22 constexpr char DEFAULT_AUTH_ADDRESS[] = "/session/minecraft/hasJoined?username=%USERNAME%&serverId=%SERVERID%";
23 
24 
25 
26 
27 
29  Super("Authenticator"),
30  m_Server(DEFAULT_AUTH_SERVER),
31  m_Address(DEFAULT_AUTH_ADDRESS),
32  m_ShouldAuthenticate(true)
33 {
34 }
35 
36 
37 
38 
39 
41 {
42  Stop();
43 }
44 
45 
46 
47 
48 
50 {
51  m_Server = a_Settings.GetValueSet ("Authentication", "Server", DEFAULT_AUTH_SERVER);
52  m_Address = a_Settings.GetValueSet ("Authentication", "Address", DEFAULT_AUTH_ADDRESS);
53  m_ShouldAuthenticate = a_Settings.GetValueSetB("Authentication", "Authenticate", true);
54 
55  // prepend https:// if missing
56  constexpr std::string_view HttpPrefix = "http://";
57  constexpr std::string_view HttpsPrefix = "https://";
58 
59  if (
60  (std::string_view(m_Server).substr(0, HttpPrefix.size()) != HttpPrefix) &&
61  (std::string_view(m_Server).substr(0, HttpsPrefix.size()) != HttpsPrefix)
62  )
63  {
64  m_Server = "https://" + m_Server;
65  }
66 
67  {
68  auto [IsSuccessful, ErrorMessage] = cUrlParser::Validate(m_Server);
69  if (!IsSuccessful)
70  {
71  LOGWARNING("%s %d: Supplied invalid URL for configuration value [Authentication: Server]: \"%s\", using default! Error: %s", __FUNCTION__, __LINE__, m_Server.c_str(), ErrorMessage.c_str());
73  }
74  }
75 
76  {
77  auto [IsSuccessful, ErrorMessage] = cUrlParser::Validate(m_Server);
78  if (!IsSuccessful)
79  {
80  LOGWARNING("%s %d: Supplied invalid URL for configuration value [Authentication: Address]: \"%s\", using default! Error: %s", __FUNCTION__, __LINE__, m_Address.c_str(), ErrorMessage.c_str());
82  }
83  }
84 }
85 
86 
87 
88 
89 
90 void cAuthenticator::Authenticate(int a_ClientID, const std::string_view a_Username, const std::string_view a_ServerHash)
91 {
93  {
94  // An "authenticated" username, which is just what the client sent since auth is off.
95  std::string OfflineUsername(a_Username);
96 
97  // A specially constructed UUID based wholly on the username.
98  const auto OfflineUUID = cClientHandle::GenerateOfflineUUID(OfflineUsername);
99 
100  // "Authenticate" the user based on what little information we have:
101  cRoot::Get()->GetServer()->AuthenticateUser(a_ClientID, std::move(OfflineUsername), OfflineUUID, Json::Value());
102  return;
103  }
104 
105  cCSLock Lock(m_CS);
106  m_Queue.emplace_back(a_ClientID, a_Username, a_ServerHash);
108 }
109 
110 
111 
112 
113 
115 {
116  ReadSettings(a_Settings);
117  Super::Start();
118 }
119 
120 
121 
122 
123 
125 {
126  m_ShouldTerminate = true;
128  Super::Stop();
129 }
130 
131 
132 
133 
134 
136 {
137  for (;;)
138  {
139  cCSLock Lock(m_CS);
140  while (!m_ShouldTerminate && (m_Queue.size() == 0))
141  {
142  cCSUnlock Unlock(Lock);
144  }
145  if (m_ShouldTerminate)
146  {
147  return;
148  }
149  ASSERT(!m_Queue.empty());
150 
151  cAuthenticator::cUser User = std::move(m_Queue.front());
152  int & ClientID = User.m_ClientID;
153  AString & Username = User.m_Name;
154  AString & ServerID = User.m_ServerID;
155  m_Queue.pop_front();
156  Lock.Unlock();
157 
158  cUUID UUID;
159  Json::Value Properties;
160  if (AuthWithYggdrasil(Username, ServerID, UUID, Properties))
161  {
162  LOGINFO("User %s authenticated with UUID %s", Username.c_str(), UUID.ToShortString().c_str());
163  cRoot::Get()->GetServer()->AuthenticateUser(ClientID, std::move(Username), UUID, std::move(Properties));
164  }
165  else
166  {
167  cRoot::Get()->KickUser(ClientID, "Failed to authenticate account!");
168  }
169  } // for (-ever)
170 }
171 
172 
173 
174 
175 
176 bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_ServerId, cUUID & a_UUID, Json::Value & a_Properties) const
177 {
178  LOGD("Trying to authenticate user %s", a_UserName.c_str());
179 
180  // Create the GET request:
181  AString ActualAddress = m_Address;
182  ReplaceURL(ActualAddress, "%USERNAME%", a_UserName);
183  ReplaceURL(ActualAddress, "%SERVERID%", a_ServerId);
184 
185  // Create and send the HTTP request
186  auto [IsSuccessful, Response] = cUrlClient::BlockingGet(m_Server + ActualAddress);
187  if (!IsSuccessful)
188  {
189  return false;
190  }
191 
192  // Parse the Json response:
193  if (Response.empty())
194  {
195  return false;
196  }
197  Json::Value root;
198  if (!JsonUtils::ParseString(Response, root))
199  {
200  LOGWARNING("%s: Cannot parse received data (authentication) to JSON!", __FUNCTION__);
201  return false;
202  }
203  a_UserName = root.get("name", "Unknown").asString();
204  a_Properties = root["properties"];
205  if (!a_UUID.FromString(root.get("id", "").asString()))
206  {
207  LOGWARNING("%s: Received invalid UUID format", __FUNCTION__);
208  return false;
209  }
210 
211  // Store the player's profile in the MojangAPI caches:
212  cRoot::Get()->GetMojangAPI().AddPlayerProfile(a_UserName, a_UUID, a_Properties);
213 
214  return true;
215 }
216 
217 
218 
219 #ifdef ENABLE_PROPERTIES
220 
221 /* In case we want to export this function to the plugin API later - don't forget to add the relevant INI configuration lines for DEFAULT_PROPERTIES_ADDRESS */
222 
223 #define DEFAULT_PROPERTIES_ADDRESS "/session/minecraft/profile/%UUID%"
224 
225 // Gets the properties, such as skin, of a player based on their UUID via Mojang's API
226 bool GetPlayerProperties(const AString & a_UUID, Json::Value & a_Properties);
227 
228 bool cAuthenticator::GetPlayerProperties(const AString & a_UUID, Json::Value & a_Properties)
229 {
230  LOGD("Trying to get properties for user %s", a_UUID.c_str());
231 
232  // Create and send the HTTP request
233  auto [IsSuccessful, Response] = cUrlClient::BlockingGet(m_Server + ActualAddress);
234  if (!IsSuccessful)
235  {
236  return false;
237  }
238 
239  // Parse the Json response:
240  if (Response.empty())
241  {
242  return false;
243  }
244 
245  Json::Value root;
246  Json::Reader reader;
247  if (!reader.parse(Response, root, false))
248  {
249  LOGWARNING("cAuthenticator: Cannot parse received properties data to JSON!");
250  return false;
251  }
252 
253  a_Properties = root["properties"];
254  return true;
255 }
256 #endif
257 
258 
259 
260 
#define ASSERT(x)
Definition: Globals.h:276
void LOGWARNING(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:67
#define LOGD
Definition: LoggerSimple.h:83
void LOGINFO(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:61
constexpr char DEFAULT_AUTH_ADDRESS[]
constexpr char DEFAULT_AUTH_SERVER[]
void ReplaceURL(AString &iHayStack, const AString &iNeedle, const AString &iReplaceWith)
Replaces each occurence of iNeedle in iHayStack with iReplaceWith, after URL-encoding iReplaceWith.
std::string AString
Definition: StringUtils.h:11
bool ParseString(const AString &a_JsonStr, Json::Value &a_Root, AString *a_ErrorMsg)
Definition: JsonUtils.cpp:34
static cUUID GenerateOfflineUUID(const AString &a_Username)
Generates an UUID based on the player name provided.
static std::pair< bool, AString > BlockingGet(const AString &a_URL, AStringMap a_Headers={}, const AString &a_Body={}, const AStringMap &a_Options={})
Alias for BlockingRequest("GET", ...)
Definition: UrlClient.cpp:779
static std::pair< bool, AString > Validate(const AString &a_Url)
Checks if the supplied URL is valid.
Definition: UrlParser.cpp:202
RAII for cCriticalSection - locks the CS on creation, unlocks on destruction.
void Unlock(void)
Temporary RAII unlock for a cCSLock.
void Wait(void)
Waits until the event has been set.
Definition: Event.cpp:23
void Set(void)
Sets the event - releases one thread that has been waiting in Wait().
Definition: Event.cpp:52
std::atomic< bool > m_ShouldTerminate
The overriden Execute() method should check this value periodically and terminate if this is true.
Definition: IsThread.h:45
void Stop(void)
Signals the thread to terminate and waits until it's finished.
Definition: IsThread.cpp:48
void Start(void)
Starts the thread; returns without waiting for the actual start.
Definition: IsThread.cpp:35
cCriticalSection m_CS
Definition: Authenticator.h:71
bool m_ShouldAuthenticate
Definition: Authenticator.h:85
cEvent m_QueueNonempty
Definition: Authenticator.h:73
void ReadSettings(cSettingsRepositoryInterface &a_Settings)
(Re-)read server and address from INI:
void Stop(void)
Stops the authenticator thread.
AString m_Server
The server that is to be contacted for auth / UUID conversions.
Definition: Authenticator.h:76
virtual ~cAuthenticator() override
virtual void Execute(void) override
cIsThread override:
AString m_Address
The URL to use for auth, without server part.
Definition: Authenticator.h:82
bool AuthWithYggdrasil(AString &a_UserName, const AString &a_ServerId, cUUID &a_UUID, Json::Value &a_Properties) const
Returns true if the user authenticated okay, false on error Returns the case-corrected username,...
void Authenticate(int a_ClientID, std::string_view a_Username, std::string_view a_ServerHash)
Queues a request for authenticating a user.
cUserList m_Queue
Definition: Authenticator.h:72
void AddPlayerProfile(const AString &a_PlayerName, const cUUID &a_UUID, const Json::Value &a_Properties)
Called by the Authenticator to add a profile that it has received from authenticating a user.
Definition: MojangAPI.cpp:369
cServer * GetServer(void)
Definition: Root.h:71
static cRoot * Get()
Definition: Root.h:52
void KickUser(int a_ClientID, const AString &a_Reason)
Kicks the user, no matter in what world they are.
Definition: Root.cpp:550
cMojangAPI & GetMojangAPI(void)
Definition: Root.h:113
void AuthenticateUser(int a_ClientID, AString &&a_Username, const cUUID &a_UUID, Json::Value &&a_Properties)
Authenticates the specified user, called by cAuthenticator supplying player details from Mojang.
Definition: Server.cpp:715
virtual bool GetValueSetB(const AString &keyname, const AString &valuename, const bool defValue=false)=0
virtual AString GetValueSet(const AString &keyname, const AString &valuename, const AString &defValue="")=0
Gets the value; if not found, write the default to the repository.
Definition: UUID.h:11
AString ToShortString() const
Converts the UUID to a short form string (i.e without dashes).
Definition: UUID.cpp:133
bool FromString(const AString &a_StringUUID)
Tries to interpret the string as a short or long form UUID and assign from it.
Definition: UUID.cpp:102