Loading data/admin.html +54 −1 Changes for data/admin.html: 54 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -308,9 +308,13 @@ function loadDomains() { } } html += '</table>'; html += '<ul class="domaintoolbar">'; if (isSuperAdmin) { html += '<ul class="domaintoolbar"><li><a class="button" href="#" onclick="createDomainForm(); return false;">CreateDomain</a></li></ul>'; html += '<li><a class="button" href="#" onclick="createDomainForm(); return false;">CreateDomain</a></li>'; } html += '<li><a class="button" href="#" onclick="exportDb(); return false;">Export</a></li>'; html += '<li><a class="button" href="#" onclick="importDbForm(); return false;">Import</a></li>'; html += '</ul>'; setContent(html); }); }); Loading Loading @@ -348,6 +352,55 @@ function removeDomain(did) { }); } function exportDb() { ajaxGet("/settings/" + currentDomain + "/api/export", function(err, data) { if (err) { showError(err); return; } var jsonStr = JSON.stringify(data, null, 2); var blob = new Blob([jsonStr], {type: "application/json"}); var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = currentDomain + "-export.json"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }); } function importDbForm() { var html = '<h3>Import Database</h3>'; html += '<form id="importdbform" class="admin-form">'; html += '<table>'; html += '<tr><td><label>JSON File:</label></td><td><input type="file" id="import_file" accept=".json"></td></tr>'; html += '<tr><td></td><td><input type="button" value="Import" onclick="submitImportDb()"></td></tr>'; html += '</table></form>'; setContent(html); } function submitImportDb() { var fileInput = document.getElementById("import_file"); if (!fileInput.files || !fileInput.files[0]) { showError("Bitte eine JSON-Datei auswaehlen!"); return; } var reader = new FileReader(); reader.onload = function(e) { var jsonData; try { jsonData = JSON.parse(e.target.result); } catch(ex) { showError("Ungueltige JSON-Datei: " + ex.message); return; } ajaxPost("/settings/" + currentDomain + "/api/import", jsonData, function(err, data) { if (err) { showError(err); return; } alert("Import erfolgreich: " + (data.imported || 0) + " Records importiert."); loadDomains(); }); }; reader.readAsText(fileInput.files[0]); } /* ---- Groups ---- */ function loadGroups() { currentView = loadGroups; Loading src/CMakeLists.txt +2 −0 Changes for src/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ list(APPEND authdbSrcs client.cpp hash.cpp hash/sha512.cpp export.cpp import.cpp ) add_library( authobj OBJECT ${authdbSrcs} ) Loading src/admin.cpp +39 −0 Changes for src/admin.cpp: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ #include "gpo.h" #include "client.h" #include "session.h" #include "export.h" #include "import.h" namespace authdb { Loading Loading @@ -1230,6 +1232,37 @@ namespace authdb { json_object_put(jobj); } /* ---- JSON API: Export Database ---- */ void apiExportDb(libhttppp::HttpRequest &curreq, const char *cdid) { DomainBackend dbackend(_AdminBackend); AuthBackend *backend = resolveDomain(cdid, _AdminBackend, dbackend); if (!backend) throw AuthBackendError("export: could not resolve domain!"); json_object *jobj = Export::toJson(*backend); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Import Database ---- */ void apiImportDb(libhttppp::HttpRequest &curreq, const char *cdid) { DomainBackend dbackend(_AdminBackend); AuthBackend *backend = resolveDomain(cdid, _AdminBackend, dbackend); if (!backend) throw AuthBackendError("import: could not resolve domain!"); std::string reqbody(curreq.RecvData.begin(), curreq.RecvData.end()); json_object *jreq = json_tokener_parse(reqbody.c_str()); if (!jreq) throw AuthBackendError("import: invalid JSON!"); int count = Import::fromJson(*backend, jreq); json_object_put(jreq); json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); json_object_object_add(jobj, "imported", json_object_new_int(count)); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- Login (keeps HTML form-based login for compatibility) ---- */ void login(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) { libhttppp::HttpForm form; Loading Loading @@ -1488,6 +1521,12 @@ namespace authdb { } else if (strcmp(apiurl, "editclient") == 0 && param[0]) { checkDomainAccess(sdat, domain); apiEditClient(curreq, domain, param); } else if (strcmp(apiurl, "export") == 0) { checkDomainAccess(sdat, domain); apiExportDb(curreq, domain); } else if (strcmp(apiurl, "import") == 0) { checkDomainAccess(sdat, domain); apiImportDb(curreq, domain); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } Loading src/export.cpp +108 −0 Changes for src/export.cpp: 108 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2025, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <memory> #include <cstring> #include <vector> #include <string> #include <netplus/base64.h> #include <uuidp.h> #include "export.h" #include "types.h" namespace authdb { json_object* Export::toJson(AuthBackend &backend) { json_object *jroot = json_object_new_object(); json_object *jarr = json_object_new_array(); size_t rd = sizeof(AuthHeader), end = backend.end(); while (rd < end) { std::shared_ptr<AuthData::Record> cur = std::make_shared<AuthData::Record>(); cur->type = EmptyData; backend.setPos(rd); backend.read(reinterpret_cast<unsigned char*>(cur.get()), sizeof(AuthData::Record)); rd = backend.getPos() + cur->datasize; // Skip empty/deleted records if (cur->type == EmptyData) continue; // Read record data std::vector<char> rawdata; if (cur->datasize > 0) { rawdata.resize(cur->datasize); backend.read(reinterpret_cast<unsigned char*>(rawdata.data()), cur->datasize); } // Build JSON record json_object *jrec = json_object_new_object(); // ruid as uuid string json_object_object_add(jrec, "ruid", json_object_new_string(uuid::uuid(cur->ruid).c_str())); json_object_object_add(jrec, "type", json_object_new_int(cur->type)); json_object_object_add(jrec, "storage", json_object_new_int(cur->storage)); json_object_object_add(jrec, "fieldname", json_object_new_string(cur->fieldname)); json_object_object_add(jrec, "datasize", json_object_new_int64(static_cast<int64_t>(cur->datasize))); json_object_object_add(jrec, "count", json_object_new_int64(static_cast<int64_t>(cur->count))); // Base64 encode the data if (!rawdata.empty()) { netplus::base64 b64; b64 << rawdata; std::string encoded; b64 >> encoded; json_object_object_add(jrec, "data", json_object_new_string(encoded.c_str())); } else { json_object_object_add(jrec, "data", json_object_new_string("")); } json_object_array_add(jarr, jrec); } json_object_object_add(jroot, "version", json_object_new_int(VERSION)); json_object_object_add(jroot, "records", jarr); return jroot; } }; src/export.h +43 −0 Changes for src/export.h: 43 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2025, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once #include <json-c/json.h> #include "authdb.h" namespace authdb { class Export { public: /** * Export all records from a backend to a JSON object. * Record data is base64-encoded. * Returns a new json_object* — caller must json_object_put() it. */ static json_object* toJson(AuthBackend &backend); }; }; Loading
data/admin.html +54 −1 Changes for data/admin.html: 54 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -308,9 +308,13 @@ function loadDomains() { } } html += '</table>'; html += '<ul class="domaintoolbar">'; if (isSuperAdmin) { html += '<ul class="domaintoolbar"><li><a class="button" href="#" onclick="createDomainForm(); return false;">CreateDomain</a></li></ul>'; html += '<li><a class="button" href="#" onclick="createDomainForm(); return false;">CreateDomain</a></li>'; } html += '<li><a class="button" href="#" onclick="exportDb(); return false;">Export</a></li>'; html += '<li><a class="button" href="#" onclick="importDbForm(); return false;">Import</a></li>'; html += '</ul>'; setContent(html); }); }); Loading Loading @@ -348,6 +352,55 @@ function removeDomain(did) { }); } function exportDb() { ajaxGet("/settings/" + currentDomain + "/api/export", function(err, data) { if (err) { showError(err); return; } var jsonStr = JSON.stringify(data, null, 2); var blob = new Blob([jsonStr], {type: "application/json"}); var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = currentDomain + "-export.json"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }); } function importDbForm() { var html = '<h3>Import Database</h3>'; html += '<form id="importdbform" class="admin-form">'; html += '<table>'; html += '<tr><td><label>JSON File:</label></td><td><input type="file" id="import_file" accept=".json"></td></tr>'; html += '<tr><td></td><td><input type="button" value="Import" onclick="submitImportDb()"></td></tr>'; html += '</table></form>'; setContent(html); } function submitImportDb() { var fileInput = document.getElementById("import_file"); if (!fileInput.files || !fileInput.files[0]) { showError("Bitte eine JSON-Datei auswaehlen!"); return; } var reader = new FileReader(); reader.onload = function(e) { var jsonData; try { jsonData = JSON.parse(e.target.result); } catch(ex) { showError("Ungueltige JSON-Datei: " + ex.message); return; } ajaxPost("/settings/" + currentDomain + "/api/import", jsonData, function(err, data) { if (err) { showError(err); return; } alert("Import erfolgreich: " + (data.imported || 0) + " Records importiert."); loadDomains(); }); }; reader.readAsText(fileInput.files[0]); } /* ---- Groups ---- */ function loadGroups() { currentView = loadGroups; Loading
src/CMakeLists.txt +2 −0 Changes for src/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -10,6 +10,8 @@ list(APPEND authdbSrcs client.cpp hash.cpp hash/sha512.cpp export.cpp import.cpp ) add_library( authobj OBJECT ${authdbSrcs} ) Loading
src/admin.cpp +39 −0 Changes for src/admin.cpp: 39 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ #include "gpo.h" #include "client.h" #include "session.h" #include "export.h" #include "import.h" namespace authdb { Loading Loading @@ -1230,6 +1232,37 @@ namespace authdb { json_object_put(jobj); } /* ---- JSON API: Export Database ---- */ void apiExportDb(libhttppp::HttpRequest &curreq, const char *cdid) { DomainBackend dbackend(_AdminBackend); AuthBackend *backend = resolveDomain(cdid, _AdminBackend, dbackend); if (!backend) throw AuthBackendError("export: could not resolve domain!"); json_object *jobj = Export::toJson(*backend); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- JSON API: Import Database ---- */ void apiImportDb(libhttppp::HttpRequest &curreq, const char *cdid) { DomainBackend dbackend(_AdminBackend); AuthBackend *backend = resolveDomain(cdid, _AdminBackend, dbackend); if (!backend) throw AuthBackendError("import: could not resolve domain!"); std::string reqbody(curreq.RecvData.begin(), curreq.RecvData.end()); json_object *jreq = json_tokener_parse(reqbody.c_str()); if (!jreq) throw AuthBackendError("import: invalid JSON!"); int count = Import::fromJson(*backend, jreq); json_object_put(jreq); json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); json_object_object_add(jobj, "imported", json_object_new_int(count)); sendJson(curreq, jobj); json_object_put(jobj); } /* ---- Login (keeps HTML form-based login for compatibility) ---- */ void login(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) { libhttppp::HttpForm form; Loading Loading @@ -1488,6 +1521,12 @@ namespace authdb { } else if (strcmp(apiurl, "editclient") == 0 && param[0]) { checkDomainAccess(sdat, domain); apiEditClient(curreq, domain, param); } else if (strcmp(apiurl, "export") == 0) { checkDomainAccess(sdat, domain); apiExportDb(curreq, domain); } else if (strcmp(apiurl, "import") == 0) { checkDomainAccess(sdat, domain); apiImportDb(curreq, domain); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } Loading
src/export.cpp +108 −0 Changes for src/export.cpp: 108 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2025, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <memory> #include <cstring> #include <vector> #include <string> #include <netplus/base64.h> #include <uuidp.h> #include "export.h" #include "types.h" namespace authdb { json_object* Export::toJson(AuthBackend &backend) { json_object *jroot = json_object_new_object(); json_object *jarr = json_object_new_array(); size_t rd = sizeof(AuthHeader), end = backend.end(); while (rd < end) { std::shared_ptr<AuthData::Record> cur = std::make_shared<AuthData::Record>(); cur->type = EmptyData; backend.setPos(rd); backend.read(reinterpret_cast<unsigned char*>(cur.get()), sizeof(AuthData::Record)); rd = backend.getPos() + cur->datasize; // Skip empty/deleted records if (cur->type == EmptyData) continue; // Read record data std::vector<char> rawdata; if (cur->datasize > 0) { rawdata.resize(cur->datasize); backend.read(reinterpret_cast<unsigned char*>(rawdata.data()), cur->datasize); } // Build JSON record json_object *jrec = json_object_new_object(); // ruid as uuid string json_object_object_add(jrec, "ruid", json_object_new_string(uuid::uuid(cur->ruid).c_str())); json_object_object_add(jrec, "type", json_object_new_int(cur->type)); json_object_object_add(jrec, "storage", json_object_new_int(cur->storage)); json_object_object_add(jrec, "fieldname", json_object_new_string(cur->fieldname)); json_object_object_add(jrec, "datasize", json_object_new_int64(static_cast<int64_t>(cur->datasize))); json_object_object_add(jrec, "count", json_object_new_int64(static_cast<int64_t>(cur->count))); // Base64 encode the data if (!rawdata.empty()) { netplus::base64 b64; b64 << rawdata; std::string encoded; b64 >> encoded; json_object_object_add(jrec, "data", json_object_new_string(encoded.c_str())); } else { json_object_object_add(jrec, "data", json_object_new_string("")); } json_object_array_add(jarr, jrec); } json_object_object_add(jroot, "version", json_object_new_int(VERSION)); json_object_object_add(jroot, "records", jarr); return jroot; } };
src/export.h +43 −0 Changes for src/export.h: 43 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* * Copyright (c) 2025, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #pragma once #include <json-c/json.h> #include "authdb.h" namespace authdb { class Export { public: /** * Export all records from a backend to a JSON object. * Record data is base64-encoded. * Returns a new json_object* — caller must json_object_put() it. */ static json_object* toJson(AuthBackend &backend); }; };