From 15ae0bb24851862beaa9cdc4fafdf042961cfa52 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Sat, 10 Aug 2019 12:35:26 +0100 Subject: [PATCH] WIP - some documentation --- README.md | 89 +++++++++++++++++++++++-- lib/framework/APSettingsService.h | 2 +- lib/framework/AdminSettingsService.h | 46 +++++++++++++ lib/framework/NTPSettingsService.h | 2 +- lib/framework/OTASettingsService.h | 2 +- lib/framework/SecuritySettingsService.h | 2 +- lib/framework/SettingsService.h | 39 ----------- lib/framework/WiFiSettingsService.h | 2 +- src/DemoProject.h | 2 +- src/main.cpp | 9 ++- 10 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 lib/framework/AdminSettingsService.h diff --git a/README.md b/README.md index 0cf9adc..86e485b 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,9 @@ Resource | Description ---- | ----------- [data/](data) | The file system image directory [interface/](interface) | React based front end -[src/](src) | C++ back end for the ESP8266 device +[src/](src) | The main.cpp and demo project to get you started [platformio.ini](platformio.ini) | PlatformIO project configuration file +[lib/framework/](lib/framework) | C++ back end for the ESP8266 device ### Building the firmware @@ -247,13 +248,90 @@ There is also a manifest file which contains the app name to use when adding the } ``` -## Back End Overview +## Back end overview -The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The source is split up by feature, for example [WiFiScanner.h](src/WiFiScanner.h) implements the end points for scanning for available networks. +The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The ['lib/framework'](lib/framework) directory contains the majority of the back end code. The framework contains of a number of useful utility classes which you can use when extending it. The project also comes with a demo project to give you some help getting started. -There is an abstract class [SettingsService.h](src/SettingsService.h) that provides an easy means of adding configurable services/features to the device. It takes care of writing the settings as JSON to SPIFFS. All you need to do is extend the class with your required configuration and implement the functions which serialize the settings to/from JSON. JSON serialization utilizes the excellent [ArduinoJson](https://github.com/bblanchon/ArduinoJson) library. +The framework's source is split up by feature, for example [WiFiScanner.h](lib/framework/WiFiScanner.h) implements the end points for scanning for available networks where as [WiFiSettingsService.h](lib/framework/WiFiSettingsService.h) handles configuring the WiFi settings and managing the WiFi connection. -Here is a example of a service with username and password settings: +### Initializing the framework + +The ['src/main.cpp'](src/main.cpp) file constructs the webserver and initializes the framework. You can add endpoints to the server here to support your IoT project. The main loop is also accessable so you can run your own code easily. + +The following code creates the web server, esp8266React framework and the demo project instance: + +```cpp +AsyncWebServer server(80); +ESP8266React framework(&SPIFFS); +DemoProject demoProject = DemoProject(&SPIFFS, framework.getSecurityManager()); +``` + +Now in the `setup()` function the initialization is performed: + +```cpp +void setup() { + // start serial and filesystem + Serial.begin(SERIAL_BAUD_RATE); + SPIFFS.begin(); + + // set up the framework + framework.init(&server); + + // begin the demo project + demoProject.init(&server); + + // start the server + server.begin(); +} +``` + +Finally the loop calls the framework's loop function to service the frameworks features. You can add your own code in here, as shown with the demo project: + +```cpp +void loop() { + // run the framework's loop function + framework.loop(); + + // run the demo project's loop function + demoProject.loop(); +} +``` + +### Adding endpoints + +There are some simple classes that support adding configurable services/features to the device: + +Class | Description +----- | ----------- +[SimpleService.h](lib/framework/SimpleService.h) | Exposes an endpoint to read and write settings as JSON. Extend this class and implement the functions which serialize the settings to/from JSON. +[SettingsService.h](lib/framework/SettingsService.h) | As above, however this class also handles persisting the settings as JSON to the file system. +[AdminSettingsService.h](lib/framework/AdminSettingsService.h) | Extends SettingsService to secure the endpoint to administrators only, the authentication predicate can be overridden if required. + +The demo project shows how these can be used, explore the framework classes for more examples. + +### Security features + +The framework has security features to prevent unauthorized use of the device. This is driven by [SecurityManager.h](lib/framework/SecurityManager.h). + +On successful authentication, the /rest/signIn endpoint issues a JWT which is then sent using Bearer Authentication. The framework come with built in predicates for verifying a users access level. The built in AuthenticationPredicates can be found in [SecurityManager.h](lib/framework/SecurityManager.h): + +Predicate | Description +-------------------- | ----------- +NONE_REQUIRED | No authentication is required. +IS_AUTHENTICATED | Any authentication is permitted. +IS_AUTHENTICATED | Any authentication is permitted. + +You can use the security manager to wrap any web handler with an authentication predicate: + +```cpp +server->on("/rest/someService", HTTP_GET, + _securityManager->wrapRequest(std::bind(&SomeService::someService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED) +); +``` + +Alternatively you can extend [AdminSettingsService.h](lib/framework/AdminSettingsService.h) and optionally override `getAuthenticationPredicate()` to secure an endpoint. + +## Extending the framework ```cpp #include @@ -321,6 +399,7 @@ void reconfigureTheService() { * [React](https://reactjs.org/) * [Material-UI](https://material-ui-next.com/) +* [notistack](https://github.com/iamhosseindhv/notistack) * [Time](https://github.com/PaulStoffregen/Time) * [NtpClient](https://github.com/gmag11/NtpClient) * [ArduinoJson](https://github.com/bblanchon/ArduinoJson) diff --git a/lib/framework/APSettingsService.h b/lib/framework/APSettingsService.h index d08f1ef..fb2e9d5 100644 --- a/lib/framework/APSettingsService.h +++ b/lib/framework/APSettingsService.h @@ -1,7 +1,7 @@ #ifndef APSettingsConfig_h #define APSettingsConfig_h -#include +#include #include #include diff --git a/lib/framework/AdminSettingsService.h b/lib/framework/AdminSettingsService.h new file mode 100644 index 0000000..cb5be59 --- /dev/null +++ b/lib/framework/AdminSettingsService.h @@ -0,0 +1,46 @@ +#ifndef AdminSettingsService_h +#define AdminSettingsService_h + +#include + +class AdminSettingsService : public SettingsService { + + public: + AdminSettingsService(FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath): + SettingsService(fs, servicePath, filePath), _securityManager(securityManager) { + } + + protected: + // will validate the requests with the security manager + SecurityManager* _securityManager; + + void fetchConfig(AsyncWebServerRequest *request) { + // verify the request against the predicate + Authentication authentication = _securityManager->authenticateRequest(request); + if (!getAuthenticationPredicate()(authentication)) { + request->send(401); + return; + } + // delegate to underlying implemetation + SettingsService::fetchConfig(request); + } + + void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) { + // verify the request against the predicate + Authentication authentication = _securityManager->authenticateRequest(request); + if (!getAuthenticationPredicate()(authentication)) { + request->send(401); + return; + } + // delegate to underlying implemetation + SettingsService::updateConfig(request, jsonDocument); + } + + // override this to replace the default authentication predicate, IS_ADMIN + AuthenticationPredicate getAuthenticationPredicate() { + return AuthenticationPredicates::IS_ADMIN; + } + +}; + +#endif // end AdminSettingsService diff --git a/lib/framework/NTPSettingsService.h b/lib/framework/NTPSettingsService.h index 0dc36e5..8474fec 100644 --- a/lib/framework/NTPSettingsService.h +++ b/lib/framework/NTPSettingsService.h @@ -1,7 +1,7 @@ #ifndef NTPSettingsService_h #define NTPSettingsService_h -#include +#include #include #include diff --git a/lib/framework/OTASettingsService.h b/lib/framework/OTASettingsService.h index 7b65fb0..60bb9da 100644 --- a/lib/framework/OTASettingsService.h +++ b/lib/framework/OTASettingsService.h @@ -1,7 +1,7 @@ #ifndef OTASettingsService_h #define OTASettingsService_h -#include +#include #if defined(ESP8266) #include diff --git a/lib/framework/SecuritySettingsService.h b/lib/framework/SecuritySettingsService.h index 356ee67..bc758e9 100644 --- a/lib/framework/SecuritySettingsService.h +++ b/lib/framework/SecuritySettingsService.h @@ -1,7 +1,7 @@ #ifndef SecuritySettingsService_h #define SecuritySettingsService_h -#include +#include #include #define SECURITY_SETTINGS_FILE "/config/securitySettings.json" diff --git a/lib/framework/SettingsService.h b/lib/framework/SettingsService.h index f645852..3c447e8 100644 --- a/lib/framework/SettingsService.h +++ b/lib/framework/SettingsService.h @@ -78,43 +78,4 @@ protected: }; -class AdminSettingsService : public SettingsService { - public: - AdminSettingsService(FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath): - SettingsService(fs, servicePath, filePath), _securityManager(securityManager) { - } - - protected: - // will validate the requests with the security manager - SecurityManager* _securityManager; - - void fetchConfig(AsyncWebServerRequest *request) { - // verify the request against the predicate - Authentication authentication = _securityManager->authenticateRequest(request); - if (!getAuthenticationPredicate()(authentication)) { - request->send(401); - return; - } - // delegate to underlying implemetation - SettingsService::fetchConfig(request); - } - - void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) { - // verify the request against the predicate - Authentication authentication = _securityManager->authenticateRequest(request); - if (!getAuthenticationPredicate()(authentication)) { - request->send(401); - return; - } - // delegate to underlying implemetation - SettingsService::updateConfig(request, jsonDocument); - } - - // override to override the default authentication predicate, IS_ADMIN - AuthenticationPredicate getAuthenticationPredicate() { - return AuthenticationPredicates::IS_ADMIN; - } - -}; - #endif // end SettingsService diff --git a/lib/framework/WiFiSettingsService.h b/lib/framework/WiFiSettingsService.h index 87ce575..b967591 100644 --- a/lib/framework/WiFiSettingsService.h +++ b/lib/framework/WiFiSettingsService.h @@ -1,7 +1,7 @@ #ifndef WiFiSettingsService_h #define WiFiSettingsService_h -#include +#include #include #define WIFI_SETTINGS_FILE "/config/wifiSettings.json" diff --git a/src/DemoProject.h b/src/DemoProject.h index 7b85111..0b434a9 100644 --- a/src/DemoProject.h +++ b/src/DemoProject.h @@ -1,7 +1,7 @@ #ifndef DemoProject_h #define DemoProject_h -#include +#include #define BLINK_LED 2 #define MAX_DELAY 1000 diff --git a/src/main.cpp b/src/main.cpp index cda4d75..ea4d706 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,9 +5,8 @@ #define SERIAL_BAUD_RATE 115200 AsyncWebServer server(80); -ESP8266React espServer(&SPIFFS); - -DemoProject demoProject = DemoProject(&SPIFFS, espServer.getSecurityManager()); +ESP8266React framework(&SPIFFS); +DemoProject demoProject = DemoProject(&SPIFFS, framework.getSecurityManager()); void setup() { // start serial and filesystem @@ -15,7 +14,7 @@ void setup() { SPIFFS.begin(); // set up the framework - espServer.init(&server); + framework.init(&server); // begin the demo project demoProject.init(&server); @@ -26,7 +25,7 @@ void setup() { void loop() { // run the framework's loop function - espServer.loop(); + framework.loop(); // run the demo project's loop function demoProject.loop();