From 60ea186a78f543a4f4dcb71b6ffddaaeae65b77e Mon Sep 17 00:00:00 2001 From: Noah Botimer Date: Mon, 13 Nov 2023 13:05:47 -0500 Subject: [PATCH] Build out /authorized API call - Add HttpParams for query parameter passing - Add echo endpoint to mock server to test parameter handling - Update ApiClient test to use new HTTP interface - Use slightly more real data for "authorized?" call - Update ApiClient.isAllowed implementation to use req values as parameters (that is, use the new API call) --- apache/client/include/lauth/http_client.hpp | 3 ++ apache/client/include/lauth/http_params.hpp | 13 +++++++ apache/client/meson.build | 2 ++ apache/client/src/lauth/api_client.cpp | 9 +++-- apache/client/src/lauth/http_client.cpp | 16 +++++++-- apache/client/test/lauth/api_client_test.cpp | 36 ++++++++++++------- apache/client/test/lauth/authorizer_test.cpp | 4 +-- apache/client/test/lauth/http_client_test.cpp | 22 ++++++++++++ apache/client/test/lauth/mocks.hpp | 2 ++ apache/client/test/mock_service.cpp | 11 ++++++ 10 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 apache/client/include/lauth/http_params.hpp diff --git a/apache/client/include/lauth/http_client.hpp b/apache/client/include/lauth/http_client.hpp index d67551c8..7ce0d8ca 100644 --- a/apache/client/include/lauth/http_client.hpp +++ b/apache/client/include/lauth/http_client.hpp @@ -1,6 +1,8 @@ #ifndef __LAUTH_HTTP_CLIENT_HPP__ #define __LAUTH_HTTP_CLIENT_HPP__ +#include "lauth/http_params.hpp" + #include #include @@ -11,6 +13,7 @@ namespace mlibrary::lauth { virtual ~HttpClient() = default; virtual std::optional get(const std::string &path); + virtual std::optional get(const std::string &path, const HttpParams ¶ms); protected: const std::string baseUrl; diff --git a/apache/client/include/lauth/http_params.hpp b/apache/client/include/lauth/http_params.hpp new file mode 100644 index 00000000..e87ba02a --- /dev/null +++ b/apache/client/include/lauth/http_params.hpp @@ -0,0 +1,13 @@ +#ifndef __LAUTH_HTTP_PARAMS_HPP__ +#define __LAUTH_HTTP_PARAMS_HPP__ + +#include + +#include +#include + +namespace mlibrary::lauth { +using HttpParams = std::multimap; +} + +#endif // __LAUTH_HTTP_PARAMS_HPP__ diff --git a/apache/client/meson.build b/apache/client/meson.build index 81016654..1f5122ba 100644 --- a/apache/client/meson.build +++ b/apache/client/meson.build @@ -44,6 +44,7 @@ install_headers( 'include/lauth/api_client.hpp', 'include/lauth/authorizer.hpp', 'include/lauth/http_client.hpp', + 'include/lauth/http_params.hpp', 'include/lauth/request.hpp', subdir: 'lauth') @@ -88,6 +89,7 @@ executable( files(['test/mock_service.cpp']), dependencies: [ cpp_httplib, + json, ], link_args: httplib_links ) diff --git a/apache/client/src/lauth/api_client.cpp b/apache/client/src/lauth/api_client.cpp index 2c399c0f..c01c5bbf 100644 --- a/apache/client/src/lauth/api_client.cpp +++ b/apache/client/src/lauth/api_client.cpp @@ -5,9 +5,12 @@ namespace mlibrary::lauth { bool ApiClient::isAllowed(Request req) { - std::stringstream url; - url << "/users/" << req.user << "/is_allowed"; - auto result = client->get(url.str()); + HttpParams params { + {"uri", req.uri}, + {"user", req.user} + }; + + auto result = client->get("/authorized", params); return result == "yes"; } } diff --git a/apache/client/src/lauth/http_client.cpp b/apache/client/src/lauth/http_client.cpp index ed3979ff..21b23590 100644 --- a/apache/client/src/lauth/http_client.cpp +++ b/apache/client/src/lauth/http_client.cpp @@ -1,8 +1,10 @@ #include "lauth/http_client.hpp" +#include + #include -#include +#include "lauth/http_params.hpp" namespace mlibrary::lauth { std::optional HttpClient::get(const std::string& path) { @@ -14,5 +16,15 @@ namespace mlibrary::lauth { else return std::nullopt; } -} + std::optional HttpClient::get(const std::string& path, const HttpParams& params) { + httplib::Client client(baseUrl); + httplib::Headers headers; + + auto res = client.Get(path, params, headers); + if (res) + return res->body; + else + return std::nullopt; + } +} diff --git a/apache/client/test/lauth/api_client_test.cpp b/apache/client/test/lauth/api_client_test.cpp index 97524aa5..19f03863 100644 --- a/apache/client/test/lauth/api_client_test.cpp +++ b/apache/client/test/lauth/api_client_test.cpp @@ -14,15 +14,20 @@ using namespace mlibrary::lauth; TEST(ApiClient, RequestByAuthorizedUserIsAllowed) { auto client = std::make_unique(); - EXPECT_CALL(*client, get("/users/authorized/is_allowed")).WillOnce(Return("yes")); - ApiClient api_client(std::move(client)); - Request req { - .ip = "", - .uri = "", - .user = "authorized", + .ip = "127.0.0.1", + .uri = "/resource-restricted-to-authorized-users", + .user = "authorized-user", + }; + + HttpParams params { + {"uri", req.uri}, + {"user", req.user} }; + EXPECT_CALL(*client, get("/authorized", params)).WillOnce(Return("yes")); + ApiClient api_client(std::move(client)); + auto allowed = api_client.isAllowed(req); EXPECT_THAT(allowed, true); @@ -30,15 +35,20 @@ TEST(ApiClient, RequestByAuthorizedUserIsAllowed) { TEST(ApiClient, RequestByUnauthorizedUserIsDenied) { auto client = std::make_unique(); - EXPECT_CALL(*client, get("/users/unauthorized/is_allowed")).WillOnce(Return("no")); - ApiClient api_client(std::move(client)); - Request req { - .ip = "", - .uri = "", - .user = "unauthorized", + .ip = "127.0.0.1", + .uri = "/resource-restricted-to-authorized-users", + .user = "unauthorized-user", + }; + + HttpParams params { + {"uri", req.uri}, + {"user", req.user} }; + EXPECT_CALL(*client, get("/authorized", params)).WillOnce(Return("no")); + ApiClient api_client(std::move(client)); + auto allowed = api_client.isAllowed(req); EXPECT_THAT(allowed, false); @@ -64,4 +74,4 @@ TEST(ApiClient, UsesTheSuppliedApiUrl) { << "correct URL... pushing everything back to config and likely " << "a factory/builder rather than concrete class dependency."; ApiClient client("http://api.invalid"); -} \ No newline at end of file +} diff --git a/apache/client/test/lauth/authorizer_test.cpp b/apache/client/test/lauth/authorizer_test.cpp index 9e6f86dd..e7ee292c 100644 --- a/apache/client/test/lauth/authorizer_test.cpp +++ b/apache/client/test/lauth/authorizer_test.cpp @@ -23,7 +23,7 @@ TEST(AuthorizerTest, AllowsAccessWhenApiSaysAuthorized) { Request req { .ip = "", - .uri = "", + .uri = "/user/", .user = "lauth-allowed", }; auto allowed = authorizer.isAllowed(req); @@ -38,7 +38,7 @@ TEST(AuthorizerTest, DeniesAccessWhenApiSaysUnauthorized) { Request req { .ip = "", - .uri = "", + .uri = "/user/", .user = "lauth-denied", }; auto allowed = authorizer.isAllowed(req); diff --git a/apache/client/test/lauth/http_client_test.cpp b/apache/client/test/lauth/http_client_test.cpp index 133f267e..c0dd0328 100644 --- a/apache/client/test/lauth/http_client_test.cpp +++ b/apache/client/test/lauth/http_client_test.cpp @@ -6,6 +6,7 @@ #include "mocks.hpp" #include "lauth/http_client.hpp" +#include "lauth/http_params.hpp" #include "lauth/request.hpp" #include @@ -51,3 +52,24 @@ TEST(HttpClient, get_request_with_path_returns_body) { auto response = client.get("/ping"); EXPECT_THAT(response, "pong"); } + +TEST(HttpClient, GetRequestWithOneParameterEncodesIt) { + HttpClient client(MOCK_API_URL()); + + HttpParams params; + params.emplace("foo", "bar"); + auto response = client.get("/echo", params); + + EXPECT_THAT(*response, Eq(R"({"foo":"bar"})")); +} + +TEST(HttpClient, GetRequestWithMultipleParametersEncodesThem) { + HttpClient client(MOCK_API_URL()); + + HttpParams params; + params.emplace("foo", "bar"); + params.emplace("something", "else"); + auto response = client.get("/echo", params); + + EXPECT_THAT(*response, Eq(R"({"foo":"bar","something":"else"})")); +} diff --git a/apache/client/test/lauth/mocks.hpp b/apache/client/test/lauth/mocks.hpp index 0f191d7a..69aa8ff3 100644 --- a/apache/client/test/lauth/mocks.hpp +++ b/apache/client/test/lauth/mocks.hpp @@ -3,6 +3,7 @@ #include "lauth/api_client.hpp" #include "lauth/http_client.hpp" +#include "lauth/http_params.hpp" #include @@ -12,6 +13,7 @@ class MockHttpClient : public HttpClient { public: MockHttpClient() : HttpClient("http://api.invalid") {}; MOCK_METHOD(std::optional, get, (const std::string&), (override)); + MOCK_METHOD(std::optional, get, (const std::string&, const HttpParams&), (override)); }; class MockApiClient : public ApiClient { diff --git a/apache/client/test/mock_service.cpp b/apache/client/test/mock_service.cpp index b335c7d7..227fd0e5 100644 --- a/apache/client/test/mock_service.cpp +++ b/apache/client/test/mock_service.cpp @@ -1,4 +1,7 @@ #include +#include + +using json = nlohmann::json; int main(int argc, char **argv) { using namespace httplib; @@ -24,6 +27,14 @@ int main(int argc, char **argv) { res.set_content("pong", "text/plain"); }); + // Echo GET query parameters as sorted json object + server.Get("/echo", [](const Request &req, Response &res) { + json params(req.params); + + std::cout << "GET /echo" << std::endl; + res.set_content(params.dump().c_str(), "application/json"); + }); + server.Get("/users/authorized/is_allowed", [](const Request &, Response &res) { std::cout << "GET /users/authorized/is_allowed" << std::endl; res.set_content("yes", "text/plain");