From 73433586b6426cc5813c12a163cbe7e309b35914 Mon Sep 17 00:00:00 2001 From: Rick Watson Date: Wed, 29 May 2019 23:48:16 +0100 Subject: [PATCH] add security to all admin endpoints --- .../src/authentication/Authentication.js | 2 +- interface/src/components/RestComponent.js | 88 +++++++------- .../src/containers/WiFiNetworkScanner.js | 5 +- src/APSettingsService.cpp | 2 +- src/APSettingsService.h | 4 +- src/ArduinoJsonJWT.cpp | 6 +- src/ArduinoJsonJWT.h | 2 + src/AsyncAuthJsonWebHandler.h | 52 -------- src/AuthenticationService.cpp | 4 +- src/AuthenticationService.h | 3 + src/NTPSettingsService.cpp | 2 +- src/NTPSettingsService.h | 4 +- src/OTASettingsService.cpp | 2 +- src/OTASettingsService.h | 4 +- src/SecurityManager.cpp | 48 ++------ src/SecurityManager.h | 51 ++++---- src/SecuritySettingsService.cpp | 35 ++++++ src/SecuritySettingsService.h | 26 ++++ src/SettingsService.h | 114 ++++++++++++------ src/WiFiScanner.cpp | 10 +- src/WiFiScanner.h | 3 +- src/WiFiSettingsService.cpp | 2 +- src/WiFiSettingsService.h | 4 +- src/main.cpp | 21 ++-- 24 files changed, 264 insertions(+), 230 deletions(-) delete mode 100644 src/AsyncAuthJsonWebHandler.h create mode 100644 src/SecuritySettingsService.cpp create mode 100644 src/SecuritySettingsService.h diff --git a/interface/src/authentication/Authentication.js b/interface/src/authentication/Authentication.js index d379fb2..2de39e7 100644 --- a/interface/src/authentication/Authentication.js +++ b/interface/src/authentication/Authentication.js @@ -47,7 +47,7 @@ export function redirectingAuthorizedFetch(url, params) { return new Promise(function (resolve, reject) { authorizedFetch(url, params).then(response => { if (response.status === 401) { - history.go("/"); + history.push("/"); } else { resolve(response); } diff --git a/interface/src/components/RestComponent.js b/interface/src/components/RestComponent.js index 4d29d40..288bd35 100644 --- a/interface/src/components/RestComponent.js +++ b/interface/src/components/RestComponent.js @@ -1,6 +1,6 @@ import React from 'react'; -import {withNotifier} from '../components/SnackbarNotification'; - +import { withNotifier } from '../components/SnackbarNotification'; +import { redirectingAuthorizedFetch } from '../authentication/Authentication'; /* * It is unlikely this application will grow complex enough to require redux. * @@ -16,11 +16,11 @@ export const restComponent = (endpointUrl, FormComponent) => { constructor(props) { super(props); - this.state={ - data:null, - fetched: false, - errorMessage:null - }; + this.state = { + data: null, + fetched: false, + errorMessage: null + }; this.setState = this.setState.bind(this); this.loadData = this.loadData.bind(this); @@ -30,78 +30,78 @@ export const restComponent = (endpointUrl, FormComponent) => { setData(data) { this.setState({ - data:data, - fetched: true, - errorMessage:null - }); + data: data, + fetched: true, + errorMessage: null + }); } loadData() { this.setState({ - data:null, - fetched: false, - errorMessage:null - }); - fetch(endpointUrl) + data: null, + fetched: false, + errorMessage: null + }); + redirectingAuthorizedFetch(endpointUrl) .then(response => { if (response.status === 200) { return response.json(); } throw Error("Invalid status code: " + response.status); }) - .then(json => {this.setState({data: json, fetched:true})}) - .catch(error =>{ + .then(json => { this.setState({ data: json, fetched: true }) }) + .catch(error => { this.props.raiseNotification("Problem fetching: " + error.message); - this.setState({data: null, fetched:true, errorMessage:error.message}); + this.setState({ data: null, fetched: true, errorMessage: error.message }); }); } saveData(e) { - this.setState({fetched: false}); - fetch(endpointUrl, { + this.setState({ fetched: false }); + redirectingAuthorizedFetch(endpointUrl, { method: 'POST', body: JSON.stringify(this.state.data), headers: new Headers({ 'Content-Type': 'application/json' }) }) - .then(response => { - if (response.status === 200) { - return response.json(); - } - throw Error("Invalid status code: " + response.status); - }) - .then(json => { - this.props.raiseNotification("Changes successfully applied."); - this.setState({data: json, fetched:true}); - }).catch(error => { - this.props.raiseNotification("Problem saving: " + error.message); - this.setState({data: null, fetched:true, errorMessage:error.message}); - }); + .then(response => { + if (response.status === 200) { + return response.json(); + } + throw Error("Invalid status code: " + response.status); + }) + .then(json => { + this.props.raiseNotification("Changes successfully applied."); + this.setState({ data: json, fetched: true }); + }).catch(error => { + this.props.raiseNotification("Problem saving: " + error.message); + this.setState({ data: null, fetched: true, errorMessage: error.message }); + }); } handleValueChange = name => event => { const { data } = this.state; data[name] = event.target.value; - this.setState({data}); + this.setState({ data }); }; handleCheckboxChange = name => event => { const { data } = this.state; data[name] = event.target.checked; - this.setState({data}); + this.setState({ data }); } render() { return ; + handleValueChange={this.handleValueChange} + handleCheckboxChange={this.handleCheckboxChange} + setData={this.setData} + saveData={this.saveData} + loadData={this.loadData} + {...this.state} + {...this.props} + />; } } diff --git a/interface/src/containers/WiFiNetworkScanner.js b/interface/src/containers/WiFiNetworkScanner.js index e32e222..0f064d5 100644 --- a/interface/src/containers/WiFiNetworkScanner.js +++ b/interface/src/containers/WiFiNetworkScanner.js @@ -5,6 +5,7 @@ import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/E import SectionContent from '../components/SectionContent'; import WiFiNetworkSelector from '../forms/WiFiNetworkSelector'; import {withNotifier} from '../components/SnackbarNotification'; +import { redirectingAuthorizedFetch } from '../authentication/Authentication'; const NUM_POLLS = 10 const POLLING_FREQUENCY = 500 @@ -38,7 +39,7 @@ class WiFiNetworkScanner extends Component { scanNetworks() { this.pollCount = 0; this.setState({scanningForNetworks:true, networkList: null, errorMessage:null}); - fetch(SCAN_NETWORKS_ENDPOINT).then(response => { + redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT).then(response => { if (response.status === 202) { this.schedulePollTimeout(); return; @@ -70,7 +71,7 @@ class WiFiNetworkScanner extends Component { } pollNetworkList() { - fetch(LIST_NETWORKS_ENDPOINT) + redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT) .then(response => { if (response.status === 200) { return response.json(); diff --git a/src/APSettingsService.cpp b/src/APSettingsService.cpp index 895766d..a5f966d 100644 --- a/src/APSettingsService.cpp +++ b/src/APSettingsService.cpp @@ -1,6 +1,6 @@ #include -APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) { +APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) { onConfigUpdated(); } diff --git a/src/APSettingsService.h b/src/APSettingsService.h index 3fcd2a7..252df7f 100644 --- a/src/APSettingsService.h +++ b/src/APSettingsService.h @@ -19,11 +19,11 @@ #define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" -class APSettingsService : public SettingsService { +class APSettingsService : public AdminSettingsService { public: - APSettingsService(AsyncWebServer* server, FS* fs); + APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); ~APSettingsService(); void loop(); diff --git a/src/ArduinoJsonJWT.cpp b/src/ArduinoJsonJWT.cpp index fddfc03..0c945dc 100644 --- a/src/ArduinoJsonJWT.cpp +++ b/src/ArduinoJsonJWT.cpp @@ -3,7 +3,11 @@ ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) { } void ArduinoJsonJWT::setSecret(String secret){ - _secret = secret; + _secret = secret; +} + +String ArduinoJsonJWT::getSecret(){ + return _secret; } /* diff --git a/src/ArduinoJsonJWT.h b/src/ArduinoJsonJWT.h index 829041b..8d29e39 100644 --- a/src/ArduinoJsonJWT.h +++ b/src/ArduinoJsonJWT.h @@ -31,6 +31,8 @@ public: ArduinoJsonJWT(String secret); void setSecret(String secret); + String getSecret(); + String buildJWT(JsonObject &payload); void parseJWT(String jwt, JsonDocument &jsonDocument); }; diff --git a/src/AsyncAuthJsonWebHandler.h b/src/AsyncAuthJsonWebHandler.h deleted file mode 100644 index a767a9a..0000000 --- a/src/AsyncAuthJsonWebHandler.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef AsyncAuthJsonWebHandler_H_ -#define AsyncAuthJsonWebHandler_H_ - -#include -#include -#include -#include - -typedef std::function AuthenticationJsonRequestCallback; - -/** - * Extends AsyncJsonWebHandler with a wrapper which verifies the user is authenticated. - * - * TODO - Extend with role checking support, possibly with a callback to verify the user. - */ -class AsyncAuthJsonWebHandler: public AsyncJsonWebHandler { - - private: - SecurityManager *_securityManager; - using AsyncJsonWebHandler::onRequest; - - public: - - AsyncAuthJsonWebHandler() : - AsyncJsonWebHandler(), _securityManager(NULL) {} - - ~AsyncAuthJsonWebHandler() {} - - void setSecurityManager(SecurityManager *securityManager) { - _securityManager = securityManager; - } - - void onRequest(AuthenticationJsonRequestCallback callback) { - AsyncJsonWebHandler::onRequest([this, callback](AsyncWebServerRequest *request, JsonDocument &jsonDocument) { - if(!_securityManager) { - Serial.print("Security manager not configured for endpoint: "); - Serial.println(_uri); - request->send(500); - return; - } - Authentication authentication = _securityManager->authenticateRequest(request); - if (!authentication.isAuthenticated()) { - request->send(401); - return; - } - callback(request, jsonDocument, authentication); - }); - } - -}; - -#endif // end AsyncAuthJsonWebHandler_H_ \ No newline at end of file diff --git a/src/AuthenticationService.cpp b/src/AuthenticationService.cpp index 1a9e321..c67f091 100644 --- a/src/AuthenticationService.cpp +++ b/src/AuthenticationService.cpp @@ -6,7 +6,7 @@ AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityMan _signInHandler.setUri(SIGN_IN_PATH); _signInHandler.setMethod(HTTP_POST); - _signInHandler.setMaxContentLength(MAX_SECURITY_MANAGER_SETTINGS_SIZE); + _signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE); _signInHandler.onRequest(std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2)); server->addHandler(&_signInHandler); } @@ -31,7 +31,7 @@ void AuthenticationService::signIn(AsyncWebServerRequest *request, JsonDocument Authentication authentication = _securityManager->authenticate(username, password); if (authentication.isAuthenticated()) { User* user = authentication.getUser(); - AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_AUTHENTICATION_SIZE); JsonObject jsonObject = response->getRoot(); jsonObject["access_token"] = _securityManager->generateJWT(user); response->setLength(); diff --git a/src/AuthenticationService.h b/src/AuthenticationService.h index 71d44f9..15d2941 100644 --- a/src/AuthenticationService.h +++ b/src/AuthenticationService.h @@ -2,6 +2,9 @@ #define AuthenticationService_H_ #include +#include +#include +#include #define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization" #define SIGN_IN_PATH "/rest/signIn" diff --git a/src/NTPSettingsService.cpp b/src/NTPSettingsService.cpp index 99962a2..b09e025 100644 --- a/src/NTPSettingsService.cpp +++ b/src/NTPSettingsService.cpp @@ -1,6 +1,6 @@ #include -NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, NTP_SETTINGS_SERVICE_PATH, NTP_SETTINGS_FILE) { +NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, NTP_SETTINGS_SERVICE_PATH, NTP_SETTINGS_FILE) { #if defined(ESP8266) _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1)); diff --git a/src/NTPSettingsService.h b/src/NTPSettingsService.h index c3624e7..e24f237 100644 --- a/src/NTPSettingsService.h +++ b/src/NTPSettingsService.h @@ -17,11 +17,11 @@ #define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" -class NTPSettingsService : public SettingsService { +class NTPSettingsService : public AdminSettingsService { public: - NTPSettingsService(AsyncWebServer* server, FS* fs); + NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); ~NTPSettingsService(); void loop(); diff --git a/src/OTASettingsService.cpp b/src/OTASettingsService.cpp index 64e98c2..2c9490a 100644 --- a/src/OTASettingsService.cpp +++ b/src/OTASettingsService.cpp @@ -1,6 +1,6 @@ #include -OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, OTA_SETTINGS_SERVICE_PATH, OTA_SETTINGS_FILE) { +OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, OTA_SETTINGS_SERVICE_PATH, OTA_SETTINGS_FILE) { #if defined(ESP8266) _onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1)); #elif defined(ESP_PLATFORM) diff --git a/src/OTASettingsService.h b/src/OTASettingsService.h index 32e76ed..27993a8 100644 --- a/src/OTASettingsService.h +++ b/src/OTASettingsService.h @@ -19,11 +19,11 @@ #define OTA_SETTINGS_FILE "/config/otaSettings.json" #define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings" -class OTASettingsService : public SettingsService { +class OTASettingsService : public AdminSettingsService { public: - OTASettingsService(AsyncWebServer* server, FS* fs); + OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); ~OTASettingsService(); void loop(); diff --git a/src/SecurityManager.cpp b/src/SecurityManager.cpp index 36bd636..e973d0d 100644 --- a/src/SecurityManager.cpp +++ b/src/SecurityManager.cpp @@ -1,41 +1,5 @@ #include -SecurityManager::SecurityManager(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, SECURITY_SETTINGS_PATH, SECURITY_SETTINGS_FILE) {} -SecurityManager::~SecurityManager() {} - -void SecurityManager::readFromJsonObject(JsonObject& root) { - // secret - _jwtSecret = root["jwt_secret"] | DEFAULT_JWT_SECRET; - _jwtHandler.setSecret(_jwtSecret); - - // users - _users.clear(); - if (root["users"].is()) { - for (JsonVariant user : root["users"].as()) { - _users.push_back(User(user["username"], user["password"], user["admin"])); - } - } -} - - -void SecurityManager::writeToJsonObject(JsonObject& root) { - // secret - root["jwt_secret"] = _jwtSecret; - - // users - JsonArray users = root.createNestedArray("users"); - for (User _user : _users) { - JsonObject user = users.createNestedObject(); - user["username"] = _user.getUsername(); - user["password"] = _user.getPassword(); - user["admin"] = _user.isAdmin(); - } -} - -void SecurityManager::begin() { - readFromFS(); -} - Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) { AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); if (authorizationHeader) { @@ -90,3 +54,15 @@ String SecurityManager::generateJWT(User *user) { populateJWTPayload(payload, user); return _jwtHandler.buildJWT(payload); } + +ArRequestHandlerFunction SecurityManager::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) { + return [this, onRequest, predicate](AsyncWebServerRequest *request){ + Authentication authentication = authenticateRequest(request); + if (!predicate(authentication)) { + request->send(401); + return; + } + onRequest(request); + }; +} + \ No newline at end of file diff --git a/src/SecurityManager.h b/src/SecurityManager.h index c289b81..0b35fb9 100644 --- a/src/SecurityManager.h +++ b/src/SecurityManager.h @@ -2,27 +2,16 @@ #define SecurityManager_h #include - -#include -#include -#include #include +#include #define DEFAULT_JWT_SECRET "esp8266-react" -#define SECURITY_SETTINGS_FILE "/config/securitySettings.json" - -#define SECURITY_SETTINGS_PATH "/rest/securitySettings" - #define AUTHORIZATION_HEADER "Authorization" #define AUTHORIZATION_HEADER_PREFIX "Bearer " #define AUTHORIZATION_HEADER_PREFIX_LEN 7 #define MAX_JWT_SIZE 128 -#define MAX_SECURITY_MANAGER_SETTINGS_SIZE 512 -#define SECURITY_MANAGER_MAX_USERS 5 - -#define MAX_USERS_SIZE 1024 class User { private: @@ -62,15 +51,25 @@ class Authentication { } }; -class SecurityManager : public SettingsService { +typedef std::function AuthenticationPredicate; + +class AuthenticationPredicates { + public: + static bool NONE_REQUIRED(Authentication &authentication) { + return true; + }; + static bool IS_AUTHENTICATED(Authentication &authentication) { + return authentication.isAuthenticated(); + }; + static bool IS_ADMIN(Authentication &authentication) { + return authentication.isAuthenticated() && authentication.getUser()->isAdmin(); + }; +}; + +class SecurityManager { public: - SecurityManager(AsyncWebServer* server, FS* fs); - ~SecurityManager(); - - void begin(); - /* * Authenticate, returning the user if found */ @@ -86,21 +85,17 @@ class SecurityManager : public SettingsService { */ String generateJWT(User *user); + /** + * Wrap the provided request to provide validation against an AuthenticationPredicate. + */ + ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate); + protected: - void readFromJsonObject(JsonObject& root); - void writeToJsonObject(JsonObject& root); - - private: - // jwt handler ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET); - - // access point settings - String _jwtSecret; std::list _users; - // endpoint functions - void fetchUsers(AsyncWebServerRequest *request); + private: /* * Lookup the user by JWT diff --git a/src/SecuritySettingsService.cpp b/src/SecuritySettingsService.cpp new file mode 100644 index 0000000..51c355d --- /dev/null +++ b/src/SecuritySettingsService.cpp @@ -0,0 +1,35 @@ +#include + +SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : AdminSettingsService(server, fs, this, SECURITY_SETTINGS_PATH, SECURITY_SETTINGS_FILE), SecurityManager() {} +SecuritySettingsService::~SecuritySettingsService() {} + +void SecuritySettingsService::readFromJsonObject(JsonObject& root) { + // secret + _jwtHandler.setSecret(root["jwt_secret"] | DEFAULT_JWT_SECRET); + + // users + _users.clear(); + if (root["users"].is()) { + for (JsonVariant user : root["users"].as()) { + _users.push_back(User(user["username"], user["password"], user["admin"])); + } + } +} + +void SecuritySettingsService::writeToJsonObject(JsonObject& root) { + // secret + root["jwt_secret"] = _jwtHandler.getSecret(); + + // users + JsonArray users = root.createNestedArray("users"); + for (User _user : _users) { + JsonObject user = users.createNestedObject(); + user["username"] = _user.getUsername(); + user["password"] = _user.getPassword(); + user["admin"] = _user.isAdmin(); + } +} + +void SecuritySettingsService::begin() { + readFromFS(); +} diff --git a/src/SecuritySettingsService.h b/src/SecuritySettingsService.h new file mode 100644 index 0000000..7e5b622 --- /dev/null +++ b/src/SecuritySettingsService.h @@ -0,0 +1,26 @@ +#ifndef SecuritySettingsService_h +#define SecuritySettingsService_h + +#include +#include + +#define SECURITY_SETTINGS_FILE "/config/securitySettings.json" +#define SECURITY_SETTINGS_PATH "/rest/securitySettings" + +class SecuritySettingsService : public AdminSettingsService, public SecurityManager { + + public: + + SecuritySettingsService(AsyncWebServer* server, FS* fs); + ~SecuritySettingsService(); + + void begin(); + + protected: + + void readFromJsonObject(JsonObject& root); + void writeToJsonObject(JsonObject& root); + +}; + +#endif // end SecuritySettingsService_h \ No newline at end of file diff --git a/src/SettingsService.h b/src/SettingsService.h index 5d7cab0..ecf0d6a 100644 --- a/src/SettingsService.h +++ b/src/SettingsService.h @@ -9,54 +9,19 @@ #include #endif +#include #include #include #include #include #include + /* * Abstraction of a service which stores it's settings as JSON in a file system. */ class SettingsService : public SettingsPersistence { -private: - - AsyncJsonWebHandler _updateHandler; - - void fetchConfig(AsyncWebServerRequest *request){ - AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE); - JsonObject jsonObject = response->getRoot(); - writeToJsonObject(jsonObject); - response->setLength(); - request->send(response); - } - - void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) { - if (jsonDocument.is()){ - JsonObject newConfig = jsonDocument.as(); - readFromJsonObject(newConfig); - writeToFS(); - - // write settings back with a callback to reconfigure the wifi - AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE); - JsonObject jsonObject = response->getRoot(); - writeToJsonObject(jsonObject); - response->setLength(); - request->send(response); - } else { - request->send(400); - } - } - - protected: - - // will serve setting endpoints from here - AsyncWebServer* _server; - - // implement to perform action when config has been updated - virtual void onConfigUpdated(){} - public: SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath): @@ -79,6 +44,81 @@ private: readFromFS(); } +protected: + // will serve setting endpoints from here + AsyncWebServer* _server; + + AsyncJsonWebHandler _updateHandler; + + virtual void fetchConfig(AsyncWebServerRequest *request) { + // handle the request + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); + response->setLength(); + request->send(response); + } + + virtual void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) { + // handle the request + if (jsonDocument.is()){ + JsonObject newConfig = jsonDocument.as(); + readFromJsonObject(newConfig); + writeToFS(); + + // write settings back with a callback to reconfigure the wifi + AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); + response->setLength(); + request->send(response); + } else { + request->send(400); + } + } + + // implement to perform action when config has been updated + virtual void onConfigUpdated(){} + +}; + +class AdminSettingsService : public SettingsService { + public: + AdminSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath): + SettingsService(server, 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/src/WiFiScanner.cpp b/src/WiFiScanner.cpp index 7bf1591..e3277a5 100644 --- a/src/WiFiScanner.cpp +++ b/src/WiFiScanner.cpp @@ -1,8 +1,12 @@ #include -WiFiScanner::WiFiScanner(AsyncWebServer *server) : _server(server) { - _server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1)); - _server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1)); +WiFiScanner::WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager) : _server(server) { + _server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, + securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN) + ); + _server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, + securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN) + ); } void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) { diff --git a/src/WiFiScanner.h b/src/WiFiScanner.h index 10c4d7a..ca14db0 100644 --- a/src/WiFiScanner.h +++ b/src/WiFiScanner.h @@ -13,6 +13,7 @@ #include #include #include +#include #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" #define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks" @@ -23,7 +24,7 @@ class WiFiScanner { public: - WiFiScanner(AsyncWebServer *server); + WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager); private: diff --git a/src/WiFiSettingsService.cpp b/src/WiFiSettingsService.cpp index 2f65d5e..9e6c5c2 100644 --- a/src/WiFiSettingsService.cpp +++ b/src/WiFiSettingsService.cpp @@ -1,6 +1,6 @@ #include -WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {} +WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : AdminSettingsService(server, fs, securityManager, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {} WiFiSettingsService::~WiFiSettingsService() {} diff --git a/src/WiFiSettingsService.h b/src/WiFiSettingsService.h index 18949c6..1675471 100644 --- a/src/WiFiSettingsService.h +++ b/src/WiFiSettingsService.h @@ -7,11 +7,11 @@ #define WIFI_SETTINGS_FILE "/config/wifiSettings.json" #define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings" -class WiFiSettingsService : public SettingsService { +class WiFiSettingsService : public AdminSettingsService { public: - WiFiSettingsService(AsyncWebServer* server, FS* fs); + WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager); ~WiFiSettingsService(); void begin(); diff --git a/src/main.cpp b/src/main.cpp index 694fde9..9bfbe87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,7 @@ #include -#include +#include #include #include #include @@ -27,15 +27,14 @@ AsyncWebServer server(80); -SecurityManager securityManager = SecurityManager(&server, &SPIFFS); +SecuritySettingsService securitySettingsService = SecuritySettingsService(&server, &SPIFFS); +WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS, &securitySettingsService); +APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS, &securitySettingsService); +NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS, &securitySettingsService); +OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS, &securitySettingsService); +AuthenticationService authenticationService = AuthenticationService(&server, &securitySettingsService); -WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS); -APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS); -NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS); -OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS); -AuthenticationService authenticationService = AuthenticationService(&server, &securityManager); - -WiFiScanner wifiScanner = WiFiScanner(&server); +WiFiScanner wifiScanner = WiFiScanner(&server, &securitySettingsService); WiFiStatus wifiStatus = WiFiStatus(&server); NTPStatus ntpStatus = NTPStatus(&server); APStatus apStatus = APStatus(&server); @@ -48,8 +47,8 @@ void setup() { Serial.begin(SERIAL_BAUD_RATE); SPIFFS.begin(); - // start security manager - securityManager.begin(); + // start security settings service first + securitySettingsService.begin(); // start services ntpSettingsService.begin();