| #include "routing.hpp" | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <system_error> // NOLINT | |
| #include <gtest/gtest.h> // IWYU pragma: keep | |
| #include "boost/beast/http/message.hpp" // IWYU pragma: keep // NOLINT | |
| #include "boost/beast/http/verb.hpp" // NOLINT | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" // IWYU pragma: keep | |
| #include "sessions.hpp" | |
| // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> | |
| // IWYU pragma: no_include "gtest/gtest_pred_impl.h" | |
| // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> | |
| // IWYU pragma: no_include <gtest/gtest-message.h> | |
| // IWYU pragma: no_include <gtest/gtest-test-part.h> | |
| // IWYU pragma: no_forward_declare bmcweb::AsyncResp | |
| namespace crow { | |
| namespace { | |
| using ::crow::black_magic::getParameterTag; | |
| TEST(Router, CatchAllRuleIsAdded) { | |
| Router router(/*allowSessionEmpty=*/true); | |
| router.setCatchAllRule<0>().methods(boost::beast::http::verb::get)( | |
| [](const Request&, const std::shared_ptr<bmcweb::AsyncResp>& async_resp) { | |
| async_resp->res.jsonValue["test"] = "test"; | |
| }); | |
| router.validate(); | |
| crow::Request req; | |
| req.target("/foo"); | |
| req.set_method(boost::beast::http::verb::get); | |
| auto async_resp = std::make_shared<bmcweb::AsyncResp>(); | |
| router.handle(req, async_resp); | |
| EXPECT_EQ(async_resp->res.jsonValue["test"], "test"); | |
| } | |
| TEST(Router, AllowHeader) { | |
| // Callback handler that does nothing | |
| auto null_callback = [](const Request&, | |
| const std::shared_ptr<bmcweb::AsyncResp>&) {}; | |
| Router router(/*allowSessionEmpty=*/true); | |
| std::error_code ec; | |
| constexpr std::string_view url = "/foo"; | |
| Request req{{boost::beast::http::verb::get, url, 11}, ec}; | |
| // No route should return no methods. | |
| router.validate(); | |
| EXPECT_EQ(router.findRoute(req).allowHeader, ""); | |
| EXPECT_EQ(router.findRoute(req).route.rule, nullptr); | |
| router.newRuleTagged<getParameterTag(url)>(url).methods( | |
| boost::beast::http::verb::get)(null_callback); | |
| router.validate(); | |
| EXPECT_EQ(router.findRoute(req).allowHeader, "GET"); | |
| EXPECT_NE(router.findRoute(req).route.rule, nullptr); | |
| Request patch_req{{boost::beast::http::verb::patch, url, 11}, ec}; | |
| EXPECT_EQ(router.findRoute(patch_req).route.rule, nullptr); | |
| router.newRuleTagged<getParameterTag(url)>(url).methods( | |
| boost::beast::http::verb::patch)(null_callback); | |
| router.validate(); | |
| EXPECT_EQ(router.findRoute(req).allowHeader, "GET, PATCH"); | |
| EXPECT_NE(router.findRoute(req).route.rule, nullptr); | |
| EXPECT_NE(router.findRoute(patch_req).route.rule, nullptr); | |
| } | |
| TEST(Router, 404) { | |
| bool callback_called = false; | |
| // Callback handler that does nothing | |
| auto callback = [&](const Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>&) { | |
| BMCWEB_LOG_DEBUG << "=> req: " << req.target(); | |
| callback_called = true; | |
| }; | |
| Router router(/*allowSessionEmpty=*/true); | |
| std::error_code ec; | |
| constexpr std::string_view url = "/foo/bar"; | |
| Request req{{boost::beast::http::verb::get, url, 11}, ec}; | |
| req.session = std::make_shared<persistent_data::UserSession>(); | |
| req.session->username = "batman"; | |
| router.newRuleTagged<getParameterTag(url)>("/foo/<path>") | |
| .methods(boost::beast::http::verb::get) | |
| .notFound()(callback); | |
| router.validate(); | |
| { | |
| std::shared_ptr<bmcweb::AsyncResp> async_resp = | |
| std::make_shared<bmcweb::AsyncResp>(); | |
| router.handle(req, async_resp); | |
| BMCWEB_LOG_DEBUG << "=> res: " | |
| << " resultInt: " << async_resp->res.resultInt() | |
| << " reason: " << async_resp->res.reason(); | |
| } | |
| EXPECT_TRUE(callback_called); | |
| } | |
| TEST(Router, 405) { | |
| // Callback handler that does nothing | |
| auto null_callback = [](const Request&, | |
| const std::shared_ptr<bmcweb::AsyncResp>&) {}; | |
| bool called = false; | |
| auto not_allowed_callback = | |
| [&called](const Request&, const std::shared_ptr<bmcweb::AsyncResp>&) { | |
| called = true; | |
| }; | |
| Router router(/*allowSessionEmpty=*/true); | |
| std::error_code ec; | |
| constexpr std::string_view url = "/foo/bar"; | |
| Request req{{boost::beast::http::verb::patch, url, 11}, ec}; | |
| req.session = std::make_shared<persistent_data::UserSession>(); | |
| req.session->username = "batman"; | |
| router.newRuleTagged<getParameterTag(url)>(url).methods( | |
| boost::beast::http::verb::get)(null_callback); | |
| router.newRuleTagged<getParameterTag(url)>("/foo/<path>") | |
| .methodNotAllowed()(not_allowed_callback); | |
| router.validate(); | |
| { | |
| std::shared_ptr<bmcweb::AsyncResp> async_resp = | |
| std::make_shared<bmcweb::AsyncResp>(); | |
| router.handle(req, async_resp); | |
| BMCWEB_LOG_DEBUG << "=> res: " | |
| << " resultInt: " << async_resp->res.resultInt() | |
| << " reason: " << async_resp->res.reason(); | |
| } | |
| EXPECT_TRUE(called); | |
| } | |
| TEST(Trie, TrieTestReturnsRuleIndexSuccessfullyWithoutWildcards) { | |
| Trie trie; | |
| trie.add("/redfish", 1); | |
| trie.add("/redfish/v1", 2); | |
| trie.add("/redfish/v1/Chassis", 3); | |
| EXPECT_EQ(trie.find("/redfish").first, 1); | |
| EXPECT_EQ(trie.find("/redfish/v1").first, 2); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis").first, 3); | |
| } | |
| TEST(Trie, TrieTestReturnsRuleIndexSuccessfullyWithStringAndPathWildcard) { | |
| Trie trie; | |
| trie.add("/redfish", 1); | |
| trie.add("/redfish/v1", 2); | |
| trie.add("/redfish/v1/Chassis", 3); | |
| trie.add("/redfish/v1/Chassis/<str>", 4); | |
| trie.add("/redfish/<path>", 5); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis/test").first, 4); | |
| EXPECT_EQ(trie.find("/redfish/v1/random/path/here").first, 5); | |
| } | |
| #if !defined(PLATFORM_PLUGINS_ENABLED) && \ | |
| !defined(GOOGLE_COMMON_PLUGINS_ENABLED) && \ | |
| !defined(GOOGLE_PLUGINS_INTERNAL_ENABLED) | |
| TEST(Trie, TrieTestReturnsLowestIndexWhenUrlMatchesMultipleRules) { | |
| Trie trie; | |
| trie.add("/redfish", 1); | |
| trie.add("/redfish/<path>", 2); | |
| trie.add("/redfish/v1", 3); | |
| EXPECT_EQ(trie.find("/redfish/v1").first, 2); | |
| } | |
| #endif | |
| #if defined(PLATFORM_PLUGINS_ENABLED) || \ | |
| defined(GOOGLE_COMMON_PLUGINS_ENABLED) || \ | |
| defined(GOOGLE_PLUGINS_INTERNAL_ENABLED) | |
| TEST(Trie, TrieReturnsFixedPathBeforeWildCard) { | |
| Trie trie; | |
| trie.add("/redfish/<path>", 0); | |
| trie.add("/redfish", 1); | |
| trie.add("/redfish/v1", 2); | |
| trie.add("/redfish/v1/Chassis", 3); | |
| trie.add("/redfish/v1/Chassis/<str>", 4); | |
| trie.add("/redfish/v1/Chassis/test", 6); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis/test").first, 6); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis/test1").first, 4); | |
| EXPECT_EQ(trie.find("/redfish/v1/random/path/here").first, 0); | |
| } | |
| TEST(Trie, TrieReturnsFixedPathBeforeWildCardWhenAddedInReverseOrder) { | |
| Trie trie; | |
| trie.add("/redfish/<path>", 0); | |
| trie.add("/redfish", 1); | |
| trie.add("/redfish/v1", 2); | |
| trie.add("/redfish/v1/Chassis", 3); | |
| trie.add("/redfish/v1/Chassis/test", 4); | |
| trie.add("/redfish/v1/Chassis/<str>", 5); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis/test").first, 4); | |
| EXPECT_EQ(trie.find("/redfish/v1/Chassis/test1").first, 5); | |
| EXPECT_EQ(trie.find("/redfish/v1/random/path/here").first, 0); | |
| } | |
| #endif | |
| TEST(Router, isAccessAllowedFromInsecureTransport) { | |
| std::error_code ec; | |
| // ComputerSystem PATCH is allowed | |
| Request patch_system_req{ | |
| {boost::beast::http::verb::patch, "/redfish/v1/Systems/system", 11}, ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| patch_system_req, patch_system_req.url().encoded_path())); | |
| // ComputerSystem GET is allowed | |
| Request get_system_req{ | |
| {boost::beast::http::verb::get, "/redfish/v1/Systems/system", 11}, ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| get_system_req, get_system_req.url().encoded_path())); | |
| // BootOptionCollection with various methods is allowed | |
| Request get_boot_options_req{ | |
| {boost::beast::http::verb::get, | |
| "/redfish/v1/Systems/system/Boot/BootOptions", 11}, | |
| ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| get_boot_options_req, get_boot_options_req.url().encoded_path())); | |
| Request post_boot_options_req{ | |
| {boost::beast::http::verb::post, | |
| "/redfish/v1/Systems/system/Boot/BootOptions", 11}, | |
| ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| post_boot_options_req, post_boot_options_req.url().encoded_path())); | |
| // BootOption with various methods is allowed | |
| Request patch_boot_option_req{ | |
| {boost::beast::http::verb::patch, | |
| "/redfish/v1/Systems/system/Boot/BootOptions/1", 11}, | |
| ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| patch_boot_option_req, patch_boot_option_req.url().encoded_path())); | |
| Request delete_boot_option_req{ | |
| {boost::beast::http::verb::delete_, | |
| "/redfish/v1/Systems/system/Boot/BootOptions/1", 11}, | |
| ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| delete_boot_option_req, delete_boot_option_req.url().encoded_path())); | |
| // GET on any entity like Chassis is allowed | |
| Request get_chassis_req{ | |
| {boost::beast::http::verb::get, "/redfish/v1/Chassis/chassis", 11}, ec}; | |
| EXPECT_TRUE(Router::isAccessAllowedFromInsecureTransport( | |
| get_chassis_req, get_chassis_req.url().encoded_path())); | |
| // Non-GET/non-allowed methods on other entities like Chassis are disallowed | |
| Request patch_chassis_req{ | |
| {boost::beast::http::verb::patch, "/redfish/v1/Chassis/chassis", 11}, ec}; | |
| EXPECT_FALSE(Router::isAccessAllowedFromInsecureTransport( | |
| patch_chassis_req, patch_chassis_req.url().encoded_path())); | |
| } | |
| TEST(Router, needsAuthorizationWhenFromGrpcIsTrue) { | |
| std::error_code ec; | |
| Request grpc_req{ | |
| {boost::beast::http::verb::get, "/redfish/v1/Systems/system", 11}, ec}; | |
| grpc_req.fromGrpc = true; | |
| EXPECT_TRUE( | |
| Router::needsAuthorization(grpc_req, grpc_req.url().encoded_path())); | |
| Request grpc_patch_req{ | |
| {boost::beast::http::verb::patch, "/redfish/v1/Systems/system", 11}, ec}; | |
| grpc_patch_req.fromGrpc = true; | |
| EXPECT_TRUE(Router::needsAuthorization(grpc_patch_req, | |
| grpc_patch_req.url().encoded_path())); | |
| } | |
| TEST(Router, needsAuthorizationWhenFromGrpcIsFalse) { | |
| std::error_code ec; | |
| // Since restrictRedfishRoutesFromInsecureTransport is false, never needs | |
| // authorization | |
| Request req{ | |
| {boost::beast::http::verb::patch, "/redfish/v1/Chassis/chassis", 11}, ec}; | |
| req.fromGrpc = false; | |
| EXPECT_FALSE(Router::needsAuthorization(req, req.url().encoded_path())); | |
| } | |
| } // namespace | |
| } // namespace crow |