Allow features to be disabled at build time (#143)

* Add framework for built-time feature selection
* Allow MQTT, NTP, OTA features to be disabled at build time
* Allow Project screens to be disabled at build time
* Allow security features to be disabled at build time
* Switch to std::function for StatefulService function aliases for greater flexibility
* Bump various UI lib versions
* Update docs
This commit is contained in:
rjwats
2020-06-09 21:57:44 +01:00
committed by GitHub
parent 88748ac30d
commit 449d3c91ce
36 changed files with 800 additions and 193 deletions

View File

@ -96,4 +96,4 @@ class APSettingsService : public StatefulService<APSettings> {
void handleDNS();
};
#endif // end APSettingsConfig_h
#endif // end APSettingsConfig_h

View File

@ -1,5 +1,7 @@
#include <AuthenticationService.h>
#if FT_ENABLED(FT_SECURITY)
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) :
_securityManager(securityManager),
_signInHandler(SIGN_IN_PATH,
@ -42,3 +44,5 @@ void AuthenticationService::signIn(AsyncWebServerRequest* request, JsonVariant&
AsyncWebServerResponse* response = request->beginResponse(401);
request->send(response);
}
#endif // end FT_ENABLED(FT_SECURITY)

View File

@ -1,6 +1,7 @@
#ifndef AuthenticationService_H_
#define AuthenticationService_H_
#include <Features.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
@ -10,6 +11,8 @@
#define MAX_AUTHENTICATION_SIZE 256
#if FT_ENABLED(FT_SECURITY)
class AuthenticationService {
public:
AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager);
@ -23,4 +26,5 @@ class AuthenticationService {
void verifyAuthorization(AsyncWebServerRequest* request);
};
#endif // end SecurityManager_h
#endif // end FT_ENABLED(FT_SECURITY)
#endif // end SecurityManager_h

View File

@ -1,20 +1,29 @@
#include <ESP8266React.h>
ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) :
_featureService(server),
_securitySettingsService(server, fs),
_wifiSettingsService(server, fs, &_securitySettingsService),
_apSettingsService(server, fs, &_securitySettingsService),
_ntpSettingsService(server, fs, &_securitySettingsService),
_otaSettingsService(server, fs, &_securitySettingsService),
_mqttSettingsService(server, fs, &_securitySettingsService),
_restartService(server, &_securitySettingsService),
_factoryResetService(server, fs, &_securitySettingsService),
_authenticationService(server, &_securitySettingsService),
_wifiScanner(server, &_securitySettingsService),
_wifiStatus(server, &_securitySettingsService),
_ntpStatus(server, &_securitySettingsService),
_apSettingsService(server, fs, &_securitySettingsService),
_apStatus(server, &_securitySettingsService, &_apSettingsService),
#if FT_ENABLED(FT_NTP)
_ntpSettingsService(server, fs, &_securitySettingsService),
_ntpStatus(server, &_securitySettingsService),
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService(server, fs, &_securitySettingsService),
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService(server, fs, &_securitySettingsService),
_mqttStatus(server, &_mqttSettingsService, &_securitySettingsService),
#endif
#if FT_ENABLED(FT_SECURITY)
_authenticationService(server, &_securitySettingsService),
#endif
_restartService(server, &_securitySettingsService),
_factoryResetService(server, fs, &_securitySettingsService),
_systemStatus(server, &_securitySettingsService) {
#ifdef PROGMEM_WWW
// Serve static resources from PROGMEM
@ -69,17 +78,29 @@ ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) :
}
void ESP8266React::begin() {
_securitySettingsService.begin();
_wifiSettingsService.begin();
_apSettingsService.begin();
#if FT_ENABLED(FT_NTP)
_ntpSettingsService.begin();
#endif
#if FT_ENABLED(FT_OTA)
_otaSettingsService.begin();
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService.begin();
#endif
#if FT_ENABLED(FT_SECURITY)
_securitySettingsService.begin();
#endif
}
void ESP8266React::loop() {
_wifiSettingsService.loop();
_apSettingsService.loop();
#if FT_ENABLED(FT_OTA)
_otaSettingsService.loop();
#endif
#if FT_ENABLED(FT_MQTT)
_mqttSettingsService.loop();
#endif
}

