| #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 |