Commit 228759b5 authored by Rafael's avatar Rafael Committed by Jean-Baptiste Guerraz

Provides support for http basic authentication

parent 5a472099
...@@ -242,7 +242,7 @@ class ServerFragment : Fragment(), ServerView { ...@@ -242,7 +242,7 @@ class ServerFragment : Fragment(), ServerView {
serverUrlDisposable = text_server_url.asObservable() serverUrlDisposable = text_server_url.asObservable()
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.subscribe { .subscribe {
if (it.toString().isValidUrl()) { if ("$protocol${it.toString()}".isValidUrl()) {
enableButtonConnect() enableButtonConnect()
} else { } else {
disableButtonConnect() disableButtonConnect()
......
...@@ -40,6 +40,10 @@ import chat.rocket.android.server.domain.PermissionsRepository ...@@ -40,6 +40,10 @@ import chat.rocket.android.server.domain.PermissionsRepository
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
...@@ -53,6 +57,7 @@ import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRep ...@@ -53,6 +57,7 @@ import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRep
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.BasicAuthenticatorInterceptor
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.internal.ISO8601Date
...@@ -106,9 +111,22 @@ class AppModule { ...@@ -106,9 +111,22 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor): OkHttpClient { fun provideBasicAuthenticatorInterceptor(
getBasicAuthInteractor: GetBasicAuthInteractor,
saveBasicAuthInteractor: SaveBasicAuthInteractor
): BasicAuthenticatorInterceptor {
return BasicAuthenticatorInterceptor(
getBasicAuthInteractor,
saveBasicAuthInteractor
)
}
@Provides
@Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor, basicAuthenticator: BasicAuthenticatorInterceptor): OkHttpClient {
return OkHttpClient.Builder() return OkHttpClient.Builder()
.addInterceptor(logger) .addInterceptor(logger)
.addInterceptor(basicAuthenticator)
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS)
...@@ -273,6 +291,14 @@ class AppModule { ...@@ -273,6 +291,14 @@ class AppModule {
return MessageParser(context, configuration, settingsInteractor.get(url)) return MessageParser(context, configuration, settingsInteractor.get(url))
} }
@Provides
@Singleton
fun provideBasicAuthRepository (
preferences: SharedPreferences,
moshi: Moshi
): BasicAuthRepository =
SharedPrefsBasicAuthRepository(preferences, moshi)
@Provides @Provides
@Singleton @Singleton
fun provideAccountsRepository( fun provideAccountsRepository(
......
package chat.rocket.android.server.domain
import chat.rocket.android.server.domain.model.BasicAuth
interface BasicAuthRepository {
fun save(basicAuth: BasicAuth)
fun load(): List<BasicAuth>
}
package chat.rocket.android.server.domain
import javax.inject.Inject
class GetBasicAuthInteractor @Inject constructor(val repository: BasicAuthRepository) {
fun getAll() = repository.load().listIterator()
}
package chat.rocket.android.server.domain
import chat.rocket.android.server.domain.model.BasicAuth
import javax.inject.Inject
class SaveBasicAuthInteractor @Inject constructor(val repository: BasicAuthRepository) {
fun save(basicAuth: BasicAuth) = repository.save(basicAuth)
}
package chat.rocket.android.server.domain.model
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class BasicAuth(
val host: String,
val credentials: String
)
package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences
import androidx.core.content.edit
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.model.BasicAuth
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
private const val BASICAUTHS_KEY = "BASICAUTHS_KEY"
class SharedPrefsBasicAuthRepository(
private val preferences: SharedPreferences,
private val moshi: Moshi
) : BasicAuthRepository {
override fun save(basicAuth: BasicAuth) {
val newList = load().filter { basicAuth -> basicAuth.host != basicAuth.host }
.toMutableList()
newList.add(0, basicAuth)
save(newList)
}
override fun load(): List<BasicAuth> {
val json = preferences.getString(BASICAUTHS_KEY, "[]")
val type = Types.newParameterizedType(List::class.java, BasicAuth::class.java)
val adapter = moshi.adapter<List<BasicAuth>>(type)
return adapter.fromJson(json) ?: emptyList()
}
private fun save(basicAuths: List<BasicAuth>) {
val type = Types.newParameterizedType(List::class.java, BasicAuth::class.java)
val adapter = moshi.adapter<List<BasicAuth>>(type)
preferences.edit {
putString(BASICAUTHS_KEY, adapter.toJson(basicAuths))
}
}
}
package chat.rocket.android.util
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.Credentials
import java.io.IOException
import chat.rocket.android.server.domain.model.BasicAuth
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import javax.inject.Inject
/**
* An OkHttp interceptor which adds Authorization header based on URI userInfo
* part. Can be applied as an
* [application interceptor][OkHttpClient.interceptors]
* or as a [ ][OkHttpClient.networkInterceptors].
*/
class BasicAuthenticatorInterceptor @Inject constructor (
private val getBasicAuthInteractor: GetBasicAuthInteractor,
private val saveBasicAuthInteractor: SaveBasicAuthInteractor
): Interceptor {
private val credentials = HashMap<String, String>()
init {
val basicAuths = getBasicAuthInteractor.getAll()
for (basicAuth in basicAuths){
credentials[basicAuth.host] = basicAuth.credentials
}
}
private fun saveCredentials(host: String, basicCredentials: String) {
saveBasicAuthInteractor.save(
BasicAuth(
host,
basicCredentials
)
)
credentials[host] = basicCredentials
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val url = request.url()
val host = url.host()
val username = url.username()
if (!username.isNullOrEmpty()) {
saveCredentials(host, Credentials.basic(username, url.password()))
request = request.newBuilder().url(
url.newBuilder().username("").password("").build()
).build()
}
credentials[host]?.let {
request = request.newBuilder().header("Authorization", it).build()
}
return chain.proceed(request)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment