replace api/settings and customizatons with secure REST calls #501

This commit is contained in:
Proddy
2022-05-16 21:30:06 +02:00
parent ae9af3bf0b
commit fc29a3ad95
9 changed files with 110 additions and 57 deletions

View File

@@ -38,3 +38,4 @@ export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSet
export function readLogEntries(): AxiosPromise<LogEntries> {
return AXIOS_BIN.get('/fetchLog');
}

View File

@@ -24,7 +24,22 @@ const HelpInformation: FC = () => {
const { me } = useContext(AuthenticatedContext);
const onDownload = async (endpoint: string) => {
const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json';
a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
a.setAttribute('download', filename);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' });
};
const callAPI = async (endpoint: string) => {
try {
const response = await EMSESP.API({
device: 'system',
@@ -34,19 +49,33 @@ const HelpInformation: FC = () => {
if (response.status !== 200) {
enqueueSnackbar('API call failed', { variant: 'error' });
} else {
const json = response.data;
const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json';
a.href = URL.createObjectURL(
new Blob([JSON.stringify(json, null, 2)], {
type: 'text/plain'
})
);
a.setAttribute('download', filename);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' });
saveFile(response.data, endpoint);
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
}
};
const downloadSettings = async () => {
try {
const response = await EMSESP.getSettings();
if (response.status !== 200) {
enqueueSnackbar('Unable to get settings', { variant: 'error' });
} else {
saveFile(response.data, 'settings');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
}
};
const downloadCustomizations = async () => {
try {
const response = await EMSESP.getCustomizations();
if (response.status !== 200) {
enqueueSnackbar('Unable to get customizations', { variant: 'error' });
} else {
saveFile(response.data, 'customizations');
}
} catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' });
@@ -103,7 +132,7 @@ const HelpInformation: FC = () => {
</ListItemAvatar>
<ListItemText>
To report an issue or request a feature, please&nbsp;
<Link component="button" variant="body1" onClick={() => onDownload('info')}>
<Link component="button" variant="body1" onClick={() => callAPI('info')}>
download
</Link>
&nbsp;the debug information and include in a new&nbsp;
@@ -131,7 +160,7 @@ const HelpInformation: FC = () => {
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => onDownload('settings')}
onClick={() => downloadSettings()}
>
settings
</Button>
@@ -139,7 +168,7 @@ const HelpInformation: FC = () => {
startIcon={<DownloadIcon />}
variant="outlined"
color="primary"
onClick={() => onDownload('customizations')}
onClick={() => downloadCustomizations()}
>
customizations
</Button>

View File

@@ -86,3 +86,11 @@ export function resetCustomizations(): AxiosPromise<void> {
export function API(apiCall: APIcall): AxiosPromise<void> {
return AXIOS_API.post('/', apiCall);
}
export function getSettings(): AxiosPromise<void> {
return AXIOS.get('/getSettings');
}
export function getCustomizations(): AxiosPromise<void> {
return AXIOS.get('/getCustomizations');
}

View File

@@ -1193,16 +1193,19 @@ rest_server.get(SYSTEM_INFO_ENDPOINT, (req, res) => {
res.json(emsesp_info)
})
const SYSTEM_SETTINGS_ENDPOINT = API_ENDPOINT_ROOT + 'system/settings'
rest_server.post(SYSTEM_SETTINGS_ENDPOINT, (req, res) => {
console.log('System Settings POST: ' + JSON.stringify(req.body))
res.sendStatus(200)
})
rest_server.get(SYSTEM_SETTINGS_ENDPOINT, (req, res) => {
console.log('System Settings GET')
const GET_SETTINGS_ENDPOINT = REST_ENDPOINT_ROOT + 'getSettings'
rest_server.get(GET_SETTINGS_ENDPOINT, (req, res) => {
console.log('System Settings:')
res.json(settings)
})
const GET_CUSTOMIZATIONS_ENDPOINT = REST_ENDPOINT_ROOT + 'getCustomizations'
rest_server.get(GET_CUSTOMIZATIONS_ENDPOINT, (req, res) => {
console.log('Customizations:')
// not implemented yet
res.sendStatus(200)
})
// start server
const expressServer = rest_server.listen(port, () =>
console.log(`Mock server for EMS-ESP is up and running at http://localhost:${port}`),

View File

@@ -689,8 +689,6 @@ void System::commands_init() {
// these commands will return data in JSON format
Command::add(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info, F("show system status"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings, F("fetch system settings"), CommandFlag::ADMIN_ONLY);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(customizations), System::command_customizations, F("fetch system customizations"));
Command::add(EMSdevice::DeviceType::SYSTEM, F_(commands), System::command_commands, F("fetch system commands"));
#if defined(EMSESP_DEBUG)
@@ -973,34 +971,6 @@ bool System::saveSettings(const char * filename, const char * section, JsonObjec
return false; // not found
}
// export all settings to JSON text
// we need to keep the original format so the import/upload works as we just replace files
// http://ems-esp/api/system/settings
bool System::command_settings(const char * value, const int8_t id, JsonObject & output) {
output["type"] = "settings";
JsonObject node = output.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
extractSettings(NETWORK_SETTINGS_FILE, "Network", output);
extractSettings(AP_SETTINGS_FILE, "AP", output);
extractSettings(MQTT_SETTINGS_FILE, "MQTT", output);
extractSettings(NTP_SETTINGS_FILE, "NTP", output);
extractSettings(OTA_SETTINGS_FILE, "OTA", output);
extractSettings(SECURITY_SETTINGS_FILE, "Security", output);
extractSettings(EMSESP_SETTINGS_FILE, "Settings", output);
return true;
}
// http://ems-esp/api/system/customizations
// we need to keep the original format so the import/upload works as we just replace file
bool System::command_customizations(const char * value, const int8_t id, JsonObject & output) {
output["type"] = "customizations";
extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", output);
return true;
}
// export status information including the device information
// http://ems-esp/api/system/info
bool System::command_info(const char * value, const int8_t id, JsonObject & output) {

View File

@@ -61,8 +61,6 @@ class System {
static bool command_watch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & output);
static bool command_settings(const char * value, const int8_t id, JsonObject & output);
static bool command_customizations(const char * value, const int8_t id, JsonObject & output);
static bool command_commands(const char * value, const int8_t id, JsonObject & output);
std::string reset_reason(uint8_t cpu) const;

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.4.0b17"
#define EMSESP_APP_VERSION "3.4.0b18"

View File

@@ -32,6 +32,12 @@ WebAPIService::WebAPIService(AsyncWebServer * server, SecurityManager * security
, _apiHandler("/api", std::bind(&WebAPIService::webAPIService_post, this, _1, _2), 256) { // for POSTS, must use 'Content-Type: application/json' in header
server->on("/api", HTTP_GET, std::bind(&WebAPIService::webAPIService_get, this, _1)); // for GETS
server->addHandler(&_apiHandler);
// for settings
server->on(GET_SETTINGS_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebAPIService::getSettings, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(GET_CUSTOMIZATIONS_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WebAPIService::getCustomizations, this, _1), AuthenticationPredicates::IS_ADMIN));
}
// HTTP GET
@@ -150,4 +156,37 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject & input) {
#endif
}
void WebAPIService::getSettings(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
root["type"] = "settings";
JsonObject node = root.createNestedObject("System");
node["version"] = EMSESP_APP_VERSION;
System::extractSettings(NETWORK_SETTINGS_FILE, "Network", root);
System::extractSettings(AP_SETTINGS_FILE, "AP", root);
System::extractSettings(MQTT_SETTINGS_FILE, "MQTT", root);
System::extractSettings(NTP_SETTINGS_FILE, "NTP", root);
System::extractSettings(OTA_SETTINGS_FILE, "OTA", root);
System::extractSettings(SECURITY_SETTINGS_FILE, "Security", root);
System::extractSettings(EMSESP_SETTINGS_FILE, "Settings", root);
response->setLength();
request->send(response);
}
void WebAPIService::getCustomizations(AsyncWebServerRequest * request) {
auto * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_XLARGE_DYN);
JsonObject root = response->getRoot();
root["type"] = "customizations";
System::extractSettings(EMSESP_CUSTOMIZATION_FILE, "Customizations", root);
response->setLength();
request->send(response);
}
} // namespace emsesp

View File

@@ -20,6 +20,8 @@
#define WebAPIService_h
#define EMSESP_API_SERVICE_PATH "/api"
#define GET_SETTINGS_PATH "/rest/getSettings"
#define GET_CUSTOMIZATIONS_PATH "/rest/getCustomizations"
namespace emsesp {
@@ -46,6 +48,9 @@ class WebAPIService {
static uint16_t api_fails_;
void parse(AsyncWebServerRequest * request, JsonObject & input);
void getSettings(AsyncWebServerRequest * request);
void getCustomizations(AsyncWebServerRequest * request);
};
} // namespace emsesp