Browse Source

0.8.70 - 2024-02-01

* prevent sending commands to inverter which isn't active #1387
* protect commands from popup in `/live` if password is set #1199
pull/1402/head
lumapu 8 months ago
parent
commit
4d7362fa6b
  1. 1
      src/CHANGES.md
  2. 5
      src/app.cpp
  3. 22
      src/app.h
  4. 6
      src/appInterface.h
  5. 2
      src/defines.h
  6. 4
      src/utils/spiPatcher.cpp
  7. 4
      src/utils/spiPatcher.h
  8. 7
      src/web/Protection.cpp
  9. 93
      src/web/Protection.h
  10. 15
      src/web/RestApi.h
  11. 6
      src/web/lang.h
  12. 49
      src/web/web.h

1
src/CHANGES.md

@ -2,6 +2,7 @@
## 0.8.70 - 2024-02-01 ## 0.8.70 - 2024-02-01
* prevent sending commands to inverter which isn't active #1387 * prevent sending commands to inverter which isn't active #1387
* protect commands from popup in `/live` if password is set #1199
## 0.8.69 - 2024-01-31 ## 0.8.69 - 2024-01-31
* merge PR: Dynamic retries, pendular first rx chan #1394 * merge PR: Dynamic retries, pendular first rx chan #1394

5
src/app.cpp

