From 91f8996fbaa28a703e99f9794cc58e6e742a1b24 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 10 May 2023 08:59:02 +0200 Subject: [PATCH] Implemented basic authentication Also fixed Tankers and AWACS which now operate as expected --- client/app.js | 10 +- client/src/index.ts | 6 +- client/src/panels/unitcontrolpanel.ts | 10 +- client/src/server/server.ts | 4 + olympus.json | 3 + src/core/core.vcxproj | 2 +- src/core/include/server.h | 2 + src/core/include/unit.h | 11 +- src/core/src/scheduler.cpp | 2 - src/core/src/server.cpp | 161 +++++++++++++++----------- src/core/src/unit.cpp | 38 ++++-- third-party/base64/include/base64.hpp | 81 +++++++++++++ 12 files changed, 226 insertions(+), 104 deletions(-) create mode 100644 third-party/base64/include/base64.hpp diff --git a/client/app.js b/client/app.js index 9b65e625..8fbd41c1 100644 --- a/client/app.js +++ b/client/app.js @@ -12,10 +12,6 @@ var usersRouter = require('./routes/users'); var app = express(); -app.use('/demo', basicAuth({ - users: { 'admin': 'socks' } -})) - app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); @@ -31,7 +27,8 @@ app.set('view engine', 'ejs'); let rawdata = fs.readFileSync('../olympus.json'); let config = JSON.parse(rawdata); -app.get('/config', (req, res) => res.send(config)); +if (config["server"] != undefined) + app.get('/config', (req, res) => res.send(config["server"])); module.exports = app; @@ -43,5 +40,8 @@ app.get('/demo/bullseyes', (req, res) => demoDataGenerator.bullseyes(req, res)); app.get('/demo/airbases', (req, res) => demoDataGenerator.airbases(req, res)); app.get('/demo/mission', (req, res) => demoDataGenerator.mission(req, res)); +app.use('/demo', basicAuth({ + users: { 'admin': 'socks' } +})) diff --git a/client/src/index.ts b/client/src/index.ts index 5e41f88b..b3e7e63e 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -82,9 +82,9 @@ function setup() { } function readConfig(config: any) { - if (config && config["server"] != undefined && config["server"]["address"] != undefined && config["server"]["port"] != undefined) { - const address = config["server"]["address"]; - const port = config["server"]["port"]; + if (config && config["address"] != undefined && config["port"] != undefined) { + const address = config["address"]; + const port = config["port"]; if (typeof address === 'string' && typeof port == 'number') setAddress(address == "*" ? window.location.hostname : address, port); } diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 4723d1ba..4fe1de17 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -219,7 +219,7 @@ export class UnitControlPanel extends Panel { // Default values for "normal" units this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); // Input values var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") @@ -241,7 +241,7 @@ export class UnitControlPanel extends Panel { this.#radioDecimalsDropdown.setValue("." + radioDecimals); // Make sure its in the valid range - if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign)) + if (!this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1)) this.#radioCallsignDropdown.selectValue(0); // Set options for tankers @@ -249,7 +249,7 @@ export class UnitControlPanel extends Panel { if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker")){ this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.remove("hide"); this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); } else { this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.classList.add("hide"); @@ -259,7 +259,7 @@ export class UnitControlPanel extends Panel { if (roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS")){ this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.remove("hide"); this.#radioCallsignDropdown.setOptions(["Overlord", "Magic", "Wizard", "Focus", "Darkstar"]); - this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); + this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign - 1); } else { this.#advancedSettingsDialog.querySelector("#AWACS-checkbox")?.classList.add("hide"); } @@ -276,7 +276,7 @@ export class UnitControlPanel extends Panel { const TACANCallsign = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input")?.value const radioMHz = Number(this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input")?.value); const radioDecimals = this.#radioDecimalsDropdown.getValue(); - const radioCallsign = this.#radioCallsignDropdown.getIndex(); + const radioCallsign = this.#radioCallsignDropdown.getIndex() + 1; const radioCallsignNumber = Number(this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input")?.value); var radioFrequency = (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000; diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 92c5b2d2..80e10563 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -52,6 +52,10 @@ export function GET(callback: CallableFunction, uri: string, options?: string) { setConnected(false); } }; + xmlHttp.onreadystatechange = function (res) { + console.error("An error occurred during the XMLHttpRequest"); + setConnected(false); + }; xmlHttp.onerror = function (res) { console.error("An error occurred during the XMLHttpRequest"); setConnected(false); diff --git a/olympus.json b/olympus.json index 7b1b026b..3618c236 100644 --- a/olympus.json +++ b/olympus.json @@ -2,5 +2,8 @@ "server": { "address": "localhost", "port": 30000 + }, + "authentication": { + "password": "password" } } diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index b6c5614d..4759cb4f 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -185,7 +185,7 @@ NotUsing - include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include + include;..\..\third-party\base64\include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include stdcpp20 diff --git a/src/core/include/server.h b/src/core/include/server.h index f77cba5b..ec08d264 100644 --- a/src/core/include/server.h +++ b/src/core/include/server.h @@ -27,5 +27,7 @@ private: void task(); atomic runListener; + + wstring password = L""; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 0f80a3ba..e54401ff 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -96,14 +96,13 @@ public: void pushActivePathBack(Coords newActivePathBack); void popActivePathFront(); void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} - void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));} - void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));} - void setTACANOn(bool newTACANOn); + void setIsTanker(bool newIsTanker); + void setIsAWACS(bool newIsAWACS); void setTACANChannel(int newTACANChannel); void setTACANXY(wstring newTACANXY); void setTACANCallsign(wstring newTACANCallsign); void setTACAN(); - void setRadioOn(bool newRadioOn); + void setEPLRS(bool state); void setRadioFrequency(int newRadioFrequency); void setRadioCallsign(int newRadioCallsign); void setRadioCallsignNumber(int newRadioCallsignNumber); @@ -116,11 +115,9 @@ public: int getTargetID() { return targetID; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } - bool getTACANOn() { return TACANOn; } int getTACANChannel() { return TACANChannel; } wstring getTACANXY() { return TACANXY; } wstring getTACANCallsign() { return TACANCallsign; } - bool getRadioOn() { return radioOn; } int getRadioFrequency() { return radioFrequency; } int getRadioCallsign() { return radioCallsign; } int getRadioCallsignNumber() { return radioCallsignNumber; } @@ -182,11 +179,9 @@ protected: int targetID = NULL; bool isTanker = false; bool isAWACS = false; - bool TACANOn = false; int TACANChannel = 40; wstring TACANXY = L"X"; wstring TACANCallsign = L"TKR"; - bool radioOn = false; int radioFrequency = 260000000; // MHz int radioCallsign = 1; int radioCallsignNumber = 1; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 2bd4c98c..8bf448e9 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -251,13 +251,11 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->setIsTanker(value[L"isTanker"].as_bool()); unit->setIsAWACS(value[L"isAWACS"].as_bool()); - unit->setTACANOn(true); // TODO Remove unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32()); unit->setTACANXY(value[L"TACANXY"].as_string()); unit->setTACANCallsign(value[L"TACANCallsign"].as_string()); unit->setTACAN(); - unit->setRadioOn(true); // TODO Remove unit->setRadioFrequency(value[L"radioFrequency"].as_number().to_int32()); unit->setRadioCallsign(value[L"radioCallsign"].as_number().to_int32()); unit->setRadioCallsignNumber(value[L"radioCallsignNumber"].as_number().to_int32()); diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 192a0440..a2b01978 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -6,9 +6,11 @@ #include "luatools.h" #include #include +#include "base64.hpp" #include using namespace std::chrono; +using namespace base64; extern UnitsManager* unitsManager; extern Scheduler* scheduler; @@ -54,10 +56,10 @@ void Server::stop(lua_State* L) void Server::handle_options(http_request request) { http_response response(status_codes::OK); - response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS")); + response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Origin"), U("*")); - response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS")); - response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type")); + response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); + response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); request.reply(response); } @@ -68,87 +70,102 @@ void Server::handle_get(http_request request) lock_guard guard(mutexLock); http_response response(status_codes::OK); - response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS")); - response.headers().add(U("Access-Control-Allow-Origin"), U("*")); - response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS")); - response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type")); + string authorization = to_base64("admin:" + to_string(password)); + if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + { + std::exception_ptr eptr; + try { + auto answer = json::value::object(); + auto path = uri::split_path(uri::decode(request.relative_uri().path())); - std::exception_ptr eptr; - try { - auto answer = json::value::object(); - auto path = uri::split_path(uri::decode(request.relative_uri().path())); - - if (path.size() > 0) - { - if (path[0] == UNITS_URI) + if (path.size() > 0) { - map query = request.relative_uri().split_query(request.relative_uri().query()); - long long time = 0; - if (query.find(L"time") != query.end()) + if (path[0] == UNITS_URI) { - try { - time = stoll((*(query.find(L"time"))).second); - } - catch (const std::exception& e) { - time = 0; + map query = request.relative_uri().split_query(request.relative_uri().query()); + long long time = 0; + if (query.find(L"time") != query.end()) + { + try { + time = stoll((*(query.find(L"time"))).second); + } + catch (const std::exception& e) { + time = 0; + } } + unitsManager->getData(answer, time); } - unitsManager->getData(answer, time); - } - else if (path[0] == LOGS_URI) - { - auto logs = json::value::object(); - getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries - answer[L"logs"] = logs; - } - else if (path[0] == AIRBASES_URI) - answer[L"airbases"] = airbases; - else if (path[0] == BULLSEYE_URI) - answer[L"bullseyes"] = bullseyes; - else if (path[0] == MISSION_URI) - answer[L"mission"] = mission; + else if (path[0] == LOGS_URI) + { + auto logs = json::value::object(); + getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries + answer[L"logs"] = logs; + } + else if (path[0] == AIRBASES_URI) + answer[L"airbases"] = airbases; + else if (path[0] == BULLSEYE_URI) + answer[L"bullseyes"] = bullseyes; + else if (path[0] == MISSION_URI) + answer[L"mission"] = mission; - milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - answer[L"time"] = json::value::string(to_wstring(ms.count())); - answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + answer[L"time"] = json::value::string(to_wstring(ms.count())); + answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + } + + response.set_body(answer); } + catch (...) { + eptr = std::current_exception(); // capture + } + handle_eptr(eptr); + } + else { + response = status_codes::Unauthorized; + } - response.set_body(answer); - } - catch (...) { - eptr = std::current_exception(); // capture - } - handle_eptr(eptr); + response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); + response.headers().add(U("Access-Control-Allow-Origin"), U("*")); + response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); + response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); request.reply(response); } void Server::handle_request(http_request request, function action) { - auto answer = json::value::object(); - request.extract_json().then([&answer, &action](pplx::task task) - { - try - { - auto const& jvalue = task.get(); - - if (!jvalue.is_null()) - { - action(jvalue, answer); - } - } - catch (http_exception const& e) - { - log(e.what()); - } - }).wait(); - http_response response(status_codes::OK); - response.headers().add(U("Allow"), U("GET, POST, PUT, OPTIONS")); + string authorization = to_base64("admin:" + to_string(password)); + if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + { + auto answer = json::value::object(); + request.extract_json().then([&answer, &action](pplx::task task) + { + try + { + auto const& jvalue = task.get(); + + if (!jvalue.is_null()) + { + action(jvalue, answer); + } + } + catch (http_exception const& e) + { + log(e.what()); + } + }).wait(); + response.set_body(answer); + } + else { + response = status_codes::Unauthorized; + } + + response.headers().add(U("Allow"), U("GET, PUT, OPTIONS")); response.headers().add(U("Access-Control-Allow-Origin"), U("*")); - response.headers().add(U("Access-Control-Allow-Methods"), U("GET, POST, PUT, OPTIONS")); - response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type")); - response.set_body(answer); + response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS")); + response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization")); + request.reply(response); } @@ -197,9 +214,15 @@ void Server::task() log(L"Starting server on " + address); } else - { log(L"Error reading configuration file. Starting server on " + address); + + if (config.is_object() && config.has_object_field(L"authentication") && + config[L"authentication"].has_string_field(L"password")) + { + password = config[L"authentication"][L"password"].as_string(); } + else + log(L"Error reading configuration file. No password set."); free(buf); } else diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 228a26a9..5e9dd25d 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -337,9 +337,17 @@ void Unit::landAt(Coords loc) { setState(State::LAND); } -void Unit::setTACANOn(bool newTACANOn) { - TACANOn = newTACANOn; - addMeasure(L"TACANOn", json::value(newTACANOn)); +void Unit::setIsTanker(bool newIsTanker) { + isTanker = newIsTanker; + resetTask(); + addMeasure(L"isTanker", json::value(newIsTanker)); +} + +void Unit::setIsAWACS(bool newIsAWACS) { + isAWACS = newIsAWACS; + resetTask(); + addMeasure(L"isAWACS", json::value(newIsAWACS)); + setEPLRS(true); } void Unit::setTACANChannel(int newTACANChannel) { @@ -356,11 +364,6 @@ void Unit::setTACANCallsign(wstring newTACANCallsign) { addMeasure(L"TACANCallsign", json::value(newTACANCallsign)); } -void Unit::setRadioOn(bool newRadioOn) { - radioOn = newRadioOn; - addMeasure(L"radioOn", json::value(newRadioOn)); -} - void Unit::setRadioFrequency(int newRadioFrequency) { radioFrequency = newRadioFrequency; addMeasure(L"radioFrequency", json::value(newRadioFrequency)); @@ -376,6 +379,19 @@ void Unit::setRadioCallsignNumber(int newRadioCallsignNumber) { addMeasure(L"radioCallsignNumber", json::value(newRadioCallsignNumber)); } +void Unit::setEPLRS(bool state) +{ + std::wostringstream commandSS; + commandSS << "{" + << "id = 'EPLRS'," + << "params = {" + << "value = " << (state? "true": "false") << ", " + << "}" + << "}"; + Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + scheduler->appendCommand(command); +} + void Unit::setTACAN() { std::wostringstream commandSS; @@ -383,9 +399,9 @@ void Unit::setTACAN() << "id = 'ActivateBeacon'," << "params = {" << "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << "," - << "system = 4," - << "name = Olympus_TACAN," - << "callsign = " << TACANCallsign << ", " + << "system = 3," + << "name = \"Olympus_TACAN\"," + << "callsign = \"" << TACANCallsign << "\", " << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << "," << "}" << "}"; diff --git a/third-party/base64/include/base64.hpp b/third-party/base64/include/base64.hpp new file mode 100644 index 00000000..adcf1c04 --- /dev/null +++ b/third-party/base64/include/base64.hpp @@ -0,0 +1,81 @@ +#ifndef BASE_64_HPP +#define BASE_64_HPP + +#include +#include + +namespace base64 { + +inline std::string get_base64_chars() { + static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return base64_chars; +} + +inline std::string to_base64(std::string const &data) { + int counter = 0; + uint32_t bit_stream = 0; + const std::string base64_chars = get_base64_chars(); + std::string encoded; + int offset = 0; + for (unsigned char c : data) { + auto num_val = static_cast(c); + offset = 16 - counter % 3 * 8; + bit_stream += num_val << offset; + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 18 & 0x3f); + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + } + if (offset == 0 && counter != 3) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += base64_chars.at(bit_stream & 0x3f); + bit_stream = 0; + } + counter++; + } + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + encoded += "=="; + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += '='; + } + return encoded; +} + +inline std::string from_base64(std::string const &data) { + int counter = 0; + uint32_t bit_stream = 0; + std::string decoded; + int offset = 0; + const std::string base64_chars = get_base64_chars(); + for (unsigned char c : data) { + auto num_val = base64_chars.find(c); + if (num_val != std::string::npos) { + offset = 18 - counter % 4 * 6; + bit_stream += num_val << offset; + if (offset == 12) { + decoded += static_cast(bit_stream >> 16 & 0xff); + } + if (offset == 6) { + decoded += static_cast(bit_stream >> 8 & 0xff); + } + if (offset == 0 && counter != 4) { + decoded += static_cast(bit_stream & 0xff); + bit_stream = 0; + } + } else if (c != '=') { + return std::string(); + } + counter++; + } + return decoded; +} + +} + +#endif // BASE_64_HPP \ No newline at end of file