add security to all admin endpoints
This commit is contained in:
parent
71e5830d6c
commit
73433586b6
@ -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);
|
||||
}
|
||||
|
@ -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 <FormComponent
|
||||
handleValueChange={this.handleValueChange}
|
||||
handleCheckboxChange={this.handleCheckboxChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
loadData={this.loadData}
|
||||
{...this.state}
|
||||
{...this.props}
|
||||
/>;
|
||||
handleValueChange={this.handleValueChange}
|
||||
handleCheckboxChange={this.handleCheckboxChange}
|
||||
setData={this.setData}
|
||||
saveData={this.saveData}
|
||||
loadData={this.loadData}
|
||||
{...this.state}
|
||||
{...this.props}
|
||||
/>;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -1,6 +1,6 @@
|
||||
#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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -3,7 +3,11 @@
|
||||
ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) { }
|
||||
|
||||
void ArduinoJsonJWT::setSecret(String secret){
|
||||
_secret = secret;
|
||||
_secret = secret;
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::getSecret(){
|
||||
return _secret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -31,6 +31,8 @@ public:
|
||||
ArduinoJsonJWT(String secret);
|
||||
|
||||
void setSecret(String secret);
|
||||
String getSecret();
|
||||
|
||||
String buildJWT(JsonObject &payload);
|
||||
void parseJWT(String jwt, JsonDocument &jsonDocument);
|
||||
};
|
||||
|
@ -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_
|
@ -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();
|
||||
|
@ -2,6 +2,9 @@
|
||||
#define AuthenticationService_H_
|
||||
|
||||
#include <SecurityManager.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
|
||||
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
|
||||
#define SIGN_IN_PATH "/rest/signIn"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#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)
|
||||
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
|
||||
|
@ -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();
|
||||
|
@ -1,6 +1,6 @@
|
||||
#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)
|
||||
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
|
||||
#elif defined(ESP_PLATFORM)
|
||||
|
@ -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();
|
||||
|
@ -1,41 +1,5 @@
|
||||
#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) {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
@ -2,27 +2,16 @@
|
||||
#define SecurityManager_h
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <SettingsService.h>
|
||||
#include <DNSServer.h>
|
||||
#include <IPAddress.h>
|
||||
#include <ArduinoJsonJWT.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#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<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:
|
||||
|
||||
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<User> _users;
|
||||
|
||||
// endpoint functions
|
||||
void fetchUsers(AsyncWebServerRequest *request);
|
||||
private:
|
||||
|
||||
/*
|
||||
* Lookup the user by JWT
|
||||
|
35
src/SecuritySettingsService.cpp
Normal file
35
src/SecuritySettingsService.cpp
Normal 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();
|
||||
}
|
26
src/SecuritySettingsService.h
Normal file
26
src/SecuritySettingsService.h
Normal 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
|
@ -9,54 +9,19 @@
|
||||
#include <AsyncTCP.h>
|
||||
#endif
|
||||
|
||||
#include <SecurityManager.h>
|
||||
#include <SettingsPersistence.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJsonWebHandler.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
|
||||
|
||||
/*
|
||||
* 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>()){
|
||||
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:
|
||||
|
||||
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>()){
|
||||
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
|
||||
|
@ -1,8 +1,12 @@
|
||||
#include <WiFiScanner.h>
|
||||
|
||||
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) {
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncArduinoJson6.h>
|
||||
#include <TimeLib.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#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:
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#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() {}
|
||||
|
||||
|
@ -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();
|
||||
|
21
src/main.cpp
21
src/main.cpp
@ -11,7 +11,7 @@
|
||||
|
||||
#include <FS.h>
|
||||
|
||||
#include <SecurityManager.h>
|
||||
#include <SecuritySettingsService.h>
|
||||
#include <WiFiSettingsService.h>
|
||||
#include <APSettingsService.h>
|
||||
#include <NTPSettingsService.h>
|
||||
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user