From 7e0aabc2437bbb6c6f65ad5ff8b8e8afced19893 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:36:24 +0300 Subject: [PATCH 01/24] ws-auth: Extract zone from ID to macro Ensures consistent handling. --- pdns/ws-auth.cc | 99 ++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 71 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index d3c0e243ec9e..f24fbdaee5d7 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -52,7 +52,8 @@ using json11::Json; extern StatBag S; -static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp); +// NOLINTNEXTLINE(readability-identifier-length) +static void patchZone(UeberBackend& B, const DNSName& zonename, DomainInfo& di, HttpRequest* req, HttpResponse* resp); // QTypes that MUST NOT have multiple records of the same type in a given RRset. static const std::set onlyOneEntryTypes = { QType::CNAME, QType::DNAME, QType::SOA }; @@ -928,6 +929,22 @@ static bool isValidMetadataKind(const string& kind, bool readonly) { return found; } +// this is easier as macro since we need UeberBackend instance in most places +// NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-identifier-length) +#define zoneFromId(req) \ + DNSName zonename = apiZoneIdToName((req)->parameters["id"]); \ + UeberBackend B; \ + DNSSECKeeper dk(&B); \ + DomainInfo di; \ + try { \ + if (!B.getDomainInfo(zonename, di)) { \ + throw HttpNotFoundException(); \ + } \ + } catch(const PDNSException &e) { \ + throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason); \ + } +// NOLINTEND(cppcoreguidelines-macro-usage, readability-identifier-length) + /* Return OpenAPI document describing the supported API. */ #include "apidocfiles.h" @@ -946,13 +963,7 @@ void apiDocs(HttpRequest* req, HttpResponse* resp) { } static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } + zoneFromId(req); if (req->method == "GET") { map > md; @@ -1033,13 +1044,7 @@ static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) { } static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } + zoneFromId(req); string kind = req->parameters["kind"]; @@ -1395,14 +1400,7 @@ static void apiZoneCryptokeysPUT(const DNSName& zonename, int inquireKeyId, Http * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed). * */ static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - - UeberBackend B; - DNSSECKeeper dk(&B); - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } + zoneFromId(req); int inquireKeyId = -1; if (req->parameters.count("key_id")) { @@ -1942,17 +1940,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) { } static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - - UeberBackend B; - DomainInfo di; - try { - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } - } catch(const PDNSException &e) { - throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason); - } + zoneFromId(req); if(req->method == "PUT") { // update domain contents and/or settings @@ -2075,7 +2063,7 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { resp->status = 204; // No Content: declare that the zone is gone now return; } else if (req->method == "PATCH") { - patchZone(B, req, resp); + patchZone(B, zonename, di, req, resp); return; } else if (req->method == "GET") { fillZone(B, zonename, resp, req); @@ -2085,19 +2073,13 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { } static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); + zoneFromId(req); if(req->method != "GET") throw HttpMethodNotAllowedException(); ostringstream ss; - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } - DNSResourceRecord rr; SOAData sd; di.backend->list(zonename, di.id); @@ -2123,17 +2105,11 @@ static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { } static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); + zoneFromId(req); if(req->method != "PUT") throw HttpMethodNotAllowedException(); - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } - if (di.primaries.empty()) throw ApiException("Domain '" + zonename.toString() + "' is not a secondary domain (or has no primary defined)"); @@ -2143,17 +2119,11 @@ static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) { } static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); + zoneFromId(req); if(req->method != "PUT") throw HttpMethodNotAllowedException(); - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } - if(!Communicator.notifyDomain(zonename, &B)) throw ApiException("Failed to add to the queue - see server log"); @@ -2161,19 +2131,11 @@ static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) { } static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); + zoneFromId(req); if(req->method != "PUT") throw HttpMethodNotAllowedException(); - UeberBackend B; - DomainInfo di; - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } - - DNSSECKeeper dk(&B); - if (dk.isPresigned(zonename)) throw ApiException("Zone '" + zonename.toString() + "' is pre-signed, not rectifying."); @@ -2185,15 +2147,10 @@ static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) { resp->setSuccessResult("Rectified"); } -static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) // NOLINT(readability-function-cognitive-complexity) +static void patchZone(UeberBackend& B, const DNSName& zonename, DomainInfo& di, HttpRequest* req, HttpResponse* resp) // NOLINT(readability-function-cognitive-complexity, readability-identifier-length) { bool zone_disabled; SOAData sd; - DomainInfo di; - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - if (!B.getDomainInfo(zonename, di)) { - throw HttpNotFoundException(); - } vector new_records; vector new_comments; From 1929764e7f9b8be5003b700f163840d611551acf Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:44:56 +0300 Subject: [PATCH 02/24] ws-auth.cc: Split apiZoneMetadata to GET and POST variant Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 133 +++++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index f24fbdaee5d7..ed366050472e 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -962,84 +962,101 @@ void apiDocs(HttpRequest* req, HttpResponse* resp) { } } -static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) { +static void apiZoneMetadataGET(HttpRequest* req, HttpResponse *resp) { zoneFromId(req); - if (req->method == "GET") { - map > md; - Json::array document; + map > metas; + Json::array document; - if (!B.getAllDomainMetadata(zonename, md)) - throw HttpNotFoundException(); + if (!B.getAllDomainMetadata(zonename, metas)) { + throw HttpNotFoundException(); + } - for (const auto& i : md) { - Json::array entries; - for (const string& j : i.second) - entries.push_back(j); + for (const auto& meta : metas) { + Json::array entries; + for (const string& value : meta.second) { + entries.push_back(value); + } - Json::object key { - { "type", "Metadata" }, - { "kind", i.first }, - { "metadata", entries } - }; + Json::object key { + { "type", "Metadata" }, + { "kind", meta.first }, + { "metadata", entries } + }; + document.push_back(key); + } + resp->setJsonBody(document); +} - document.push_back(key); - } +static void apiZoneMetadataPOST(HttpRequest* req, HttpResponse *resp) { + zoneFromId(req); - resp->setJsonBody(document); - } else if (req->method == "POST") { - auto document = req->json(); - string kind; - vector entries; + const auto& document = req->json(); + string kind; + vector entries; - try { - kind = stringFromJson(document, "kind"); - } catch (const JsonException&) { + try { + kind = stringFromJson(document, "kind"); + } catch (const JsonException&) { throw ApiException("kind is not specified or not a string"); - } + } - if (!isValidMetadataKind(kind, false)) - throw ApiException("Unsupported metadata kind '" + kind + "'"); + if (!isValidMetadataKind(kind, false)) { + throw ApiException("Unsupported metadata kind '" + kind + "'"); + } - vector vecMetadata; + vector vecMetadata; - if (!B.getDomainMetadata(zonename, kind, vecMetadata)) - throw ApiException("Could not retrieve metadata entries for domain '" + - zonename.toString() + "'"); + if (!B.getDomainMetadata(zonename, kind, vecMetadata)) { + throw ApiException("Could not retrieve metadata entries for domain '" + + zonename.toString() + "'"); + } - auto& metadata = document["metadata"]; - if (!metadata.is_array()) - throw ApiException("metadata is not specified or not an array"); + const auto& metadata = document["metadata"]; + if (!metadata.is_array()) { + throw ApiException("metadata is not specified or not an array"); + } - for (const auto& i : metadata.array_items()) { - if (!i.is_string()) - throw ApiException("metadata must be strings"); - else if (std::find(vecMetadata.cbegin(), - vecMetadata.cend(), - i.string_value()) == vecMetadata.cend()) { - vecMetadata.push_back(i.string_value()); - } + for (const auto& value : metadata.array_items()) { + if (!value.is_string()) { + throw ApiException("metadata must be strings"); + } + if (std::find(vecMetadata.cbegin(), + vecMetadata.cend(), + value.string_value()) == vecMetadata.cend()) { + vecMetadata.push_back(value.string_value()); } + } - if (!B.setDomainMetadata(zonename, kind, vecMetadata)) - throw ApiException("Could not update metadata entries for domain '" + - zonename.toString() + "'"); + if (!B.setDomainMetadata(zonename, kind, vecMetadata)) { + throw ApiException("Could not update metadata entries for domain '" + + zonename.toString() + "'"); + } - DNSSECKeeper::clearMetaCache(zonename); + DNSSECKeeper::clearMetaCache(zonename); - Json::array respMetadata; - for (const string& s : vecMetadata) - respMetadata.push_back(s); + Json::array respMetadata; + for (const string& value : vecMetadata) { + respMetadata.push_back(value); + } - Json::object key { - { "type", "Metadata" }, - { "kind", document["kind"] }, - { "metadata", respMetadata } - }; + Json::object key { + { "type", "Metadata" }, + { "kind", document["kind"] }, + { "metadata", respMetadata } + }; - resp->status = 201; - resp->setJsonBody(key); - } else + resp->status = 201; + resp->setJsonBody(key); +} + +static void apiZoneMetadata(HttpRequest *req, HttpResponse* resp) +{ + if (req->method == "GET") + apiZoneMetadataGET(req, resp); + else if (req->method == "POST") + apiZoneMetadataPOST(req, resp); + else throw HttpMethodNotAllowedException(); } From a406b334bd495773491dacbf2a319240e7970382 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:48:39 +0300 Subject: [PATCH 03/24] ws-auth.cc: Split apiZoneMetadataKind to GET, PUT and DELETE variants Enables us to specify method routes for this later. --- .../swagger/authoritative-api-swagger.yaml | 2 +- pdns/ws-auth.cc | 116 +++++++++++------- regression-tests.api/test_Zones.py | 4 +- 3 files changed, 75 insertions(+), 47 deletions(-) diff --git a/docs/http-api/swagger/authoritative-api-swagger.yaml b/docs/http-api/swagger/authoritative-api-swagger.yaml index e4f006b3c16d..c231b9d1fdd1 100644 --- a/docs/http-api/swagger/authoritative-api-swagger.yaml +++ b/docs/http-api/swagger/authoritative-api-swagger.yaml @@ -625,7 +625,7 @@ paths: required: true description: The kind of metadata responses: - '200': + '204': description: OK <<: *commonErrors diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index ed366050472e..fba63872b235 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1060,68 +1060,96 @@ static void apiZoneMetadata(HttpRequest *req, HttpResponse* resp) throw HttpMethodNotAllowedException(); } -static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) { +static void apiZoneMetadataKindGET(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); string kind = req->parameters["kind"]; - if (req->method == "GET") { - vector metadata; - Json::object document; - Json::array entries; + vector metadata; + Json::object document; + Json::array entries; + + if (!B.getDomainMetadata(zonename, kind, metadata)) { + throw HttpNotFoundException(); + } + if (!isValidMetadataKind(kind, true)) { + throw ApiException("Unsupported metadata kind '" + kind + "'"); + } - if (!B.getDomainMetadata(zonename, kind, metadata)) - throw HttpNotFoundException(); - else if (!isValidMetadataKind(kind, true)) - throw ApiException("Unsupported metadata kind '" + kind + "'"); + document["type"] = "Metadata"; + document["kind"] = kind; - document["type"] = "Metadata"; - document["kind"] = kind; + for (const string& value : metadata) { + entries.push_back(value); + } - for (const string& i : metadata) - entries.push_back(i); + document["metadata"] = entries; + resp->setJsonBody(document); +} - document["metadata"] = entries; - resp->setJsonBody(document); - } else if (req->method == "PUT") { - auto document = req->json(); +static void apiZoneMetadataKindPUT(HttpRequest* req, HttpResponse* resp) { + zoneFromId(req); + + string kind = req->parameters["kind"]; - if (!isValidMetadataKind(kind, false)) - throw ApiException("Unsupported metadata kind '" + kind + "'"); + const auto& document = req->json(); - vector vecMetadata; - auto& metadata = document["metadata"]; - if (!metadata.is_array()) - throw ApiException("metadata is not specified or not an array"); + if (!isValidMetadataKind(kind, false)) { + throw ApiException("Unsupported metadata kind '" + kind + "'"); + } - for (const auto& i : metadata.array_items()) { - if (!i.is_string()) - throw ApiException("metadata must be strings"); - vecMetadata.push_back(i.string_value()); + vector vecMetadata; + const auto& metadata = document["metadata"]; + if (!metadata.is_array()) { + throw ApiException("metadata is not specified or not an array"); + } + for (const auto& value : metadata.array_items()) { + if (!value.is_string()) { + throw ApiException("metadata must be strings"); } + vecMetadata.push_back(value.string_value()); + } - if (!B.setDomainMetadata(zonename, kind, vecMetadata)) - throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'"); + if (!B.setDomainMetadata(zonename, kind, vecMetadata)) { + throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'"); + } - DNSSECKeeper::clearMetaCache(zonename); + DNSSECKeeper::clearMetaCache(zonename); - Json::object key { - { "type", "Metadata" }, - { "kind", kind }, - { "metadata", metadata } - }; + Json::object key { + { "type", "Metadata" }, + { "kind", kind }, + { "metadata", metadata } + }; - resp->setJsonBody(key); - } else if (req->method == "DELETE") { - if (!isValidMetadataKind(kind, false)) - throw ApiException("Unsupported metadata kind '" + kind + "'"); + resp->setJsonBody(key); +} - vector md; // an empty vector will do it - if (!B.setDomainMetadata(zonename, kind, md)) - throw ApiException("Could not delete metadata for domain '" + zonename.toString() + "' (" + kind + ")"); +static void apiZoneMetadataKindDELETE(HttpRequest* req, HttpResponse* resp) { + zoneFromId(req); + + const string& kind = req->parameters["kind"]; + if (!isValidMetadataKind(kind, false)) { + throw ApiException("Unsupported metadata kind '" + kind + "'"); + } + + vector metadata; // an empty vector will do it + if (!B.setDomainMetadata(zonename, kind, metadata)) { + throw ApiException("Could not delete metadata for domain '" + zonename.toString() + "' (" + kind + ")"); + } - DNSSECKeeper::clearMetaCache(zonename); - } else + DNSSECKeeper::clearMetaCache(zonename); + resp->status = 204; +} + +static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) { + if (req->method == "GET") + apiZoneMetadataKindGET(req, resp); + else if (req->method == "PUT") + apiZoneMetadataKindPUT(req, resp); + else if (req->method == "DELETE") + apiZoneMetadataKindDELETE(req, resp); + else throw HttpMethodNotAllowedException(); } diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index 6bd02bfc872a..45ec5cee95bb 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -729,7 +729,7 @@ def test_retrieve_zone_metadata(self): def test_delete_zone_metadata(self): r = self.session.delete(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE")) - self.assertEqual(r.status_code, 200) + self.assertEqual(r.status_code, 204) r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE")) rdata = r.json() self.assertEqual(r.status_code, 200) @@ -2710,4 +2710,4 @@ def test_get_keys_with_cds(self): self.assertEqual(len(keydata), 4) r = self.session.delete(self.url("/api/v1/servers/localhost/zones/powerdnssec.org./metadata/PUBLISH-CDS")) - self.assertEqual(r.status_code, 200) + self.assertEqual(r.status_code, 204) From 2775cadd55a7742c850ae32670bac863f258bd9b Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:51:30 +0300 Subject: [PATCH 04/24] ws-auth.cc: Split apiServerTSIGKeys to GET and POST variant Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 74 +++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index fba63872b235..bf359580edb0 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1584,47 +1584,55 @@ static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent=tr return makeJSONTSIGKey(key.name, key.algorithm, doContent ? key.key : ""); } -static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; - if (req->method == "GET") { - vector keys; - - if (!B.getTSIGKeys(keys)) { - throw HttpInternalServerErrorException("Unable to retrieve TSIG keys"); - } +static void apiServerTSIGKeysGET(HttpRequest* /* req */, HttpResponse* resp) { + UeberBackend B; // NOLINT(readability-identifier-length) + vector keys; - Json::array doc; + if (!B.getTSIGKeys(keys)) { + throw HttpInternalServerErrorException("Unable to retrieve TSIG keys"); + } - for(const auto &key : keys) { - doc.push_back(makeJSONTSIGKey(key, false)); - } - resp->setJsonBody(doc); - } else if (req->method == "POST") { - auto document = req->json(); - DNSName keyname(stringFromJson(document, "name")); - DNSName algo(stringFromJson(document, "algorithm")); - string content = document["key"].string_value(); + Json::array doc; - if (content.empty()) { - try { - content = makeTSIGKey(algo); - } catch (const PDNSException& e) { - throw HttpBadRequestException(e.reason); - } - } + for(const auto &key : keys) { + doc.push_back(makeJSONTSIGKey(key, false)); + } + resp->setJsonBody(doc); +} - // Will throw an ApiException or HttpConflictException on error - checkTSIGKey(B, keyname, algo, content); +static void apiServerTSIGKeysPOST(HttpRequest* req, HttpResponse* resp) { + UeberBackend B; // NOLINT(readability-identifier-length) + const auto& document = req->json(); + DNSName keyname(stringFromJson(document, "name")); + DNSName algo(stringFromJson(document, "algorithm")); + string content = document["key"].string_value(); - if(!B.setTSIGKey(keyname, algo, content)) { - throw HttpInternalServerErrorException("Unable to add TSIG key"); + if (content.empty()) { + try { + content = makeTSIGKey(algo); + } catch (const PDNSException& exc) { + throw HttpBadRequestException(exc.reason); } + } - resp->status = 201; - resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content)); - } else { - throw HttpMethodNotAllowedException(); + // Will throw an ApiException or HttpConflictException on error + checkTSIGKey(B, keyname, algo, content); + + if(!B.setTSIGKey(keyname, algo, content)) { + throw HttpInternalServerErrorException("Unable to add TSIG key"); } + + resp->status = 201; + resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content)); +} + +static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) { + if (req->method == "GET") + apiServerTSIGKeysGET(req, resp); + else if (req->method == "POST") + apiServerTSIGKeysPOST(req, resp); + else + HttpMethodNotAllowedException(); } static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) { From ca740e49bd57b7036d64ded7582650445ffcd46e Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:59:54 +0300 Subject: [PATCH 05/24] ws-auth.cc: Split apiServerTSIGKeyDetail to GET, PUT and DELETE variant Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 120 ++++++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index bf359580edb0..75c240679b39 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1635,67 +1635,85 @@ static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) { HttpMethodNotAllowedException(); } -static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; - DNSName keyname = apiZoneIdToName(req->parameters["id"]); - DNSName algo; - string content; +// NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-identifier-length) +#define TSIGKeyFromId(req) \ + UeberBackend B; \ + DNSName keyname = apiZoneIdToName((req)->parameters["id"]); \ + DNSName algo; \ + string content; \ + try { \ + if (!B.getTSIGKey(keyname, algo, content)) { \ + throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"' not found"); \ + } \ + } catch(const PDNSException &e) { \ + throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason); \ + } \ + struct TSIGKey tsk; \ + tsk.name = keyname; \ + tsk.algorithm = algo; \ + tsk.key = std::move(content); +// NOLINTEND(cppcoreguidelines-macro-usage, readability-identifier-length) - if (!B.getTSIGKey(keyname, algo, content)) { - throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"' not found"); - } +static void apiServerTSIGKeyDetailGET(HttpRequest* req, HttpResponse* resp) { + TSIGKeyFromId(req); - struct TSIGKey tsk; - tsk.name = keyname; - tsk.algorithm = algo; - tsk.key = std::move(content); + resp->setJsonBody(makeJSONTSIGKey(tsk)); +} - if (req->method == "GET") { - resp->setJsonBody(makeJSONTSIGKey(tsk)); - } else if (req->method == "PUT") { - json11::Json document; - if (!req->body.empty()) { - document = req->json(); - } - if (document["name"].is_string()) { - tsk.name = DNSName(document["name"].string_value()); - } - if (document["algorithm"].is_string()) { - tsk.algorithm = DNSName(document["algorithm"].string_value()); +static void apiServerTSIGKeyDetailPUT(HttpRequest* req, HttpResponse* resp) { + TSIGKeyFromId(req); - TSIGHashEnum the; - if (!getTSIGHashEnum(tsk.algorithm, the)) { - throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogString()); - } - } - if (document["key"].is_string()) { - string new_content = document["key"].string_value(); - string decoded; - if (B64Decode(new_content, decoded) == -1) { - throw ApiException("Can not base64 decode key content '" + new_content + "'"); - } - tsk.key = std::move(new_content); - } - if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) { - throw HttpInternalServerErrorException("Unable to save TSIG Key"); + const auto& document = req->json(); + + if (document["name"].is_string()) { + tsk.name = DNSName(document["name"].string_value()); + } + if (document["algorithm"].is_string()) { + tsk.algorithm = DNSName(document["algorithm"].string_value()); + + TSIGHashEnum the; // NOLINT(cppcoreguidelines-init-variables): Gets initialized on next line + if (!getTSIGHashEnum(tsk.algorithm, the)) { + throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogString()); } - if (tsk.name != keyname) { - // Remove the old key - if (!B.deleteTSIGKey(keyname)) { - throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'"); - } + } + if (document["key"].is_string()) { + string new_content = document["key"].string_value(); + string decoded; + if (B64Decode(new_content, decoded) == -1) { + throw ApiException("Can not base64 decode key content '" + new_content + "'"); } - resp->setJsonBody(makeJSONTSIGKey(tsk)); - } else if (req->method == "DELETE") { + tsk.key = std::move(new_content); + } + if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) { + throw HttpInternalServerErrorException("Unable to save TSIG Key"); + } + if (tsk.name != keyname) { + // Remove the old key if (!B.deleteTSIGKey(keyname)) { throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'"); - } else { - resp->body = ""; - resp->status = 204; } - } else { - throw HttpMethodNotAllowedException(); } + resp->setJsonBody(makeJSONTSIGKey(tsk)); +} + +static void apiServerTSIGKeyDetailDELETE(HttpRequest* req, HttpResponse* resp) { + TSIGKeyFromId(req); + if (!B.deleteTSIGKey(keyname)) { + throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'"); + } + resp->body = ""; + resp->status = 204; +} + +static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) { + if (req->method == "GET") + apiServerTSIGKeyDetailGET(req, resp); + else if (req->method == "PUT") + apiServerTSIGKeyDetailPUT(req, resp); + else if (req->method == "DELETE") + apiServerTSIGKeyDetailDELETE(req, resp); + else + throw HttpMethodNotAllowedException(); } static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) { From 241dc035653e1ab2d4e7e38936b408c384ebf4cc Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 11:05:05 +0300 Subject: [PATCH 06/24] ws-auth.cc: Split apiServerAutoprimaries to GET and POST variants Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 74 ++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 75c240679b39..2e2e370470ae 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1729,41 +1729,53 @@ static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) { } } -static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; +static void apiServerAutoprimariesGET(HttpRequest* /* req */, HttpResponse* resp) { + UeberBackend B; // NOLINT(readability-identifier-length) - if (req->method == "GET") { - std::vector primaries; - if (!B.autoPrimariesList(primaries)) - throw HttpInternalServerErrorException("Unable to retrieve autoprimaries"); - Json::array doc; - for (const auto& primary: primaries) { - Json::object obj = { - { "ip", primary.ip }, - { "nameserver", primary.nameserver }, - { "account", primary.account } - }; - doc.push_back(obj); - } - resp->setJsonBody(doc); - } else if (req->method == "POST") { - auto document = req->json(); - AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), ""); + std::vector primaries; + if (!B.autoPrimariesList(primaries)) { + throw HttpInternalServerErrorException("Unable to retrieve autoprimaries"); + } + Json::array doc; + for (const auto& primary: primaries) { + const Json::object obj = { + { "ip", primary.ip }, + { "nameserver", primary.nameserver }, + { "account", primary.account } + }; + doc.push_back(obj); + } + resp->setJsonBody(doc); +} - if (document["account"].is_string()) { - primary.account = document["account"].string_value(); - } +static void apiServerAutoprimariesPOST(HttpRequest* req, HttpResponse* resp) { + UeberBackend B; // NOLINT(readability-identifier-length) - if (primary.ip=="" or primary.nameserver=="") { - throw ApiException("ip and nameserver fields must be filled"); - } - if (!B.autoPrimaryAdd(primary)) - throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature"); - resp->body = ""; - resp->status = 201; - } else { - throw HttpMethodNotAllowedException(); + const auto& document = req->json(); + + AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), ""); + + if (document["account"].is_string()) { + primary.account = document["account"].string_value(); + } + + if (primary.ip.empty() or primary.nameserver.empty()) { + throw ApiException("ip and nameserver fields must be filled"); + } + if (!B.autoPrimaryAdd(primary)) { + throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature"); } + resp->body = ""; + resp->status = 201; +} + +static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) { + if (req->method == "GET") + apiServerAutoprimariesGET(req, resp); + else if (req->method == "POST") + apiServerAutoprimariesPOST(req, resp); + else + throw HttpMethodNotAllowedException(); } // create new zone From 61fb497ad98d77dad8ded9e65892f2a4b73c1571 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Thu, 7 Dec 2023 17:22:31 +0200 Subject: [PATCH 07/24] ws-auth: Add apiServerAutoprimaryDetailDELETE --- pdns/ws-auth.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 2e2e370470ae..4ee6ac6e3b1c 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1716,14 +1716,19 @@ static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) { throw HttpMethodNotAllowedException(); } +static void apiServerAutoprimaryDetailDELETE(HttpRequest* req, HttpResponse* resp) { + UeberBackend B; // NOLINT(readability-identifier-length) + const AutoPrimary& primary{req->parameters["ip"], req->parameters["nameserver"], ""}; + if (!B.autoPrimaryRemove(primary)) { + throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature"); + } + resp->body = ""; + resp->status = 204; +} + static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; if (req->method == "DELETE") { - const AutoPrimary primary(req->parameters["ip"], req->parameters["nameserver"], ""); - if (!B.autoPrimaryRemove(primary)) - throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature"); - resp->body = ""; - resp->status = 204; + apiServerAutoprimaryDetailDELETE(req, resp); } else { throw HttpMethodNotAllowedException(); } From fb9be4619960bec6326e69280dd4234078752f72 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 11:10:11 +0300 Subject: [PATCH 08/24] ws-auth.cc: Split apiServerZones to GET and POST variants Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 4ee6ac6e3b1c..9a719c1eadcc 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1784,11 +1784,11 @@ static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) { } // create new zone -static void apiServerZonesPost(HttpRequest* req, HttpResponse* resp) { +static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { UeberBackend B; DNSSECKeeper dk(&B); DomainInfo di; - auto document = req->json(); + const auto& document = req->json(); DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); apiCheckNameAllowedCharacters(zonename.toString()); zonename.makeUsLowerCase(); @@ -1974,7 +1974,7 @@ static void apiServerZonesPost(HttpRequest* req, HttpResponse* resp) { } // list known zones -static void apiServerZonesGet(HttpRequest* req, HttpResponse* resp) { +static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp) { UeberBackend B; DNSSECKeeper dk(&B); vector domains; @@ -2014,17 +2014,12 @@ static void apiServerZonesGet(HttpRequest* req, HttpResponse* resp) { } static void apiServerZones(HttpRequest* req, HttpResponse* resp) { - if (req->method == "POST") { - apiServerZonesPost(req, resp); - return; - } - - if (req->method == "GET") { - apiServerZonesGet(req, resp); - return; - } - - throw HttpMethodNotAllowedException(); + if (req->method == "GET") + apiServerZonesGET(req, resp); + else if (req->method == "POST") + apiServerZonesPOST(req, resp); + else + throw HttpMethodNotAllowedException(); } static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { From 21b4feba0ecf63b5edea0ed1d0944a966ea5bb30 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Thu, 7 Dec 2023 16:35:25 +0200 Subject: [PATCH 09/24] ws-auth: Add NOLINTs to apiServerZonesPOST() --- pdns/ws-auth.cc | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 9a719c1eadcc..9aa661fab762 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1785,9 +1785,9 @@ static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) { // create new zone static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; - DNSSECKeeper dk(&B); - DomainInfo di; + UeberBackend B; // NOLINT(readability-identifier-length) + DNSSECKeeper dk(&B); // NOLINT(readability-identifier-length) + DomainInfo di; // NOLINT(readability-identifier-length) const auto& document = req->json(); DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); apiCheckNameAllowedCharacters(zonename.toString()); @@ -1816,7 +1816,7 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { throw ApiException("You cannot give rrsets AND zone data as text"); } - auto nameservers = document["nameservers"]; + const auto& nameservers = document["nameservers"]; if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Secondary && zonekind != DomainInfo::Consumer) { throw ApiException("Nameservers is not a list"); } @@ -1849,15 +1849,15 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { gatherRecordsFromZone(zonestring, new_records, zonename); } } - catch (const JsonException& e) { - throw ApiException("New RRsets are invalid: " + string(e.what())); + catch (const JsonException& exc) { + throw ApiException("New RRsets are invalid: " + string(exc.what())); } if (zonekind == DomainInfo::Consumer && !new_records.empty()) { throw ApiException("Zone data MUST NOT be given for Consumer zones"); } - for(auto& rr : new_records) { + for(auto& rr : new_records) { // NOLINT(readability-identifier-length) rr.qname.makeUsLowerCase(); if (!rr.qname.isPartOf(zonename) && rr.qname != zonename) { throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": Name is out of zone"); @@ -1883,7 +1883,7 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { // synthesize a SOA record so the zone "really" exists string soa = ::arg()["default-soa-content"]; boost::replace_all(soa, "@", zonename.toStringNoDot()); - SOAData sd; + SOAData sd; // NOLINT(readability-identifier-length) fillSOAData(soa, sd); sd.serial=document["serial"].int_value(); autorr.qtype = QType::SOA; @@ -1945,13 +1945,13 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { // will be overridden by updateDomainSettingsFromDocument, if given in document. di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT"); - for(auto& rr : new_records) { + for(auto& rr : new_records) { // NOLINT(readability-identifier-length) rr.domain_id = static_cast(di.id); di.backend->feedRecord(rr, DNSName()); } - for(Comment& c : new_comments) { - c.domain_id = static_cast(di.id); - if (!di.backend->feedComment(c)) { + for(Comment& comment : new_comments) { + comment.domain_id = static_cast(di.id); + if (!di.backend->feedComment(comment)) { throw ApiException("Hosting backend does not support editing comments."); } } @@ -1975,8 +1975,8 @@ static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { // list known zones static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp) { - UeberBackend B; - DNSSECKeeper dk(&B); + UeberBackend B; // NOLINT(readability-identifier-length) + DNSSECKeeper dk(&B); // NOLINT(readability-identifier-length) vector domains; if (req->getvars.count("zone")) { @@ -1984,14 +1984,14 @@ static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp) { apiCheckNameAllowedCharacters(zone); DNSName zonename = apiNameToDNSName(zone); zonename.makeUsLowerCase(); - DomainInfo di; + DomainInfo di; // NOLINT(readability-identifier-length) if (B.getDomainInfo(zonename, di)) { domains.push_back(di); } } else { try { B.getAllDomains(&domains, true, true); // incl. serial and disabled - } catch(const PDNSException &e) { + } catch(const PDNSException &e) { // NOLINT(readability-identifier-length) throw HttpInternalServerErrorException("Could not retrieve all domain information: " + e.reason); } } From b62707f99ea1b9bec98bf88de2dc4833e03172b8 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 11:13:54 +0300 Subject: [PATCH 10/24] ws-auth.cc: Split apiServerZoneDetail to GET, PATCH, PUT and DELETE variants Enables us to specify method routes for this later. --- pdns/ws-auth.cc | 224 ++++++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 104 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 9aa661fab762..0645efcd3310 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -2022,137 +2022,153 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) { throw HttpMethodNotAllowedException(); } -static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { +static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); - if(req->method == "PUT") { - // update domain contents and/or settings - auto document = req->json(); + // update domain contents and/or settings + const auto& document = req->json(); - auto rrsets = document["rrsets"]; - bool zoneWasModified = false; - DomainInfo::DomainKind newKind = di.kind; - if (document["kind"].is_string()) { - newKind = DomainInfo::stringToKind(stringFromJson(document, "kind")); - } + auto rrsets = document["rrsets"]; + bool zoneWasModified = false; + DomainInfo::DomainKind newKind = di.kind; + if (document["kind"].is_string()) { + newKind = DomainInfo::stringToKind(stringFromJson(document, "kind")); + } - // if records/comments are given, load, check and insert them - if (rrsets.is_array()) { - zoneWasModified = true; - bool haveSoa = false; - string soaEditApiKind; - string soaEditKind; - di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soaEditApiKind); - di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soaEditKind); + // if records/comments are given, load, check and insert them + if (rrsets.is_array()) { + zoneWasModified = true; + bool haveSoa = false; + string soaEditApiKind; + string soaEditKind; + di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soaEditApiKind); + di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soaEditKind); - vector new_records; - vector new_comments; + vector new_records; + vector new_comments; - try { - for (const auto& rrset : rrsets.array_items()) { - DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name")); - apiCheckQNameAllowedCharacters(qname.toString()); - QType qtype; - qtype = stringFromJson(rrset, "type"); - if (qtype.getCode() == 0) { - throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given"); - } - if (rrset["records"].is_array()) { - uint32_t ttl = uintFromJson(rrset, "ttl"); - gatherRecords(rrset, qname, qtype, ttl, new_records); - } - if (rrset["comments"].is_array()) { - gatherComments(rrset, qname, qtype, new_comments); - } + try { + for (const auto& rrset : rrsets.array_items()) { + DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name")); + apiCheckQNameAllowedCharacters(qname.toString()); + QType qtype; + qtype = stringFromJson(rrset, "type"); + if (qtype.getCode() == 0) { + throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given"); } - } - catch (const JsonException& e) { - throw ApiException("New RRsets are invalid: " + string(e.what())); - } - - for(auto& rr : new_records) { - rr.qname.makeUsLowerCase(); - if (!rr.qname.isPartOf(zonename) && rr.qname != zonename) { - throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": Name is out of zone"); + if (rrset["records"].is_array()) { + uint32_t ttl = uintFromJson(rrset, "ttl"); + gatherRecords(rrset, qname, qtype, ttl, new_records); } - apiCheckQNameAllowedCharacters(rr.qname.toString()); - - if (rr.qtype.getCode() == QType::SOA && rr.qname == zonename) { - haveSoa = true; + if (rrset["comments"].is_array()) { + gatherComments(rrset, qname, qtype, new_comments); } } + } + catch (const JsonException& exc) { + throw ApiException("New RRsets are invalid: " + string(exc.what())); + } - if (!haveSoa && newKind != DomainInfo::Secondary && newKind != DomainInfo::Consumer) { - // Require SOA if this is a primary zone. - throw ApiException("Must give SOA record for zone when replacing all RR sets"); - } - if (newKind == DomainInfo::Consumer && !new_records.empty()) { - // Allow deleting all RRsets, just not modifying them. - throw ApiException("Modifying RRsets in Consumer zones is unsupported"); + for(auto& rr : new_records) { // NOLINT(readability-identifier-length) + rr.qname.makeUsLowerCase(); + if (!rr.qname.isPartOf(zonename) && rr.qname != zonename) { + throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": Name is out of zone"); } + apiCheckQNameAllowedCharacters(rr.qname.toString()); - checkNewRecords(new_records, zonename); - - di.backend->startTransaction(zonename, static_cast(di.id)); - for(auto& rr : new_records) { - rr.domain_id = static_cast(di.id); - di.backend->feedRecord(rr, DNSName()); - } - for(Comment& c : new_comments) { - c.domain_id = static_cast(di.id); - di.backend->feedComment(c); + if (rr.qtype.getCode() == QType::SOA && rr.qname == zonename) { + haveSoa = true; } + } - if (!haveSoa && (newKind == DomainInfo::Secondary || newKind == DomainInfo::Consumer)) { - di.backend->setStale(di.id); - } - } else { - // avoid deleting current zone contents - di.backend->startTransaction(zonename, -1); + if (!haveSoa && newKind != DomainInfo::Secondary && newKind != DomainInfo::Consumer) { + // Require SOA if this is a primary zone. + throw ApiException("Must give SOA record for zone when replacing all RR sets"); + } + if (newKind == DomainInfo::Consumer && !new_records.empty()) { + // Allow deleting all RRsets, just not modifying them. + throw ApiException("Modifying RRsets in Consumer zones is unsupported"); } - // updateDomainSettingsFromDocument will rectify the zone and update SOA serial. - updateDomainSettingsFromDocument(B, di, zonename, document, zoneWasModified); - di.backend->commitTransaction(); + checkNewRecords(new_records, zonename); - purgeAuthCaches(zonename.toString() + "$"); + di.backend->startTransaction(zonename, static_cast(di.id)); + for(auto& rr : new_records) { // NOLINT(readability-identifier-length) + rr.domain_id = static_cast(di.id); + di.backend->feedRecord(rr, DNSName()); + } + for(Comment& comment : new_comments) { + comment.domain_id = static_cast(di.id); + di.backend->feedComment(comment); + } - resp->body = ""; - resp->status = 204; // No Content, but indicate success - return; + if (!haveSoa && (newKind == DomainInfo::Secondary || newKind == DomainInfo::Consumer)) { + di.backend->setStale(di.id); + } + } else { + // avoid deleting current zone contents + di.backend->startTransaction(zonename, -1); } - else if(req->method == "DELETE") { - // delete domain - di.backend->startTransaction(zonename, -1); - try { - if(!di.backend->deleteDomain(zonename)) - throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported"); + // updateDomainSettingsFromDocument will rectify the zone and update SOA serial. + updateDomainSettingsFromDocument(B, di, zonename, document, zoneWasModified); + di.backend->commitTransaction(); - di.backend->commitTransaction(); + purgeAuthCaches(zonename.toString() + "$"); - g_zoneCache.remove(zonename); - } catch (...) { - di.backend->abortTransaction(); - throw; + resp->body = ""; + resp->status = 204; // No Content, but indicate success +} + +static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp) { + zoneFromId(req); + + // delete domain + + di.backend->startTransaction(zonename, -1); + try { + if(!di.backend->deleteDomain(zonename)) { + throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported"); } - // clear caches - DNSSECKeeper::clearCaches(zonename); - purgeAuthCaches(zonename.toString() + "$"); + di.backend->commitTransaction(); - // empty body on success - resp->body = ""; - resp->status = 204; // No Content: declare that the zone is gone now - return; - } else if (req->method == "PATCH") { - patchZone(B, zonename, di, req, resp); - return; - } else if (req->method == "GET") { - fillZone(B, zonename, resp, req); - return; + purgeAuthCaches(zonename.toString() + "$"); + } catch (...) { + di.backend->abortTransaction(); + throw; } - throw HttpMethodNotAllowedException(); + + // clear caches + DNSSECKeeper::clearCaches(zonename); + purgeAuthCaches(zonename.toString() + "$"); + + // empty body on success + resp->body = ""; + resp->status = 204; // No Content: declare that the zone is gone now +} + +static void apiServerZoneDetailPATCH(HttpRequest* req, HttpResponse* resp) { + zoneFromId(req); + patchZone(B, zonename, di, req, resp); +} + +static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp) { + zoneFromId(req); + fillZone(B, zonename, resp, req); +} + +static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { + if (req->method == "GET") + apiServerZoneDetailGET(req, resp); + else if (req->method == "PATCH") + apiServerZoneDetailPATCH(req, resp); + else if (req->method == "PUT") + apiServerZoneDetailPUT(req, resp); + else if (req->method == "DELETE") + apiServerZoneDetailDELETE(req, resp); + else + throw HttpMethodNotAllowedException(); } static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { From 20f8dbd73a4da94fa434cca165db9d389278e426 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 12:43:56 +0300 Subject: [PATCH 11/24] ws-auth.cc: Prepare apiZoneCryptokeys for method routing --- pdns/ws-auth.cc | 96 +++++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 0645efcd3310..b05ac65fe77d 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -1168,8 +1168,17 @@ static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquire } } -static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, HttpResponse *resp, DNSSECKeeper *dk) { - DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false); +static inline int getInquireKeyId(HttpRequest *req, const DNSName& zonename, DNSSECKeeper *dnsseckeeper) { + int inquireKeyId = -1; + if (req->parameters.count("key_id") == 1) { + inquireKeyId = std::stoi(req->parameters["key_id"]); + apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, dnsseckeeper); + } + return inquireKeyId; +} + +static void apiZoneCryptokeysExport(const DNSName& zonename, int64_t inquireKeyId, HttpResponse *resp, DNSSECKeeper *dnssec_dk) { + DNSSECKeeper::keyset_t keyset=dnssec_dk->getKeys(zonename, false); bool inquireSingleKey = inquireKeyId >= 0; @@ -1188,18 +1197,18 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http Json::object key { { "type", "Cryptokey" }, - { "id", (int)value.second.id }, + { "id", static_cast(value.second.id) }, { "active", value.second.active }, { "published", value.second.published }, { "keytype", keyType }, - { "flags", (uint16_t)value.first.getFlags() }, + { "flags", static_cast(value.first.getFlags()) }, { "dnskey", value.first.getDNSKEY().getZoneRepresentation() }, { "algorithm", DNSSECKeeper::algorithm2name(value.first.getAlgorithm()) }, { "bits", value.first.getKey()->getBits() } }; string publishCDS; - dk->getPublishCDS(zonename, publishCDS); + dnssec_dk->getPublishCDS(zonename, publishCDS); vector digestAlgos; stringtok(digestAlgos, publishCDS, ", "); @@ -1241,7 +1250,13 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http throw HttpNotFoundException(); } resp->setJsonBody(doc); +} +static void apiZoneCryptokeysGET(HttpRequest *req, HttpResponse *resp) { + zoneFromId(req); + const auto inquireKeyId = getInquireKeyId(req, zonename, &dk); + + apiZoneCryptokeysExport(zonename, inquireKeyId, resp, &dk); } /* @@ -1255,8 +1270,15 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http * Case 3: the key or zone does not exist. * The server returns 404 Not Found * */ -static void apiZoneCryptokeysDELETE(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) { - if (dk->removeKey(zonename, inquireKeyId)) { +static void apiZoneCryptokeysDELETE(HttpRequest *req, HttpResponse *resp) { + zoneFromId(req); + const auto inquireKeyId = getInquireKeyId(req, zonename, &dk); + + if (inquireKeyId == -1) { + throw HttpBadRequestException(); + } + + if (dk.removeKey(zonename, inquireKeyId)) { resp->body = ""; resp->status = 204; } else { @@ -1300,8 +1322,10 @@ static void apiZoneCryptokeysDELETE(const DNSName& zonename, int inquireKeyId, H * The server returns 201 Created and all public data about the added cryptokey */ -static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) { - auto document = req->json(); +static void apiZoneCryptokeysPOST(HttpRequest *req, HttpResponse *resp) { + zoneFromId(req); + + const auto& document = req->json(); string privatekey_fieldname = "privatekey"; auto privatekey = document["privatekey"]; if (privatekey.is_null()) { @@ -1334,11 +1358,12 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt } } int algorithm = DNSSECKeeper::shorthand2algorithm(keyOrZone ? ::arg()["default-ksk-algorithm"] : ::arg()["default-zsk-algorithm"]); - auto providedAlgo = document["algorithm"]; + const auto& providedAlgo = document["algorithm"]; if (providedAlgo.is_string()) { algorithm = DNSSECKeeper::shorthand2algorithm(providedAlgo.string_value()); - if (algorithm == -1) + if (algorithm == -1) { throw ApiException("Unknown algorithm: " + providedAlgo.string_value()); + } } else if (providedAlgo.is_number()) { algorithm = providedAlgo.int_value(); } else if (!providedAlgo.is_null()) { @@ -1346,7 +1371,7 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt } try { - if (!dk->addKey(zonename, keyOrZone, algorithm, insertedId, bits, active, published)) { + if (!dk.addKey(zonename, keyOrZone, algorithm, insertedId, bits, active, published)) { throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?"); } } catch (std::runtime_error& error) { @@ -1355,7 +1380,7 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt if (insertedId < 0) throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?"); } else if (document["bits"].is_null() && document["algorithm"].is_null()) { - auto keyData = stringFromJson(document, privatekey_fieldname); + const auto& keyData = stringFromJson(document, privatekey_fieldname); DNSKEYRecordContent dkrc; DNSSECPrivateKey dpk; try { @@ -1378,18 +1403,19 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt catch (std::runtime_error& error) { throw ApiException("Key could not be parsed. Make sure your key format is correct."); } try { - if (!dk->addKey(zonename, dpk,insertedId, active, published)) { + if (!dk.addKey(zonename, dpk, insertedId, active, published)) { throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?"); } } catch (std::runtime_error& error) { throw ApiException(error.what()); } - if (insertedId < 0) + if (insertedId < 0) { throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?"); + } } else { throw ApiException("Either you submit just the 'privatekey' field or you leave 'privatekey' empty and submit the other fields."); } - apiZoneCryptokeysGET(zonename, insertedId, resp, dk); + apiZoneCryptokeysExport(zonename, insertedId, resp, &dk); resp->status = 201; } @@ -1404,31 +1430,37 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt * Case 3: the backend returns false on de/activation. An error occurred. * The sever returns 422 Unprocessable Entity with message "Could not de/activate Key: :cryptokey_id in Zone: :zone_name" * */ -static void apiZoneCryptokeysPUT(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) { +static void apiZoneCryptokeysPUT(HttpRequest *req, HttpResponse *resp) { + zoneFromId(req); + const auto inquireKeyId = getInquireKeyId(req, zonename, &dk); + + if (inquireKeyId == -1) { + throw HttpBadRequestException(); + } //throws an exception if the Body is empty - auto document = req->json(); + const auto& document = req->json(); //throws an exception if the key does not exist or is not a bool bool active = boolFromJson(document, "active"); bool published = boolFromJson(document, "published", true); if (active) { - if (!dk->activateKey(zonename, inquireKeyId)) { + if (!dk.activateKey(zonename, inquireKeyId)) { resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422); return; } } else { - if (!dk->deactivateKey(zonename, inquireKeyId)) { + if (!dk.deactivateKey(zonename, inquireKeyId)) { resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422); return; } } if (published) { - if (!dk->publishKey(zonename, inquireKeyId)) { + if (!dk.publishKey(zonename, inquireKeyId)) { resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422); return; } } else { - if (!dk->unpublishKey(zonename, inquireKeyId)) { + if (!dk.unpublishKey(zonename, inquireKeyId)) { resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422); return; } @@ -1445,26 +1477,14 @@ static void apiZoneCryptokeysPUT(const DNSName& zonename, int inquireKeyId, Http * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed). * */ static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) { - zoneFromId(req); - - int inquireKeyId = -1; - if (req->parameters.count("key_id")) { - inquireKeyId = std::stoi(req->parameters["key_id"]); - apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, &dk); - } - if (req->method == "GET") { - apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk); + apiZoneCryptokeysGET(req, resp); } else if (req->method == "DELETE") { - if (inquireKeyId == -1) - throw HttpBadRequestException(); - apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk); + apiZoneCryptokeysDELETE(req, resp); } else if (req->method == "POST") { - apiZoneCryptokeysPOST(zonename, req, resp, &dk); + apiZoneCryptokeysPOST(req, resp); } else if (req->method == "PUT") { - if (inquireKeyId == -1) - throw HttpBadRequestException(); - apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk); + apiZoneCryptokeysPUT(req, resp); } else { throw HttpMethodNotAllowedException(); //Returns method not allowed } From 478e1699844a2a642822e375991a5e5deb7cb785 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Fri, 18 Aug 2023 10:09:56 +0300 Subject: [PATCH 12/24] webserver: Allow specifying supported method If method is not empty and it does not match the request, throw exception. --- ext/yahttp/yahttp/router.cpp | 27 +++++++++++++++++++++------ ext/yahttp/yahttp/router.hpp | 10 ++++++++-- pdns/webserver.cc | 19 ++++++++++++------- pdns/webserver.hh | 6 +++--- 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/ext/yahttp/yahttp/router.cpp b/ext/yahttp/yahttp/router.cpp index 90612ad8ec62..83465e318a30 100644 --- a/ext/yahttp/yahttp/router.cpp +++ b/ext/yahttp/yahttp/router.cpp @@ -24,10 +24,11 @@ namespace YaHTTP { routes.push_back(funcptr::make_tuple(method2, url, handler, name)); }; - bool Router::route(Request *req, THandlerFunction& handler) { + RoutingResult Router::route(Request *req, THandlerFunction& handler) { std::map params; int pos1,pos2; bool matched = false; + bool seen = false; std::string rname; // iterate routes @@ -36,8 +37,8 @@ namespace YaHTTP { std::string pname; std::string method, url; funcptr::tie(method, url, handler, rname) = *i; - - if (method.empty() == false && req->method != method) continue; // no match on method + matched = false; + // see if we can't match the url params.clear(); // simple matcher func @@ -53,6 +54,7 @@ namespace YaHTTP { pname = pname.substr(1); // this matches whatever comes after it, basically end of string pos2 = req->url.path.size(); + matched = true; if (pname != "") params[pname] = funcptr::tie(pos1,pos2); k1 = url.size(); @@ -78,10 +80,23 @@ namespace YaHTTP { matched = false; else matched = true; + + if (matched && method.empty() == false && req->method != method) { + // method did not match, record it though so we can return correct result + matched = false; + seen = true; + continue; + } + } + + if (!matched) { + if (seen) + return RouteNoMethod; + // no route + return RouteNotFound; } - if (!matched) { return false; } // no route - req->parameters.clear(); + req->parameters.clear(); for(std::map::iterator i = params.begin(); i != params.end(); i++) { int p1,p2; @@ -93,7 +108,7 @@ namespace YaHTTP { req->routeName = std::move(rname); - return true; + return RouteFound; }; void Router::printRoutes(std::ostream &os) { diff --git a/ext/yahttp/yahttp/router.hpp b/ext/yahttp/yahttp/router.hpp index 205119c7d41e..0261bd4cd5dc 100644 --- a/ext/yahttp/yahttp/router.hpp +++ b/ext/yahttp/yahttp/router.hpp @@ -25,6 +25,12 @@ namespace funcptr = boost; #include namespace YaHTTP { + enum RoutingResult { + RouteFound = 1, + RouteNotFound = 0, + RouteNoMethod = -1, + }; + typedef funcptr::function THandlerFunction; //!< Handler function pointer typedef funcptr::tuple TRoute; //!< Route tuple (method, urlmask, handler, name) typedef std::vector TRouteList; //!< List of routes in order of evaluation @@ -44,7 +50,7 @@ is consumed but not stored. Note that only path is matched, scheme, host and url static Router router; // urlFor(const std::string &name, const strstr_map_t& arguments); //url.path + static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found. static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; // URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //(req), static_cast(resp)); } -void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler) +void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method) { YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);}; - YaHTTP::Router::Any(url, std::move(f)); + YaHTTP::Router::Map(method, url, std::move(f)); } static bool optionsHandler(HttpRequest* req, HttpResponse* resp) { @@ -215,9 +215,9 @@ void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpReques } } -void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword) { +void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method, bool allowPassword) { auto f = [=](HttpRequest *req, HttpResponse* resp){apiWrapper(handler, req, resp, allowPassword);}; - registerBareHandler(url, f); + registerBareHandler(url, f, method); } void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) { @@ -233,9 +233,9 @@ void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpReques handler(req, resp); } -void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler) { +void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method) { auto f = [=](HttpRequest *req, HttpResponse *resp){webWrapper(handler, req, resp);}; - registerBareHandler(url, f); + registerBareHandler(url, f, method); } static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr client) { @@ -293,11 +293,16 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const } YaHTTP::THandlerFunction handler; - if (!YaHTTP::Router::Route(&req, handler)) { + YaHTTP::RoutingResult res = YaHTTP::Router::Route(&req, handler); + + if (res == YaHTTP::RouteNotFound) { SLOG(g_log<info(Logr::Debug, "No route found")); throw HttpNotFoundException(); } + if (res == YaHTTP::RouteNoMethod) { + throw HttpMethodNotAllowedException(); + } const string msg = "HTTP ISE Exception"; try { diff --git a/pdns/webserver.hh b/pdns/webserver.hh index 6f9c59e8497b..28d901dbd52d 100644 --- a/pdns/webserver.hh +++ b/pdns/webserver.hh @@ -225,8 +225,8 @@ public: void handleRequest(HttpRequest& request, HttpResponse& resp) const; typedef std::function HandlerFunction; - void registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword=false); - void registerWebHandler(const string& url, const HandlerFunction& handler); + void registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method = "", bool allowPassword=false); + void registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method = ""); enum class LogLevel : uint8_t { None = 0, // No logs from requests at all @@ -266,7 +266,7 @@ public: #endif protected: - void registerBareHandler(const string& url, const HandlerFunction& handler); + static void registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method); void logRequest(const HttpRequest& req, const ComboAddress& remote) const; void logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const; From a0281be936f92db399f92889e3015cf3b09aa975 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 20:13:11 +0300 Subject: [PATCH 13/24] ext/yahttp: Move route matching to separate function --- ext/yahttp/yahttp/router.cpp | 119 ++++++++++++++++++++--------------- ext/yahttp/yahttp/router.hpp | 3 + 2 files changed, 70 insertions(+), 52 deletions(-) diff --git a/ext/yahttp/yahttp/router.cpp b/ext/yahttp/yahttp/router.cpp index 83465e318a30..e123b3847963 100644 --- a/ext/yahttp/yahttp/router.cpp +++ b/ext/yahttp/yahttp/router.cpp @@ -5,8 +5,6 @@ #include "router.hpp" namespace YaHTTP { - typedef funcptr::tuple TDelim; - // router is defined here. YaHTTP::Router Router::router; @@ -24,86 +22,103 @@ namespace YaHTTP { routes.push_back(funcptr::make_tuple(method2, url, handler, name)); }; - RoutingResult Router::route(Request *req, THandlerFunction& handler) { - std::map params; - int pos1,pos2; - bool matched = false; - bool seen = false; - std::string rname; - - // iterate routes - for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) { - int k1,k2,k3; - std::string pname; - std::string method, url; - funcptr::tie(method, url, handler, rname) = *i; - matched = false; - - // see if we can't match the url - params.clear(); - // simple matcher func - for(k1=0, k2=0; k1 < static_cast(url.size()) && k2 < static_cast(req->url.path.size()); ) { - if (url[k1] == '<') { - pos1 = k2; - k3 = k1+1; + bool Router::match(const std::string& route, const URL& requrl, std::map ¶ms) { + size_t rpos = 0; + size_t upos = 0; + size_t npos = 0; + size_t nstart = 0; + size_t nend = 0; + std::string pname; + for(; rpos < route.size() && upos < requrl.path.size(); ) { + if (route[rpos] == '<') { + nstart = upos; + npos = rpos+1; // start of parameter - while(k1 < static_cast(url.size()) && url[k1] != '>') k1++; - pname = std::string(url.begin()+k3, url.begin()+k1); + while(rpos < route.size() && route[rpos] != '>') { + rpos++; + } + pname = std::string(route.begin()+static_cast(npos), route.begin()+static_cast(rpos)); // then we also look it on the url - if (pname[0]=='*') { + if (pname[0] == '*') { pname = pname.substr(1); // this matches whatever comes after it, basically end of string - pos2 = req->url.path.size(); - matched = true; - if (pname != "") - params[pname] = funcptr::tie(pos1,pos2); - k1 = url.size(); - k2 = req->url.path.size(); + nend = requrl.path.size(); + if (!pname.empty()) { + params[pname] = funcptr::tie(nstart,nend); + } + rpos = route.size(); + upos = requrl.path.size(); break; } else { - // match until url[k1] - while(k2 < static_cast(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++; - pos2 = k2; - params[pname] = funcptr::tie(pos1,pos2); + // match until url[upos] or next / if pattern is at end + while (upos < requrl.path.size()) { + if (route[rpos+1] == '\0' && requrl.path[upos] == '/') { + break; + } + if (requrl.path[upos] == route[rpos+1]) { + break; + } + upos++; + } + nend = upos; + params[pname] = funcptr::tie(nstart, nend); } - k2--; + upos--; } - else if (url[k1] != req->url.path[k2]) { + else if (route[rpos] != requrl.path[upos]) { break; } - k1++; k2++; + rpos++; upos++; } + return route[rpos] == requrl.path[upos]; + } - // ensure. - if (url[k1] != req->url.path[k2]) - matched = false; - else - matched = true; + RoutingResult Router::route(Request *req, THandlerFunction& handler) { + std::map params; + bool matched = false; + bool seen = false; + std::string rname; + + // iterate routes + for (auto& route: routes) { + std::string method; + std::string url; + funcptr::tie(method, url, handler, rname) = route; - if (matched && method.empty() == false && req->method != method) { + // see if we can't match the url + params.clear(); + // simple matcher func + matched = match(url, req->url, params); + + if (matched && !method.empty() && req->method != method) { // method did not match, record it though so we can return correct result matched = false; seen = true; continue; } + if (matched) { + break; + } } if (!matched) { - if (seen) + if (seen) { return RouteNoMethod; + } // no route return RouteNotFound; } req->parameters.clear(); - for(std::map::iterator i = params.begin(); i != params.end(); i++) { - int p1,p2; - funcptr::tie(p1,p2) = i->second; - std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2); + for (const auto& param: params) { + int nstart = 0; + int nend = 0; + funcptr::tie(nstart, nend) = param.second; + std::string value(req->url.path.begin() + nstart, req->url.path.begin() + nend); value = Utility::decodeURL(value); - req->parameters[i->first] = std::move(value); + req->parameters[param.first] = std::move(value); } req->routeName = std::move(rname); diff --git a/ext/yahttp/yahttp/router.hpp b/ext/yahttp/yahttp/router.hpp index 0261bd4cd5dc..c9332078d0a0 100644 --- a/ext/yahttp/yahttp/router.hpp +++ b/ext/yahttp/yahttp/router.hpp @@ -34,6 +34,7 @@ namespace YaHTTP { typedef funcptr::function THandlerFunction; //!< Handler function pointer typedef funcptr::tuple TRoute; //!< Route tuple (method, urlmask, handler, name) typedef std::vector TRouteList; //!< List of routes in order of evaluation + typedef funcptr::tuple TDelim; /*! Implements simple router. @@ -53,6 +54,7 @@ is consumed but not stored. Note that only path is matched, scheme, host and url RoutingResult route(Request *req, THandlerFunction& handler); // urlFor(const std::string &name, const strstr_map_t& arguments); //& params); //& params) { return router.match(route, requrl, params); }; static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found. static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; // Date: Fri, 18 Aug 2023 10:14:07 +0300 Subject: [PATCH 14/24] ws-auth.cc: Move method checking to router --- pdns/ws-auth.cc | 190 +++++++++++------------------------------------- 1 file changed, 41 insertions(+), 149 deletions(-) diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index b05ac65fe77d..1698297a0525 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -950,9 +950,6 @@ static bool isValidMetadataKind(const string& kind, bool readonly) { #include "apidocfiles.h" void apiDocs(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - if (req->accept_yaml) { resp->setYamlBody(g_api_swagger_yaml); } else if (req->accept_json) { @@ -1050,16 +1047,6 @@ static void apiZoneMetadataPOST(HttpRequest* req, HttpResponse *resp) { resp->setJsonBody(key); } -static void apiZoneMetadata(HttpRequest *req, HttpResponse* resp) -{ - if (req->method == "GET") - apiZoneMetadataGET(req, resp); - else if (req->method == "POST") - apiZoneMetadataPOST(req, resp); - else - throw HttpMethodNotAllowedException(); -} - static void apiZoneMetadataKindGET(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); @@ -1142,17 +1129,6 @@ static void apiZoneMetadataKindDELETE(HttpRequest* req, HttpResponse* resp) { resp->status = 204; } -static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiZoneMetadataKindGET(req, resp); - else if (req->method == "PUT") - apiZoneMetadataKindPUT(req, resp); - else if (req->method == "DELETE") - apiZoneMetadataKindDELETE(req, resp); - else - throw HttpMethodNotAllowedException(); -} - // Throws 404 if the key with inquireKeyId does not exist static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquireKeyId, DNSSECKeeper *dk) { DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false); @@ -1471,25 +1447,6 @@ static void apiZoneCryptokeysPUT(HttpRequest *req, HttpResponse *resp) { return; } -/* - * This method chooses the right functionality for the request. It also checks for a cryptokey_id which has to be passed - * by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id . - * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed). - * */ -static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) { - if (req->method == "GET") { - apiZoneCryptokeysGET(req, resp); - } else if (req->method == "DELETE") { - apiZoneCryptokeysDELETE(req, resp); - } else if (req->method == "POST") { - apiZoneCryptokeysPOST(req, resp); - } else if (req->method == "PUT") { - apiZoneCryptokeysPUT(req, resp); - } else { - throw HttpMethodNotAllowedException(); //Returns method not allowed - } -} - static void gatherRecordsFromZone(const std::string& zonestring, vector& new_records, const DNSName& zonename) { DNSResourceRecord rr; vector zonedata; @@ -1646,15 +1603,6 @@ static void apiServerTSIGKeysPOST(HttpRequest* req, HttpResponse* resp) { resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content)); } -static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiServerTSIGKeysGET(req, resp); - else if (req->method == "POST") - apiServerTSIGKeysPOST(req, resp); - else - HttpMethodNotAllowedException(); -} - // NOLINTBEGIN(cppcoreguidelines-macro-usage, readability-identifier-length) #define TSIGKeyFromId(req) \ UeberBackend B; \ @@ -1725,17 +1673,6 @@ static void apiServerTSIGKeyDetailDELETE(HttpRequest* req, HttpResponse* resp) { resp->status = 204; } -static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiServerTSIGKeyDetailGET(req, resp); - else if (req->method == "PUT") - apiServerTSIGKeyDetailPUT(req, resp); - else if (req->method == "DELETE") - apiServerTSIGKeyDetailDELETE(req, resp); - else - throw HttpMethodNotAllowedException(); -} - static void apiServerAutoprimaryDetailDELETE(HttpRequest* req, HttpResponse* resp) { UeberBackend B; // NOLINT(readability-identifier-length) const AutoPrimary& primary{req->parameters["ip"], req->parameters["nameserver"], ""}; @@ -1746,14 +1683,6 @@ static void apiServerAutoprimaryDetailDELETE(HttpRequest* req, HttpResponse* res resp->status = 204; } -static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) { - if (req->method == "DELETE") { - apiServerAutoprimaryDetailDELETE(req, resp); - } else { - throw HttpMethodNotAllowedException(); - } -} - static void apiServerAutoprimariesGET(HttpRequest* /* req */, HttpResponse* resp) { UeberBackend B; // NOLINT(readability-identifier-length) @@ -1794,15 +1723,6 @@ static void apiServerAutoprimariesPOST(HttpRequest* req, HttpResponse* resp) { resp->status = 201; } -static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiServerAutoprimariesGET(req, resp); - else if (req->method == "POST") - apiServerAutoprimariesPOST(req, resp); - else - throw HttpMethodNotAllowedException(); -} - // create new zone static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { UeberBackend B; // NOLINT(readability-identifier-length) @@ -2033,15 +1953,6 @@ static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp) { resp->setJsonBody(doc); } -static void apiServerZones(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiServerZonesGET(req, resp); - else if (req->method == "POST") - apiServerZonesPOST(req, resp); - else - throw HttpMethodNotAllowedException(); -} - static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); @@ -2178,25 +2089,9 @@ static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp) { fillZone(B, zonename, resp, req); } -static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { - if (req->method == "GET") - apiServerZoneDetailGET(req, resp); - else if (req->method == "PATCH") - apiServerZoneDetailPATCH(req, resp); - else if (req->method == "PUT") - apiServerZoneDetailPUT(req, resp); - else if (req->method == "DELETE") - apiServerZoneDetailDELETE(req, resp); - else - throw HttpMethodNotAllowedException(); -} - static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - ostringstream ss; DNSResourceRecord rr; @@ -2226,9 +2121,6 @@ static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); - if(req->method != "PUT") - throw HttpMethodNotAllowedException(); - if (di.primaries.empty()) throw ApiException("Domain '" + zonename.toString() + "' is not a secondary domain (or has no primary defined)"); @@ -2240,9 +2132,6 @@ static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); - if(req->method != "PUT") - throw HttpMethodNotAllowedException(); - if(!Communicator.notifyDomain(zonename, &B)) throw ApiException("Failed to add to the queue - see server log"); @@ -2252,9 +2141,6 @@ static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) { zoneFromId(req); - if(req->method != "PUT") - throw HttpMethodNotAllowedException(); - if (dk.isPresigned(zonename)) throw ApiException("Zone '" + zonename.toString() + "' is pre-signed, not rectifying."); @@ -2453,9 +2339,6 @@ static void patchZone(UeberBackend& B, const DNSName& zonename, DomainInfo& di, } static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - string q = req->getvars["q"]; string sMax = req->getvars["max"]; string sObjectType = req->getvars["object_type"]; @@ -2561,9 +2444,6 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) { } static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { - if(req->method != "PUT") - throw HttpMethodNotAllowedException(); - DNSName canon = apiNameToDNSName(req->getvars["domain"]); if (g_zoneCache.isEnabled()) { @@ -2598,9 +2478,6 @@ static std::ostream& operator<<(std::ostream& os, StatType statType) } static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) { - if (req->method != "GET") - throw HttpMethodNotAllowedException(); - std::ostringstream output; for (const auto &metricName : S.getEntries()) { // Prometheus suggest using '_' instead of '-' @@ -2661,34 +2538,49 @@ void AuthWebServer::webThread() try { setThreadName("pdns/webserver"); if(::arg().mustDo("api")) { - d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush); - d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig); - d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData); - d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics); - d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries//", &apiServerAutoprimaryDetail); - d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimaries); - d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/", apiServerTSIGKeyDetail); - d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeys); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//axfr-retrieve", apiServerZoneAxfrRetrieve); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys/", apiZoneCryptokeys); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys", apiZoneCryptokeys); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//export", apiServerZoneExport); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata/", apiZoneMetadataKind); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata", apiZoneMetadata); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//notify", apiServerZoneNotify); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones//rectify", apiServerZoneRectify); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones); - d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail); - d_ws->registerApiHandler("/api/v1/servers", apiServer); - d_ws->registerApiHandler("/api/v1", apiDiscoveryV1); - d_ws->registerApiHandler("/api/docs", apiDocs); - d_ws->registerApiHandler("/api", apiDiscovery); + d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries//", &apiServerAutoprimaryDetailDELETE, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/", apiServerTSIGKeyDetailGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/", apiServerTSIGKeyDetailPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/", apiServerTSIGKeyDetailDELETE, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//axfr-retrieve", apiServerZoneAxfrRetrieve, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys/", apiZoneCryptokeysGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys/", apiZoneCryptokeysPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys/", apiZoneCryptokeysPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys/", apiZoneCryptokeysDELETE, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys", apiZoneCryptokeysGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//cryptokeys", apiZoneCryptokeysPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//export", apiServerZoneExport, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata/", apiZoneMetadataKindGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata/", apiZoneMetadataKindPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata/", apiZoneMetadataKindDELETE, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata", apiZoneMetadataGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//metadata", apiZoneMetadataPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//notify", apiServerZoneNotify, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones//rectify", apiServerZoneRectify, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailPATCH, "PATCH"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailDELETE, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET"); + d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET"); + d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET"); + d_ws->registerApiHandler("/api/docs", apiDocs, "GET"); + d_ws->registerApiHandler("/api", apiDiscovery, "GET"); } if (::arg().mustDo("webserver")) { - d_ws->registerWebHandler("/style.css", [this](HttpRequest *req, HttpResponse *resp){cssfunction(req, resp);}); - d_ws->registerWebHandler("/", [this](HttpRequest *req, HttpResponse *resp){indexfunction(req, resp);}); - d_ws->registerWebHandler("/metrics", prometheusMetrics); + d_ws->registerWebHandler("/style.css", [this](HttpRequest *req, HttpResponse *resp){cssfunction(req, resp);}, "GET"); + d_ws->registerWebHandler("/", [this](HttpRequest *req, HttpResponse *resp){indexfunction(req, resp);}, "GET"); + d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET"); } d_ws->go(); } From 464b26431ce3b1b1a96631016250a4e1131f2ca1 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 15:38:18 +0300 Subject: [PATCH 15/24] ws-recursor.cc: Add methods to routes --- pdns/recursordist/ws-recursor.cc | 39 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index e02ec1a340da..feabb16dd082 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -1303,27 +1303,32 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) // legacy dispatch d_ws->registerApiHandler( - "/jsonstat", [](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, true); - d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", &apiServerConfigAllowNotifyFrom); - d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig); - d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats); - d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData); - d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, true); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones); - d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, true); - d_ws->registerApiHandler("/api/v1/servers", apiServer); - d_ws->registerApiHandler("/api/v1", apiDiscoveryV1); - d_ws->registerApiHandler("/api", apiDiscovery); + "/jsonstat", [](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, "GET", true); + d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFrom, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFrom, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET", true); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET", true); + d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET"); + d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET"); + d_ws->registerApiHandler("/api", apiDiscovery, "GET"); for (const auto& url : g_urlmap) { - d_ws->registerWebHandler("/" + url.first, serveStuff); + d_ws->registerWebHandler("/" + url.first, serveStuff, "GET"); } - d_ws->registerWebHandler("/", serveStuff); - d_ws->registerWebHandler("/metrics", prometheusMetrics); + d_ws->registerWebHandler("/", serveStuff, "GET"); + d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET"); d_ws->go(); } From bfb7a704d8a222d2af9ca0cdfecec26b3d927a67 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 15:57:46 +0300 Subject: [PATCH 16/24] ws-recursor.cc: Split apiServerConfigACL to GET and PUT variant --- pdns/recursordist/ws-recursor.cc | 143 ++++++++++++++++--------------- 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index feabb16dd082..41bcdd770297 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -86,91 +86,100 @@ static void apiWriteConfigFile(const string& filebasename, const string& content ofconf.close(); } -static void apiServerConfigACL(const std::string& aclType, HttpRequest* req, HttpResponse* resp) +static void apiServerConfigACLGET(const std::string& aclType, HttpRequest* /* req */, HttpResponse* resp) { - if (req->method == "PUT") { - Json document = req->json(); + // Return currently configured ACLs + vector entries; + if (t_allowFrom && aclType == "allow-from") { + entries = t_allowFrom->toStringVector(); + } + else if (t_allowNotifyFrom && aclType == "allow-notify-from") { + entries = t_allowNotifyFrom->toStringVector(); + } - auto jlist = document["value"]; - if (!jlist.is_array()) { - throw ApiException("'value' must be an array"); - } + resp->setJsonBody(Json::object{ + {"name", aclType}, + {"value", entries}, + }); +} - if (g_yamlSettings) { - ::rust::Vec<::rust::String> vec; - for (const auto& value : jlist.array_items()) { - vec.emplace_back(value.string_value()); - } +static void apiServerConfigACLPUT(const std::string& aclType, HttpRequest* req, HttpResponse* resp) +{ + const auto& document = req->json(); + + const auto& jlist = document["value"]; + + if (!jlist.is_array()) { + throw ApiException("'value' must be an array"); + } + + if (g_yamlSettings) { + ::rust::Vec<::rust::String> vec; + for (const auto& value : jlist.array_items()) { + vec.emplace_back(value.string_value()); + } + try { + ::pdns::rust::settings::rec::validate_allow_from(aclType, vec); + } + catch (const ::rust::Error& e) { + throw ApiException(string("Unable to convert: ") + e.what()); + } + ::rust::String yaml; + if (aclType == "allow-from") { + yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_from", "allow_from_file", vec); + } + else { + yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_notify_from", "allow_notify_from_file", vec); + } + apiWriteConfigFile(aclType, string(yaml)); + } + else { + NetmaskGroup nmg; + for (const auto& value : jlist.array_items()) { try { - ::pdns::rust::settings::rec::validate_allow_from(aclType, vec); - } - catch (const ::rust::Error& e) { - throw ApiException(string("Unable to convert: ") + e.what()); + nmg.addMask(value.string_value()); } - ::rust::String yaml; - if (aclType == "allow-from") { - yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_from", "allow_from_file", vec); - } - else { - yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_notify_from", "allow_notify_from_file", vec); + catch (const NetmaskException& e) { + throw ApiException(e.reason); } - apiWriteConfigFile(aclType, string(yaml)); } - else { - NetmaskGroup nmg; - for (const auto& value : jlist.array_items()) { - try { - nmg.addMask(value.string_value()); - } - catch (const NetmaskException& e) { - throw ApiException(e.reason); - } - } - ostringstream strStream; + ostringstream strStream; - // Clear -from-file if set, so our changes take effect - strStream << aclType << "-file=" << endl; + // Clear -from-file if set, so our changes take effect + strStream << aclType << "-file=" << endl; - // Clear ACL setting, and provide a "parent" value - strStream << aclType << "=" << endl; - strStream << aclType << "+=" << nmg.toString() << endl; + // Clear ACL setting, and provide a "parent" value + strStream << aclType << "=" << endl; + strStream << aclType << "+=" << nmg.toString() << endl; - apiWriteConfigFile(aclType, strStream.str()); - } + apiWriteConfigFile(aclType, strStream.str()); + } - parseACLs(); + parseACLs(); - // fall through to GET - } - else if (req->method != "GET") { - throw HttpMethodNotAllowedException(); - } + apiServerConfigACLGET(aclType, req, resp); +} - // Return currently configured ACLs - vector entries; - if (t_allowFrom && aclType == "allow-from") { - entries = t_allowFrom->toStringVector(); - } - else if (t_allowNotifyFrom && aclType == "allow-notify-from") { - entries = t_allowNotifyFrom->toStringVector(); - } +static void apiServerConfigAllowFromGET(HttpRequest* req, HttpResponse* resp) +{ + apiServerConfigACLGET("allow-from", req, resp); +} - resp->setJsonBody(Json::object{ - {"name", aclType}, - {"value", entries}, - }); +static void apiServerConfigAllowNotifyFromGET(HttpRequest* req, HttpResponse* resp) +{ + apiServerConfigACLGET("allow-notify-from", req, resp); } -static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp) +static void apiServerConfigAllowFromPUT(HttpRequest* req, HttpResponse* resp) { - apiServerConfigACL("allow-from", req, resp); + apiServerConfigACLPUT("allow-from", req, resp); } -static void apiServerConfigAllowNotifyFrom(HttpRequest* req, HttpResponse* resp) +static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* resp) { - apiServerConfigACL("allow-notify-from", req, resp); + apiServerConfigACLPUT("allow-notify-from", req, resp); } static void fillZone(const DNSName& zonename, HttpResponse* resp) @@ -1305,10 +1314,10 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) d_ws->registerApiHandler( "/jsonstat", [](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, "GET", true); d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT"); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom, "PUT"); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFrom, "GET"); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFrom, "GET"); - d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFrom, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromPUT, "PUT"); d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET"); From e15a061881306bfcfdecaef01c2cfcf013aec0f5 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 15:59:52 +0300 Subject: [PATCH 17/24] ws-recursor.cc: Split apiServerZones to GET and POST variant --- pdns/recursordist/ws-recursor.cc | 44 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 41bcdd770297..89d86238c2b2 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -346,38 +346,34 @@ static bool doDeleteZone(const DNSName& zonename) return true; } -static void apiServerZones(HttpRequest* req, HttpResponse* resp) +static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp) { - if (req->method == "POST") { - if (::arg()["api-config-dir"].empty()) { - throw ApiException("Config Option \"api-config-dir\" must be set"); - } - - Json document = req->json(); + if (::arg()["api-config-dir"].empty()) { + throw ApiException("Config Option \"api-config-dir\" must be set"); + } - DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); + Json document = req->json(); - auto iter = SyncRes::t_sstorage.domainmap->find(zonename); - if (iter != SyncRes::t_sstorage.domainmap->end()) { - throw ApiException("Zone already exists"); - } + DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); - doCreateZone(document); - reloadZoneConfiguration(g_yamlSettings); - fillZone(zonename, resp); - resp->status = 201; - return; + const auto& iter = SyncRes::t_sstorage.domainmap->find(zonename); + if (iter != SyncRes::t_sstorage.domainmap->cend()) { + throw ApiException("Zone already exists"); } - if (req->method != "GET") { - throw HttpMethodNotAllowedException(); - } + doCreateZone(document); + reloadZoneConfiguration(g_yamlSettings); + fillZone(zonename, resp); + resp->status = 201; +} +static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) +{ Json::array doc; - for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) { + for (const auto& val : *SyncRes::t_sstorage.domainmap) { const SyncRes::AuthDomain& zone = val.second; Json::array servers; - for (const ComboAddress& server : zone.d_servers) { + for (const auto& server : zone.d_servers) { servers.emplace_back(server.toStringWithPort()); } // id is the canonical lookup key, which doesn't actually match the name (in some cases) @@ -1325,8 +1321,8 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "PUT"); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "DELETE"); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones, "GET"); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones, "POST"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST"); d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET", true); d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET"); d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET"); From 3bf2df56d7f81546353e541d17da60c18dfe3c57 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 16:02:51 +0300 Subject: [PATCH 18/24] ws-recursor.cc: Split apiServerZoneDetail to GET, PUT, DELETE variants --- pdns/recursordist/ws-recursor.cc | 64 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index 89d86238c2b2..b1880b6754e0 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -389,40 +389,44 @@ static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp) resp->setJsonBody(doc); } -static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) +static inline DNSName findZoneById(HttpRequest* req) { - DNSName zonename = apiZoneIdToName(req->parameters["id"]); - - auto iter = SyncRes::t_sstorage.domainmap->find(zonename); - if (iter == SyncRes::t_sstorage.domainmap->end()) { + auto zonename = apiZoneIdToName(req->parameters["id"]); + if (SyncRes::t_sstorage.domainmap->find(zonename) == SyncRes::t_sstorage.domainmap->end()) { throw ApiException("Could not find domain '" + zonename.toLogString() + "'"); } + return zonename; +} - if (req->method == "PUT") { - Json document = req->json(); +static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp) +{ + auto zonename = findZoneById(req); + const auto& document = req->json(); - doDeleteZone(zonename); - doCreateZone(document); - reloadZoneConfiguration(g_yamlSettings); - resp->body = ""; - resp->status = 204; // No Content, but indicate success - } - else if (req->method == "DELETE") { - if (!doDeleteZone(zonename)) { - throw ApiException("Deleting domain failed"); - } + doDeleteZone(zonename); + doCreateZone(document); + reloadZoneConfiguration(g_yamlSettings); + resp->body = ""; + resp->status = 204; // No Content, but indicate success +} - reloadZoneConfiguration(g_yamlSettings); - // empty body on success - resp->body = ""; - resp->status = 204; // No Content: declare that the zone is gone now - } - else if (req->method == "GET") { - fillZone(zonename, resp); - } - else { - throw HttpMethodNotAllowedException(); +static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp) +{ + auto zonename = findZoneById(req); + if (!doDeleteZone(zonename)) { + throw ApiException("Deleting domain failed"); } + + reloadZoneConfiguration(g_yamlSettings); + // empty body on success + resp->body = ""; + resp->status = 204; // No Content: declare that the zone is gone now +} + +static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp) +{ + auto zonename = findZoneById(req); + fillZone(zonename, resp); } static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) @@ -1318,9 +1322,9 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET", true); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "GET"); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "PUT"); - d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetail, "DELETE"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailGET, "GET"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailPUT, "PUT"); + d_ws->registerApiHandler("/api/v1/servers/localhost/zones/", apiServerZoneDetailDELETE, "DELETE"); d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET"); d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST"); d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET", true); From 0ac79b601cc24eab89c50e85d572a04dc1ec9a94 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 16:05:26 +0300 Subject: [PATCH 19/24] ws-recursor.cc: Remove redundant checks for method --- pdns/recursordist/ws-recursor.cc | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index b1880b6754e0..a1ada80c7a6c 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -431,10 +431,6 @@ static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp) static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) { - if (req->method != "GET") { - throw HttpMethodNotAllowedException(); - } - string qVar = req->getvars["q"]; if (qVar.empty()) { throw ApiException("Query q can't be blank"); @@ -476,10 +472,6 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { - if (req->method != "PUT") { - throw HttpMethodNotAllowedException(); - } - DNSName canon = apiNameToDNSName(req->getvars["domain"]); bool subtree = req->getvars.count("subtree") > 0 && req->getvars["subtree"] == "true"; uint16_t qtype = 0xffff; @@ -493,12 +485,8 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {"result", "Flushed cache."}}); } -static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp) +static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp) { - if (req->method != "GET") { - throw HttpMethodNotAllowedException(); - } - auto luaconf = g_luaconfs.getLocal(); auto numZones = luaconf->dfe.size(); @@ -531,10 +519,6 @@ static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) { static MetricDefinitionStorage s_metricDefinitions; - if (req->method != "GET") { - throw HttpMethodNotAllowedException(); - } - std::ostringstream output; // Argument controls disabling of any stats. So From 72c988e7fdc6d111f5bf2a7e58fcfebb6db4f611 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 16:06:03 +0300 Subject: [PATCH 20/24] ws-api.cc: Remove redundant checks for method --- pdns/ws-api.cc | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc index f479336ef1b4..4bba973f7d1e 100644 --- a/pdns/ws-api.cc +++ b/pdns/ws-api.cc @@ -109,10 +109,7 @@ static Json getServerDetail() { /* Return information about the supported API versions. * The format of this MUST NEVER CHANGE at it's not versioned. */ -void apiDiscovery(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - +void apiDiscovery(HttpRequest* /* req */, HttpResponse* resp) { Json version1 = Json::object { { "version", 1 }, { "url", "/api/v1" } @@ -122,10 +119,7 @@ void apiDiscovery(HttpRequest* req, HttpResponse* resp) { resp->setJsonBody(doc); } -void apiDiscoveryV1(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - +void apiDiscoveryV1(HttpRequest* /* req */, HttpResponse* resp) { Json version1 = Json::object { { "server_url", "/api/v1/servers{/server}" }, { "api_features", Json::array {} } @@ -136,25 +130,16 @@ void apiDiscoveryV1(HttpRequest* req, HttpResponse* resp) { } -void apiServer(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - +void apiServer(HttpRequest* /* req */ , HttpResponse* resp) { Json doc = Json::array {getServerDetail()}; resp->setJsonBody(doc); } -void apiServerDetail(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - +void apiServerDetail(HttpRequest* /* req */, HttpResponse* resp) { resp->setJsonBody(getServerDetail()); } -void apiServerConfig(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - +void apiServerConfig(HttpRequest* /* req */, HttpResponse* resp) { vector items = ::arg().list(); string value; Json::array doc; @@ -174,9 +159,6 @@ void apiServerConfig(HttpRequest* req, HttpResponse* resp) { } void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { - if(req->method != "GET") - throw HttpMethodNotAllowedException(); - Json::array doc; string name = req->getvars["statistic"]; if (!name.empty()) { From fe921322aa177df64d53fd8197215e942f0c01ce Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 20:12:50 +0300 Subject: [PATCH 21/24] webserver.cc: Add resource aware OPTIONS handler --- pdns/webserver.cc | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/pdns/webserver.cc b/pdns/webserver.cc index 700de7e71978..95fee8aebda5 100644 --- a/pdns/webserver.cc +++ b/pdns/webserver.cc @@ -148,23 +148,7 @@ void WebServer::registerBareHandler(const string& url, const HandlerFunction& ha YaHTTP::Router::Map(method, url, std::move(f)); } -static bool optionsHandler(HttpRequest* req, HttpResponse* resp) { - if (req->method == "OPTIONS") { - resp->headers["access-control-allow-origin"] = "*"; - resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key"; - resp->headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"; - resp->headers["access-control-max-age"] = "3600"; - resp->status = 200; - resp->headers["content-type"]= "text/plain"; - resp->body = ""; - return true; - } - return false; -} - void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) { - if (optionsHandler(req, resp)) return; - resp->headers["access-control-allow-origin"] = "*"; if (!d_apikey) { @@ -605,6 +589,36 @@ WebServer::WebServer(string listenaddress, int port) : d_server(nullptr), d_maxbodysize(2*1024*1024) { + YaHTTP::Router::Map("OPTIONS", "/<*url>", [](YaHTTP::Request *req, YaHTTP::Response *resp) { + // look for url in routes + bool seen = false; + std::vector methods; + for(const auto& route: YaHTTP::Router::GetRoutes()) { + const auto& method = std::get<0>(route); + const auto& url = std::get<1>(route); + if (method == "OPTIONS") { + continue; + } + std::map params; + if (YaHTTP::Router::Match(url, req->url, params)) { + methods.push_back(method); + seen = true; + } + } + if (!seen) { + resp->status = 404; + resp->body = ""; + return; + } + methods.emplace_back("OPTIONS"); + resp->headers["access-control-allow-origin"] = "*"; + resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key"; + resp->headers["access-control-allow-methods"] = boost::algorithm::join(methods, ", "); + resp->headers["access-control-max-age"] = "3600"; + resp->status = 200; + resp->headers["content-type"]= "text/plain"; + resp->body = ""; + }, "OptionsHandlerRoute"); } void WebServer::bind() From e9c75b708d5ac8af5bdebfd85b510c5779e1bce0 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Wed, 23 Aug 2023 20:40:53 +0300 Subject: [PATCH 22/24] regression-tests.api/test_Basics: Update to match new dynamic CORS handler --- regression-tests.api/test_Basics.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/regression-tests.api/test_Basics.py b/regression-tests.api/test_Basics.py index bf9172fdc620..46b32a641b46 100644 --- a/regression-tests.api/test_Basics.py +++ b/regression-tests.api/test_Basics.py @@ -1,7 +1,7 @@ import requests import socket import time -from test_helper import ApiTestCase +from test_helper import ApiTestCase, is_auth class TestBasics(ApiTestCase): @@ -43,6 +43,22 @@ def test_cors(self): self.assertEqual(r.status_code, requests.codes.ok) self.assertEqual(r.headers['access-control-allow-origin'], "*") self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key') - self.assertEqual(r.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS') + self.assertEqual(r.headers['access-control-allow-methods'], 'GET, OPTIONS') + + print("response", repr(r.headers)) + + r = self.session.options(self.url("/api/v1/servers/localhost/zones/test")) + self.assertEqual(r.status_code, requests.codes.ok) + self.assertEqual(r.headers['access-control-allow-origin'], "*") + self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key') + if is_auth(): + self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PATCH, PUT, DELETE, OPTIONS') + else: + self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PUT, DELETE, OPTIONS') + + print("response", repr(r.headers)) + + r = self.session.options(self.url("/api/v1/servers/localhost/invalid")) + self.assertEqual(r.status_code, requests.codes.not_found) print("response", repr(r.headers)) From 70c1c68e3b4dd6694d564fc6dff1915d38f8eeac Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Sun, 22 Oct 2023 18:50:35 +0300 Subject: [PATCH 23/24] ws-api: Constify some variables --- pdns/ws-api.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc index 4bba973f7d1e..955c5964fb97 100644 --- a/pdns/ws-api.cc +++ b/pdns/ws-api.cc @@ -120,18 +120,18 @@ void apiDiscovery(HttpRequest* /* req */, HttpResponse* resp) { } void apiDiscoveryV1(HttpRequest* /* req */, HttpResponse* resp) { - Json version1 = Json::object { + const Json& version1 = Json::object { { "server_url", "/api/v1/servers{/server}" }, { "api_features", Json::array {} } }; - Json doc = Json::array { std::move(version1) }; + const Json& doc = Json::array { version1 }; resp->setJsonBody(doc); } void apiServer(HttpRequest* /* req */ , HttpResponse* resp) { - Json doc = Json::array {getServerDetail()}; + const Json& doc = Json::array {getServerDetail()}; resp->setJsonBody(doc); } @@ -140,7 +140,7 @@ void apiServerDetail(HttpRequest* /* req */, HttpResponse* resp) { } void apiServerConfig(HttpRequest* /* req */, HttpResponse* resp) { - vector items = ::arg().list(); + const vector& items = ::arg().list(); string value; Json::array doc; for(const string& item : items) { @@ -162,7 +162,7 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { Json::array doc; string name = req->getvars["statistic"]; if (!name.empty()) { - auto stat = productServerStatisticsFetch(name); + const auto& stat = productServerStatisticsFetch(name); if (!stat) { throw ApiException("Unknown statistic name"); } From bfaeecb3e9b17a5dfd0b8368ae44a8ae15ac0864 Mon Sep 17 00:00:00 2001 From: Aki Tuomi Date: Tue, 12 Dec 2023 10:43:29 +0200 Subject: [PATCH 24/24] Remove unused req for prometheusMetrics() --- pdns/recursordist/ws-recursor.cc | 2 +- pdns/ws-auth.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index a1ada80c7a6c..af2cbfee3ea2 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -515,7 +515,7 @@ static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp) resp->setJsonBody(ret); } -static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) +static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp) { static MetricDefinitionStorage s_metricDefinitions; diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index 1698297a0525..058b91796d50 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -2477,7 +2477,7 @@ static std::ostream& operator<<(std::ostream& os, StatType statType) return os << static_cast(statType); } -static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) { +static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp) { std::ostringstream output; for (const auto &metricName : S.getEntries()) { // Prometheus suggest using '_' instead of '-'