blob: 2fe16f31e50272afa44ab659bf5ab9ca9028bd20 [file] [log] [blame]
#include "proxy.h"
#include <memory>
#include <string>
#include <utility>
#include "net/proto2/contrib/parse_proto/parse_text_proto.h"
#include "gmock.h"
#include "gunit.h"
#include "testing/fuzzing/fuzztest.h"
#include "absl/functional/bind_front.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "redfish_query_engine/http/codes.h"
#include "fake_auth_context.h"
#include "mock_redfish_plugin.h"
#include "redfish_plugin.h"
#include "request_response.h"
#include "resource_authz.h"
#include "sse/sse_parser.h"
#include "voyager/deferrable_priority_queue.hpp"
#include "voyager/voyager_telemetry.pb.h"
namespace {
using ::testing::_;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::FieldsAre;
using ::testing::HasSubstr;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::MatcherCast;
using ::testing::MockFunction;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::Pointer;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::status::IsOkAndHolds;
using ::testing::status::StatusIs;
using ::google::protobuf::contrib::parse_proto::ParseTextProtoOrDie;
using ProxyRequest = milotic::ProxyRequest;
using ProxyResponse = milotic::ProxyResponse;
using RequestVerb = milotic::RedfishPlugin::RequestVerb;
using RequestAction = milotic::RedfishPlugin::RequestAction;
using MockPlugin = milotic::MockRedfishPlugin;
using MockEventHandler = milotic::MockEventHandler;
TEST(TestPlugins, no_plugins_ok) {
milotic::Proxy proxy("test_endpoint");
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result = proxy.DispatchRequest(
milotic::RedfishPlugin::RequestVerb::kGet, std::move(request));
EXPECT_THAT(result, StatusIs(absl::StatusCode::kUnimplemented));
}
TEST(TestPlugins, plugin_not_activated) {
auto default_plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*default_plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*default_plugin,
PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test/uri"))))
.WillOnce(Invoke([](RequestVerb, std::unique_ptr<ProxyRequest> req) {
req.reset(); // take ownership and delete
return ProxyResponse{{
.code = ecclesia::HTTP_CODE_BAD_REQUEST,
.body = "test body.",
.headers = {{"test-header", "test-header-value"}},
}};
}));
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kNext));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(plugin), std::move(default_plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result =
proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(result,
IsOkAndHolds(FieldsAre(
ecclesia::HTTP_CODE_BAD_REQUEST, "test body.",
ElementsAre(Pair("test-header", "test-header-value")))));
}
TEST(TestPlugins, PluginHandlesRequest) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test/uri"))))
.WillOnce(Invoke([](RequestVerb, std::unique_ptr<ProxyRequest> req) {
return ProxyResponse{{
.code = ecclesia::HTTP_CODE_REQUEST_OK,
.body = "plugin body.",
}};
}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result =
proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(result, IsOkAndHolds(FieldsAre(ecclesia::HTTP_CODE_REQUEST_OK,
"plugin body.", _)));
}
TEST(TestPlugins, PluginFails) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test/uri"))))
.WillOnce(Invoke([](RequestVerb, std::unique_ptr<ProxyRequest> req) {
return absl::InternalError("plugin error");
}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result =
proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(result, StatusIs(absl::StatusCode::kInternal));
}
TEST(PluginsDeathTest, HardFailIfPluginInitFails) {
auto default_plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*default_plugin, Initialize).Times(0);
auto plugin = std::make_unique<MockPlugin>();
ON_CALL(*plugin, Initialize)
.WillByDefault(Return(absl::InternalError("test plugin init error")));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(plugin), std::move(default_plugin)};
EXPECT_DEATH(
{ milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins)); },
HasSubstr("test plugin init error"));
}
TEST(TestPlugins, PluginModifiesRequest) {
auto default_plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*default_plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*default_plugin,
PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri/modified")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test/uri/modified"))))
.WillOnce(Invoke([](RequestVerb, std::unique_ptr<ProxyRequest> req) {
req.reset(); // take ownership and delete
return ProxyResponse{{
.code = ecclesia::HTTP_CODE_BAD_REQUEST,
.body = "test body.",
.headers = {{"test-header", "test-header-value"}},
}};
}));
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Invoke([](RequestVerb, ProxyRequest& request) {
request.uri = "test/uri/modified";
return RequestAction::kNext;
}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(plugin), std::move(default_plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result =
proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(result,
IsOkAndHolds(FieldsAre(
ecclesia::HTTP_CODE_BAD_REQUEST, "test body.",
ElementsAre(Pair("test-header", "test-header-value")))));
}
TEST(TestPlugins, PluginDropsRequest) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kGet,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kDrop));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
absl::StatusOr<ProxyResponse> result =
proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(result, StatusIs(absl::StatusCode::kUnavailable));
}
constexpr int kFakeEventsCount = 2;
template <typename Event = milotic::ServerSentEvent>
absl::Status FakeEvents(const ProxyResponse& response,
std::unique_ptr<ProxyRequest> request,
milotic::RedfishPlugin::EventHandler* event_handler) {
absl::Status status = event_handler->OnResponse(response);
if (!status.ok()) return status;
Event event;
for (int i = 0; i < kFakeEventsCount; ++i) {
event.id = absl::StrCat(i);
absl::Status event_status = event_handler->OnEvent(event);
if (!event_status.ok()) return event_status;
}
return absl::OkStatus();
}
TEST(TestPlugins, SubscribePluginOk) {
using Event = milotic::ServerSentEvent;
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kSubscribe,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin,
Subscribe(Pointee(Field(&ProxyRequest::uri, "test/uri")), _))
.WillOnce(
Invoke(absl::bind_front(FakeEvents<>, ProxyResponse{{.code = 200}})));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
MockEventHandler handler;
ON_CALL(handler, IsCancelled()).WillByDefault(Return(false));
::testing::InSequence event_sequence;
EXPECT_CALL(handler, OnResponse(Field(&ProxyResponse::code, 200)))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(handler,
OnEvent(MatcherCast<const Event&>(Field(&Event::id, "0"))))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(handler,
OnEvent(MatcherCast<const Event&>(Field(&Event::id, "1"))))
.WillOnce(Return(absl::OkStatus()));
EXPECT_OK(proxy.Subscribe(std::move(request), &handler));
}
TEST(TestPlugins, SubscribePluginResponseError) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kSubscribe,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin,
Subscribe(Pointee(Field(&ProxyRequest::uri, "test/uri")), _))
.WillOnce(
Invoke(absl::bind_front(FakeEvents<>, ProxyResponse{{.code = 200}})));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
StrictMock<MockEventHandler> handler;
ON_CALL(handler, IsCancelled()).WillByDefault(Return(false));
EXPECT_CALL(handler, OnResponse(Field(&ProxyResponse::code, 200)))
.WillOnce(Return(absl::InternalError("Test error")));
EXPECT_THAT(proxy.Subscribe(std::move(request), &handler),
StatusIs(absl::StatusCode::kInternal, "Test error"));
}
TEST(TestPlugins, SubscribePluginEventError) {
using Event = milotic::ServerSentEvent;
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kSubscribe,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin,
Subscribe(Pointee(Field(&ProxyRequest::uri, "test/uri")), _))
.WillOnce(
Invoke(absl::bind_front(FakeEvents<>, ProxyResponse{{.code = 200}})));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
StrictMock<MockEventHandler> handler;
EXPECT_CALL(handler, IsCancelled()).WillRepeatedly(Return(false));
::testing::InSequence event_sequence;
EXPECT_CALL(handler, OnResponse(Field(&ProxyResponse::code, 200)))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(handler,
OnEvent(MatcherCast<const Event&>(Field(&Event::id, "0"))))
.WillOnce(Return(absl::InternalError("Test error")));
EXPECT_THAT(proxy.Subscribe(std::move(request), &handler),
StatusIs(absl::StatusCode::kInternal, "Test error"));
}
TEST(TestPlugins, BidirectionalStreamPluginOk) {
using third_party_voyager::Update;
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest(RequestVerb::kBidirectionalStream,
Field(&ProxyRequest::uri, "test/uri")))
.WillOnce(Return(RequestAction::kHandle));
auto write_handler = std::make_unique<MockEventHandler>();
auto* write_handler_ptr = write_handler.get();
StrictMock<MockEventHandler> read_handler;
EXPECT_CALL(*plugin, BidirectionalStream(
Pointee(Field(&ProxyRequest::uri, "test/uri")),
Pointer(&read_handler)))
.WillOnce(Return(std::move(write_handler)));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", absl::MakeSpan(plugins));
auto request = std::make_unique<ProxyRequest>();
request->uri = "test/uri";
EXPECT_THAT(proxy.BidirectionalStream(std::move(request), &read_handler),
IsOkAndHolds(Pointer(write_handler_ptr)));
}
TEST(TestCreateRequest, EndpointNameIsCopiedToHostHeader) {
milotic::Proxy proxy("hostname.test:1234");
std::unique_ptr<ProxyRequest> request = proxy.CreateRequest("some/resource");
EXPECT_THAT(request->headers, Contains(Pair("Host", "hostname.test:1234")));
}
TEST(TestCreateRequest, EndpointProtocolIsRemoved) {
milotic::Proxy proxy("https://hostname.test:1234");
std::unique_ptr<ProxyRequest> request = proxy.CreateRequest("some/resource");
EXPECT_THAT(request->headers, Contains(Pair("Host", "hostname.test:1234")));
}
TEST(TestDispatchRequest, HandlerRunsAfterRequestIsCompleted) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest)
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin, HandleRequest(RequestVerb::kGet, _))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
voyager::DeferrablePriorityQueue queue(1);
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", plugins, &queue);
StrictMock<MockFunction<milotic::Proxy::RequestJob::Handler>> handler;
auto request_job = proxy.CreateRequestJob(
RequestVerb::kGet, proxy.CreateRequest("/test_resource"));
absl::StatusOr<ProxyResponse> called_with;
request_job->Handle(handler.AsStdFunction());
ASSERT_OK(proxy.DispatchRequestToQueue(std::move(request_job)));
EXPECT_CALL(handler, Call).WillOnce(SaveArg<0>(&called_with));
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_THAT(called_with,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
}
TEST(TestDispatchRequest, HandlerRunsIfRequestIsAlreadyCompleted) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest)
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin, HandleRequest(RequestVerb::kGet, _))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
voyager::DeferrablePriorityQueue queue(1);
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", plugins, &queue);
StrictMock<MockFunction<milotic::Proxy::RequestJob::Handler>> handler;
ASSERT_OK_AND_ASSIGN(
std::unique_ptr<milotic::Proxy::RequestJob> job,
proxy.DispatchRequestToQueue(RequestVerb::kGet,
proxy.CreateRequest("/test_resource")));
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_CALL(
handler,
Call(IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body")))));
job->Handle(handler.AsStdFunction());
}
TEST(TestDispatchRequest, HandlerRunsWhenScheduled) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest)
.WillRepeatedly(Return(RequestAction::kHandle));
EXPECT_CALL(*plugin, HandleRequest(RequestVerb::kGet, _))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
voyager::DeferrablePriorityQueue queue(1);
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", plugins, &queue);
StrictMock<MockFunction<milotic::Proxy::RequestJob::Handler>> handler;
auto request_job = proxy.CreateRequestJob(
RequestVerb::kGet, proxy.CreateRequest("/test_resource"));
absl::StatusOr<ProxyResponse> called_with;
request_job->Handle(handler.AsStdFunction());
absl::Time run_after = absl::Now() + absl::Seconds(1);
ASSERT_OK(proxy.DispatchRequestToQueue(std::move(request_job),
milotic::Proxy::RequestPriority::kLow,
absl::Now() + absl::Seconds(1)));
absl::Time ran_at;
EXPECT_CALL(
handler,
Call(IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body")))))
.WillOnce(InvokeWithoutArgs([&ran_at] { ran_at = absl::Now(); }));
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_GE(ran_at, run_after);
}
TEST(TestDispatchRequest, HandlerRunsAfterRequestFails) {
auto plugin = std::make_unique<StrictMock<MockPlugin>>();
EXPECT_CALL(*plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*plugin, PreprocessRequest)
.WillOnce(Return(RequestAction::kDrop));
voyager::DeferrablePriorityQueue queue(1);
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(plugin)};
milotic::Proxy proxy("test_endpoint", plugins, &queue);
StrictMock<MockFunction<milotic::Proxy::RequestJob::Handler>> handler;
auto request_job = proxy.CreateRequestJob(
RequestVerb::kGet, proxy.CreateRequest("/test_resource"));
absl::StatusOr<ProxyResponse> called_with;
request_job->Handle(handler.AsStdFunction());
ASSERT_OK(proxy.DispatchRequestToQueue(std::move(request_job)));
EXPECT_CALL(handler, Call).WillOnce(SaveArg<0>(&called_with));
ASSERT_OK(queue.ProcessQueue(1));
EXPECT_THAT(called_with, StatusIs(absl::StatusCode::kUnavailable));
}
TEST(TestProxyConfig, DispatchRequestToQueueFailsIfNotConfigured) {
milotic::Proxy proxy("test_endpoint");
EXPECT_THAT(proxy.DispatchRequestToQueue(RequestVerb::kGet,
std::make_unique<ProxyRequest>()),
StatusIs(absl::StatusCode::kUnavailable));
}
class MockPermissionChecker : public milotic::PermissionChecker {
public:
MOCK_METHOD(bool, Check,
(const milotic::AuthorizationContext& context,
absl::string_view permission_id),
(override));
};
TEST(TestGetRequestAction, NoMatchReturnsNext) {
milotic::Proxy proxy("test_endpoint");
EXPECT_EQ(
proxy.GetRequestAction(RequestVerb::kGet, ProxyRequest("/test/resource"),
{{RequestVerb::kGet, "/other/resource"}}),
RequestAction::kNext);
}
TEST(TestGetRequestAction, MatchReturnsHandle) {
milotic::Proxy proxy("test_endpoint");
EXPECT_EQ(proxy.GetRequestAction(RequestVerb::kGet,
ProxyRequest("test_endpoint/test/resource"),
{{RequestVerb::kGet, "/other/resource"},
{RequestVerb::kGet, "/test/resource"}}),
RequestAction::kHandle);
}
TEST(TestGetRequestAction, ResourceWithMultipleVerbsReturnsHandle) {
milotic::Proxy proxy("test_endpoint");
EXPECT_EQ(proxy.GetRequestAction(RequestVerb::kGet,
ProxyRequest("test_endpoint/test/resource"),
{{RequestVerb::kPost, "/test/resource"},
{RequestVerb::kGet, "/test/resource"},
{RequestVerb::kPatch, "/test/resource"}}),
RequestAction::kHandle);
}
TEST(TestGetRequestAction, NonMatchingVerbReturnsDrop) {
milotic::Proxy proxy("test_endpoint");
EXPECT_EQ(proxy.GetRequestAction(RequestVerb::kGet,
ProxyRequest("test_endpoint/test/resource"),
{{RequestVerb::kPost, "/test/resource"}}),
RequestAction::kDrop);
}
TEST(TestProxyAuthz, AuthorizationPolicyIsApplied) {
auto permission_checker = std::make_unique<MockPermissionChecker>();
EXPECT_CALL(*permission_checker, Check(_, "test-permission"))
.WillOnce(Return(true))
.WillOnce(Return(false));
milotic::Proxy proxy("test_endpoint", {}, nullptr, {},
ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping1"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
allow: {
key: "permission",
value: { permission_id: "test-permission" }
}
}
)pb"),
std::move(permission_checker));
milotic::FakeAuthContext auth_context(false);
EXPECT_OK(proxy.CreateAuthorizedRequest(RequestVerb::kGet, "/some/resource",
auth_context));
EXPECT_THAT(proxy.CreateAuthorizedRequest(RequestVerb::kGet, "/some/resource",
auth_context),
StatusIs(absl::StatusCode::kPermissionDenied));
EXPECT_THAT(proxy.CreateAuthorizedRequest(RequestVerb::kPost,
"/some/resource", auth_context),
StatusIs(absl::StatusCode::kPermissionDenied));
EXPECT_THAT(proxy.CreateAuthorizedRequest(
RequestVerb::kGet, "/some/other/resource", auth_context),
StatusIs(absl::StatusCode::kPermissionDenied));
}
TEST(TestProxyAuthz, QueryParametersAreRemoved) {
auto permission_checker = std::make_unique<MockPermissionChecker>();
EXPECT_CALL(*permission_checker, Check(_, "test-permission"))
.Times(2)
.WillRepeatedly(Return(true));
milotic::Proxy proxy("test_endpoint", {}, nullptr, {},
ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping1"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
allow: {
key: "permission",
value: { permission_id: "test-permission" }
}
with_subtree: true
}
)pb"),
std::move(permission_checker));
milotic::FakeAuthContext auth_context(false);
ASSERT_OK_AND_ASSIGN(
auto request,
proxy.CreateAuthorizedRequest(
RequestVerb::kGet, "/some/resource?query_params?123", auth_context));
EXPECT_EQ(request->uri, "test_endpoint/some/resource");
ASSERT_OK_AND_ASSIGN(
request, proxy.CreateAuthorizedRequest(RequestVerb::kGet,
"/some/?$expand=*", auth_context));
EXPECT_EQ(request->uri, "test_endpoint/some/");
}
TEST(TestProxyAuthz, QueryParametersArePreservedIfConfigIsSet) {
auto permission_checker = std::make_unique<MockPermissionChecker>();
EXPECT_CALL(*permission_checker, Check(_, "test-permission"))
.Times(2)
.WillRepeatedly(Return(true));
milotic::Proxy proxy("test_endpoint", {}, nullptr, {},
ParseTextProtoOrDie(R"pb(
keep_query_params: true
mappings: {
name: "test_mapping1"
request_verb: REQUEST_VERB_GET
resource_path: "/some"
allow: {
key: "permission",
value: { permission_id: "test-permission" }
}
with_subtree: true
}
)pb"),
std::move(permission_checker));
milotic::FakeAuthContext auth_context(false);
ASSERT_OK_AND_ASSIGN(
auto request,
proxy.CreateAuthorizedRequest(
RequestVerb::kGet, "/some/resource?query_params?123", auth_context));
EXPECT_EQ(request->uri, "test_endpoint/some/resource?query_params?123");
ASSERT_OK_AND_ASSIGN(
request, proxy.CreateAuthorizedRequest(RequestVerb::kGet,
"/some/?$expand=*", auth_context));
EXPECT_EQ(request->uri, "test_endpoint/some/?$expand=*");
}
void ExpectQueryArbitraryParametersAreRemoved(absl::string_view parameters) {
auto permission_checker = std::make_unique<MockPermissionChecker>();
EXPECT_CALL(*permission_checker, Check(_, "test-permission"))
.WillOnce(Return(true));
milotic::Proxy proxy("test_endpoint", {}, nullptr, {},
ParseTextProtoOrDie(R"pb(
mappings: {
name: "test_mapping1"
request_verb: REQUEST_VERB_GET
resource_path: "/some/resource"
allow: {
key: "permission",
value: { permission_id: "test-permission" }
}
}
)pb"),
std::move(permission_checker));
milotic::FakeAuthContext auth_context(false);
ASSERT_OK_AND_ASSIGN(
auto request,
proxy.CreateAuthorizedRequest(RequestVerb::kGet,
absl::StrCat("/some/resource?", parameters),
auth_context));
EXPECT_EQ(request->uri, "test_endpoint/some/resource");
}
FUZZ_TEST(ProxyAuthzFuzzTest, ExpectQueryArbitraryParametersAreRemoved);
} // namespace