blob: 94e63ffc36f3b06e5e272c85e9567749693b3efb [file] [log] [blame]
#include "redfish_session_auth.h"
#include <array>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "gmock.h"
#include "gunit.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.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/protobuf/parse.h"
#include "nlohmann/json_fwd.hpp"
#include "mock_redfish_plugin.h"
#include "proxy_config.pb.h"
#include "redfish_plugin.h"
#include "remote_credentials.h"
#include "request_response.h"
#include "voyager/deferrable_priority_queue.hpp"
#include "thread/thread.h"
#include "thread/thread_options.h"
#include "util/task/status_macros.h"
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::DoAll;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::NotNull;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::ResultOf;
using ::testing::Return;
using ::testing::ReturnPointee;
using ::testing::SaveArgPointee;
using ::testing::StrictMock;
using ::testing::status::IsOkAndHolds;
using milotic::ProxyRequest;
using milotic::ProxyResponse;
using RequestVerb = milotic::RedfishPlugin::RequestVerb;
using RequestAction = milotic::RedfishPlugin::RequestAction;
using RedfishSessionAuth = milotic_grpc_proxy::Plugin::RedfishSessionAuth;
using milotic::RedfishSessionAuthPlugin;
using MockPlugin = milotic::MockRedfishPlugin;
class RedfishSessionAuthPluginWithMockTime : public RedfishSessionAuthPlugin {
public:
explicit RedfishSessionAuthPluginWithMockTime(
const RedfishSessionAuth& config)
: RedfishSessionAuthPlugin(config) {}
MOCK_METHOD(absl::Time, Now, (), (override));
};
std::unique_ptr<RedfishSessionAuthPluginWithMockTime> CreatePlugin(
const RedfishSessionAuth& config =
ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
password: "test_password"
)pb")) {
auto plugin =
std::make_unique<NiceMock<RedfishSessionAuthPluginWithMockTime>>(config);
plugin->SetName("test_plugin");
return plugin;
}
nlohmann::json parse_json(const std::string& str) {
return nlohmann::json::parse(str, nullptr, false);
}
struct TestEnv {
explicit TestEnv(absl::Span<std::unique_ptr<milotic::RedfishPlugin>> plugins)
: queue(20), proxy("test_endpoint", plugins, &queue) {}
~TestEnv() { queue.Shutdown(false); }
voyager::DeferrablePriorityQueue queue;
milotic::Proxy proxy;
};
ProxyResponse ValidSessionService() {
return ProxyResponse(200,
R"json({
"@odata.id": "/redfish/v1/SessionService/",
"@odata.type": "#SessionService.v1_0_2.SessionService",
"Description": "Session Service",
"Id": "SessionService",
"Name": "Session Service",
"ServiceEnabled": true,
"SessionTimeout": 1800,
"Sessions": {
"@odata.id": "/redfish/v1/SessionService/Sessions"
}
})json",
{{"OData-Version", "4.0"}});
}
ProxyResponse ValidSession(absl::string_view id, absl::string_view token) {
return ProxyResponse(
201,
absl::StrFormat(R"json({
"@odata.id": "/redfish/v1/SessionService/Sessions/%1$s",
"@odata.type": "#Session.v1_2_1.Session",
"Id": "%1$s",
"Name": "User Session test_session",
"UserName": "test_username"
})json",
id),
{{"X-Auth-Token", std::string(token)},
{"OData-Version", "4.0"},
{"Location", absl::StrCat("/redfish/v1/SessionService/Sessions/", id)}});
}
ProxyResponse ValidSessions(absl::Span<const absl::string_view> ids) {
nlohmann::json json = nlohmann::json::parse(R"json({
"@odata.id": "/redfish/v1/SessionService/Sessions",
"@odata.type": "#SessionCollection.SessionCollection",
"Name": "Session Collection"
})json");
for (absl::string_view id : ids) {
json["Members"].push_back({{"@odata.id", std::string(id)}});
}
json["Members@odata.count"] = ids.size();
return ProxyResponse(200, json.dump(), {{"OData-Version", "4.0"}});
}
absl::StatusOr<std::string> GetState(TestEnv* test_env) {
ASSIGN_OR_RETURN(ProxyResponse resp,
test_env->proxy.DispatchRequest(
RequestVerb::kInternal,
test_env->proxy.CreateRequest(
RedfishSessionAuthPlugin::SessionStateResourcePath(
"test_plugin"))));
if (resp.code != 200) {
return absl::InternalError(absl::StrCat("Code = ", resp.code));
}
return resp.body;
}
TEST(RedfishSessionAuthTest, SessionAuthRunsOnce) {
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
{
::testing::InSequence redfish_session_sequence;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
IsSupersetOf({Pair("Content-Type",
"application/json;charset=utf-8"),
Pair("OData-Version", "4.0")}))))))
.WillOnce(Return(session));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
}
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.Times(2)
.WillRepeatedly(
Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 2; ++i) {
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
}
}
TEST(RedfishSessionAuthTest, SessionAuthRunsAgainOnTimeout) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session1 = ValidSession("test_session1", "fake-token-1");
const ProxyResponse session2 = ValidSession("test_session2", "fake-token-2");
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
auto instance = CreatePlugin();
ASSERT_THAT(instance, NotNull());
absl::Time now = absl::Now();
ON_CALL(*instance, Now()).WillByDefault(ReturnPointee(&now));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
testing::InSequence seq;
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
Not(Contains(Pair("X-Auth-Token", _))))))))
.WillOnce(Return(session1));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kDelete,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions/"
"test_session1"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 204}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session2));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-2")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 2; ++i) {
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
now += absl::Seconds(1800);
}
}
TEST(RedfishSessionAuthTest, SessionAuthRunsAgainOnForce) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session1 = ValidSession("test_session1", "fake-token-1");
const ProxyResponse session2 = ValidSession("test_session2", "fake-token-2");
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
auto instance = CreatePlugin();
ASSERT_THAT(instance, NotNull());
absl::Time now = absl::Now();
ON_CALL(*instance, Now()).WillByDefault(ReturnPointee(&now));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
testing::InSequence seq;
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session1));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kDelete,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions/"
"test_session1"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 204}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session2));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-2")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 2; ++i) {
auto request = test_env.proxy.CreateRequest("/test/url");
request->headers["Force-Refresh-Token"] = "True";
absl::StatusOr<ProxyResponse> resp =
test_env.proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
}
}
TEST(RedfishSessionAuthTest, SessionRefreshWorksIfDeleteFails) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session1 = ValidSession("test_session1", "fake-token-1");
const ProxyResponse session2 = ValidSession("test_session2", "fake-token-2");
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
auto instance = CreatePlugin();
ASSERT_THAT(instance, NotNull());
absl::Time now = absl::Now();
ON_CALL(*instance, Now()).WillByDefault(ReturnPointee(&now));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
testing::InSequence seq;
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session1));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kDelete,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions/"
"test_session1"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-1")))))))
.WillOnce(Return(ProxyResponse{{.code = 404}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session2));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token-2")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 2; ++i) {
auto request = test_env.proxy.CreateRequest("/test/url");
request->headers["Force-Refresh-Token"] = "True";
absl::StatusOr<ProxyResponse> resp =
test_env.proxy.DispatchRequest(RequestVerb::kGet, std::move(request));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
}
}
TEST(RedfishSessionAuthTest, ParallelRequestsGetTokenOnce) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
{
::testing::InSequence redfish_session_sequence;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(
AllOf(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Invoke([&session] {
absl::SleepFor(absl::Seconds(1));
return ProxyResponse(session);
}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
}
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.Times(2)
.WillRepeatedly(
Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
struct RequestThread : public Thread {
explicit RequestThread(milotic::Proxy& proxy)
: Thread(thread::Options().set_joinable(true), "request-worker"),
proxy(proxy) {}
void Run() override {
resp = proxy.DispatchRequest(RequestVerb::kGet,
proxy.CreateRequest("/test/url"));
}
milotic::Proxy& proxy;
absl::StatusOr<ProxyResponse> resp;
};
std::array<RequestThread, 2> threads = {RequestThread(test_env.proxy),
RequestThread(test_env.proxy)};
for (RequestThread& thread : threads) {
thread.Start();
}
for (RequestThread& thread : threads) {
thread.Join();
EXPECT_THAT(thread.resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
}
}
TEST(RedfishSessionAuthTest, RequestsTryToRunAfterSessionFail) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session_failure(401);
ProxyResponse missing_token = ValidSession("test_session", "");
missing_token.headers.erase("X-Auth-Token");
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillRepeatedly(Return(session_service));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request))))))
.WillOnce(Return(session_failure))
.WillOnce(Return(missing_token));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Not(Contains(Pair("X-Auth-Token", _))))))))
.Times(2)
.WillRepeatedly(Return(ProxyResponse{{.code = 401}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 2; i++) {
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp, IsOkAndHolds(Field(&ProxyResponse::code, 401)));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kFailed));
}
}
TEST(RedfishSessionAuthTest, RequestsRunIfBadSessionService) {
const ProxyResponse not_found(
404,
// This won't normally be there, but we should ignore it if it is.
R"json({
"@odata.id": "/redfish/v1/SessionService/",
"@odata.type": "#SessionService.v1_0_2.SessionService",
"Description": "Session Service",
"Id": "SessionService",
"Name": "Session Service",
"SessionTimeout": 100,
"ServiceEnabled": true,
"Sessions": {
"@odata.id": "/redfish/v1/SessionService/Sessions"
}
})json");
const ProxyResponse invalid_json(200, "invalid json");
const ProxyResponse missing_session_timeout(200,
R"json({
"@odata.id": "/redfish/v1/SessionService/",
"@odata.type": "#SessionService.v1_0_2.SessionService",
"Description": "Session Service",
"Id": "SessionService",
"Name": "Session Service",
"ServiceEnabled": true,
"Sessions": {
"@odata.id": "/redfish/v1/SessionService/Sessions"
}
})json",
{{"OData-Version", "4.0"}});
const ProxyResponse invalid_session_timeout(200,
R"json({
"@odata.id": "/redfish/v1/SessionService/",
"@odata.type": "#SessionService.v1_0_2.SessionService",
"Description": "Session Service",
"Id": "SessionService",
"Name": "Session Service",
"SessionTimeout": "not valid",
"ServiceEnabled": true,
"Sessions": {
"@odata.id": "/redfish/v1/SessionService/Sessions"
}
})json",
{{"OData-Version", "4.0"}});
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(not_found))
.WillOnce(Return(invalid_json))
.WillOnce(Return(missing_session_timeout))
.WillOnce(Return(invalid_session_timeout));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.Times(4)
.WillRepeatedly(
Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
for (int i = 0; i < 4; i++) {
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
}
}
TEST(RedfishSessionAuthTest, FallbackToBasicAuth) {
const ProxyResponse not_found(404);
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillRepeatedly(Return(not_found));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(
Pair("Authorization",
"Basic dGVzdF91c2VybmFtZTp0ZXN0X3Bhc3N3b3Jk")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kFailed));
}
TEST(RedfishSessionAuthTest, BasicAuthOnlyMode) {
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
password: "test_password"
only_use_basic_auth: true
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.Times(0);
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(
Pair("Authorization",
"Basic dGVzdF91c2VybmFtZTp0ZXN0X3Bhc3N3b3Jk")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kDisabled));
}
TEST(RedfishSessionAuthTest, CredentialsReadFromFile) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
credentials_file: { path: "/some/path" }
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
StrictMock<MockFunction<milotic::CredentialsFileReader>> mock_file_reader;
RedfishSessionAuthPlugin::CredentialsFileReader() =
mock_file_reader.AsStdFunction();
EXPECT_CALL(mock_file_reader, Call("/some/path", "test_endpoint"))
.WillOnce(Return(milotic::RemoteCredentials{.username = "new_username",
.password = "new_password"}));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
ProxyRequest session_request;
{
::testing::InSequence redfish_session_sequence;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(DoAll(SaveArgPointee<1>(&session_request), Return(session)));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
}
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test_endpoint/test/url"))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
nlohmann::json request = nlohmann::json::parse(session_request.body);
EXPECT_EQ(request["UserName"], "new_username");
EXPECT_EQ(request["Password"], "new_password");
}
TEST(RedfishSessionAuthTest, CredentialsWithoutUsernameReadFromFile) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
credentials_file: { path: "/some/path" }
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
MockFunction<milotic::CredentialsFileReader> mock_file_reader;
RedfishSessionAuthPlugin::CredentialsFileReader() =
mock_file_reader.AsStdFunction();
EXPECT_CALL(mock_file_reader, Call("/some/path", "test_endpoint"))
.WillOnce(Return(milotic::RemoteCredentials{.password = "new_password"}));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
ProxyRequest session_request;
{
::testing::InSequence redfish_session_sequence;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(DoAll(SaveArgPointee<1>(&session_request), Return(session)));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
}
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test_endpoint/test/url"))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
absl::StatusOr<ProxyResponse> resp = test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url"));
EXPECT_THAT(resp,
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
nlohmann::json request = nlohmann::json::parse(session_request.body);
EXPECT_EQ(request["UserName"], "test_username");
EXPECT_EQ(request["Password"], "new_password");
}
TEST(RedfishSessionAuthTest, CredentialsErrorCausesRetries) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
credentials_file: { path: "/some/path" }
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
StrictMock<MockFunction<milotic::CredentialsFileReader>> mock_file_reader;
RedfishSessionAuthPlugin::CredentialsFileReader() =
mock_file_reader.AsStdFunction();
EXPECT_CALL(mock_file_reader, Call("/some/path", "test_endpoint"))
.WillOnce(Return(absl::InternalError("Test error")))
.WillOnce(Return(milotic::RemoteCredentials{
.username = "test_username", .password = "test_password"}));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(session));
ProxyRequest resource_request_fail;
ProxyRequest resource_request_pass;
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri, "test_endpoint/test/url"))))
.WillOnce(DoAll(SaveArgPointee<1>(&resource_request_fail),
Return(ProxyResponse{{.code = 401}})))
.WillOnce(
DoAll(SaveArgPointee<1>(&resource_request_pass),
Return(ProxyResponse{{.code = 200, .body = "test body"}})));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_OK(test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url")));
EXPECT_THAT(
GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kNoCredentials));
EXPECT_OK(test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url")));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
EXPECT_THAT(resource_request_fail.headers,
AllOf(Not(Contains(Pair("X-Auth-Token", _))),
Not(Contains(Pair("Authorization", _)))));
EXPECT_THAT(resource_request_pass.headers,
Contains(Pair("X-Auth-Token", "fake-token")));
}
TEST(RedfishSessionAuthTest, SessionIsCreatedAtStartup) {
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
constexpr std::string_view sessions_list[] = {
"/redfish/v1/SessionService/Session/test_session",
"/redfish/v1/SessionService/Sessions/other_session"};
const ProxyResponse sessions = ValidSessions(sessions_list);
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
testing::InSequence seq;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
IsSupersetOf(
{Pair("Content-Type", "application/json;charset=utf-8"),
Pair("OData-Version", "4.0")}))))))
.WillOnce(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(sessions));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_OK(test_env.queue.ProcessQueue(1));
}
TEST(RedfishSessionAuthTest, SessionIsCheckedAtSpecifiedIntervals) {
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
constexpr std::string_view sessions_list[] = {
"/redfish/v1/SessionService/Session/test_session",
"/redfish/v1/SessionService/Sessions/other_session"};
const ProxyResponse sessions = ValidSessions(sessions_list);
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
password: "test_password"
session_check_interval_sec: 1
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
testing::InSequence seq;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
IsSupersetOf(
{Pair("Content-Type", "application/json;charset=utf-8"),
Pair("OData-Version", "4.0")}))))))
.WillOnce(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
std::vector<absl::Time> session_check_times;
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.Times(2)
.WillRepeatedly(DoAll(InvokeWithoutArgs([&session_check_times] {
session_check_times.push_back(absl::Now());
}),
Return(sessions)));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_OK(test_env.queue.ProcessQueue(1));
ASSERT_FALSE(test_env.queue.Empty());
EXPECT_OK(test_env.queue.ProcessQueue(1));
ASSERT_EQ(session_check_times.size(), 2);
EXPECT_GE(session_check_times[1], session_check_times[0] + absl::Seconds(1));
}
TEST(RedfishSessionAuthTest, SessionIsRefreshedIfLost) {
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
constexpr std::string_view sessions_list[] = {
"/redfish/v1/SessionService/Session/test_session",
"/redfish/v1/SessionService/Sessions/other_session"};
const ProxyResponse sessions = ValidSessions(sessions_list);
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
password: "test_password"
session_check_interval_sec: 1
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
IsSupersetOf(
{Pair("Content-Type", "application/json;charset=utf-8"),
Pair("OData-Version", "4.0")}))))))
.Times(2)
.WillRepeatedly(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(sessions))
.WillOnce(Return(ProxyResponse{{.code = 402}}))
.WillOnce(Return(sessions));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_OK(test_env.queue.ProcessQueue(1));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
ASSERT_FALSE(test_env.queue.Empty());
EXPECT_OK(test_env.queue.ProcessQueue(1));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
ASSERT_FALSE(test_env.queue.Empty());
EXPECT_OK(test_env.queue.ProcessQueue(1));
}
TEST(RedfishSessionAuthTest, SessionIsRefreshedIfLostAndNotRecovered) {
const nlohmann::json expected_request = {{"UserName", "test_username"},
{"Password", "test_password"}};
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
constexpr std::string_view sessions_list[] = {
"/redfish/v1/SessionService/Session/test_session",
"/redfish/v1/SessionService/Sessions/other_session"};
const ProxyResponse sessions = ValidSessions(sessions_list);
auto instance =
CreatePlugin(ecclesia::ParseTextAsProtoOrDie<RedfishSessionAuth>(R"pb(
username: "test_username"
password: "test_password"
session_check_interval_sec: 1
)pb"));
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"))))
.WillOnce(Return(session_service));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(AllOf(
Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"),
Field(&ProxyRequest::body,
ResultOf(parse_json, expected_request)),
Field(&ProxyRequest::headers,
IsSupersetOf(
{Pair("Content-Type", "application/json;charset=utf-8"),
Pair("OData-Version", "4.0")}))))))
.WillOnce(Return(session))
.WillOnce(Return(ProxyResponse{{.code = 500}}))
.WillOnce(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(sessions))
.WillOnce(Return(ProxyResponse{{.code = 401}}));
EXPECT_CALL(*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(
Field(&ProxyRequest::uri, "test_endpoint/test_resource"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.WillOnce(Return(ProxyResponse{{.code = 200}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_OK(test_env.queue.ProcessQueue(1));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
ASSERT_FALSE(test_env.queue.Empty());
EXPECT_OK(test_env.queue.ProcessQueue(1));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kFailed));
EXPECT_THAT(
test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test_resource")),
IsOkAndHolds(Field(&ProxyResponse::code, 200)));
EXPECT_THAT(GetState(&test_env),
IsOkAndHolds(RedfishSessionAuthPlugin::SessionState::kOk));
}
TEST(RedfishSessionAuthTest, InternalSessionStateWorks) {
auto instance = CreatePlugin();
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {std::move(instance)};
TestEnv test_env(plugins);
ASSERT_OK_AND_ASSIGN(
ProxyResponse resp,
test_env.proxy.DispatchRequest(
RequestVerb::kInternal,
test_env.proxy.CreateRequest(
RedfishSessionAuthPlugin::SessionStateResourcePath(
"test_plugin"))));
EXPECT_EQ(resp.code, 200);
EXPECT_EQ(resp.body, RedfishSessionAuthPlugin::SessionState::kNoCredentials);
}
TEST(RedfishSessionAuthTest, InternalResourceRequestsNotModified) {
auto instance = CreatePlugin();
auto default_plugin = std::make_unique<MockPlugin>();
EXPECT_CALL(*default_plugin, Initialize).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*default_plugin, PreprocessRequest(RequestVerb::kInternal, _))
.WillOnce(Return(RequestAction::kHandle));
EXPECT_CALL(*default_plugin, HandleRequest(RequestVerb::kInternal, _))
.WillOnce(Return(ProxyResponse{{.code = 999}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
ASSERT_OK_AND_ASSIGN(
ProxyResponse resp,
test_env.proxy.DispatchRequest(
RequestVerb::kInternal,
test_env.proxy.CreateRequest("/some_internal_resource")));
EXPECT_EQ(resp.code, 999);
EXPECT_THAT(resp.headers, IsEmpty());
}
TEST(RedfishSessionAuthTest, SessionServiceIsAccessedWithToken) {
const ProxyResponse session_service = ValidSessionService();
const ProxyResponse session = ValidSession("test_session", "fake-token");
auto instance = CreatePlugin();
ON_CALL(*instance, Now()).WillByDefault(Return(absl::Now()));
auto default_plugin = std::make_unique<NiceMock<MockPlugin>>();
ON_CALL(*default_plugin, Initialize).WillByDefault(Return(absl::OkStatus()));
ON_CALL(*default_plugin, PreprocessRequest)
.WillByDefault(Return(RequestAction::kHandle));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kPost,
Pointee(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService/Sessions"))))
.WillOnce(Return(session));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri,
"test_endpoint/redfish/v1/SessionService"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.WillOnce(Return(session_service));
EXPECT_CALL(
*default_plugin,
HandleRequest(
RequestVerb::kGet,
Pointee(AllOf(Field(&ProxyRequest::uri, "test_endpoint/test/url"),
Field(&ProxyRequest::headers,
Contains(Pair("X-Auth-Token", "fake-token")))))))
.WillOnce(Return(ProxyResponse{{.code = 200, .body = "test body"}}));
std::unique_ptr<milotic::RedfishPlugin> plugins[] = {
std::move(instance), std::move(default_plugin)};
TestEnv test_env(plugins);
EXPECT_THAT(test_env.proxy.DispatchRequest(
RequestVerb::kGet, test_env.proxy.CreateRequest("/test/url")),
IsOkAndHolds(AllOf(Field(&ProxyResponse::code, 200),
Field(&ProxyResponse::body, "test body"))));
}
} // namespace