@ -97,9 +97,8 @@ void app::setup() {
esp_task_wdt_reset(); esp_task_wdt_reset();
mWeb.setup(this, &mSys, mConfig); mWeb.setup(this, &mSys, mConfig);
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig); mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
mProtection = Protection::getInstance(mConfig->sys.adminPwd);
#ifdef ENABLE_SYSLOG #ifdef ENABLE_SYSLOG
mDbgSyslog.setup(mConfig); // be sure to init after mWeb.setup (webSerial uses also debug callback) mDbgSyslog.setup(mConfig); // be sure to init after mWeb.setup (webSerial uses also debug callback)
@ -182,6 +181,8 @@ void app::onNetwork(bool gotIp) {
void app::regularTickers(void) { void app::regularTickers(void) {
DPRINTLN(DBG_DEBUG, F("regularTickers")); DPRINTLN(DBG_DEBUG, F("regularTickers"));
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
everySec([this]() { mProtection->tickSecond(); }, "prot");
// Plugins // Plugins
#if defined(PLUGIN_DISPLAY) #if defined(PLUGIN_DISPLAY)
if (DISP_TYPE_T0_NONE != mConfig->plugin.display.type) if (DISP_TYPE_T0_NONE != mConfig->plugin.display.type)

22
src/app.h

@ -30,6 +30,7 @@
#include "utils/scheduler.h" #include "utils/scheduler.h"
#include "utils/syslog.h" #include "utils/syslog.h"
#include "web/RestApi.h" #include "web/RestApi.h"
#include "web/Protection.h"
#if defined(ENABLE_HISTORY) #if defined(ENABLE_HISTORY)
#include "plugins/history.h" #include "plugins/history.h"
#endif /*ENABLE_HISTORY*/ #endif /*ENABLE_HISTORY*/
@ -246,8 +247,24 @@ class app : public IApp, public ah::Scheduler {
#endif #endif
} }
bool getProtection(AsyncWebServerRequest *request) { void lock(void) override {
return mWeb.isProtected(request); mProtection->lock();
}
void unlock(const char *clientIp) override {
mProtection->unlock(clientIp);
}
void resetLockTimeout(void) override {
mProtection->resetLockTimeout();
}
bool isProtected(void) const override {
return mProtection->isProtected();
}
bool isProtected(const char *clientIp) const override {
return mProtection->isProtected(clientIp);
} }
bool getNrfEnabled(void) { bool getNrfEnabled(void) {
@ -387,6 +404,7 @@ class app : public IApp, public ah::Scheduler {
#endif /* defined(ETHERNET) */ #endif /* defined(ETHERNET) */
WebType mWeb; WebType mWeb;
RestApiType mApi; RestApiType mApi;
Protection *mProtection;
#ifdef ENABLE_SYSLOG #ifdef ENABLE_SYSLOG
DbgSyslog mDbgSyslog; DbgSyslog mDbgSyslog;
#endif #endif

6
src/appInterface.h

@ -61,7 +61,11 @@ class IApp {
virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttRxCnt() = 0;
virtual uint32_t getMqttTxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0;
virtual bool getProtection(AsyncWebServerRequest *request) = 0; virtual void lock(void) = 0;
virtual void unlock(const char *clientIp) = 0;
virtual void resetLockTimeout(void) = 0;
virtual bool isProtected(void) const = 0;
virtual bool isProtected(const char *clientIp) const = 0;
virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0;
virtual uint16_t getHistoryMaxDay() = 0; virtual uint16_t getHistoryMaxDay() = 0;

2
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 69 #define VERSION_PATCH 70
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

4
src/utils/spiPatcher.cpp

@ -1,6 +1,6 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 // 2024 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#if defined(ESP32) #if defined(ESP32)

4
src/utils/spiPatcher.h

@ -1,6 +1,6 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 // 2024 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#ifndef __SPI_PATCHER_H__ #ifndef __SPI_PATCHER_H__

7
src/web/Protection.cpp

@ -0,0 +1,7 @@
//-----------------------------------------------------------------------------
// 2024 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#include "Protection.h"
Protection *Protection::mInstance = nullptr;

93
src/web/Protection.h

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// 2024 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __PROTECTION_H__
#define __PROTECTION_H__
#pragma once
#include <array>
#include <cstdint>
#include "../config/config.h"
#include "../utils/helper.h"
class Protection {
protected:
Protection(const char *pwd) {
mPwd = pwd;
mLogoutTimeout = 0;
mLoginIp.fill(0);
// no password set - unlock
if(pwd[0] == '\0')
mProtected = false;
}
public:
Protection(Protection &other) = delete;
void operator=(const Protection &) = delete;
static Protection* getInstance(const char *pwd) {
if(nullptr == mInstance)
mInstance = new Protection(pwd);
return mInstance;
}
void tickSecond() {
// auto logout
if(0 != mLogoutTimeout) {
if (0 == --mLogoutTimeout) {
if(mPwd[0] == '\0')
mProtected = true;
}
}
}
void lock(void) {
mProtected = true;
mLoginIp.fill(0);
}
void unlock(const char *clientIp) {
mProtected = false;
ah::ip2Arr(static_cast<uint8_t*>(mLoginIp.data()), clientIp);
}
void resetLockTimeout(void) {
mLogoutTimeout = LOGOUT_TIMEOUT;
}
bool isProtected(void) const {
return mProtected;
}
bool isProtected(const char *clientIp) const {
if(mProtected)
return true;
if(mPwd[0] == '\0')
return false;
uint8_t ip[4];
ah::ip2Arr(ip, clientIp);
for(uint8_t i = 0; i < 4; i++) {
if(mLoginIp[i] != ip[i])
return true;
}
return true;
}
protected:
static Protection *mInstance;
private:
const char *mPwd;
bool mProtected = true;
uint16_t mLogoutTimeout = LOGOUT_TIMEOUT;
std::array<uint8_t, 4> mLoginIp;
};
#endif /*__PROTECTION_H__*/

15
src/web/RestApi.h

@ -68,7 +68,7 @@ class RestApi {
DynamicJsonDocument json(128); DynamicJsonDocument json(128);
JsonObject dummy = json.as<JsonObject>(); JsonObject dummy = json.as<JsonObject>();
if(obj[F("path")] == "ctrl") if(obj[F("path")] == "ctrl")
setCtrl(obj, dummy); setCtrl(obj, dummy, "api");
else if(obj[F("path")] == "setup") else if(obj[F("path")] == "setup")
setSetup(obj, dummy); setSetup(obj, dummy);
} }
@ -168,7 +168,7 @@ class RestApi {
if(!err) { if(!err) {
String path = request->url().substring(5); String path = request->url().substring(5);
if(path == "ctrl") if(path == "ctrl")
root[F("success")] = setCtrl(obj, root); root[F("success")] = setCtrl(obj, root, request->client()->remoteIP().toString().c_str());
else if(path == "setup") else if(path == "setup")
root[F("success")] = setSetup(obj, root); root[F("success")] = setSetup(obj, root);
else { else {
@ -263,7 +263,7 @@ class RestApi {
obj[F("modules")] = String(mApp->getVersionModules()); obj[F("modules")] = String(mApp->getVersionModules());
obj[F("build")] = String(AUTO_GIT_HASH); obj[F("build")] = String(AUTO_GIT_HASH);
obj[F("env")] = String(ENV_NAME); obj[F("env")] = String(ENV_NAME);
obj[F("menu_prot")] = mApp->getProtection(request); obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str());
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
obj[F("cst_lnk")] = String(mConfig->plugin.customLink); obj[F("cst_lnk")] = String(mConfig->plugin.customLink);
@ -847,7 +847,7 @@ class RestApi {
} }
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) {
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
bool accepted = true; bool accepted = true;
if(NULL == iv) { if(NULL == iv) {
@ -856,6 +856,13 @@ class RestApi {
} }
jsonOut[F("id")] = jsonIn[F("id")]; jsonOut[F("id")] = jsonIn[F("id")];
if(strncmp("api", clientIP, 3) != 0) {
if(mApp->isProtected(clientIP)) {
jsonOut[F("error")] = F(INV_IS_PROTECTED);
return false;
}
}
if(F("power") == jsonIn[F("cmd")]) if(F("power") == jsonIn[F("cmd")])
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
else if(F("restart") == jsonIn[F("cmd")]) else if(F("restart") == jsonIn[F("cmd")])

6
src/web/lang.h

@ -36,6 +36,12 @@
#define UNKNOWN_CMD "unknown cmd: '" #define UNKNOWN_CMD "unknown cmd: '"
#endif #endif
#ifdef LANG_DE
#define INV_IS_PROTECTED "nicht angemeldet, Kommando nicht möglich!"
#else /*LANG_EN*/
#define INV_IS_PROTECTED "not logged in, command not possible!"
#endif
#ifdef LANG_DE #ifdef LANG_DE
#define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich" #define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich"
#else /*LANG_EN*/ #else /*LANG_EN*/

49
src/web/web.h

@ -47,9 +47,6 @@ template <class HMSYSTEM>
class Web { class Web {
public: public:
Web(void) : mWeb(80), mEvts("/events") { Web(void) : mWeb(80), mEvts("/events") {
mProtected = true;
mLogoutTimeout = 0;
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
mSerialBufFill = 0; mSerialBufFill = 0;
mSerialAddTime = true; mSerialAddTime = true;
@ -110,16 +107,6 @@ class Web {
} }
void tickSecond() { void tickSecond() {
if (0 != mLogoutTimeout) {
mLogoutTimeout -= 1;
if (0 == mLogoutTimeout) {
if (strlen(mConfig->sys.adminPwd) > 0)
mProtected = true;
}
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
}
if (mSerialClientConnnected) { if (mSerialClientConnnected) {
if (mSerialBufFill > 0) { if (mSerialBufFill > 0) {
mEvts.send(mSerialBuf, "serial", millis()); mEvts.send(mSerialBuf, "serial", millis());
@ -133,27 +120,6 @@ class Web {
return &mWeb; return &mWeb;
} }
void setProtection(bool protect) {
mProtected = protect;
}
bool isProtected(AsyncWebServerRequest *request) {
bool prot;
prot = mProtected;
if(!prot) {
if(strlen(mConfig->sys.adminPwd) > 0) {
uint8_t ip[4];
ah::ip2Arr(ip, request->client()->remoteIP().toString().c_str());
for(uint8_t i = 0; i < 4; i++) {
if(mLoginIp[i] != ip[i])
prot = true;
}
}
}
return prot;
}
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) { if (!index) {
Serial.printf("Update Start: %s\n", filename.c_str()); Serial.printf("Update Start: %s\n", filename.c_str());
@ -264,7 +230,7 @@ class Web {
} }
void checkProtection(AsyncWebServerRequest *request) { void checkProtection(AsyncWebServerRequest *request) {
if(isProtected(request)) { if(mApp->isProtected(request->client()->remoteIP().toString().c_str())) {
checkRedirect(request); checkRedirect(request);
return; return;
} }
@ -351,8 +317,7 @@ class Web {
if (request->args() > 0) { if (request->args() > 0) {
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false; mApp->unlock(request->client()->remoteIP().toString().c_str());
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
request->redirect("/"); request->redirect("/");
} }
} }
@ -366,8 +331,7 @@ class Web {
DPRINTLN(DBG_VERBOSE, F("onLogout")); DPRINTLN(DBG_VERBOSE, F("onLogout"));
checkProtection(request); checkProtection(request);
mApp->lock();
mProtected = true;
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -390,7 +354,6 @@ class Web {
void onCss(AsyncWebServerRequest *request) { void onCss(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onCss")); DPRINTLN(DBG_VERBOSE, F("onCss"));
mLogoutTimeout = LOGOUT_TIMEOUT;
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
if(request->hasParam("v")) { if(request->hasParam("v")) {
@ -424,6 +387,7 @@ class Web {
AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
request->send(response); request->send(response);
mApp->resetLockTimeout();
} }
void showNotFound(AsyncWebServerRequest *request) { void showNotFound(AsyncWebServerRequest *request) {
@ -495,7 +459,7 @@ class Web {
// protection // protection
if (request->arg("adminpwd") != "{PWD}") { if (request->arg("adminpwd") != "{PWD}") {
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
mProtected = (strlen(mConfig->sys.adminPwd) > 0); mApp->lock();
} }
mConfig->sys.protectionMask = 0x0000; mConfig->sys.protectionMask = 0x0000;
for (uint8_t i = 0; i < 7; i++) { for (uint8_t i = 0; i < 7; i++) {
@ -945,9 +909,6 @@ class Web {
#endif #endif
AsyncWebServer mWeb; AsyncWebServer mWeb;
AsyncEventSource mEvts; AsyncEventSource mEvts;
bool mProtected;
uint32_t mLogoutTimeout;
uint8_t mLoginIp[4];
IApp *mApp; IApp *mApp;
HMSYSTEM *mSys; HMSYSTEM *mSys;

Loading…
Cancel
Save