View File

@ -11,6 +11,7 @@
#include <ESPAsyncTCP.h>
#endif
#include <FeaturesService.h>
#include <APSettingsService.h>
#include <APStatus.h>
#include <AuthenticationService.h>
@ -42,9 +43,11 @@ class ESP8266React {
return &_securitySettingsService;
}
#if FT_ENABLED(FT_SECURITY)
StatefulService<SecuritySettings>* getSecuritySettingsService() {
return &_securitySettingsService;
}
#endif
StatefulService<WiFiSettings>* getWiFiSettingsService() {
return &_wifiSettingsService;
@ -54,14 +57,19 @@ class ESP8266React {
return &_apSettingsService;
}
#if FT_ENABLED(FT_NTP)
StatefulService<NTPSettings>* getNTPSettingsService() {
return &_ntpSettingsService;
}
#endif
#if FT_ENABLED(FT_OTA)
StatefulService<OTASettings>* getOTASettingsService() {
return &_otaSettingsService;
}
#endif
#if FT_ENABLED(FT_MQTT)
StatefulService<MqttSettings>* getMqttSettingsService() {
return &_mqttSettingsService;
}
@ -69,28 +77,36 @@ class ESP8266React {
AsyncMqttClient* getMqttClient() {
return _mqttSettingsService.getMqttClient();
}
#endif
void factoryReset() {
_factoryResetService.factoryReset();
}
private:
FeaturesService _featureService;
SecuritySettingsService _securitySettingsService;
WiFiSettingsService _wifiSettingsService;
APSettingsService _apSettingsService;
NTPSettingsService _ntpSettingsService;
OTASettingsService _otaSettingsService;
MqttSettingsService _mqttSettingsService;
RestartService _restartService;
FactoryResetService _factoryResetService;
AuthenticationService _authenticationService;
WiFiScanner _wifiScanner;
WiFiStatus _wifiStatus;
NTPStatus _ntpStatus;
APSettingsService _apSettingsService;
APStatus _apStatus;
#if FT_ENABLED(FT_NTP)
NTPSettingsService _ntpSettingsService;
NTPStatus _ntpStatus;
#endif
#if FT_ENABLED(FT_OTA)
OTASettingsService _otaSettingsService;
#endif
#if FT_ENABLED(FT_MQTT)
MqttSettingsService _mqttSettingsService;
MqttStatus _mqttStatus;
#endif
#if FT_ENABLED(FT_SECURITY)
AuthenticationService _authenticationService;
#endif
RestartService _restartService;
FactoryResetService _factoryResetService;
SystemStatus _systemStatus;
};

31
lib/framework/Features.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef Features_h
#define Features_h
#define FT_ENABLED(feature) feature
// project feature off by default
#ifndef FT_PROJECT
#define FT_PROJECT 0
#endif
// security feature on by default
#ifndef FT_SECURITY
#define FT_SECURITY 1
#endif
// mqtt feature on by default
#ifndef FT_MQTT
#define FT_MQTT 1
#endif
// ntp feature on by default
#ifndef FT_NTP
#define FT_NTP 1
#endif
// mqtt feature on by default
#ifndef FT_OTA
#define FT_OTA 1
#endif
#endif

View File

@ -0,0 +1,37 @@
#include <FeaturesService.h>
FeaturesService::FeaturesService(AsyncWebServer* server) {
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, std::placeholders::_1));
}
void FeaturesService::features(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_FEATURES_SIZE);
JsonObject root = response->getRoot();
#if FT_ENABLED(FT_PROJECT)
root["project"] = true;
#else
root["project"] = false;
#endif
#if FT_ENABLED(FT_SECURITY)
root["security"] = true;
#else
root["security"] = false;
#endif
#if FT_ENABLED(FT_MQTT)
root["mqtt"] = true;
#else
root["mqtt"] = false;
#endif
#if FT_ENABLED(FT_NTP)
root["ntp"] = true;
#else
root["ntp"] = false;
#endif
#if FT_ENABLED(FT_OTA)
root["ota"] = true;
#else
root["ota"] = false;
#endif
response->setLength();
request->send(response);
}

