Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions include/glaze/net/cors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,16 @@ namespace glz
}
}

// If no specific origins are configured or "*" is present, allow all
if ((config.allowed_origins.empty() && !config.allowed_origins_validator) ||
std::find(config.allowed_origins.begin(), config.allowed_origins.end(), "*") !=
config.allowed_origins.end()) {
// A wildcard ("*") or an unset origin list grants every origin, but only when
// credentials are disabled. The CORS spec forbids "*" with credentials, and the
// middleware reflects the request origin when credentials are on, so honoring the
// wildcard there would echo any origin back next to
// Access-Control-Allow-Credentials: true and let any site read authenticated
// responses. With credentials enabled, fall through to the exact-match check.
const bool wildcard =
std::find(config.allowed_origins.begin(), config.allowed_origins.end(), "*") != config.allowed_origins.end();
if (!config.allow_credentials &&
((config.allowed_origins.empty() && !config.allowed_origins_validator) || wildcard)) {
return true;
}

Expand Down
1 change: 1 addition & 0 deletions tests/networking_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_subdirectory(http_server_post_test)
add_subdirectory(asio_repe)
add_subdirectory(http_router_test)
add_subdirectory(url_test)
add_subdirectory(cors_test)
add_subdirectory(http_chunked_test)
add_subdirectory(http_client_test)
add_subdirectory(http_client_pool_test)
Expand Down
7 changes: 7 additions & 0 deletions tests/networking_tests/cors_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
project(cors_test)

add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp)

target_link_libraries(${PROJECT_NAME} PRIVATE glz_test_exceptions)

add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
76 changes: 76 additions & 0 deletions tests/networking_tests/cors_test/cors_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Glaze Library
// For the license information refer to glaze.hpp

#include "glaze/net/cors.hpp"

#include "ut/ut.hpp"

using namespace ut;

namespace
{
glz::response run_cors(const glz::cors_config& config, std::string_view origin)
{
auto middleware = glz::create_cors_middleware(config);
glz::request req{};
req.method = glz::http_method::GET;
req.headers["origin"] = std::string(origin);
glz::response res{};
middleware(req, res);
return res;
}
}

suite cors_origin_tests = [] {
"wildcard_without_credentials_allows_any"_test = [] {
glz::cors_config config;
config.allowed_origins = {"*"};
config.allow_credentials = false;
expect(glz::is_origin_allowed(config, "https://evil.example"));

auto res = run_cors(config, "https://evil.example");
expect(res.response_headers["access-control-allow-origin"] == "*");
expect(res.response_headers.count("access-control-allow-credentials") == 0);
};

"wildcard_with_credentials_rejects_unlisted_origin"_test = [] {
glz::cors_config config;
config.allowed_origins = {"*"};
config.allow_credentials = true;
expect(not glz::is_origin_allowed(config, "https://evil.example"));

auto res = run_cors(config, "https://evil.example");
expect(res.response_headers.count("access-control-allow-origin") == 0);
expect(res.response_headers.count("access-control-allow-credentials") == 0);
};

"credentials_allow_exact_listed_origin"_test = [] {
glz::cors_config config;
config.allowed_origins = {"https://app.example", "*"};
config.allow_credentials = true;
expect(glz::is_origin_allowed(config, "https://app.example"));
expect(not glz::is_origin_allowed(config, "https://evil.example"));

auto res = run_cors(config, "https://app.example");
expect(res.response_headers["access-control-allow-origin"] == "https://app.example");
expect(res.response_headers["access-control-allow-credentials"] == "true");
};

"empty_origins_with_credentials_rejects"_test = [] {
glz::cors_config config;
config.allowed_origins = {};
config.allow_credentials = true;
expect(not glz::is_origin_allowed(config, "https://evil.example"));
};

"validator_still_grants_with_credentials"_test = [] {
glz::cors_config config;
config.allowed_origins = {};
config.allow_credentials = true;
config.allowed_origins_validator = [](std::string_view o) { return o == "https://trusted.example"; };
expect(glz::is_origin_allowed(config, "https://trusted.example"));
expect(not glz::is_origin_allowed(config, "https://evil.example"));
};
};

int main() {}
23 changes: 13 additions & 10 deletions tests/networking_tests/http_client_test/http_client_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,30 +659,33 @@ suite working_http_tests = [] {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
};

"cors_wildcard_with_credentials_echoes_origin"_test = [] {
"cors_wildcard_with_credentials_rejects_origin"_test = [] {
// A wildcard origin list with credentials enabled must not reflect the request
// origin. Echoing the origin next to Access-Control-Allow-Credentials: true would
// let any site read authenticated cross-origin responses, so an unlisted origin is
// rejected and no Allow-Origin/Allow-Credentials headers are emitted.
glz::cors_config config;
config.allowed_origins = {"*"};
config.allow_credentials = true;
config.allowed_methods = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"};

working_test_server server;
server.set_cors_config(config);
expect(server.start()) << "Server should start\n";

simple_test_client client;
auto result = client.options(server.base_url() + "/hello",
{{"Origin", "http://auth.local"}, {"Access-Control-Request-Method", "POST"}});
{{"Origin", "http://auth.local"}, {"Access-Control-Request-Method", "GET"}});

expect(result.has_value());
if (result.has_value()) {
auto origin_it = result->response_headers.find("access-control-allow-origin");
expect(origin_it != result->response_headers.end());
if (origin_it != result->response_headers.end()) {
expect(origin_it->second == "http://auth.local");
}
expect(result->status_code == 403)
<< "Wildcard + credentials should reject an unlisted origin (got " << result->status_code << ")\n";

auto credentials_it = result->response_headers.find("access-control-allow-credentials");
expect(credentials_it != result->response_headers.end());
expect(result->response_headers.find("access-control-allow-origin") == result->response_headers.end())
<< "Allow-Origin must not be echoed for a wildcard + credentials config\n";

expect(result->response_headers.find("access-control-allow-credentials") == result->response_headers.end())
<< "Allow-Credentials must not be sent for a rejected origin\n";
}

server.stop();
Expand Down
Loading