aboutsummaryrefslogtreecommitdiff
path: root/src/server/bnetserver/REST/LoginRESTService.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2017-10-03 18:03:57 +0200
committerShauren <shauren.trinity@gmail.com>2017-10-03 18:03:57 +0200
commit4d5eacd3af8406e41f6f335355b1f860f2323ee1 (patch)
tree72bf785977783898fa1fd5a22926ce9e9634f3df /src/server/bnetserver/REST/LoginRESTService.cpp
parent0b36d80115000ab9d1005e5999aad0c3acd9e041 (diff)
Core/Bnet: Added support for -launcherlogin logins (external client launcher required)
Diffstat (limited to 'src/server/bnetserver/REST/LoginRESTService.cpp')
-rw-r--r--src/server/bnetserver/REST/LoginRESTService.cpp228
1 files changed, 180 insertions, 48 deletions
diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp
index 9fd4b246df7..06115a7dc29 100644
--- a/src/server/bnetserver/REST/LoginRESTService.cpp
+++ b/src/server/bnetserver/REST/LoginRESTService.cpp
@@ -32,16 +32,15 @@
int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; }
-class AsyncLoginRequest
+class AsyncRequest
{
public:
- AsyncLoginRequest(std::shared_ptr<soap> client)
- : _client(std::move(client)) { }
+ AsyncRequest(soap const& server) : _client(server) { }
- AsyncLoginRequest(AsyncLoginRequest const&) = delete;
- AsyncLoginRequest& operator=(AsyncLoginRequest const&) = delete;
- AsyncLoginRequest(AsyncLoginRequest&&) = default;
- AsyncLoginRequest& operator=(AsyncLoginRequest&&) = default;
+ AsyncRequest(AsyncRequest const&) = delete;
+ AsyncRequest& operator=(AsyncRequest const&) = delete;
+ AsyncRequest(AsyncRequest&&) = default;
+ AsyncRequest& operator=(AsyncRequest&&) = default;
bool InvokeIfReady()
{
@@ -49,22 +48,25 @@ public:
return _callback->InvokeIfReady() == QueryCallback::Completed;
}
- soap* GetClient() const { return _client.get(); }
+ soap* GetClient() { return &_client; }
void SetCallback(std::unique_ptr<QueryCallback> callback) { _callback = std::move(callback); }
+ int32 GetResponseStatus() const { return _responseStatus; }
+ void SetResponseStatus(int32 responseStatus) { _responseStatus = responseStatus; }
private:
- std::shared_ptr<soap> _client;
+ soap _client;
std::unique_ptr<QueryCallback> _callback;
+ int32 _responseStatus;
};
int32 handle_get_plugin(soap* soapClient)
{
- return sLoginService.HandleGet(soapClient);
+ return sLoginService.HandleHttpRequest(soapClient, "GET", sLoginService._getHandlers);
}
int32 handle_post_plugin(soap* soapClient)
{
- return sLoginService.HandlePost(soapClient);
+ return sLoginService.HandleHttpRequest(soapClient, "POST", sLoginService._postHandlers);
}
bool LoginRESTService::Start(boost::asio::io_service* ioService)
@@ -124,6 +126,8 @@ bool LoginRESTService::Start(boost::asio::io_service* ioService)
input->set_type("submit");
input->set_label("Log In");
+ _loginTicketDuration = sConfigMgr->GetIntDefault("LoginREST.TicketDuration", 3600);
+
_thread = std::thread(std::bind(&LoginRESTService::Run, this));
return true;
}
@@ -171,9 +175,17 @@ void LoginRESTService::Run()
{ nullptr, nullptr }
};
+ _getHandlers["/bnetserver/login/"] = &LoginRESTService::HandleGetForm;
+ _getHandlers["/bnetserver/gameAccounts/"] = &LoginRESTService::HandleGetGameAccounts;
+ _getHandlers["/bnetserver/portal/"] = &LoginRESTService::HandleGetPortal;
+
+ _postHandlers["/bnetserver/login/"] = &LoginRESTService::HandlePostLogin;
+ _postHandlers["/bnetserver/refreshLoginTicket/"] = &LoginRESTService::HandlePostRefreshLoginTicket;
+
soap_register_plugin_arg(&soapServer, &http_get, (void*)&handle_get_plugin);
soap_register_plugin_arg(&soapServer, &http_post, handlers);
soap_register_plugin_arg(&soapServer, &ContentTypePlugin::Init, (void*)"application/json;charset=utf-8");
+ soap_register_plugin_arg(&soapServer, &ResponseCodePlugin::Init, nullptr);
// Use our already ready ssl context
soapServer.ctx = Battlenet::SslContext::instance().native_handle();
@@ -184,21 +196,20 @@ void LoginRESTService::Run()
if (!soap_valid_socket(soap_accept(&soapServer)))
continue; // ran into an accept timeout
- std::shared_ptr<soap> soapClient = std::make_shared<soap>(soapServer);
- boost::asio::ip::address_v4 address(soapClient->ip);
- if (soap_ssl_accept(soapClient.get()) != SOAP_OK)
+ std::shared_ptr<AsyncRequest> soapClient = std::make_shared<AsyncRequest>(soapServer);
+ if (soap_ssl_accept(soapClient->GetClient()) != SOAP_OK)
{
- TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP=%s", address.to_string().c_str());
+ TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP=%s", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string().c_str());
continue;
}
- TC_LOG_DEBUG("server.rest", "Accepted connection from IP=%s", address.to_string().c_str());
+ TC_LOG_DEBUG("server.rest", "Accepted connection from IP=%s", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string().c_str());
_ioService->post([soapClient]()
{
- soapClient->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count
- soap_begin(soapClient.get());
- soap_begin_recv(soapClient.get());
+ soapClient->GetClient()->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count
+ soap_begin(soapClient->GetClient());
+ soap_begin_recv(soapClient->GetClient());
});
}
@@ -208,51 +219,113 @@ void LoginRESTService::Run()
TC_LOG_INFO("server.rest", "Login service exiting...");
}
-int32 LoginRESTService::HandleGet(soap* soapClient)
+int32 LoginRESTService::HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers)
{
- boost::asio::ip::address_v4 address(soapClient->ip);
- std::string ip_address = address.to_string();
+ TC_LOG_DEBUG("server.rest", "[%s:%d] Handling %s request path=\"%s\"",
+ boost::asio::ip::address_v4(soapClient->ip).to_string().c_str(), soapClient->port, method, soapClient->path);
+
+ size_t pathLength = strlen(soapClient->path);
+ if (char const* queryPart = strchr(soapClient->path, '?'))
+ pathLength = queryPart - soapClient->path;
- TC_LOG_DEBUG("server.rest", "[%s:%d] Handling GET request path=\"%s\"", ip_address.c_str(), soapClient->port, soapClient->path);
+ auto handler = handlers.find(std::string{ soapClient->path, pathLength });
+ if (handler != handlers.end())
+ {
+ int32 status = (this->*handler->second)(*reinterpret_cast<std::shared_ptr<AsyncRequest>*>(soapClient->user));
+ if (status != SOAP_OK)
+ {
+ ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = status;
+ return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
+ }
- static std::string const expectedPath = "/bnetserver/login/";
- if (strstr(soapClient->path, expectedPath.c_str()) != &soapClient->path[0])
- return 404;
+ return SOAP_OK;
+ }
- return SendResponse(soapClient, _formInputs);
+ ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = 404;
+ return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse());
}
-int32 LoginRESTService::HandlePost(soap* soapClient)
+int32 LoginRESTService::HandleGetForm(std::shared_ptr<AsyncRequest> request)
{
- boost::asio::ip::address_v4 address(soapClient->ip);
- std::string ip_address = address.to_string();
+ return SendResponse(request->GetClient(), _formInputs);
+}
- TC_LOG_DEBUG("server.rest", "[%s:%d] Handling POST request path=\"%s\"", ip_address.c_str(), soapClient->port, soapClient->path);
+int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request)
+{
+ if (!request->GetClient()->userid)
+ return 401;
+
+ request->SetCallback(Trinity::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST);
+ stmt->setString(0, request->GetClient()->userid);
+ return stmt;
+ }())
+ .WithPreparedCallback([this, request](PreparedQueryResult result)
+ {
+ Battlenet::JSON::Login::GameAccountList response;
+ if (result)
+ {
+ auto formatDisplayName = [](char const* name) -> std::string
+ {
+ if (char const* hashPos = strchr(name, '#'))
+ return std::string("WoW") + ++hashPos;
+ else
+ return name;
+ };
+
+ time_t now = time(nullptr);
+ do
+ {
+ Field* fields = result->Fetch();
+ Battlenet::JSON::Login::GameAccountInfo* gameAccount = response.add_game_accounts();
+ gameAccount->set_display_name(formatDisplayName(fields[0].GetCString()));
+ gameAccount->set_expansion(fields[1].GetUInt8());
+ if (!fields[2].IsNull())
+ {
+ uint32 banDate = fields[2].GetUInt32();
+ uint32 unbanDate = fields[3].GetUInt32();
+ gameAccount->set_is_suspended(unbanDate > now);
+ gameAccount->set_is_banned(banDate == unbanDate);
+ gameAccount->set_suspension_reason(fields[4].GetString());
+ gameAccount->set_suspension_expires(unbanDate);
+ }
+ } while (result->NextRow());
+ }
+
+ SendResponse(request->GetClient(), response);
+ })));
+
+ _ioService->post([this, request]() { HandleAsyncRequest(request); });
+
+ return SOAP_OK;
+}
- static std::string const expectedPath = "/bnetserver/login/";
- if (strstr(soapClient->path, expectedPath.c_str()) != &soapClient->path[0])
- return 404;
+int32 LoginRESTService::HandleGetPortal(std::shared_ptr<AsyncRequest> request)
+{
+ boost::asio::ip::tcp::endpoint const& endpoint = GetAddressForClient(boost::asio::ip::address_v4(request->GetClient()->ip));
+ std::string response = Trinity::StringFormat("%s:%d", endpoint.address().to_string().c_str(), sConfigMgr->GetIntDefault("BattlenetPort", 1119));
+ soap_response(request->GetClient(), SOAP_FILE);
+ soap_send_raw(request->GetClient(), response.c_str(), response.length());
+ return soap_end_send(request->GetClient());
+}
+
+int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request)
+{
char *buf;
size_t len;
- soap_http_body(soapClient, &buf, &len);
+ soap_http_body(request->GetClient(), &buf, &len);
Battlenet::JSON::Login::LoginForm loginForm;
if (!JSON::Deserialize(buf, &loginForm))
{
- if (soap_register_plugin_arg(soapClient, &ResponseCodePlugin::Init, nullptr) != SOAP_OK)
- return 500;
-
- ResponseCodePlugin* responseCode = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(soapClient, ResponseCodePlugin::PluginId));
- ASSERT(responseCode);
-
- responseCode->ErrorCode = 400;
+ ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = 400;
Battlenet::JSON::Login::LoginResult loginResult;
loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN);
loginResult.set_error_code("UNABLE_TO_DECODE");
loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later.");
- return SendResponse(soapClient, loginResult);
+ return SendResponse(request->GetClient(), loginResult);
}
std::string login;
@@ -274,7 +347,6 @@ int32 LoginRESTService::HandlePost(soap* soapClient)
std::string sentPasswordHash = CalculateShaPassHash(login, password);
- std::shared_ptr<AsyncLoginRequest> request = std::make_shared<AsyncLoginRequest>(*reinterpret_cast<std::shared_ptr<soap>*>(soapClient->user));
request->SetCallback(Trinity::make_unique<QueryCallback>(LoginDatabase.AsyncQuery(stmt)
.WithChainingPreparedCallback([request, login, sentPasswordHash, this](QueryCallback& callback, PreparedQueryResult result)
{
@@ -300,7 +372,7 @@ int32 LoginRESTService::HandlePost(soap* soapClient)
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION);
stmt->setString(0, loginTicket);
- stmt->setUInt32(1, time(nullptr) + 3600);
+ stmt->setUInt32(1, time(nullptr) + _loginTicketDuration);
stmt->setUInt32(2, accountId);
callback.WithPreparedCallback([request, loginTicket](PreparedQueryResult)
{
@@ -358,12 +430,53 @@ int32 LoginRESTService::HandlePost(soap* soapClient)
}
}
}
+
Battlenet::JSON::Login::LoginResult loginResult;
loginResult.set_authentication_state(Battlenet::JSON::Login::DONE);
sLoginService.SendResponse(request->GetClient(), loginResult);
})));
- _ioService->post(std::bind(&LoginRESTService::HandleAsyncRequest, this, std::move(request)));
+ _ioService->post([this, request]() { HandleAsyncRequest(request); });
+
+ return SOAP_OK;
+}
+
+int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request)
+{
+ if (!request->GetClient()->userid)
+ return 401;
+
+ request->SetCallback(Trinity::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] {
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION);
+ stmt->setString(0, request->GetClient()->userid);
+ return stmt;
+ }())
+ .WithPreparedCallback([this, request](PreparedQueryResult result)
+ {
+ Battlenet::JSON::Login::LoginRefreshResult loginRefreshResult;
+ if (result)
+ {
+ uint32 loginTicketExpiry = (*result)[0].GetUInt32();
+ time_t now = time(nullptr);
+ if (loginTicketExpiry > now)
+ {
+ loginRefreshResult.set_login_ticket_expiry(now + _loginTicketDuration);
+
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION);
+ stmt->setUInt32(0, uint32(now + _loginTicketDuration));
+ stmt->setString(1, request->GetClient()->userid);
+ LoginDatabase.Execute(stmt);
+ }
+ else
+ loginRefreshResult.set_is_expired(true);
+ }
+ else
+ loginRefreshResult.set_is_expired(true);
+
+ SendResponse(request->GetClient(), loginRefreshResult);
+ })));
+
+ _ioService->post([this, request]() { HandleAsyncRequest(request); });
return SOAP_OK;
}
@@ -377,10 +490,17 @@ int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message
return soap_end_send(soapClient);
}
-void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncLoginRequest> request)
+void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncRequest> request)
{
if (!request->InvokeIfReady())
- _ioService->post(std::bind(&LoginRESTService::HandleAsyncRequest, this, std::move(request)));
+ {
+ _ioService->post([this, request]() { HandleAsyncRequest(request); });
+ }
+ else if (request->GetResponseStatus())
+ {
+ ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = request->GetResponseStatus();
+ SendResponse(request->GetClient(), Battlenet::JSON::Login::ErrorResponse());
+ }
}
std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password)
@@ -417,6 +537,7 @@ int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void*
data->fresponse = s->fresponse;
p->id = PluginId;
+ p->fcopy = &Copy;
p->fdelete = &Destroy;
p->data = data;
@@ -424,6 +545,12 @@ int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void*
return SOAP_OK;
}
+int32 LoginRESTService::ResponseCodePlugin::Copy(soap* s, soap_plugin* dst, soap_plugin* src)
+{
+ dst->data = new ResponseCodePlugin(*reinterpret_cast<ResponseCodePlugin*>(src->data));
+ return SOAP_OK;
+}
+
void LoginRESTService::ResponseCodePlugin::Destroy(soap* s, soap_plugin* p)
{
ResponseCodePlugin* data = reinterpret_cast<ResponseCodePlugin*>(p->data);
@@ -437,6 +564,11 @@ int32 LoginRESTService::ResponseCodePlugin::ChangeResponse(soap* s, int32 origin
return self->fresponse(s, self->ErrorCode && originalResponse == SOAP_FILE ? self->ErrorCode : originalResponse, contentLength);
}
+LoginRESTService::ResponseCodePlugin* LoginRESTService::ResponseCodePlugin::GetForClient(soap* s)
+{
+ return ASSERT_NOTNULL(reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId)));
+}
+
char const* const LoginRESTService::ContentTypePlugin::PluginId = "bnet-content-type";
int32 LoginRESTService::ContentTypePlugin::Init(soap* s, soap_plugin* p, void* arg)