fix authentication check for GET commands that need admin - Refactor MQTT subscriptions and API calls #173

This commit is contained in:
proddy
2021-11-09 20:54:41 +01:00
parent 234533f241
commit 88f78f6541
4 changed files with 53 additions and 40 deletions

View File

@@ -30,7 +30,7 @@ std::vector<Command::CmdFunction> Command::cmdfunctions_;
// the path is leading so if duplicate keys are in the input JSON it will be ignored // the path is leading so if duplicate keys are in the input JSON it will be ignored
// the entry point will be either via the Web API (api/) or MQTT (<base>/) // the entry point will be either via the Web API (api/) or MQTT (<base>/)
// returns a return code and json output // returns a return code and json output
uint8_t Command::process(const char * path, const bool authenticated, const JsonObject & input, JsonObject & output) { uint8_t Command::process(const char * path, const bool is_admin, const JsonObject & input, JsonObject & output) {
SUrlParser p; // parse URL for the path names SUrlParser p; // parse URL for the path names
p.parse(path); p.parse(path);
@@ -146,16 +146,16 @@ uint8_t Command::process(const char * path, const bool authenticated, const Json
// call the command based on the type // call the command based on the type
uint8_t return_code = CommandRet::ERROR; uint8_t return_code = CommandRet::ERROR;
if (data.is<const char *>()) { if (data.is<const char *>()) {
return_code = Command::call(device_type, command_p, data.as<const char *>(), authenticated, id_n, output); return_code = Command::call(device_type, command_p, data.as<const char *>(), is_admin, id_n, output);
} else if (data.is<int>()) { } else if (data.is<int>()) {
char data_str[10]; char data_str[10];
return_code = Command::call(device_type, command_p, Helpers::itoa(data_str, (int16_t)data.as<int>()), authenticated, id_n, output); return_code = Command::call(device_type, command_p, Helpers::itoa(data_str, (int16_t)data.as<int>()), is_admin, id_n, output);
} else if (data.is<float>()) { } else if (data.is<float>()) {
char data_str[10]; char data_str[10];
return_code = Command::call(device_type, command_p, Helpers::render_value(data_str, (float)data.as<float>(), 2), authenticated, id_n, output); return_code = Command::call(device_type, command_p, Helpers::render_value(data_str, (float)data.as<float>(), 2), is_admin, id_n, output);
} else if (data.isNull()) { } else if (data.isNull()) {
// empty // empty
return_code = Command::call(device_type, command_p, "", authenticated, id_n, output); return_code = Command::call(device_type, command_p, "", is_admin, id_n, output);
} else { } else {
// can't process // can't process
output.clear(); output.clear();
@@ -243,7 +243,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
// calls a command. Takes a json object for output. // calls a command. Takes a json object for output.
// id may be used to represent a heating circuit for example // id may be used to represent a heating circuit for example
// returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed // returns 0 if the command errored, 1 (TRUE) if ok, 2 if not found, 3 if error or 4 if not allowed
uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & output) { uint8_t Command::call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output) {
uint8_t return_code = CommandRet::OK; uint8_t return_code = CommandRet::OK;
std::string dname = EMSdevice::device_type_2_device_name(device_type); std::string dname = EMSdevice::device_type_2_device_name(device_type);
@@ -274,7 +274,7 @@ uint8_t Command::call(const uint8_t device_type, const char * cmd, const char *
} }
// check permissions // check permissions
if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !authenticated) { if (cf->has_flags(CommandFlag::ADMIN_ONLY) && !is_admin) {
output["message"] = "authentication failed"; output["message"] = "authentication failed";
return CommandRet::NOT_ALLOWED; // command not allowed return CommandRet::NOT_ALLOWED; // command not allowed
} }

View File

@@ -104,7 +104,7 @@ class Command {
#define add_ #define add_
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, bool authenticated, const int8_t id, JsonObject & output); static uint8_t call(const uint8_t device_type, const char * cmd, const char * value, const bool is_admin, const int8_t id, JsonObject & output);
static uint8_t call(const uint8_t device_type, const char * cmd, const char * value); static uint8_t call(const uint8_t device_type, const char * cmd, const char * value);
// with normal call back function taking a value and id // with normal call back function taking a value and id
@@ -130,7 +130,7 @@ class Command {
static bool list(const uint8_t device_type, JsonObject & output); static bool list(const uint8_t device_type, JsonObject & output);
static uint8_t process(const char * path, const bool authenticated, const JsonObject & input, JsonObject & output); static uint8_t process(const char * path, const bool is_admin, const JsonObject & input, JsonObject & output);
static const char * parse_command_string(const char * command, int8_t & id); static const char * parse_command_string(const char * command, int8_t & id);

View File

@@ -472,7 +472,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
} }
if (command == "api") { if (command == "api") {
shell.printfln(F("Testing API with MQTT and REST")); shell.printfln(F("Testing API with MQTT and REST, standalone"));
Mqtt::ha_enabled(true); Mqtt::ha_enabled(true);
// Mqtt::ha_enabled(false); // Mqtt::ha_enabled(false);
@@ -483,17 +484,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
run_test("boiler"); run_test("boiler");
run_test("thermostat"); run_test("thermostat");
/* #if defined(EMSESP_STANDALONE)
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/boiler/syspress"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode", "auto"); // set mode
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/system/send", "11 12 13");
EMSESP::mqtt_.incoming("ems-esp/system/publish");
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/system/send", "11 12 13");
return;
*/
AsyncWebServerRequest request2; AsyncWebServerRequest request2;
request2.method(HTTP_GET); request2.method(HTTP_GET);
@@ -545,11 +536,20 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
*/ */
/*
request2.url("/api/system/restart");
EMSESP::webAPIService.webAPIService_get(&request2);
return;
*/
/*
request2.url("/api/dallassensor/fdfd"); request2.url("/api/dallassensor/fdfd");
EMSESP::webAPIService.webAPIService_get(&request2); EMSESP::webAPIService.webAPIService_get(&request2);
emsesp::EMSESP::logger().notice("****"); emsesp::EMSESP::logger().notice("****");
request2.url("/api/dallassensor/info"); request2.url("/api/dallassensor/info");
EMSESP::webAPIService.webAPIService_get(&request2); EMSESP::webAPIService.webAPIService_get(&request2);
return;
*/
/* /*
AsyncWebServerRequest request2; AsyncWebServerRequest request2;
@@ -603,6 +603,8 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
cmd = Command::parse_command_string(command_s, id_n); cmd = Command::parse_command_string(command_s, id_n);
shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n); shell.printfln("test cmd parse cmd=%s id=%d", cmd, id_n);
#endif
// Console tests // Console tests
shell.invoke_command("call thermostat entities"); shell.invoke_command("call thermostat entities");
shell.invoke_command("call thermostat mode auto"); shell.invoke_command("call thermostat mode auto");
@@ -622,6 +624,19 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd) {
EMSESP::mqtt_.incoming("ems-esp/thermostat/modee", "auto"); // unknown command EMSESP::mqtt_.incoming("ems-esp/thermostat/modee", "auto"); // unknown command
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode/auto", "auto"); // invalid EMSESP::mqtt_.incoming("ems-esp/thermostat/mode/auto", "auto"); // invalid
// various MQTT checks
/*
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/boiler/syspress"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode", "auto"); // set mode
EMSESP::mqtt_.incoming("ems-esp/thermostat/mode"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/system/send", "11 12 13");
EMSESP::mqtt_.incoming("ems-esp/system/publish");
EMSESP::mqtt_.incoming("ems-esp/thermostat/seltemp"); // empty payload, sends reponse
EMSESP::mqtt_.incoming("ems-esp/system/send", "11 12 13");
return;
*/
// check long base // check long base
Mqtt::base("home/cellar/heating"); Mqtt::base("home/cellar/heating");
EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload, sends reponse EMSESP::mqtt_.incoming("home/cellar/heating/thermostat/mode"); // empty payload, sends reponse

View File

@@ -31,17 +31,17 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security
server->addHandler(&_apiHandler); server->addHandler(&_apiHandler);
} }
// HTTP GET
// GET /{device} // GET /{device}
// GET /{device}/{name} // GET /{device}/{entity}
// GET /device={device}?cmd={name}?data={value}[?id={hc}]
void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) { void WebAPIService::webAPIService_get(AsyncWebServerRequest * request) {
// has no body JSON so create dummy as empty object // has no body JSON so create dummy as empty input object
StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> input_doc; StaticJsonDocument<EMSESP_JSON_SIZE_SMALL> input_doc;
JsonObject input = input_doc.to<JsonObject>(); JsonObject input = input_doc.to<JsonObject>();
parse(request, input); parse(request, input);
} }
// For POSTS with an optional JSON body // For HTTP POSTS with an optional JSON body
// HTTP_POST | HTTP_PUT | HTTP_PATCH // HTTP_POST | HTTP_PUT | HTTP_PATCH
// POST /{device}[/{hc|id}][/{name}] // POST /{device}[/{hc|id}][/{name}]
void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) { void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVariant & json) {
@@ -60,22 +60,20 @@ void WebAPIService::webAPIService_post(AsyncWebServerRequest * request, JsonVari
// reporting back any errors // reporting back any errors
void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) { void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
auto method = request->method(); auto method = request->method();
bool authenticated = false;
// if its a POST then check authentication // see if the user has admin privelegs
if (method != HTTP_GET) { bool is_admin;
EMSESP::webSettingsService.read([&](WebSettings & settings) { EMSESP::webSettingsService.read([&](WebSettings & settings) {
Authentication authentication = _securityManager->authenticateRequest(request); Authentication authentication = _securityManager->authenticateRequest(request);
authenticated = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication); is_admin = settings.notoken_api | AuthenticationPredicates::IS_ADMIN(authentication);
}); });
}
// output json buffer // output json buffer
PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); PrettyAsyncJsonResponse * response = new PrettyAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN);
JsonObject output = response->getRoot(); JsonObject output = response->getRoot();
// call command // call command
uint8_t return_code = Command::process(request->url().c_str(), authenticated, input, output); uint8_t return_code = Command::process(request->url().c_str(), is_admin, input, output);
if (return_code != CommandRet::OK) { if (return_code != CommandRet::OK) {
char error[100]; char error[100];