This is not a WordPress site.
+ + diff --git a/test-data/login-mocks/homepage-with-link-tag.html b/test-data/login-mocks/homepage-with-link-tag.html new file mode 100644 index 000000000..1d1aabd9c --- /dev/null +++ b/test-data/login-mocks/homepage-with-link-tag.html @@ -0,0 +1,12 @@ + + + + + + + + + +WordPress site with CDN stripping Link headers
+ + diff --git a/test-data/login-mocks/homepage-with-subdirectory-link-tag.html b/test-data/login-mocks/homepage-with-subdirectory-link-tag.html new file mode 100644 index 000000000..80ec3096f --- /dev/null +++ b/test-data/login-mocks/homepage-with-subdirectory-link-tag.html @@ -0,0 +1,12 @@ + + + + + + + + + +WordPress installed in a subdirectory
+ + diff --git a/test-data/login-mocks/http-only-api-root.json b/test-data/login-mocks/http-only-api-root.json new file mode 100644 index 000000000..8c0e886c1 --- /dev/null +++ b/test-data/login-mocks/http-only-api-root.json @@ -0,0 +1,18 @@ +{ + "name": "HTTP Only Site", + "description": "", + "url": "http://no-https.wpmt.co", + "home": "http://no-https.wpmt.co", + "gmt_offset": 0, + "timezone_string": "UTC", + "namespaces": [ + "oembed/1.0", + "wp/v2", + "wp-site-health/v1" + ], + "authentication": {}, + "routes": {}, + "site_logo": 0, + "site_icon": 0, + "site_icon_url": "" +} diff --git a/test-data/login-mocks/http-only-with-app-passwords-api-root.json b/test-data/login-mocks/http-only-with-app-passwords-api-root.json new file mode 100644 index 000000000..90ece1c88 --- /dev/null +++ b/test-data/login-mocks/http-only-with-app-passwords-api-root.json @@ -0,0 +1,24 @@ +{ + "name": "HTTP Site with App Passwords", + "description": "", + "url": "http://no-https-with-application-passwords.wpmt.co", + "home": "http://no-https-with-application-passwords.wpmt.co", + "gmt_offset": 0, + "timezone_string": "UTC", + "namespaces": [ + "oembed/1.0", + "wp/v2", + "wp-site-health/v1" + ], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "http://no-https-with-application-passwords.wpmt.co/wp-admin/authorize-application.php" + } + } + }, + "routes": {}, + "site_logo": 0, + "site_icon": 0, + "site_icon_url": "" +} diff --git a/test-data/login-mocks/subdirectory-api-root.json b/test-data/login-mocks/subdirectory-api-root.json new file mode 100644 index 000000000..c346872c3 --- /dev/null +++ b/test-data/login-mocks/subdirectory-api-root.json @@ -0,0 +1,24 @@ +{ + "name": "Subdirectory Site", + "description": "", + "url": "https://subdirectory.wpmt.co/wordpress", + "home": "https://subdirectory.wpmt.co", + "gmt_offset": 0, + "timezone_string": "UTC", + "namespaces": [ + "oembed/1.0", + "wp/v2", + "wp-site-health/v1" + ], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "https://subdirectory.wpmt.co/wordpress/wp-admin/authorize-application.php" + } + } + }, + "routes": {}, + "site_logo": 0, + "site_icon": 0, + "site_icon_url": "" +} diff --git a/test-data/login-mocks/vanilla-api-root.json b/test-data/login-mocks/vanilla-api-root.json new file mode 100644 index 000000000..e8f2d0bc1 --- /dev/null +++ b/test-data/login-mocks/vanilla-api-root.json @@ -0,0 +1,24 @@ +{ + "name": "Test Site", + "description": "", + "url": "https://vanilla.wpmt.co", + "home": "https://vanilla.wpmt.co", + "gmt_offset": 0, + "timezone_string": "UTC", + "namespaces": [ + "oembed/1.0", + "wp/v2", + "wp-site-health/v1" + ], + "authentication": { + "application-passwords": { + "endpoints": { + "authorization": "https://vanilla.wpmt.co/wp-admin/authorize-application.php" + } + } + }, + "routes": {}, + "site_logo": 0, + "site_icon": 0, + "site_icon_url": "" +} diff --git a/test-data/ssl-certs/ca-cert.pem b/test-data/ssl-certs/ca-cert.pem new file mode 100644 index 000000000..26121524b --- /dev/null +++ b/test-data/ssl-certs/ca-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIUVq8ghMmnTdmRz/vPM6x88G1t6jIwDQYJKoZIhvcNAQEL +BQAwHzEdMBsGA1UEAwwUV29yZFByZXNzIFJTIFRlc3QgQ0EwHhcNMjYwMjI4MDAx +ODQyWhcNMzYwMjI2MDAxODQyWjAfMR0wGwYDVQQDDBRXb3JkUHJlc3MgUlMgVGVz +dCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlsYT0kksBzrqwj +89hljWGeAgrmlycF3UuzIOFYECWRaTzSrzeaj2ChejSHF6/yTYZW+pFjgLBZrmSd +HGPZ0+F8BWifoiHBDE+L0BrFudF8ry8pwFSxVdfwXVeUGeFolMVYW+s2l1XOto+V +/VXLnCk4uk5P+Cd5Q9wH4jqgRK8gsVXaUsYAovErUwGGdOiaVFRKFa3JnMkCrD9U +I6aa9txBAvbqP4gPoLzLX+v3lSeGke1aLmBMJLWN7OXUez63VT29bUBEsn7BM1sh +BiVTAMYWpCN5b+uak7QRd0YkZ7f2Do3eMSN+Eh3YEFtmfCT/sCnaIZax78mphuRa +zSd5WFECAwEAAaNTMFEwHQYDVR0OBBYEFOi+nEnqdvJK5DHVYl3ZgzTf+wfeMB8G +A1UdIwQYMBaAFOi+nEnqdvJK5DHVYl3ZgzTf+wfeMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBADcTxKtnHFmbhOg92/cEu77SmxuVrfxGnLwtegJt +ctzzOL2mRBd6FD8Vko4lH7y8PG92tRDbrvexlJ4NUA79GKVrMBqenz/w69WNlAXf +ySonOnUp4rUIzGevMYHrhD6HR9txlY2f89mKu0GLMKFTZ3dBbnqX60jTIUDlQa2x +oLrAekY5AAEb9M60qrh2f28KIiXE0uI+mNA/T1pGc/8oOwaIzsi0FZjylIg7pbYi +PQqtKvLepTnt7lTyJdpZC+va14srB3cnm69PXPQe6Y2geVMNzISv6BSzj9dWGI0f +iY2GIB14hxtPMMEztlY2D1x2/JhEdLejXXDc19B3rhI1fZM= +-----END CERTIFICATE----- diff --git a/test-data/ssl-certs/san-test.p12 b/test-data/ssl-certs/san-test.p12 new file mode 100644 index 000000000..14432764d Binary files /dev/null and b/test-data/ssl-certs/san-test.p12 differ diff --git a/test-data/ssl-certs/wrong-host.p12 b/test-data/ssl-certs/wrong-host.p12 new file mode 100644 index 000000000..f4bebafe7 Binary files /dev/null and b/test-data/ssl-certs/wrong-host.p12 differ diff --git a/wp_api_integration_tests/src/mock.rs b/wp_api_integration_tests/src/mock.rs index d87bf4d76..e3d1f9e86 100644 --- a/wp_api_integration_tests/src/mock.rs +++ b/wp_api_integration_tests/src/mock.rs @@ -68,6 +68,14 @@ pub mod response_helpers { json_response_from_path(&json_file_path) } + pub fn json_response_from_login_mocks(file_name: &str) -> WpNetworkResponse { + let mut json_file_path = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); + json_file_path.push("test-data"); + json_file_path.push("login-mocks"); + json_file_path.push(file_name); + json_response_from_path(&json_file_path) + } + pub fn json_response_from_path(json_file_path: &PathBuf) -> WpNetworkResponse { let json = fs::read_to_string(json_file_path).unwrap_or_else(|_| { panic!("Should have been able to read the json file at: '{json_file_path:#?}'") @@ -111,4 +119,39 @@ pub mod response_helpers { request_header_map: WpNetworkHeaderMap::default().into(), } } + + pub fn html_response_from_login_mocks(file_name: &str) -> WpNetworkResponse { + let mut file_path = std::path::PathBuf::from(env!("CARGO_WORKSPACE_DIR")); + file_path.push("test-data"); + file_path.push("login-mocks"); + file_path.push(file_name); + let html = fs::read_to_string(&file_path).unwrap_or_else(|_| { + panic!("Should have been able to read the file at: '{file_path:#?}'") + }); + let mut map = HeaderMap::new(); + map.insert( + http::header::CONTENT_TYPE, + HeaderValue::from_static("text/html; charset=UTF-8"), + ); + WpNetworkResponse { + body: html.as_bytes().to_vec(), + status_code: 200, + response_header_map: Arc::new(map.into()), + request_url: WpEndpointUrl("".to_string()), + request_header_map: WpNetworkHeaderMap::default().into(), + } + } + + pub fn response_with_status_and_headers( + status_code: u16, + headers: HeaderMap, + ) -> WpNetworkResponse { + WpNetworkResponse { + body: vec![], + status_code, + response_header_map: Arc::new(headers.into()), + request_url: WpEndpointUrl("".to_string()), + request_header_map: WpNetworkHeaderMap::default().into(), + } + } } diff --git a/wp_api_integration_tests/tests/test_login_remote.rs b/wp_api_integration_tests/tests/test_login_remote.rs index b67b9dcba..7b928224a 100644 --- a/wp_api_integration_tests/tests/test_login_remote.rs +++ b/wp_api_integration_tests/tests/test_login_remote.rs @@ -11,7 +11,9 @@ use wp_api::{ ApiDiscoveryAuthenticationMiddleware, RetryAfterMiddleware, WpApiMiddleware, WpApiMiddlewarePipeline, }, - request::{NetworkRequestAccessor, RequestExecutor}, + request::{ + NetworkRequestAccessor, RequestExecutor, WpNetworkResponse, endpoint::WpEndpointUrl, + }, reqwest_request_executor::ReqwestRequestExecutor, }; use wp_api_integration_tests::prelude::*; @@ -20,8 +22,20 @@ use wp_api_integration_tests::prelude::*; #[parallel] async fn login_spec_1_valid_site_works_correctly() { // Spec Example 1 + let executor = MockExecutor::with_execute_fn(|request| match request.url().0.as_str() { + "https://vanilla.wpmt.co/" => Ok(response_helpers::with_api_root( + "https://vanilla.wpmt.co/wp-json/", + )), + "https://vanilla.wpmt.co/wp-json/" => Ok(response_helpers::json_response_from_login_mocks( + "vanilla-api-root.json", + )), + _ => panic!("Unexpected request URL: {:#?}", request.url()), + }); + let login_url = discovery_helper(Arc::new(executor), vec![], "https://vanilla.wpmt.co") + .await + .expect("Expected api discovery to be successful"); assert_eq!( - login_url("https://vanilla.wpmt.co").await, + login_url, "https://vanilla.wpmt.co/wp-admin/authorize-application.php" ); } @@ -61,12 +75,44 @@ async fn login_spec_2_local_development_environment() { #[parallel] async fn login_spec_3_admin_url_provided() { // Spec Example 3 + // Mock handles URLs for both wp-login.php and wp-admin variants + let executor = MockExecutor::with_execute_fn(|request| match request.url().0.as_str() { + // UserInput attempts for admin URLs — return non-WP page + "https://vanilla.wpmt.co/wp-login.php" | "https://vanilla.wpmt.co/wp-admin" => Ok( + response_helpers::html_response_from_login_mocks("homepage-not-wordpress.html"), + ), + // Fallback wp-json for UserInput attempts — return error + "https://vanilla.wpmt.co/wp-login.php/wp-json" + | "https://vanilla.wpmt.co/wp-admin/wp-json" => Ok(response_helpers::empty_response(404)), + // AutoStrippedHttps attempt homepage — success with Link header + "https://vanilla.wpmt.co/" => Ok(response_helpers::with_api_root( + "https://vanilla.wpmt.co/wp-json/", + )), + // API root + "https://vanilla.wpmt.co/wp-json/" => Ok(response_helpers::json_response_from_login_mocks( + "vanilla-api-root.json", + )), + _ => panic!("Unexpected request URL: {:#?}", request.url()), + }); + let executor: Arc