@@ -2174,6 +2174,110 @@ TEST(provider_endpoints_are_retrieved_once_and_reused) {
21742174 EXPECT_EQ (*calls, 1 );
21752175}
21762176
2177+ TEST (a_provider_naming_no_authentication_method_takes_the_header) {
2178+ const std::array<std::string_view, 1 > paths{{" /portal" }};
2179+ const std::array<sourcemeta::one::Authentication::Policy, 1 > policies{
2180+ {{.paths = paths,
2181+ .type = sourcemeta::one::Authentication::Type::OIDC ,
2182+ .issuer = " https://login.test" ,
2183+ .client_id = " client" ,
2184+ .client_secret_variable = " ONE_TEST_OIDC_AUTH_ABSENT" ,
2185+ .name = " okta" ,
2186+ .session_secret_variable = SESSION_SECRET_VARIABLE }}};
2187+ const auto path{test_path (" oidc_auth_absent.bin" )};
2188+ sourcemeta::one::Authentication::save (policies, path, path, anywhere);
2189+
2190+ const std::map<std::string, std::string> responses{
2191+ {" https://login.test/.well-known/openid-configuration" ,
2192+ R"JSON( {
2193+ "issuer": "https://login.test",
2194+ "authorization_endpoint": "https://login.test/authorize",
2195+ "token_endpoint": "https://login.test/token",
2196+ "jwks_uri": "https://login.test/jwks",
2197+
2198+ "response_types_supported": [ "code" ],
2199+ "subject_types_supported": [ "public" ],
2200+ "id_token_signing_alg_values_supported": [ "RS256" ]
2201+ })JSON" }};
2202+ const sourcemeta::one::Authentication authentication{
2203+ path, stub_fetcher (responses, nullptr )};
2204+
2205+ const auto endpoints{authentication.endpoints (" okta" )};
2206+ EXPECT_TRUE (endpoints.has_value ());
2207+ // RFC 8414 Section 2 makes an absent list mean `client_secret_basic`, so
2208+ // saying nothing is an answer rather than the absence of one
2209+ EXPECT_TRUE (endpoints.value ().token_endpoint_basic_auth );
2210+ }
2211+
2212+ TEST (a_provider_naming_the_header_takes_the_header) {
2213+ const std::array<std::string_view, 1 > paths{{" /portal" }};
2214+ const std::array<sourcemeta::one::Authentication::Policy, 1 > policies{
2215+ {{.paths = paths,
2216+ .type = sourcemeta::one::Authentication::Type::OIDC ,
2217+ .issuer = " https://login.test" ,
2218+ .client_id = " client" ,
2219+ .client_secret_variable = " ONE_TEST_OIDC_AUTH_BASIC" ,
2220+ .name = " okta" ,
2221+ .session_secret_variable = SESSION_SECRET_VARIABLE }}};
2222+ const auto path{test_path (" oidc_auth_basic.bin" )};
2223+ sourcemeta::one::Authentication::save (policies, path, path, anywhere);
2224+
2225+ const std::map<std::string, std::string> responses{
2226+ {" https://login.test/.well-known/openid-configuration" ,
2227+ R"JSON( {
2228+ "issuer": "https://login.test",
2229+ "authorization_endpoint": "https://login.test/authorize",
2230+ "token_endpoint": "https://login.test/token",
2231+ "jwks_uri": "https://login.test/jwks",
2232+ "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ],
2233+ "response_types_supported": [ "code" ],
2234+ "subject_types_supported": [ "public" ],
2235+ "id_token_signing_alg_values_supported": [ "RS256" ]
2236+ })JSON" }};
2237+ const sourcemeta::one::Authentication authentication{
2238+ path, stub_fetcher (responses, nullptr )};
2239+
2240+ const auto endpoints{authentication.endpoints (" okta" )};
2241+ EXPECT_TRUE (endpoints.has_value ());
2242+ // Offering both, the header is the one RFC 6749 Section 2.3.1 asks for
2243+ EXPECT_TRUE (endpoints.value ().token_endpoint_basic_auth );
2244+ }
2245+
2246+ TEST (a_provider_refusing_the_header_gets_the_body_instead) {
2247+ const std::array<std::string_view, 1 > paths{{" /portal" }};
2248+ const std::array<sourcemeta::one::Authentication::Policy, 1 > policies{
2249+ {{.paths = paths,
2250+ .type = sourcemeta::one::Authentication::Type::OIDC ,
2251+ .issuer = " https://login.test" ,
2252+ .client_id = " client" ,
2253+ .client_secret_variable = " ONE_TEST_OIDC_AUTH_POST" ,
2254+ .name = " okta" ,
2255+ .session_secret_variable = SESSION_SECRET_VARIABLE }}};
2256+ const auto path{test_path (" oidc_auth_post.bin" )};
2257+ sourcemeta::one::Authentication::save (policies, path, path, anywhere);
2258+
2259+ const std::map<std::string, std::string> responses{
2260+ {" https://login.test/.well-known/openid-configuration" ,
2261+ R"JSON( {
2262+ "issuer": "https://login.test",
2263+ "authorization_endpoint": "https://login.test/authorize",
2264+ "token_endpoint": "https://login.test/token",
2265+ "jwks_uri": "https://login.test/jwks",
2266+ "token_endpoint_auth_methods_supported": [ "client_secret_post" ],
2267+ "response_types_supported": [ "code" ],
2268+ "subject_types_supported": [ "public" ],
2269+ "id_token_signing_alg_values_supported": [ "RS256" ]
2270+ })JSON" }};
2271+ const sourcemeta::one::Authentication authentication{
2272+ path, stub_fetcher (responses, nullptr )};
2273+
2274+ const auto endpoints{authentication.endpoints (" okta" )};
2275+ EXPECT_TRUE (endpoints.has_value ());
2276+ // A provider that does not take the header leaves the body as the only way
2277+ // to authenticate, so the preference gives way rather than the login failing
2278+ EXPECT_FALSE (endpoints.value ().token_endpoint_basic_auth );
2279+ }
2280+
21772281TEST (provider_endpoints_of_an_unreachable_provider_are_absent) {
21782282 const auto calls{std::make_shared<int >(0 )};
21792283 const std::array<std::string_view, 1 > paths{{" /portal" }};
0 commit comments