Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate default NetworkingClient implementation to use OkHttp #428

Merged
merged 2 commits into from
Jan 12, 2021
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
6 changes: 3 additions & 3 deletions auth0/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.browser:browser:1.2.0'
implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'com.squareup.okhttp:logging-interceptor:2.7.5'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.auth0.android:jwtdecode:1.3.0'

Expand All @@ -105,7 +105,7 @@ dependencies {
testImplementation 'org.mockito:mockito-core:3.6.28'
// Mockito-Kotlin: See https://github.com/nhaarman/mockito-kotlin/wiki/Parameter-specified-as-non-null-is-null
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
testImplementation 'com.squareup.okhttp:mockwebserver:2.7.5'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.0'
testImplementation 'com.jayway.awaitility:awaitility:1.7.0'
testImplementation 'org.robolectric:robolectric:4.4'
testImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
Expand Down
19 changes: 11 additions & 8 deletions auth0/src/main/java/com/auth0/android/Auth0.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ package com.auth0.android
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.auth0.android.util.Auth0UserAgent
import com.squareup.okhttp.HttpUrl
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.util.*

/**
* Represents your Auth0 account information (clientId & domain),
Expand All @@ -46,7 +49,7 @@ public open class Auth0 @JvmOverloads constructor(
public val clientId: String, domain: String, configurationDomain: String? = null
) {
private val domainUrl: HttpUrl?
private val configurationUrl: HttpUrl?
private val configurationUrl: HttpUrl

/**
* @return Auth0 user agent info sent in every request
Expand Down Expand Up @@ -165,16 +168,16 @@ public open class Auth0 @JvmOverloads constructor(
this.auth0UserAgent = auth0UserAgent
}

private fun resolveConfiguration(configurationDomain: String?, domainUrl: HttpUrl): HttpUrl? {
private fun resolveConfiguration(configurationDomain: String?, domainUrl: HttpUrl): HttpUrl {
var url = ensureValidUrl(configurationDomain)
if (url == null) {
val host = domainUrl.host()
val host = domainUrl.host
url = if (host.endsWith(DOT_AUTH0_DOT_COM)) {
val parts = host.split(".").toTypedArray()
if (parts.size > 3) {
HttpUrl.parse("https://cdn." + parts[parts.size - 3] + DOT_AUTH0_DOT_COM)
("https://cdn." + parts[parts.size - 3] + DOT_AUTH0_DOT_COM).toHttpUrl()
} else {
HttpUrl.parse(AUTH0_US_CDN_URL)
AUTH0_US_CDN_URL.toHttpUrl()
}
} else {
domainUrl
Expand All @@ -188,11 +191,11 @@ public open class Auth0 @JvmOverloads constructor(
if (url == null) {
return null
}
val normalizedUrl = url.toLowerCase()
val normalizedUrl = url.toLowerCase(Locale.ROOT)
lbalmaceda marked this conversation as resolved.
Show resolved Hide resolved
require(!normalizedUrl.startsWith("http://")) { "Invalid domain url: '$url'. Only HTTPS domain URLs are supported. If no scheme is passed, HTTPS will be used." }
val safeUrl =
if (normalizedUrl.startsWith("https://")) normalizedUrl else "https://$normalizedUrl"
return HttpUrl.parse(safeUrl)
return safeUrl.toHttpUrlOrNull()
}

private companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import com.auth0.android.result.Credentials
import com.auth0.android.result.DatabaseUser
import com.auth0.android.result.UserProfile
import com.google.gson.Gson
import com.squareup.okhttp.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.io.IOException
import java.io.Reader
import java.security.PublicKey
Expand Down Expand Up @@ -72,7 +72,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
@JvmOverloads
public constructor(
auth0: Auth0,
networkingClient: NetworkingClient = DefaultClient(auth0.connectTimeoutInSeconds)
networkingClient: NetworkingClient = DefaultClient()
lbalmaceda marked this conversation as resolved.
Show resolved Hide resolved
) : this(
auth0,
RequestFactory<AuthenticationException>(networkingClient, createErrorAdapter()),
Expand Down Expand Up @@ -194,7 +194,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
* @return a request to configure and start that will yield [Credentials]
*/
public fun loginWithNativeSocialToken(token: String, tokenType: String): AuthenticationRequest {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(OAUTH_PATH)
.addPathSegment(TOKEN_PATH)
.build()
Expand Down Expand Up @@ -328,7 +328,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
username: String? = null,
connection: String
): Request<DatabaseUser, AuthenticationException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(DB_CONNECTIONS_PATH)
.addPathSegment(SIGN_UP_PATH)
.build()
Expand Down Expand Up @@ -395,7 +395,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
email: String,
connection: String
): Request<Void, AuthenticationException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(DB_CONNECTIONS_PATH)
.addPathSegment(CHANGE_PASSWORD_PATH)
.build()
Expand Down Expand Up @@ -428,7 +428,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
.setClientId(clientId)
.set(TOKEN_KEY, refreshToken)
.asDictionary()
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(OAUTH_PATH)
.addPathSegment(REVOKE_PATH)
.build()
Expand Down Expand Up @@ -461,7 +461,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
.setRefreshToken(refreshToken)
.setGrantType(ParameterBuilder.GRANT_TYPE_REFRESH_TOKEN)
.asDictionary()
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(OAUTH_PATH)
.addPathSegment(TOKEN_PATH)
.build()
Expand Down Expand Up @@ -542,7 +542,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
* @return a request to configure and start
*/
private fun passwordless(): Request<Void, AuthenticationException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(PASSWORDLESS_PATH)
.addPathSegment(START_PATH)
.build()
Expand Down Expand Up @@ -592,7 +592,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
.set(REDIRECT_URI_KEY, redirectUri)
.set("code_verifier", codeVerifier)
.asDictionary()
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(OAUTH_PATH)
.addPathSegment(TOKEN_PATH)
.build()
Expand All @@ -611,7 +611,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
* @return a request to obtain the JSON Web Keys associated with this Auth0 account.
*/
public fun fetchJsonWebKeys(): Request<Map<String, PublicKey>, AuthenticationException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(WELL_KNOWN_PATH)
.addPathSegment(JWKS_FILE_PATH)
.build()
Expand All @@ -622,7 +622,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
}

private fun loginWithToken(parameters: Map<String, String>): AuthenticationRequest {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(OAUTH_PATH)
.addPathSegment(TOKEN_PATH)
.build()
Expand All @@ -639,7 +639,7 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe
}

private fun profileRequest(): Request<UserProfile, AuthenticationException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(USER_INFO_PATH)
.build()
val userProfileAdapter: JsonAdapter<UserProfile> = GsonAdapter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import com.auth0.android.request.internal.RequestFactory
import com.auth0.android.result.UserIdentity
import com.auth0.android.result.UserProfile
import com.google.gson.Gson
import com.squareup.okhttp.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.io.IOException
import java.io.Reader

Expand Down Expand Up @@ -65,7 +65,7 @@ public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRI
public constructor(
auth0: Auth0,
token: String,
networkingClient: NetworkingClient = DefaultClient(auth0.connectTimeoutInSeconds)
networkingClient: NetworkingClient = DefaultClient()
) : this(
auth0,
factoryForToken(token, networkingClient),
Expand Down Expand Up @@ -105,7 +105,7 @@ public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRI
primaryUserId: String,
secondaryToken: String
): Request<List<UserIdentity>, ManagementException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(API_PATH)
.addPathSegment(V2_PATH)
.addPathSegment(USERS_PATH)
Expand Down Expand Up @@ -143,7 +143,7 @@ public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRI
secondaryUserId: String,
secondaryProvider: String
): Request<List<UserIdentity>, ManagementException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(API_PATH)
.addPathSegment(V2_PATH)
.addPathSegment(USERS_PATH)
Expand Down Expand Up @@ -177,7 +177,7 @@ public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRI
userId: String,
userMetadata: Map<String, Any?>
): Request<UserProfile, ManagementException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(API_PATH)
.addPathSegment(V2_PATH)
.addPathSegment(USERS_PATH)
Expand Down Expand Up @@ -205,7 +205,7 @@ public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRI
* @return a request to start
*/
public fun getProfile(userId: String): Request<UserProfile, ManagementException> {
val url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder()
val url = auth0.getDomainUrl().toHttpUrl().newBuilder()
.addPathSegment(API_PATH)
.addPathSegment(V2_PATH)
.addPathSegment(USERS_PATH)
Expand Down
120 changes: 65 additions & 55 deletions auth0/src/main/java/com/auth0/android/request/DefaultClient.kt
Original file line number Diff line number Diff line change
@@ -1,79 +1,89 @@
package com.auth0.android.request

import android.net.Uri
import com.auth0.android.request.internal.GsonProvider
import com.google.gson.Gson
import java.io.BufferedWriter
import okhttp3.*
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import javax.net.ssl.HttpsURLConnection
import java.util.concurrent.TimeUnit

/**
* Default implementation of a Networking Client. Makes use of HttpUrlConnection.
* @param timeout the connection timeout to use when executing requests.
* Default implementation of a Networking Client.
*/
//TODO: Should this be internal?
public class DefaultClient(private val timeout: Int) : NetworkingClient {
public class DefaultClient() : NetworkingClient {

//TODO: receive this via constructor parameters
//TODO: receive this via internal constructor parameters
private val gson: Gson = GsonProvider.buildGson()
private var client: OkHttpClient

/**
* Creates and executes a networking request blocking the current thread.
* @return the response from the server.
*/
@Throws(IllegalArgumentException::class, IOException::class)
override fun load(url: String, options: RequestOptions): ServerResponse {
val parsedUri = Uri.parse(url)
val response = prepareCall(url.toHttpUrl(), options).execute()

//prepare URL
val targetUrl = if (options.method == HttpMethod.GET) {
val uriBuilder = parsedUri.buildUpon()
//setup query
options.parameters.map {
uriBuilder.appendQueryParameter(it.key, it.value)
//FIXME: Ensure body is being closed
return ServerResponse(
response.code,
response.body!!.byteStream(),
response.headers.toMultimap()
)
}

private fun prepareCall(url: HttpUrl, options: RequestOptions): Call {
val requestBuilder = Request.Builder()
val urlBuilder = url.newBuilder()

when (options.method) {
is HttpMethod.GET -> {
// add parameters as query
options.parameters.map { urlBuilder.addQueryParameter(it.key, it.value) }
requestBuilder.method(options.method.toString(), null)
}
else -> {
// add parameters as body
val body = gson.toJson(options.parameters).toRequestBody(APPLICATION_JSON_UTF8)
requestBuilder.method(options.method.toString(), body)
}
URL(uriBuilder.build().toString())
} else {
URL(url)
}
val request = requestBuilder
.url(urlBuilder.build())
.headers(options.headers.toHeaders())
.build()
return client.newCall(request)
}

// Auth0 constructor will enforce HTTPS scheme. This is here to enable running
// tests with MockWebServer using HTTP.
// TODO get tests running with MockWebServer using HTTPS, then use HttpsURLConnection
val connection: HttpURLConnection = targetUrl.openConnection() as HttpURLConnection

//FIXME: setup timeout
// connection.connectTimeout = timeout
// connection.readTimeout = timeout
init {
// TODO: possible constructor parameters
val enableLogging = true
val connectTimeout = DEFAULT_TIMEOUT_SECONDS
val readTimeout = DEFAULT_TIMEOUT_SECONDS

//setup headers
options.headers.map { connection.setRequestProperty(it.key, it.value) }
// client setup
val builder = OkHttpClient.Builder()

if (options.method == HttpMethod.POST || options.method == HttpMethod.PATCH || options.method == HttpMethod.DELETE) {
//required headers
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
connection.doInput = true
connection.doOutput = true
connection.requestMethod = options.method.toString()
val output = connection.outputStream
val writer = BufferedWriter(output.bufferedWriter())
if (options.parameters.isNotEmpty()) {
val json = gson.toJson(options.parameters)
writer.write(json)
}
writer.flush()
writer.close()
output.close()
// logging
if (enableLogging) {
val logger: Interceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)
builder.addInterceptor(logger)
}

connection.connect()
// timeouts
builder.connectTimeout(connectTimeout, TimeUnit.SECONDS)
builder.readTimeout(readTimeout, TimeUnit.SECONDS)

return ServerResponse(
connection.responseCode,
connection.errorStream ?: connection.inputStream,
connection.headerFields
)
client = builder.build()
}


private companion object {
private const val DEFAULT_TIMEOUT_SECONDS: Long = 10
private val APPLICATION_JSON_UTF8: MediaType =
"application/json; charset=utf-8".toMediaType()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import java.io.IOException

public interface NetworkingClient {

/**
* Creates and executes a networking request.
* The result is wrapped into a ServerResponse before being returned.
*/
@Throws(IOException::class)
public fun load(url: String, options: RequestOptions): ServerResponse
}
Loading