diff --git a/README.md b/README.md index 287d15e..f3fb00b 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,9 @@ const auto response = influxdb->execute("SHOW DATABASES"); An underlying transport is fully configurable by passing an URI: ``` [protocol]://[username:password@]host:port[?db=database] + +# Auth token: +[protocol]://[token@]host:port[?db=database] ```
List of supported transport is following: diff --git a/src/HTTP.cxx b/src/HTTP.cxx index c2b26ef..4ac9de4 100644 --- a/src/HTTP.cxx +++ b/src/HTTP.cxx @@ -96,10 +96,15 @@ namespace influxdb::transports session.SetAuth(cpr::Authentication{user, pass, cpr::AuthMode::BASIC}); } + void HTTP::setAuthToken(const std::string& token) + { + session.UpdateHeader(cpr::Header{{"Authorization", "Token " + token}}); + } + void HTTP::send(std::string&& lineprotocol) { session.SetUrl(cpr::Url{endpointUrl + "/write"}); - session.SetHeader(cpr::Header{{"Content-Type", "application/json"}}); + session.UpdateHeader(cpr::Header{{"Content-Type", "application/json"}}); session.SetParameters(cpr::Parameters{{"db", databaseName}}); session.SetBody(cpr::Body{lineprotocol}); diff --git a/src/HTTP.h b/src/HTTP.h index 8b2c029..66ccf02 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -64,6 +64,10 @@ namespace influxdb::transports /// \param pass password void setBasicAuthentication(const std::string& user, const std::string& pass); + /// Sets the API token for authentication + /// \param token API token + void setAuthToken(const std::string& token); + /// Sets proxy void setProxy(const Proxy& proxy) override; diff --git a/src/InfluxDBFactory.cxx b/src/InfluxDBFactory.cxx index 2c64d14..4832690 100644 --- a/src/InfluxDBFactory.cxx +++ b/src/InfluxDBFactory.cxx @@ -42,10 +42,14 @@ namespace influxdb std::unique_ptr withHttpTransport(const http::url& uri) { auto transport = std::make_unique(uri.url); - if (!uri.user.empty()) + if (!uri.user.empty() && !uri.password.empty()) { transport->setBasicAuthentication(uri.user, uri.password); } + else if (!uri.password.empty()) + { + transport->setAuthToken(uri.password); + } return transport; } diff --git a/src/UriParser.h b/src/UriParser.h index 0aebff9..8e6877b 100644 --- a/src/UriParser.h +++ b/src/UriParser.h @@ -115,8 +115,15 @@ namespace http const auto search = ExtractSearch(in); const auto path = ExtractPath(in); std::string userpass = ExtractUserpass(in); - const auto password = ExtractPassword(userpass); - const auto user = userpass; + + auto [password, user] = [](auto str) + { + if (str.find(":") != std::string::npos) + { + return std::make_pair(ExtractPassword(str), str); + } + return std::make_pair(str, std::string{""}); + }(userpass); const auto port = ExtractPort(in); const auto host = in; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7ac6bc3..333ab1b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ add_unittest(InfluxDBTest DEPENDS InfluxDB) add_unittest(InfluxDBFactoryTest DEPENDS InfluxDB) add_unittest(ProxyTest DEPENDS InfluxDB) add_unittest(HttpTest DEPENDS InfluxDB-Core InfluxDB-Internal InfluxDB-BoostSupport CprMock Threads::Threads) +add_unittest(UriParserTest) add_unittest(NoBoostSupportTest) target_sources(NoBoostSupportTest PRIVATE ${PROJECT_SOURCE_DIR}/src/NoBoostSupport.cxx) @@ -53,6 +54,7 @@ add_custom_target(unittest PointTest COMMAND InfluxDBFactoryTest COMMAND ProxyTest COMMAND HttpTest + COMMAND UriParserTest COMMAND NoBoostSupportTest COMMAND $<$,$>>:BoostSupportTest> diff --git a/test/HttpTest.cxx b/test/HttpTest.cxx index 416159b..3344105 100644 --- a/test/HttpTest.cxx +++ b/test/HttpTest.cxx @@ -82,7 +82,7 @@ namespace influxdb::test REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); REQUIRE_CALL(sessionMock, SetUrl(eq("http://localhost:8086/write"))); - REQUIRE_CALL(sessionMock, SetHeader(_)).WITH(_1.at("Content-Type") == "application/json"); + REQUIRE_CALL(sessionMock, UpdateHeader(_)).WITH(_1.at("Content-Type") == "application/json"); REQUIRE_CALL(sessionMock, SetBody(_)).WITH(_1.str() == data); REQUIRE_CALL(sessionMock, SetParameters(ParamMap{{"db", "test"}})); @@ -95,7 +95,7 @@ namespace influxdb::test REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::INTERNAL_ERROR, cpr::status::HTTP_OK)); ALLOW_CALL(sessionMock, SetUrl(_)); - ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, UpdateHeader(_)); ALLOW_CALL(sessionMock, SetBody(_)); ALLOW_CALL(sessionMock, SetParameters(_)); @@ -108,7 +108,7 @@ namespace influxdb::test REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_OK)); ALLOW_CALL(sessionMock, SetUrl(_)); - ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, UpdateHeader(_)); ALLOW_CALL(sessionMock, SetBody(_)); ALLOW_CALL(sessionMock, SetParameters(_)); @@ -121,7 +121,7 @@ namespace influxdb::test REQUIRE_CALL(sessionMock, Post()).RETURN(createResponse(cpr::ErrorCode::OK, cpr::status::HTTP_NOT_FOUND)); ALLOW_CALL(sessionMock, SetUrl(_)); - ALLOW_CALL(sessionMock, SetHeader(_)); + ALLOW_CALL(sessionMock, UpdateHeader(_)); ALLOW_CALL(sessionMock, SetBody(_)); ALLOW_CALL(sessionMock, SetParameters(_)); @@ -225,6 +225,14 @@ namespace influxdb::test http.setBasicAuthentication("user0", "pass0"); } + TEST_CASE("Set auth token sets auth header", "[HttpTest]") + { + auto http = createHttp(); + + REQUIRE_CALL(sessionMock, UpdateHeader(_)).WITH(_1.at("Authorization") == "Token not-a-real-api-token"); + http.setAuthToken("not-a-real-api-token"); + } + TEST_CASE("Set proxy without authentication", "[HttpTest]") { auto http = createHttp(); diff --git a/test/UriParserTest.cxx b/test/UriParserTest.cxx new file mode 100644 index 0000000..f2335cd --- /dev/null +++ b/test/UriParserTest.cxx @@ -0,0 +1,83 @@ +// MIT License +// +// Copyright (c) 2020-2023 offa +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "UriParser.h" +#include +#include + +namespace influxdb::test +{ + namespace + { + http::url parse(std::string in) + { + return http::ParseHttpUrl(in); + } + } + + + TEST_CASE("Parse url", "[UriParserTest]") + { + const std::string input{"https://xyz.com/fghi/jklm"}; + const auto url = parse(input); + + CHECK(url.protocol == "https"); + CHECK(url.user == ""); + CHECK(url.password == ""); + CHECK(url.host == "xyz.com"); + CHECK(url.path == "/fghi/jklm"); + CHECK(url.search == ""); + CHECK(url.url == input); + CHECK(url.port == 0); + } + + TEST_CASE("Parse protocol", "[UriParserTest]") + { + CHECK(parse("http://xyz.com").protocol == "http"); + CHECK(parse("https://xyz.com").protocol == "https"); + CHECK(parse("udp://xyz.com").protocol == "udp"); + CHECK(parse("unix://xyz.com").protocol == "unix"); + } + + TEST_CASE("Parse param", "[UriParserTest]") + { + CHECK(parse("http://xyz.com/aaa?param=value").search == "param=value"); + } + + TEST_CASE("Parse port", "[UriParserTest]") + { + CHECK(parse("http://xyz.com:12345").port == 12345); + } + + TEST_CASE("Parse basic auth", "[UriParserTest]") + { + const auto url = parse("https://aaa:bbb@host0"); + CHECK(url.user == "aaa"); + CHECK(url.password == "bbb"); + } + + TEST_CASE("Parse auth token", "[UriParserTest]") + { + CHECK(parse("http://token@xyz.com").password == "token"); + } + +} diff --git a/test/mock/CprMock.cxx b/test/mock/CprMock.cxx index b820aad..9a53bac 100644 --- a/test/mock/CprMock.cxx +++ b/test/mock/CprMock.cxx @@ -105,6 +105,10 @@ namespace cpr influxdb::test::sessionMock.SetHeader(header); } + void Session::UpdateHeader(const Header& header) + { + influxdb::test::sessionMock.UpdateHeader(header); + } Parameters::Parameters(const std::initializer_list& parameters) : CurlContainer(parameters) diff --git a/test/mock/CprMock.h b/test/mock/CprMock.h index 400a9b7..210572d 100644 --- a/test/mock/CprMock.h +++ b/test/mock/CprMock.h @@ -39,6 +39,7 @@ namespace influxdb::test MAKE_MOCK0(Post, cpr::Response()); MAKE_MOCK1(SetUrl, void(const cpr::Url&)); MAKE_MOCK1(SetHeader, void(const cpr::Header&)); + MAKE_MOCK1(UpdateHeader, void(const cpr::Header&)); MAKE_MOCK1(SetBody, void(cpr::Body&&)); MAKE_MOCK1(SetParameters, void(std::map)); MAKE_MOCK1(SetAuth, void(const cpr::Authentication&));