mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			215 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
    This file is part of clio: https://github.com/XRPLF/clio
 | 
						|
    Copyright (c) 2025, the clio developers.
 | 
						|
 | 
						|
    Permission to use, copy, modify, and distribute this software for any
 | 
						|
    purpose with or without fee is hereby granted, provided that the above
 | 
						|
    copyright notice and this permission notice appear in all copies.
 | 
						|
 | 
						|
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
						|
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
						|
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
						|
    ANY  SPECIAL,  DIRECT,  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
						|
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
						|
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
						|
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
						|
*/
 | 
						|
//==============================================================================
 | 
						|
 | 
						|
#include "util/NameGenerator.hpp"
 | 
						|
#include "util/config/Array.hpp"
 | 
						|
#include "util/config/ConfigDefinition.hpp"
 | 
						|
#include "util/config/ConfigFileJson.hpp"
 | 
						|
#include "util/config/ConfigValue.hpp"
 | 
						|
#include "util/config/Types.hpp"
 | 
						|
#include "web/ProxyIpResolver.hpp"
 | 
						|
 | 
						|
#include <boost/beast/http/field.hpp>
 | 
						|
#include <boost/json/parse.hpp>
 | 
						|
#include <fmt/format.h>
 | 
						|
#include <gtest/gtest.h>
 | 
						|
 | 
						|
#include <string>
 | 
						|
#include <unordered_set>
 | 
						|
#include <utility>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
namespace http = boost::beast::http;
 | 
						|
 | 
						|
using namespace web;
 | 
						|
 | 
						|
struct ProxyIpResolverTestParams {
 | 
						|
    std::string testName;
 | 
						|
    std::unordered_set<std::string> proxyIps;
 | 
						|
    std::unordered_set<std::string> proxyTokens;
 | 
						|
    std::vector<std::pair<std::string, std::string>> headers;
 | 
						|
    std::string connectionIp;
 | 
						|
    std::string expectedIp;
 | 
						|
};
 | 
						|
 | 
						|
class ProxyIpResolverTest : public ::testing::TestWithParam<ProxyIpResolverTestParams> {};
 | 
						|
 | 
						|
TEST_F(ProxyIpResolverTest, FromConfig)
 | 
						|
{
 | 
						|
    using namespace util::config;
 | 
						|
    ClioConfigDefinition config{{
 | 
						|
        {"server.proxy.ips.[]", Array{ConfigValue{ConfigType::String}}},
 | 
						|
        {"server.proxy.tokens.[]", Array{ConfigValue{ConfigType::String}}},
 | 
						|
    }};
 | 
						|
    auto const proxyIp = "1.2.3.4";
 | 
						|
    auto const clientIp = "5.6.7.8";
 | 
						|
    auto const proxyToken = "some_proxy_token";
 | 
						|
 | 
						|
    auto const configStr = fmt::format(
 | 
						|
        R"({{
 | 
						|
        "server": {{
 | 
						|
            "proxy": {{
 | 
						|
                "ips": ["{}"],
 | 
						|
                "tokens": ["{}"]
 | 
						|
            }}
 | 
						|
        }}
 | 
						|
    }})",
 | 
						|
        proxyIp,
 | 
						|
        proxyToken
 | 
						|
    );
 | 
						|
 | 
						|
    auto const err = config.parse(ConfigFileJson{boost::json::parse(configStr).as_object()});
 | 
						|
    ASSERT_FALSE(err.has_value());
 | 
						|
 | 
						|
    auto const proxyIpResolver = ProxyIpResolver::fromConfig(config);
 | 
						|
    ProxyIpResolver::HttpHeaders headers;
 | 
						|
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), clientIp);
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), proxyIp);
 | 
						|
 | 
						|
    headers.set(boost::beast::http::field::forwarded, fmt::format("for={}", clientIp));
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), clientIp);
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), clientIp);
 | 
						|
 | 
						|
    headers.set(ProxyIpResolver::kPROXY_TOKEN_HEADER, proxyToken);
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), clientIp);
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), clientIp);
 | 
						|
    EXPECT_EQ(proxyIpResolver.resolveClientIp("127.0.0.1", headers), clientIp);
 | 
						|
}
 | 
						|
 | 
						|
TEST_P(ProxyIpResolverTest, ResolveClientIp)
 | 
						|
{
 | 
						|
    auto const& params = GetParam();
 | 
						|
    ProxyIpResolver resolver(params.proxyIps, params.proxyTokens);
 | 
						|
    ProxyIpResolver::HttpHeaders headers;
 | 
						|
    for (auto const& [key, value] : params.headers) {
 | 
						|
        headers.set(key, value);
 | 
						|
    }
 | 
						|
    EXPECT_EQ(resolver.resolveClientIp(params.connectionIp, headers), params.expectedIp);
 | 
						|
}
 | 
						|
 | 
						|
INSTANTIATE_TEST_SUITE_P(
 | 
						|
    ProxyIpResolverTests,
 | 
						|
    ProxyIpResolverTest,
 | 
						|
    ::testing::Values(
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "NoProxy",
 | 
						|
            .proxyIps = {},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {},
 | 
						|
            .connectionIp = "1.2.3.4",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "TrustedProxyIpWithForwardedHeader",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "TrustedProxyIpWithoutForwardedHeader",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "UntrustedProxyIpWithForwardedHeader",
 | 
						|
            .proxyIps = {},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "TrustedProxyTokenWithForwardedHeader",
 | 
						|
            .proxyIps = {},
 | 
						|
            .proxyTokens = {"test_token"},
 | 
						|
            .headers =
 | 
						|
                {{std::string(ProxyIpResolver::kPROXY_TOKEN_HEADER), "test_token"},
 | 
						|
                 {std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "TrustedProxyTokenWithoutForwardedHeader",
 | 
						|
            .proxyIps = {},
 | 
						|
            .proxyTokens = {"test_token"},
 | 
						|
            .headers = {{std::string(ProxyIpResolver::kPROXY_TOKEN_HEADER), "test_token"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "UntrustedProxyTokenWithForwardedHeader",
 | 
						|
            .proxyIps = {},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers =
 | 
						|
                {{std::string(ProxyIpResolver::kPROXY_TOKEN_HEADER), "test_token"},
 | 
						|
                 {std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "ForwardedHeaderWithAdditionalFields",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers =
 | 
						|
                {{std::string(http::to_string(http::field::forwarded)),
 | 
						|
                  "by=203.0.113.43; for=1.2.3.4; host=example.com; proto=https"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "ForwardedHeaderWithDifferentCase",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "For=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "ForwardedHeaderWithoutFor",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "by=1.2.3.4"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "ForwardedHeaderWithIpInQuotes",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "for=\"1.2.3.4\""}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "1.2.3.4"
 | 
						|
        },
 | 
						|
        ProxyIpResolverTestParams{
 | 
						|
            .testName = "ForwardedHeaderIsIncorrect",
 | 
						|
            .proxyIps = {"5.6.7.8"},
 | 
						|
            .proxyTokens = {},
 | 
						|
            .headers = {{std::string(http::to_string(http::field::forwarded)), "for=\";some_other_text"}},
 | 
						|
            .connectionIp = "5.6.7.8",
 | 
						|
            .expectedIp = "5.6.7.8"
 | 
						|
        }
 | 
						|
    ),
 | 
						|
    tests::util::kNAME_GENERATOR
 | 
						|
);
 |