View File

@ -0,0 +1,29 @@
#ifndef FeaturesService_h
#define FeaturesService_h
#include <Features.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#define MAX_FEATURES_SIZE 256
#define FEATURES_SERVICE_PATH "/rest/features"
class FeaturesService {
public:
FeaturesService(AsyncWebServer* server);
private:
void features(AsyncWebServerRequest* request);
};
#endif

View File

@ -22,4 +22,4 @@ class RestartService {
void restart(AsyncWebServerRequest* request);
};
#endif // end RestartService_h
#endif // end RestartService_h

View File

@ -1,6 +1,7 @@
#ifndef SecurityManager_h
#define SecurityManager_h
#include <Features.h>
#include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h>
#include <ESPUtils.h>
@ -62,21 +63,24 @@ class AuthenticationPredicates {
class SecurityManager {
public:
#if FT_ENABLED(FT_SECURITY)
/*
* Authenticate, returning the user if found
*/
virtual Authentication authenticate(const String& username, const String& password) = 0;
/*
* Check the request header for the Authorization token
*/
virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
/*
* Generate a JWT for the user provided
*/
virtual String generateJWT(User* user) = 0;
#endif
/*
* Check the request header for the Authorization token
*/
virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
/**
* Filter a request with the provided predicate, only returning true if the predicate matches.
*/
@ -91,8 +95,8 @@ class SecurityManager {
/**
* Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
*/
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback,
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) = 0;
};
#endif // end SecurityManager_h
#endif // end SecurityManager_h

View File

@ -1,5 +1,7 @@
#include <SecuritySettingsService.h>
#if FT_ENABLED(FT_SECURITY)
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) :
_httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this),
_fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE),
@ -94,14 +96,45 @@ ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFu
};
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction callback,
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return [this, callback, predicate](AsyncWebServerRequest* request, JsonVariant& json) {
return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
}
callback(request, json);
onRequest(request, json);
};
}
#else
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() {
}
SecuritySettingsService::~SecuritySettingsService() {
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest* request) { return true; };
}
// Return the admin user on all request - disabling security features
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
return Authentication(ADMIN_USER);
}
// Return the function unwrapped
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return onRequest;
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) {
return onRequest;
}
#endif

View File

@ -1,6 +1,7 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include <Features.h>
#include <SecurityManager.h>
#include <HttpEndpoint.h>
#include <FSPersistence.h>
@ -24,6 +25,8 @@
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
#if FT_ENABLED(FT_SECURITY)
class SecuritySettings {
public:
String jwtSecret;
@ -93,4 +96,19 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
boolean validatePayload(JsonObject& parsedPayload, User* user);
};
#endif // end SecuritySettingsService_h
#else
class SecuritySettingsService : public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer* server, FS* fs);
~SecuritySettingsService();
// minimal set of functions to support framework with security settings disabled
Authentication authenticateRequest(AsyncWebServerRequest* request);
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
};
#endif // end FT_ENABLED(FT_SECURITY)
#endif // end SecuritySettingsService_h

View File

@ -21,11 +21,11 @@ enum class StateUpdateResult {
ERROR // There was a problem updating the state, propagation should not take place
};
template <class T>
using JsonStateUpdater = StateUpdateResult (*)(JsonObject& root, T& settings);
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject& root, T& settings)>;
template <class T>
using JsonStateReader = void (*)(T& settings, JsonObject& root);
template <typename T>
using JsonStateReader = std::function<void(T& settings, JsonObject& root)>;
typedef size_t update_handler_id_t;
typedef std::function<void(const String& originId)> StateUpdateCallback;

View File

@ -27,4 +27,4 @@ class SystemStatus {
void systemStatus(AsyncWebServerRequest* request);
};
#endif // end SystemStatus_h
#endif // end SystemStatus_h

View File

@ -67,4 +67,4 @@ uint8_t WiFiScanner::convertEncryptionType(uint8_t encryptionType) {
}
return -1;
}
#endif
#endif