add security to all admin endpoints

This commit is contained in:
Rick Watson 2019-05-29 23:48:16 +01:00
parent 71e5830d6c
commit 73433586b6
24 changed files with 264 additions and 230 deletions

View File

@ -47,7 +47,7 @@ export function redirectingAuthorizedFetch(url, params) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
authorizedFetch(url, params).then(response => { authorizedFetch(url, params).then(response => {
if (response.status === 401) { if (response.status === 401) {
history.go("/"); history.push("/");
} else { } else {
resolve(response); resolve(response);
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; 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. * It is unlikely this application will grow complex enough to require redux.
* *
@ -16,11 +16,11 @@ export const restComponent = (endpointUrl, FormComponent) => {
constructor(props) { constructor(props) {
super(props); super(props);
this.state={ this.state = {
data:null, data: null,
fetched: false, fetched: false,
errorMessage:null errorMessage: null
}; };
this.setState = this.setState.bind(this); this.setState = this.setState.bind(this);
this.loadData = this.loadData.bind(this); this.loadData = this.loadData.bind(this);
@ -30,78 +30,78 @@ export const restComponent = (endpointUrl, FormComponent) => {
setData(data) { setData(data) {
this.setState({ this.setState({
data:data, data: data,
fetched: true, fetched: true,
errorMessage:null errorMessage: null
}); });
} }
loadData() { loadData() {
this.setState({ this.setState({
data:null, data: null,
fetched: false, fetched: false,
errorMessage:null errorMessage: null
}); });
fetch(endpointUrl) redirectingAuthorizedFetch(endpointUrl)
.then(response => { .then(response => {
if (response.status === 200) { if (response.status === 200) {
return response.json(); return response.json();
} }
throw Error("Invalid status code: " + response.status); throw Error("Invalid status code: " + response.status);
}) })
.then(json => {this.setState({data: json, fetched:true})}) .then(json => { this.setState({ data: json, fetched: true }) })
.catch(error =>{ .catch(error => {
this.props.raiseNotification("Problem fetching: " + error.message); 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) { saveData(e) {
this.setState({fetched: false}); this.setState({ fetched: false });
fetch(endpointUrl, { redirectingAuthorizedFetch(endpointUrl, {
method: 'POST', method: 'POST',
body: JSON.stringify(this.state.data), body: JSON.stringify(this.state.data),
headers: new Headers({ headers: new Headers({
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}) })
}) })
.then(response => { .then(response => {
if (response.status === 200) { if (response.status === 200) {
return response.json(); return response.json();
} }
throw Error("Invalid status code: " + response.status); throw Error("Invalid status code: " + response.status);
}) })
.then(json => { .then(json => {
this.props.raiseNotification("Changes successfully applied."); this.props.raiseNotification("Changes successfully applied.");
this.setState({data: json, fetched:true}); this.setState({ data: json, fetched: true });
}).catch(error => { }).catch(error => {
this.props.raiseNotification("Problem saving: " + error.message); this.props.raiseNotification("Problem saving: " + error.message);
this.setState({data: null, fetched:true, errorMessage:error.message}); this.setState({ data: null, fetched: true, errorMessage: error.message });
}); });
} }
handleValueChange = name => event => { handleValueChange = name => event => {
const { data } = this.state; const { data } = this.state;
data[name] = event.target.value; data[name] = event.target.value;
this.setState({data}); this.setState({ data });
}; };
handleCheckboxChange = name => event => { handleCheckboxChange = name => event => {
const { data } = this.state; const { data } = this.state;
data[name] = event.target.checked; data[name] = event.target.checked;
this.setState({data}); this.setState({ data });
} }
render() { render() {
return <FormComponent return <FormComponent
handleValueChange={this.handleValueChange} handleValueChange={this.handleValueChange}
handleCheckboxChange={this.handleCheckboxChange} handleCheckboxChange={this.handleCheckboxChange}
setData={this.setData} setData={this.setData}
saveData={this.saveData} saveData={this.saveData}
loadData={this.loadData} loadData={this.loadData}
{...this.state} {...this.state}
{...this.props} {...this.props}
/>; />;
} }
} }

View File

@ -5,6 +5,7 @@ import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../constants/E
import SectionContent from '../components/SectionContent'; import SectionContent from '../components/SectionContent';
import WiFiNetworkSelector from '../forms/WiFiNetworkSelector'; import WiFiNetworkSelector from '../forms/WiFiNetworkSelector';
import {withNotifier} from '../components/SnackbarNotification'; import {withNotifier} from '../components/SnackbarNotification';
import { redirectingAuthorizedFetch } from '../authentication/Authentication';
const NUM_POLLS = 10 const NUM_POLLS = 10
const POLLING_FREQUENCY = 500 const POLLING_FREQUENCY = 500
@ -38,7 +39,7 @@ class WiFiNetworkScanner extends Component {
scanNetworks() { scanNetworks() {
this.pollCount = 0; this.pollCount = 0;
this.setState({scanningForNetworks:true, networkList: null, errorMessage:null}); this.setState({scanningForNetworks:true, networkList: null, errorMessage:null});
fetch(SCAN_NETWORKS_ENDPOINT).then(response => { redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT).then(response => {
if (response.status === 202) { if (response.status === 202) {
this.schedulePollTimeout(); this.schedulePollTimeout();
return; return;
@ -70,7 +71,7 @@ class WiFiNetworkScanner extends Component {
} }
pollNetworkList() { pollNetworkList() {
fetch(LIST_NETWORKS_ENDPOINT) redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT)
.then(response => { .then(response => {
if (response.status === 200) { if (response.status === 200) {
return response.json(); return response.json();

View File

@ -1,6 +1,6 @@
#include <APSettingsService.h> #include <APSettingsService.h>
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(); onConfigUpdated();
} }

View File

@ -19,11 +19,11 @@
#define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
class APSettingsService : public SettingsService { class APSettingsService : public AdminSettingsService {
public: public:
APSettingsService(AsyncWebServer* server, FS* fs); APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~APSettingsService(); ~APSettingsService();
void loop(); void loop();

View File

@ -3,7 +3,11 @@
ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) { } ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) { }
void ArduinoJsonJWT::setSecret(String secret){ void ArduinoJsonJWT::setSecret(String secret){
_secret = secret; _secret = secret;
}
String ArduinoJsonJWT::getSecret(){
return _secret;
} }
/* /*

View File

@ -31,6 +31,8 @@ public:
ArduinoJsonJWT(String secret); ArduinoJsonJWT(String secret);
void setSecret(String secret); void setSecret(String secret);
String getSecret();
String buildJWT(JsonObject &payload); String buildJWT(JsonObject &payload);
void parseJWT(String jwt, JsonDocument &jsonDocument); void parseJWT(String jwt, JsonDocument &jsonDocument);
}; };

View File

@ -1,52 +0,0 @@
#ifndef AsyncAuthJsonWebHandler_H_
#define AsyncAuthJsonWebHandler_H_
#include <ESPAsyncWebServer.h>
#include <AsyncJsonWebHandler.h>
#include <ArduinoJson.h>
#include <SecurityManager.h>
typedef std::function<void(AsyncWebServerRequest *request, JsonDocument &jsonDocument, Authentication &authentication)> 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_

View File

@ -6,7 +6,7 @@ AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityMan
_signInHandler.setUri(SIGN_IN_PATH); _signInHandler.setUri(SIGN_IN_PATH);
_signInHandler.setMethod(HTTP_POST); _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)); _signInHandler.onRequest(std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2));
server->addHandler(&_signInHandler); server->addHandler(&_signInHandler);
} }
@ -31,7 +31,7 @@ void AuthenticationService::signIn(AsyncWebServerRequest *request, JsonDocument
Authentication authentication = _securityManager->authenticate(username, password); Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.isAuthenticated()) { if (authentication.isAuthenticated()) {
User* user = authentication.getUser(); User* user = authentication.getUser();
AsyncJsonResponse * response = new AsyncJsonResponse(MAX_USERS_SIZE); AsyncJsonResponse * response = new AsyncJsonResponse(MAX_AUTHENTICATION_SIZE);
JsonObject jsonObject = response->getRoot(); JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user); jsonObject["access_token"] = _securityManager->generateJWT(user);
response->setLength(); response->setLength();

View File

@ -2,6 +2,9 @@
#define AuthenticationService_H_ #define AuthenticationService_H_
#include <SecurityManager.h> #include <SecurityManager.h>
#include <ESPAsyncWebServer.h>
#include <AsyncJsonWebHandler.h>
#include <AsyncArduinoJson6.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization" #define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn" #define SIGN_IN_PATH "/rest/signIn"

View File

@ -1,6 +1,6 @@
#include <NTPSettingsService.h> #include <NTPSettingsService.h>
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) #if defined(ESP8266)
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1)); _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));

View File

@ -17,11 +17,11 @@
#define NTP_SETTINGS_FILE "/config/ntpSettings.json" #define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings" #define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
class NTPSettingsService : public SettingsService { class NTPSettingsService : public AdminSettingsService {
public: public:
NTPSettingsService(AsyncWebServer* server, FS* fs); NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~NTPSettingsService(); ~NTPSettingsService();
void loop(); void loop();

View File

@ -1,6 +1,6 @@
#include <OTASettingsService.h> #include <OTASettingsService.h>
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) #if defined(ESP8266)
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1)); _onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
#elif defined(ESP_PLATFORM) #elif defined(ESP_PLATFORM)

View File

@ -19,11 +19,11 @@
#define OTA_SETTINGS_FILE "/config/otaSettings.json" #define OTA_SETTINGS_FILE "/config/otaSettings.json"
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings" #define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
class OTASettingsService : public SettingsService { class OTASettingsService : public AdminSettingsService {
public: public:
OTASettingsService(AsyncWebServer* server, FS* fs); OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~OTASettingsService(); ~OTASettingsService();
void loop(); void loop();

View File

@ -1,41 +1,5 @@
#include <SecurityManager.h> #include <SecurityManager.h>
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<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
_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) { Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) { if (authorizationHeader) {
@ -90,3 +54,15 @@ String SecurityManager::generateJWT(User *user) {
populateJWTPayload(payload, user); populateJWTPayload(payload, user);
return _jwtHandler.buildJWT(payload); 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);
};
}

View File

@ -2,27 +2,16 @@
#define SecurityManager_h #define SecurityManager_h
#include <list> #include <list>
#include <SettingsService.h>
#include <DNSServer.h>
#include <IPAddress.h>
#include <ArduinoJsonJWT.h> #include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h>
#define DEFAULT_JWT_SECRET "esp8266-react" #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 "Authorization"
#define AUTHORIZATION_HEADER_PREFIX "Bearer " #define AUTHORIZATION_HEADER_PREFIX "Bearer "
#define AUTHORIZATION_HEADER_PREFIX_LEN 7 #define AUTHORIZATION_HEADER_PREFIX_LEN 7
#define MAX_JWT_SIZE 128 #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 { class User {
private: private:
@ -62,15 +51,25 @@ class Authentication {
} }
}; };
class SecurityManager : public SettingsService { typedef std::function<boolean(Authentication &authentication)> 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: public:
SecurityManager(AsyncWebServer* server, FS* fs);
~SecurityManager();
void begin();
/* /*
* Authenticate, returning the user if found * Authenticate, returning the user if found
*/ */
@ -86,21 +85,17 @@ class SecurityManager : public SettingsService {
*/ */
String generateJWT(User *user); String generateJWT(User *user);
/**
* Wrap the provided request to provide validation against an AuthenticationPredicate.
*/
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
protected: protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
private:
// jwt handler
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET); ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
// access point settings
String _jwtSecret;
std::list<User> _users; std::list<User> _users;
// endpoint functions private:
void fetchUsers(AsyncWebServerRequest *request);
/* /*
* Lookup the user by JWT * Lookup the user by JWT

View File

@ -0,0 +1,35 @@
#include <SecuritySettingsService.h>
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<JsonArray>()) {
for (JsonVariant user : root["users"].as<JsonArray>()) {
_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();
}

View File

@ -0,0 +1,26 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include <SettingsService.h>
#include <SecurityManager.h>
#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

View File

@ -9,54 +9,19 @@
#include <AsyncTCP.h> #include <AsyncTCP.h>
#endif #endif
#include <SecurityManager.h>
#include <SettingsPersistence.h> #include <SettingsPersistence.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncJsonWebHandler.h> #include <AsyncJsonWebHandler.h>
#include <AsyncArduinoJson6.h> #include <AsyncArduinoJson6.h>
/* /*
* Abstraction of a service which stores it's settings as JSON in a file system. * Abstraction of a service which stores it's settings as JSON in a file system.
*/ */
class SettingsService : public SettingsPersistence { 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>()){
JsonObject newConfig = jsonDocument.as<JsonObject>();
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: public:
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath): SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath):
@ -79,6 +44,81 @@ private:
readFromFS(); 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>()){
JsonObject newConfig = jsonDocument.as<JsonObject>();
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 #endif // end SettingsService

View File

@ -1,8 +1,12 @@
#include <WiFiScanner.h> #include <WiFiScanner.h>
WiFiScanner::WiFiScanner(AsyncWebServer *server) : _server(server) { WiFiScanner::WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager) : _server(server) {
_server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1)); _server->on(SCAN_NETWORKS_SERVICE_PATH, HTTP_GET,
_server->on(LIST_NETWORKS_SERVICE_PATH, HTTP_GET, std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1)); 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) { void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) {

View File

@ -13,6 +13,7 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <AsyncArduinoJson6.h> #include <AsyncArduinoJson6.h>
#include <TimeLib.h> #include <TimeLib.h>
#include <SecurityManager.h>
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks" #define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
@ -23,7 +24,7 @@ class WiFiScanner {
public: public:
WiFiScanner(AsyncWebServer *server); WiFiScanner(AsyncWebServer *server, SecurityManager* securityManager);
private: private:

View File

@ -1,6 +1,6 @@
#include <WiFiSettingsService.h> #include <WiFiSettingsService.h>
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() {} WiFiSettingsService::~WiFiSettingsService() {}

View File

@ -7,11 +7,11 @@
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json" #define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings" #define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
class WiFiSettingsService : public SettingsService { class WiFiSettingsService : public AdminSettingsService {
public: public:
WiFiSettingsService(AsyncWebServer* server, FS* fs); WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~WiFiSettingsService(); ~WiFiSettingsService();
void begin(); void begin();

View File

@ -11,7 +11,7 @@
#include <FS.h> #include <FS.h>
#include <SecurityManager.h> #include <SecuritySettingsService.h>
#include <WiFiSettingsService.h> #include <WiFiSettingsService.h>
#include <APSettingsService.h> #include <APSettingsService.h>
#include <NTPSettingsService.h> #include <NTPSettingsService.h>
@ -27,15 +27,14 @@
AsyncWebServer server(80); 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); WiFiScanner wifiScanner = WiFiScanner(&server, &securitySettingsService);
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS);
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS);
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS);
AuthenticationService authenticationService = AuthenticationService(&server, &securityManager);
WiFiScanner wifiScanner = WiFiScanner(&server);
WiFiStatus wifiStatus = WiFiStatus(&server); WiFiStatus wifiStatus = WiFiStatus(&server);
NTPStatus ntpStatus = NTPStatus(&server); NTPStatus ntpStatus = NTPStatus(&server);
APStatus apStatus = APStatus(&server); APStatus apStatus = APStatus(&server);
@ -48,8 +47,8 @@ void setup() {
Serial.begin(SERIAL_BAUD_RATE); Serial.begin(SERIAL_BAUD_RATE);
SPIFFS.begin(); SPIFFS.begin();
// start security manager // start security settings service first
securityManager.begin(); securitySettingsService.begin();
// start services // start services
ntpSettingsService.begin(); ntpSettingsService.begin();