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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -204,7 +204,7 @@ class ApiUrlDiscoveryTest {
)

val client = WpLoginClient(
WpRequestExecutor(), WpApiMiddlewarePipeline(middlewares = listOf(valid))
WpRequestExecutor(emptyList()), WpApiMiddlewarePipeline(middlewares = listOf(valid))
)

assertEquals(
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 ->
Expand All @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> WpRequestResult<T>.assertSuccess() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class MediaEndpointTest {
password = TestCredentials.INSTANCE.adminPassword
)
val requestExecutor = WpRequestExecutor(
interceptors = emptyList(),
fileResolver = FileResolverMock(),
uploadListener = uploadListener
)
Expand Down Expand Up @@ -144,6 +145,7 @@ class MediaEndpointTest {
username = testCredentials.adminUsername, password = testCredentials.adminPassword
)
val requestExecutor = WpRequestExecutor(
interceptors = emptyList(),
fileResolver = FileResolverMock()
)
return WpApiClient(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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<Interceptor>,
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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<Interceptor>,
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Interceptor>,
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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package rs.wordpress.api.kotlin

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import javax.net.ssl.HostnameVerifier
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<Interceptor>
) : WpHttpClient() {
private var allowedHostnames: Map<String, List<String>> = emptyMap()

private var client: OkHttpClient = buildClient()

fun addAllowedAlternativeNamesForHostname(hostname: String, allowedNames: List<String>) {
// 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) }
if (allowedHostnames.isNotEmpty()) {
hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames))
}
}.build()
}

override fun getClient() = client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Interceptor>,
middlewarePipeline: WpApiMiddlewarePipeline = WpApiMiddlewarePipeline(listOf()),
dispatcher: CoroutineDispatcher = Dispatchers.IO
) : this(
requestExecutor = WpRequestExecutor(interceptors),
middlewarePipeline = middlewarePipeline,
dispatcher = dispatcher
)

suspend fun apiDiscovery(
siteUrl: String
): ApiDiscoveryResult = withContext(dispatcher) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -34,11 +35,28 @@ 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
) : RequestExecutor {

/**
* Convenience constructor that accepts a list of OkHttp interceptors.
* Uses [WpHttpClient.DefaultHttpClient] internally with the provided interceptors.
*/
constructor(
interceptors: List<Interceptor>,
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
}
}
Expand Down