test.cpp (14030B)
1 #include <iostream> 2 3 #include "matrix_coro.hpp" 4 #include "json.hpp" 5 #include <catch2/catch_test_macros.hpp> 6 #include "spdlog/spdlog.h" 7 8 #include "cppcoro/sync_wait.hpp" 9 10 class ClientTest 11 { 12 public: 13 static cppcoro::task<WellKnownResponse> test_fetch_wellknown(Client& client, const std::string& homeserver) 14 { 15 return client.fetch_wellknown(homeserver); 16 } 17 18 static cppcoro::task<AuthIssuerResponse> test_fetch_auth_issuer(Client& client, 19 const std::string& cs_endpoint) 20 { 21 return client.fetch_auth_issuer(cs_endpoint); 22 } 23 24 static cppcoro::task<ClientRegistrationResponse> test_register_client(Client& client, 25 const std::string& registration_endpoint, 26 const ClientRegistrationData& 27 registration_data) 28 { 29 return client.register_client(registration_endpoint, registration_data); 30 } 31 32 static std::string test_generate_authorize_url(Client& client, 33 const std::string& auth_endpoint, 34 const ClientRegistrationResponse& auth_data, 35 const std::string& redirect_url, 36 const std::string& state, 37 const std::string& code_verifier) 38 { 39 return client.generate_authorize_url(auth_endpoint, auth_data, redirect_url, state, code_verifier); 40 } 41 42 static cppcoro::task<OpenIDConfiguration> fetch_openid_configuration(Client& client, 43 const std::string& auth_endpoint) 44 { 45 return client.fetch_openid_configuration(auth_endpoint); 46 } 47 48 static cppcoro::task<TokenResponse> exchange_code_for_token(const Client& client, 49 const std::string& token_endpoint, 50 const std::string& client_id, 51 const std::string& code, 52 const std::string& redirect_uri, 53 const std::string& code_verifier) 54 { 55 return client.exchange_code_for_token(token_endpoint, code, code_verifier, client_id, redirect_uri); 56 } 57 }; 58 59 void initLogging() 60 { 61 spdlog::set_level(spdlog::level::debug); 62 spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v"); 63 } 64 65 66 SCENARIO("fetch_wellknown can find and parse well-known at matrix.org") 67 { 68 initLogging(); 69 GIVEN("A Client instance") 70 { 71 WHEN("fetch_wellknown is called with matrix.org") 72 { 73 Client client; 74 auto task = ClientTest::test_fetch_wellknown(client, "matrix.org"); 75 auto [homeserver, identity_server, raw] = sync_wait(task); 76 THEN("A valid WellKnownResponse should be returned") 77 { 78 REQUIRE(homeserver == "https://matrix-client.matrix.org"); 79 REQUIRE(identity_server == "https://vector.im"); 80 } 81 } 82 } 83 } 84 85 SCENARIO("fetch_wellknown throws runtime_error if curl_easy_perform fails") 86 { 87 initLogging(); 88 GIVEN("A Client instance with an invalid URL") 89 { 90 WHEN("fetch_wellknown is called with an invalid URL") 91 { 92 THEN("A runtime_error should be thrown") 93 { 94 Client client; 95 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_fetch_wellknown(client,"invalid_url")), 96 std::runtime_error); 97 } 98 } 99 } 100 } 101 102 SCENARIO("fetch_wellknown throws runtime_error if JSON parsing fails") 103 { 104 initLogging(); 105 GIVEN("A Client instance with a URL returning invalid JSON") 106 { 107 WHEN("fetch_wellknown is called with a URL returning invalid JSON") 108 { 109 THEN("A runtime_error should be thrown") 110 { 111 Client client; 112 REQUIRE_THROWS_AS( 113 sync_wait(ClientTest::test_fetch_wellknown(client,"https://example.com/invalid-json")), 114 std::runtime_error); 115 } 116 } 117 } 118 } 119 120 SCENARIO("fetch_auth_issuer can find and parse auth issuer at https://synapse-oidc.element.dev") 121 { 122 initLogging(); 123 GIVEN("A Client instance") 124 { 125 WHEN("fetch_auth_issuer is called with https://synapse-oidc.element.dev") 126 { 127 Client client; 128 auto task = ClientTest::test_fetch_auth_issuer(client, "https://synapse-oidc.element.dev"); 129 auto [issuer] = sync_wait(task); 130 THEN("A valid AuthIssuerResponse should be returned") 131 { 132 REQUIRE(issuer == "https://auth-oidc.element.dev/"); 133 } 134 } 135 } 136 } 137 138 SCENARIO("fetch_auth_issuer throws runtime_error if curl_easy_perform fails") 139 { 140 initLogging(); 141 GIVEN("A Client instance with an invalid URL") 142 { 143 WHEN("fetch_auth_issuer is called with an invalid URL") 144 { 145 THEN("A runtime_error should be thrown") 146 { 147 Client client; 148 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_fetch_auth_issuer(client,"invalid_url")), 149 std::runtime_error); 150 } 151 } 152 } 153 } 154 155 SCENARIO("fetch_auth_issuer throws runtime_error if JSON parsing fails") 156 { 157 initLogging(); 158 GIVEN("A Client instance with a URL returning invalid JSON") 159 { 160 WHEN("fetch_auth_issuer is called with a URL returning invalid JSON") 161 { 162 THEN("A runtime_error should be thrown") 163 { 164 Client client; 165 REQUIRE_THROWS_AS( 166 sync_wait(ClientTest::test_fetch_auth_issuer(client,"https://example.com/invalid-json")), 167 std::runtime_error); 168 } 169 } 170 } 171 } 172 173 SCENARIO("register_client can register a client at https://synapse-oidc.element.dev") 174 { 175 initLogging(); 176 GIVEN("A Client instance") 177 { 178 WHEN("register_client is called with https://synapse-oidc.element.dev") 179 { 180 Client client; 181 ClientRegistrationData registration_data; 182 registration_data.application_type = "web"; 183 registration_data.client_name = "Test Client"; 184 registration_data.client_uri = "https://example.com"; 185 registration_data.token_endpoint_auth_method = "none"; 186 registration_data.redirect_uris = {"https://example.com"}; 187 registration_data.response_types = {"code"}; 188 registration_data.grant_types = {"authorization_code", "refresh_token"}; 189 registration_data.contacts = {"mailto:hello@example.com"}; 190 auto task = ClientTest::test_register_client(client, "https://auth-oidc.element.dev/oauth2/registration", 191 registration_data); 192 auto [client_id, client_id_issued_at] = sync_wait(task); 193 THEN("A valid ClientRegistrationResponse should be returned") 194 { 195 REQUIRE(!client_id.empty()); 196 REQUIRE(client_id_issued_at > 0); 197 } 198 } 199 } 200 } 201 202 SCENARIO("register_client throws runtime_error if curl_easy_perform fails") 203 { 204 initLogging(); 205 GIVEN("A Client instance with an invalid URL") 206 { 207 WHEN("register_client is called with an invalid URL") 208 { 209 THEN("A runtime_error should be thrown") 210 { 211 Client client; 212 ClientRegistrationData registration_data; 213 registration_data.application_type = "web"; 214 registration_data.client_name = "Test Client"; 215 registration_data.client_uri = "https://example.com"; 216 registration_data.token_endpoint_auth_method = "none"; 217 registration_data.redirect_uris = {"https://example.com"}; 218 registration_data.response_types = {"code"}; 219 registration_data.grant_types = {"authorization_code", "refresh_token"}; 220 registration_data.contacts = {"mailto:hello@example.com"}; 221 REQUIRE_THROWS_AS(sync_wait(ClientTest::test_register_client(client,"invalid_url", registration_data)), 222 std::runtime_error); 223 } 224 } 225 } 226 } 227 228 SCENARIO("register_client throws runtime_error if JSON parsing fails") 229 { 230 initLogging(); 231 GIVEN("A Client instance with a URL returning invalid JSON") 232 { 233 WHEN("register_client is called with a URL returning invalid JSON") 234 { 235 THEN("A runtime_error should be thrown") 236 { 237 Client client; 238 ClientRegistrationData registration_data; 239 registration_data.application_type = "web"; 240 registration_data.client_name = "Test Client"; 241 registration_data.client_uri = "https://example.com"; 242 registration_data.token_endpoint_auth_method = "none"; 243 registration_data.redirect_uris = {"https://example.com"}; 244 registration_data.response_types = {"code"}; 245 registration_data.grant_types = {"authorization_code", "refresh_token"}; 246 registration_data.contacts = {"mailto:hello@example.com"}; 247 REQUIRE_THROWS_AS( 248 sync_wait(ClientTest::test_register_client(client,"https://example.com/invalid-json", 249 registration_data)), 250 std::runtime_error); 251 } 252 } 253 } 254 } 255 256 SCENARIO("generate_authorize_url can generate a valid authorize URL") 257 { 258 initLogging(); 259 GIVEN("A Client") 260 { 261 WHEN("generate_authorize_url is called") 262 { 263 Client client; 264 ClientRegistrationResponse auth_data; 265 auth_data.client_id = "test_client_id"; 266 auth_data.client_id_issued_at = 1630000000; 267 const std::string redirect_url = "https://example.com"; 268 const std::string state = "test_state"; 269 const std::string code_verifier = "test_code_verifier"; 270 auto authorize_url = ClientTest::test_generate_authorize_url( 271 client, "https://auth-oidc.element.dev/authorize", 272 auth_data, redirect_url, state, code_verifier); 273 THEN("A valid authorize URL should be returned") 274 { 275 REQUIRE( 276 authorize_url == 277 "https://auth-oidc.element.dev/authorize?response_type=code&response_mode=fragment&client_id=test_client_id&redirect_uri=https%3A%2F%2Fexample.com&scope=urn%3Amatrix%3Aorg.matrix.msc2967.client%3Aapi%3A*%20urn%3Amatrix%3Aorg.matrix.msc2967.client%3Adevice%3AABCDEFGHIJKL&state=test_state&code_challenge_method=S256&code_challenge=" 278 + (cthash::base64url_encode(cthash::simple<cthash::sha256>(code_verifier)).to_string())); 279 } 280 } 281 } 282 } 283 284 // Fetch openid configuration 285 SCENARIO("fetch_openid_configuration can find and parse openid configuration at https://auth-oidc.element.dev") 286 { 287 initLogging(); 288 GIVEN("A Client instance") 289 { 290 WHEN("fetch_openid_configuration is called with https://auth-oidc.element.dev") 291 { 292 Client client; 293 auto task = ClientTest::fetch_openid_configuration(client, "https://auth-oidc.element.dev"); 294 auto resp = sync_wait(task); 295 THEN("A valid OpenIDConfiguration should be returned") 296 { 297 REQUIRE(resp.issuer == "https://auth-oidc.element.dev/"); 298 REQUIRE(resp.authorization_endpoint == "https://auth-oidc.element.dev/authorize"); 299 REQUIRE(resp.token_endpoint == "https://auth-oidc.element.dev/oauth2/token"); 300 REQUIRE(resp.jwks_uri == "https://auth-oidc.element.dev/oauth2/keys.json"); 301 REQUIRE(resp.registration_endpoint == "https://auth-oidc.element.dev/oauth2/registration"); 302 REQUIRE(resp.revocation_endpoint == "https://auth-oidc.element.dev/oauth2/revoke"); 303 REQUIRE(resp.introspection_endpoint == "https://auth-oidc.element.dev/oauth2/introspect"); 304 REQUIRE(resp.userinfo_endpoint == "https://auth-oidc.element.dev/oauth2/userinfo"); 305 REQUIRE(resp.device_authorization_endpoint == "https://auth-oidc.element.dev/oauth2/device"); 306 REQUIRE(resp.account_management_uri == "https://auth-oidc.element.dev/account/"); 307 } 308 } 309 } 310 } 311 312 SCENARIO("fetch_openid_configuration throws runtime_error if curl_easy_perform fails") 313 { 314 initLogging(); 315 GIVEN("A Client instance with an invalid URL") 316 { 317 WHEN("fetch_openid_configuration is called with an invalid URL") 318 { 319 THEN("A runtime_error should be thrown") 320 { 321 Client client; 322 REQUIRE_THROWS_AS(sync_wait(ClientTest::fetch_openid_configuration(client,"invalid_url")), 323 std::runtime_error); 324 } 325 } 326 } 327 } 328 329 SCENARIO("fetch_openid_configuration throws runtime_error if JSON parsing fails") 330 { 331 initLogging(); 332 GIVEN("A Client instance with a URL returning invalid JSON") 333 { 334 WHEN("fetch_openid_configuration is called with a URL returning invalid JSON") 335 { 336 THEN("A runtime_error should be thrown") 337 { 338 Client client; 339 REQUIRE_THROWS_AS( 340 sync_wait(ClientTest::fetch_openid_configuration(client,"https://example.com/invalid-json")), 341 std::runtime_error); 342 } 343 } 344 } 345 }