From baee7de30d4bdb75f6fe1234d440b1c0e6f568c8 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Tue, 25 Nov 2025 12:39:39 -0500 Subject: [PATCH 1/3] Add interceptors support to DefaultHttpClient and WpRequestExecutor Allow OkHttp interceptors to be passed to DefaultHttpClient while preserving hostname verification functionality. Add a convenience constructor to WpRequestExecutor that accepts interceptors directly. This enables clients to inject interceptors (e.g., for network debugging) without needing to manage OkHttpClient configuration. Changes: - Add `interceptors` parameter to `DefaultHttpClient` - Refactor to use `buildClient()` method for client construction - Add convenience constructor to `WpRequestExecutor` --- .../rs/wordpress/api/kotlin/WpHttpClient.kt | 18 +++++++++++------- .../wordpress/api/kotlin/WpRequestExecutor.kt | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt index 7b0aee610..4f5a734eb 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt @@ -1,5 +1,6 @@ package rs.wordpress.api.kotlin +import okhttp3.Interceptor import okhttp3.OkHttpClient import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSession @@ -7,8 +8,10 @@ import javax.net.ssl.SSLSession sealed class WpHttpClient { abstract fun getClient(): OkHttpClient - class DefaultHttpClient : WpHttpClient() { - private var client: OkHttpClient = OkHttpClient() + class DefaultHttpClient( + private val interceptors: List = emptyList() + ) : WpHttpClient() { + private var client: OkHttpClient = buildClient() private var allowedHostnames: Map> = emptyMap() @@ -16,13 +19,14 @@ sealed class WpHttpClient { // Preserve the previous records for this key val previousList = allowedHostnames[hostname].orEmpty() allowedHostnames = allowedHostnames.plus(Pair(hostname, allowedNames.plus(previousList))) - updateClient() + client = buildClient() } - private fun updateClient() { - client = client.newBuilder() - .hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames)) - .build() + private fun buildClient(): OkHttpClient { + return OkHttpClient.Builder().apply { + this@DefaultHttpClient.interceptors.forEach { addInterceptor(it) } + hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames)) + }.build() } override fun getClient() = client diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index eb7cb913d..e8f63e404 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import okhttp3.Call import okhttp3.HttpUrl +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.MultipartBody import okhttp3.OkHttp @@ -39,6 +40,23 @@ class WpRequestExecutor( private val fileResolver: FileResolver = DefaultFileResolver(), private val uploadListener: UploadListener? = null ) : RequestExecutor { + + /** + * Convenience constructor that accepts a list of OkHttp interceptors. + * Uses [WpHttpClient.DefaultHttpClient] internally with the provided interceptors. + */ + constructor( + interceptors: List, + dispatcher: CoroutineDispatcher = Dispatchers.IO, + fileResolver: FileResolver = DefaultFileResolver(), + uploadListener: UploadListener? = null + ) : this( + httpClient = WpHttpClient.DefaultHttpClient(interceptors), + dispatcher = dispatcher, + fileResolver = fileResolver, + uploadListener = uploadListener + ) + override suspend fun execute(request: WpNetworkRequest): WpNetworkResponse = withContext(dispatcher) { val requestBuilder = Request.Builder().url(request.url()) From aa9ec224c5802cee934be5215615ffbbfff957cb Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Tue, 25 Nov 2025 13:45:22 -0500 Subject: [PATCH 2/3] Require explicit interceptors for API clients Remove default values for interceptors throughout the client chain, requiring consumers to explicitly pass interceptors (even if empty). This makes the API more intentional and prevents silent defaults. Changes: - Fix initialization order bug in `DefaultHttpClient` (allowedHostnames before client) - Remove default `interceptors` parameter from `DefaultHttpClient` - Remove default `httpClient`/`requestExecutor` from all API clients - Add convenience constructors accepting `List` to all clients - Update all call sites to pass `emptyList()` explicitly --- .../api/android/UsersEndpointAndroidTest.kt | 2 +- .../kotlin/ApiUrlDiscoveryTest.kt | 8 +++---- .../kotlin/AuthProviderTest.kt | 6 ++--- .../kotlin/IntegrationTestHelpers.kt | 2 +- .../kotlin/MediaEndpointTest.kt | 2 ++ .../kotlin/PluginsEndpointTest.kt | 6 +++-- .../wordpress/api/kotlin/JetpackApiClient.kt | 23 +++++++++++++++++-- .../rs/wordpress/api/kotlin/WpApiClient.kt | 23 +++++++++++++++++-- .../rs/wordpress/api/kotlin/WpComApiClient.kt | 20 +++++++++++++++- .../rs/wordpress/api/kotlin/WpHttpClient.kt | 6 ++--- .../rs/wordpress/api/kotlin/WpLoginClient.kt | 18 ++++++++++++++- .../wordpress/api/kotlin/WpRequestExecutor.kt | 2 +- .../example/ui/welcome/WelcomeActivity.kt | 2 +- .../shared/ui/plugins/PluginListViewModel.kt | 3 ++- .../shared/ui/users/UserListViewModel.kt | 3 ++- 15 files changed, 102 insertions(+), 24 deletions(-) diff --git a/native/kotlin/api/android/src/androidTest/kotlin/rs/wordpress/api/android/UsersEndpointAndroidTest.kt b/native/kotlin/api/android/src/androidTest/kotlin/rs/wordpress/api/android/UsersEndpointAndroidTest.kt index fa1a66ec9..853bed690 100644 --- a/native/kotlin/api/android/src/androidTest/kotlin/rs/wordpress/api/android/UsersEndpointAndroidTest.kt +++ b/native/kotlin/api/android/src/androidTest/kotlin/rs/wordpress/api/android/UsersEndpointAndroidTest.kt @@ -11,7 +11,7 @@ import java.net.URL class UsersEndpointAndroidTest { // https://developer.android.com/studio/run/emulator-networking private val siteUrl = "http://10.0.2.2" - private val client = WpApiClient(URL(siteUrl), WpAuthenticationProvider.none()) + private val client = WpApiClient(URL(siteUrl), WpAuthenticationProvider.none(), emptyList()) @Test fun testUserListRequest() = runTest { diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/ApiUrlDiscoveryTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/ApiUrlDiscoveryTest.kt index 4492b3f41..314961463 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/ApiUrlDiscoveryTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/ApiUrlDiscoveryTest.kt @@ -25,7 +25,7 @@ import kotlin.test.assertContains @Execution(ExecutionMode.CONCURRENT) class ApiUrlDiscoveryTest { - private val loginClient: WpLoginClient = WpLoginClient() + private val loginClient: WpLoginClient = WpLoginClient(emptyList()) @Test fun testLocalSite() = runTest { @@ -186,7 +186,7 @@ class ApiUrlDiscoveryTest { val invalid = ApiDiscoveryAuthenticationMiddleware(username = "invalid", password = "invalid") val client = WpLoginClient( - WpRequestExecutor(), WpApiMiddlewarePipeline(middlewares = listOf(invalid)) + WpRequestExecutor(emptyList()), WpApiMiddlewarePipeline(middlewares = listOf(invalid)) ) val reason = client.apiDiscovery("https://basic-auth.wpmt.co") .assertFailureFindApiRoot().getRequestExecutionErrorReason() @@ -204,7 +204,7 @@ class ApiUrlDiscoveryTest { ) val client = WpLoginClient( - WpRequestExecutor(), WpApiMiddlewarePipeline(middlewares = listOf(valid)) + WpRequestExecutor(emptyList()), WpApiMiddlewarePipeline(middlewares = listOf(valid)) ) assertEquals( @@ -283,7 +283,7 @@ class ApiUrlDiscoveryTest { @Test // Spec Example 17 (with exception) fun testInvalidHttpsWithExceptionWorks() = runTest { - val httpClient = WpHttpClient.DefaultHttpClient() + val httpClient = WpHttpClient.DefaultHttpClient(emptyList()) val executor = WpRequestExecutor(httpClient) httpClient.addAllowedAlternativeNamesForHostname( "vanilla.wpmt.co", diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/AuthProviderTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/AuthProviderTest.kt index 120b78639..92c6cdb8a 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/AuthProviderTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/AuthProviderTest.kt @@ -21,7 +21,7 @@ class AuthProviderTest { val authProvider = WpAuthenticationProvider.staticWithUsernameAndPassword( username = testCredentials.adminUsername, password = testCredentials.adminPassword ) - val client = WpApiClient(testCredentials.apiRootUrl, authProvider) + val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList()) val currentUser = client.request { requestBuilder -> requestBuilder.users().retrieveMeWithEditContext() @@ -52,7 +52,7 @@ class AuthProviderTest { val dynamicAuthProvider = DynamicAuthProvider() val authProvider = WpAuthenticationProvider.dynamic(dynamicAuthProvider) - val client = WpApiClient(testCredentials.apiRootUrl, authProvider) + val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList()) // Assert that initial unauthorized request fails assert(client.request { requestBuilder -> @@ -75,7 +75,7 @@ class AuthProviderTest { val modifiableAuthenticationProvider = ModifiableAuthenticationProvider(authentication = WpAuthentication.None) val authProvider = WpAuthenticationProvider.modifiable(modifiableAuthenticationProvider) - val client = WpApiClient(testCredentials.apiRootUrl, authProvider) + val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList()) // Assert that request fails without authentication assert(client.request { requestBuilder -> diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt index aa189080d..41b15599a 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt @@ -21,7 +21,7 @@ fun defaultApiClient(): WpApiClient { val authProvider = WpAuthenticationProvider.staticWithUsernameAndPassword( username = testCredentials.adminUsername, password = testCredentials.adminPassword ) - return WpApiClient(testCredentials.apiRootUrl, authProvider) + return WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList()) } fun WpRequestResult.assertSuccess() { diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt index adf9eb06d..5a59b9d08 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt @@ -94,6 +94,7 @@ class MediaEndpointTest { password = TestCredentials.INSTANCE.adminPassword ) val requestExecutor = WpRequestExecutor( + interceptors = emptyList(), fileResolver = FileResolverMock(), uploadListener = uploadListener ) @@ -144,6 +145,7 @@ class MediaEndpointTest { username = testCredentials.adminUsername, password = testCredentials.adminPassword ) val requestExecutor = WpRequestExecutor( + interceptors = emptyList(), fileResolver = FileResolverMock() ) return WpApiClient( diff --git a/native/kotlin/api/kotlin/src/integrationTest/kotlin/PluginsEndpointTest.kt b/native/kotlin/api/kotlin/src/integrationTest/kotlin/PluginsEndpointTest.kt index eb871d913..55c870ef0 100644 --- a/native/kotlin/api/kotlin/src/integrationTest/kotlin/PluginsEndpointTest.kt +++ b/native/kotlin/api/kotlin/src/integrationTest/kotlin/PluginsEndpointTest.kt @@ -20,14 +20,16 @@ class PluginsEndpointTest { testCredentials.apiRootUrl, WpAuthenticationProvider.staticWithUsernameAndPassword( username = testCredentials.adminUsername, password = testCredentials.adminPassword - ) + ), + emptyList() ) private val clientAsSubscriber = WpApiClient( testCredentials.apiRootUrl, WpAuthenticationProvider.staticWithUsernameAndPassword( username = testCredentials.subscriberUsername, password = testCredentials.subscriberPassword - ) + ), + emptyList() ) @Test diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/JetpackApiClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/JetpackApiClient.kt index b08df2e66..e82c9910f 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/JetpackApiClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/JetpackApiClient.kt @@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.Interceptor import uniffi.wp_api.ApiUrlResolver import uniffi.wp_api.ParsedUrl import uniffi.wp_api.RequestExecutor @@ -18,14 +19,14 @@ import java.net.URL class JetpackApiClient( apiUrlResolver: ApiUrlResolver, authProvider: WpAuthenticationProvider, - private val requestExecutor: RequestExecutor = WpRequestExecutor(), + private val requestExecutor: RequestExecutor, private val appNotifier: WpAppNotifier = EmptyAppNotifier(), private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { constructor( wpOrgSiteApiRootUrl: URL, authProvider: WpAuthenticationProvider, - requestExecutor: RequestExecutor = WpRequestExecutor(), + requestExecutor: RequestExecutor, appNotifier: WpAppNotifier = EmptyAppNotifier(), dispatcher: CoroutineDispatcher = Dispatchers.IO ) : this( @@ -36,6 +37,24 @@ class JetpackApiClient( dispatcher ) + /** + * Convenience constructor that accepts a list of OkHttp interceptors. + * Uses [WpRequestExecutor] internally with the provided interceptors. + */ + constructor( + wpOrgSiteApiRootUrl: URL, + authProvider: WpAuthenticationProvider, + interceptors: List, + appNotifier: WpAppNotifier = EmptyAppNotifier(), + dispatcher: CoroutineDispatcher = Dispatchers.IO + ) : this( + wpOrgSiteApiRootUrl, + authProvider, + requestExecutor = WpRequestExecutor(interceptors), + appNotifier, + dispatcher + ) + // Don't expose `WpRequestBuilder` directly so we can control how it's used private val requestBuilder by lazy { UniffiJetpackApiClient( diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt index ac955ae31..9372a85ff 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt @@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.Interceptor import uniffi.wp_api.ApiUrlResolver import uniffi.wp_api.ParsedUrl import uniffi.wp_api.RequestExecutor @@ -18,14 +19,14 @@ import java.net.URL class WpApiClient( apiUrlResolver: ApiUrlResolver, authProvider: WpAuthenticationProvider, - private val requestExecutor: RequestExecutor = WpRequestExecutor(), + private val requestExecutor: RequestExecutor, private val appNotifier: WpAppNotifier = EmptyAppNotifier(), private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { constructor( wpOrgSiteApiRootUrl: URL, authProvider: WpAuthenticationProvider, - requestExecutor: RequestExecutor = WpRequestExecutor(), + requestExecutor: RequestExecutor, appNotifier: WpAppNotifier = EmptyAppNotifier(), dispatcher: CoroutineDispatcher = Dispatchers.IO ) : this( @@ -36,6 +37,24 @@ class WpApiClient( dispatcher ) + /** + * Convenience constructor that accepts a list of OkHttp interceptors. + * Uses [WpRequestExecutor] internally with the provided interceptors. + */ + constructor( + wpOrgSiteApiRootUrl: URL, + authProvider: WpAuthenticationProvider, + interceptors: List, + appNotifier: WpAppNotifier = EmptyAppNotifier(), + dispatcher: CoroutineDispatcher = Dispatchers.IO + ) : this( + wpOrgSiteApiRootUrl, + authProvider, + requestExecutor = WpRequestExecutor(interceptors), + appNotifier, + dispatcher + ) + // Don't expose `WpRequestBuilder` directly so we can control how it's used private val requestBuilder by lazy { UniffiWpApiClient( diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpComApiClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpComApiClient.kt index 60c42a246..ffec42643 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpComApiClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpComApiClient.kt @@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.Interceptor import uniffi.wp_api.RequestExecutor import uniffi.wp_api.UniffiWpComApiClient import uniffi.wp_api.WpApiClientDelegate @@ -13,10 +14,27 @@ import uniffi.wp_api.WpAuthenticationProvider class WpComApiClient( authProvider: WpAuthenticationProvider, - private val requestExecutor: RequestExecutor = WpRequestExecutor(), + private val requestExecutor: RequestExecutor, private val appNotifier: WpAppNotifier = EmptyAppNotifier(), private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { + + /** + * Convenience constructor that accepts a list of OkHttp interceptors. + * Uses [WpRequestExecutor] internally with the provided interceptors. + */ + constructor( + authProvider: WpAuthenticationProvider, + interceptors: List, + appNotifier: WpAppNotifier = EmptyAppNotifier(), + dispatcher: CoroutineDispatcher = Dispatchers.IO + ) : this( + authProvider, + requestExecutor = WpRequestExecutor(interceptors), + appNotifier, + dispatcher + ) + // Don't expose `WpRequestBuilder` directly so we can control how it's used private val requestBuilder by lazy { UniffiWpComApiClient( diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt index 4f5a734eb..b3111b247 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt @@ -9,12 +9,12 @@ sealed class WpHttpClient { abstract fun getClient(): OkHttpClient class DefaultHttpClient( - private val interceptors: List = emptyList() + private val interceptors: List ) : WpHttpClient() { - private var client: OkHttpClient = buildClient() - private var allowedHostnames: Map> = emptyMap() + private var client: OkHttpClient = buildClient() + fun addAllowedAlternativeNamesForHostname(hostname: String, allowedNames: List) { // Preserve the previous records for this key val previousList = allowedHostnames[hostname].orEmpty() diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpLoginClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpLoginClient.kt index b5dbe51fd..a098d0aef 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpLoginClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpLoginClient.kt @@ -3,19 +3,35 @@ package rs.wordpress.api.kotlin import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.Interceptor import uniffi.wp_api.AutoDiscoveryAttemptFailure import uniffi.wp_api.RequestExecutor import uniffi.wp_api.UniffiWpLoginClient import uniffi.wp_api.WpApiMiddlewarePipeline class WpLoginClient( - requestExecutor: RequestExecutor = WpRequestExecutor(), + requestExecutor: RequestExecutor, middlewarePipeline: WpApiMiddlewarePipeline = WpApiMiddlewarePipeline(listOf()), private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) { + private val internalClient: UniffiWpLoginClient = UniffiWpLoginClient(requestExecutor, middlewarePipeline) + /** + * Convenience constructor that accepts a list of OkHttp interceptors. + * Uses [WpRequestExecutor] internally with the provided interceptors. + */ + constructor( + interceptors: List, + middlewarePipeline: WpApiMiddlewarePipeline = WpApiMiddlewarePipeline(listOf()), + dispatcher: CoroutineDispatcher = Dispatchers.IO + ) : this( + requestExecutor = WpRequestExecutor(interceptors), + middlewarePipeline = middlewarePipeline, + dispatcher = dispatcher + ) + suspend fun apiDiscovery( siteUrl: String ): ApiDiscoveryResult = withContext(dispatcher) { diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt index e8f63e404..c657f63a8 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt @@ -35,7 +35,7 @@ import javax.net.ssl.SSLPeerUnverifiedException const val USER_AGENT_HEADER_NAME = "User-Agent" class WpRequestExecutor( - private val httpClient: WpHttpClient = WpHttpClient.DefaultHttpClient(), + private val httpClient: WpHttpClient, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, private val fileResolver: FileResolver = DefaultFileResolver(), private val uploadListener: UploadListener? = null diff --git a/native/kotlin/example/composeApp/src/androidMain/kotlin/rs/wordpress/example/ui/welcome/WelcomeActivity.kt b/native/kotlin/example/composeApp/src/androidMain/kotlin/rs/wordpress/example/ui/welcome/WelcomeActivity.kt index 149401887..cd640986c 100644 --- a/native/kotlin/example/composeApp/src/androidMain/kotlin/rs/wordpress/example/ui/welcome/WelcomeActivity.kt +++ b/native/kotlin/example/composeApp/src/androidMain/kotlin/rs/wordpress/example/ui/welcome/WelcomeActivity.kt @@ -30,7 +30,7 @@ class WelcomeActivity : ComponentActivity() { private fun authenticateSite(url: String) { val success = runBlocking { - when (val apiDiscoveryResult = WpLoginClient().apiDiscovery(url)) { + when (val apiDiscoveryResult = WpLoginClient(emptyList()).apiDiscovery(url)) { is ApiDiscoveryResult.Success -> apiDiscoveryResult.success else -> throw IllegalStateException("Api discovery should succeed for the example app") } diff --git a/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/plugins/PluginListViewModel.kt b/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/plugins/PluginListViewModel.kt index e76c904c5..48fd2a5b9 100644 --- a/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/plugins/PluginListViewModel.kt +++ b/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/plugins/PluginListViewModel.kt @@ -17,7 +17,8 @@ class PluginListViewModel(private val authRepository: AuthenticationRepository) authRepository.authenticationForSite(authenticatedSite)?.let { apiClient = WpApiClient( wpOrgSiteApiRootUrl = authenticatedSite.apiRootUrl, - authProvider = WpAuthenticationProvider.staticWithAuth(it) + authProvider = WpAuthenticationProvider.staticWithAuth(it), + interceptors = emptyList() ) } } diff --git a/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/users/UserListViewModel.kt b/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/users/UserListViewModel.kt index 89f3e4a33..cb956d699 100644 --- a/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/users/UserListViewModel.kt +++ b/native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/users/UserListViewModel.kt @@ -17,7 +17,8 @@ class UserListViewModel(private val authRepository: AuthenticationRepository) { authRepository.authenticationForSite(authenticatedSite)?.let { apiClient = WpApiClient( wpOrgSiteApiRootUrl = authenticatedSite.apiRootUrl, - authProvider = WpAuthenticationProvider.staticWithAuth(it) + authProvider = WpAuthenticationProvider.staticWithAuth(it), + interceptors = emptyList() ) } } From 1becf3b0bf7ecdbfac76a3c144f411542b92ac39 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Wed, 26 Nov 2025 07:14:58 -0500 Subject: [PATCH 3/3] Only apply custom hostname verifier when allowedHostnames is set The custom WpRequestExecutorHostnameVerifier doesn't handle wildcards or SANs like the default OkHttp verifier. Only apply it when there are actually allowed hostname overrides configured. --- .../src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt index b3111b247..27c350095 100644 --- a/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt +++ b/native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt @@ -25,7 +25,9 @@ sealed class WpHttpClient { private fun buildClient(): OkHttpClient { return OkHttpClient.Builder().apply { this@DefaultHttpClient.interceptors.forEach { addInterceptor(it) } - hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames)) + if (allowedHostnames.isNotEmpty()) { + hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames)) + } }.build() }