store client ID and secret, and set up refresh if a request fails with 401
This commit is contained in:
parent
bf9d5287ba
commit
3370001c4c
|
@ -50,7 +50,9 @@ class CameraTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -61,7 +61,9 @@ class DrawerMenuTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -68,7 +68,9 @@ class HomeFeedTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -84,7 +84,9 @@ class IntentTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -124,7 +124,9 @@ class LoginActivityOnlineTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -96,7 +96,9 @@ class PostFragmentUITests {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -62,7 +62,9 @@ class PostTest {
|
|||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
|
|
@ -30,6 +30,9 @@ import okhttp3.HttpUrl
|
|||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Inject
|
||||
/**
|
||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||
|
@ -137,7 +140,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
hideKeyboard()
|
||||
loadingAnimation(true)
|
||||
|
||||
pixelfedAPI = apiHolder.setDomain(normalizedDomain)
|
||||
pixelfedAPI = PixelfedAPI.createFromUrl(normalizedDomain)
|
||||
|
||||
Single.zip(
|
||||
pixelfedAPI.registerApplication(
|
||||
|
@ -250,7 +253,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
//Successful authorization
|
||||
pixelfedAPI = apiHolder.setDomain(domain)
|
||||
pixelfedAPI = PixelfedAPI.createFromUrl(domain)
|
||||
|
||||
//TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯
|
||||
//Also, maybe find a nicer way to do this, this feels hacky (although it can work fine)
|
||||
|
@ -279,7 +282,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
DBUtils.storeInstance(db, instance)
|
||||
storeUser(token.access_token, token.refresh_token, instance.uri)
|
||||
storeUser(token.access_token, token.refresh_token, clientId, clientSecret, instance.uri)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
|
||||
|
@ -313,7 +316,7 @@ class LoginActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun storeUser(accessToken: String, refreshToken: String?, instance: String) {
|
||||
private fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
|
@ -326,7 +329,9 @@ class LoginActivity : AppCompatActivity() {
|
|||
instance,
|
||||
activeUser = true,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
apiHolder.setDomainToCurrentUser(db)
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
|
|
|
@ -190,6 +190,8 @@ class MainActivity : AppCompatActivity() {
|
|||
val domain = user?.instance_uri.orEmpty()
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val refreshToken = user?.refreshToken
|
||||
val clientId = user?.clientId.orEmpty()
|
||||
val clientSecret = user?.clientSecret.orEmpty()
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
api.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
|
@ -199,7 +201,7 @@ class MainActivity : AppCompatActivity() {
|
|||
) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
val account = response.body() as Account
|
||||
DBUtils.addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken)
|
||||
DBUtils.addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
||||
fillDrawerAccountInfo(account.id!!)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,7 @@ interface PixelfedAPI {
|
|||
|
||||
|
||||
companion object {
|
||||
@Deprecated(
|
||||
"Use the DI-d PixelfedAPIHolder instead",
|
||||
ReplaceWith("apiHolder.api")
|
||||
)
|
||||
fun create(baseUrl: String): PixelfedAPI {
|
||||
fun createFromUrl(baseUrl: String): PixelfedAPI {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
|
@ -52,11 +48,12 @@ interface PixelfedAPI {
|
|||
fun obtainToken(
|
||||
@Field("client_id") client_id: String,
|
||||
@Field("client_secret") client_secret: String,
|
||||
@Field("redirect_uri") redirect_uri: String,
|
||||
@Field("redirect_uri") redirect_uri: String? = null,
|
||||
@Field("scope") scope: String? = "read",
|
||||
@Field("code") code: String? = null,
|
||||
@Field("grant_type") grant_type: String? = null
|
||||
): Single<Token>
|
||||
@Field("grant_type") grant_type: String? = null,
|
||||
@Field("refresh_token") refresh_token: String? = null
|
||||
): Single<Token>
|
||||
|
||||
// get instance configuration
|
||||
@GET("/api/v1/instance")
|
||||
|
|
|
@ -24,5 +24,7 @@ data class UserDatabaseEntity(
|
|||
var avatar_static: String,
|
||||
var isActive: Boolean,
|
||||
var accessToken: String,
|
||||
val refreshToken: String?
|
||||
val refreshToken: String?,
|
||||
val clientId: String,
|
||||
val clientSecret: String
|
||||
)
|
|
@ -2,11 +2,14 @@ package com.h.pixeldroid.di
|
|||
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.entities.UserDatabaseEntity
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import okhttp3.*
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.lang.Exception
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
@ -15,26 +18,51 @@ class APIModule{
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesAPIHolder(db: AppDatabase): PixelfedAPIHolder {
|
||||
return PixelfedAPIHolder(db.userDao().getActiveUser()?.instance_uri)
|
||||
return PixelfedAPIHolder(db.userDao().getActiveUser())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TokenAuthenticator(val user: UserDatabaseEntity) : Authenticator {
|
||||
|
||||
class PixelfedAPIHolder(domain: String?){
|
||||
val pixelfedAPI = PixelfedAPI.createFromUrl(user.instance_uri)
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
|
||||
if (response.request.header("Authorization") != null) {
|
||||
return null // Give up, we've already failed to authenticate.
|
||||
}
|
||||
// Refresh the access_token using a synchronous api request
|
||||
val newAccessToken: String = try {
|
||||
pixelfedAPI.obtainToken(
|
||||
scope = "", grant_type = "refresh_token",
|
||||
refresh_token = user.refreshToken, client_id = user.clientId, client_secret = user.clientSecret
|
||||
).blockingGet().access_token
|
||||
}catch (e: Exception){
|
||||
null
|
||||
}.orEmpty()
|
||||
|
||||
// Add new header to rejected request and retry it
|
||||
return response.request.newBuilder()
|
||||
.header("Authorization", "Bearer $newAccessToken")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
class PixelfedAPIHolder(user: UserDatabaseEntity?){
|
||||
private val intermediate: Retrofit.Builder = Retrofit.Builder()
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
var api: PixelfedAPI? = if (domain != null) setDomain(domain) else null
|
||||
var api: PixelfedAPI? = if (user != null) setDomain(user) else null
|
||||
|
||||
fun setDomainToCurrentUser(db: AppDatabase): PixelfedAPI {
|
||||
return setDomain(db.userDao().getActiveUser()!!.instance_uri)
|
||||
return setDomain(db.userDao().getActiveUser()!!)
|
||||
}
|
||||
|
||||
fun setDomain(domain: String): PixelfedAPI {
|
||||
fun setDomain(user: UserDatabaseEntity): PixelfedAPI {
|
||||
val newAPI = intermediate
|
||||
.baseUrl(domain)
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
.baseUrl(user.instance_uri)
|
||||
.client(OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user)).build())
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
api = newAPI
|
||||
return newAPI
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class PostFragment : BaseFragment() {
|
|||
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
val api = apiHolder.api ?: apiHolder.setDomain(user.instance_uri)
|
||||
val api = apiHolder.api ?: apiHolder.setDomain(user)
|
||||
|
||||
val holder = StatusViewHolder(root)
|
||||
|
||||
|
|
|
@ -89,8 +89,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
|||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
val instanceUri = db.userDao().getActiveUser()!!.instance_uri
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope)
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,8 +79,8 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
|
|||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
val instanceUri = db.userDao().getActiveUser()!!.instance_uri
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope)
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,20 +19,23 @@ class DBUtils {
|
|||
}
|
||||
}
|
||||
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true, accessToken: String, refreshToken: String?) {
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id!!,
|
||||
//make sure not to normalize to https when localhost, to allow testing
|
||||
instance_uri = normalizeOrNot(instance_uri),
|
||||
username = account.username!!,
|
||||
display_name = account.getDisplayName(),
|
||||
avatar_static = account.avatar_static.orEmpty(),
|
||||
isActive = activeUser,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken
|
||||
)
|
||||
)
|
||||
user_id = account.id!!,
|
||||
//make sure not to normalize to https when localhost, to allow testing
|
||||
instance_uri = normalizeOrNot(instance_uri),
|
||||
username = account.username!!,
|
||||
display_name = account.getDisplayName(),
|
||||
avatar_static = account.avatar_static.orEmpty(),
|
||||
isActive = activeUser,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun storeInstance(db: AppDatabase, instance: Instance) {
|
||||
|
|
|
@ -5,15 +5,11 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule
|
|||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.objects.*
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import retrofit2.Call
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
|
@ -92,9 +88,9 @@ class APIUnitTest {
|
|||
val statusesHome: List<Status>
|
||||
|
||||
runBlocking {
|
||||
statuses = PixelfedAPI.create("http://localhost:8089")
|
||||
statuses = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.timelinePublic(null, null, null, null, null)
|
||||
statusesHome = PixelfedAPI.create("http://localhost:8089")
|
||||
statusesHome = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.timelineHome("abc", null, null, null,null, null)
|
||||
}
|
||||
|
||||
|
@ -116,7 +112,7 @@ class APIUnitTest {
|
|||
.withHeader("Content-Type", "application/json")
|
||||
.withBody(""" {"id":3197,"name":"Pixeldroid","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":3197,"client_secret":"hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m","vapid_key":null}"""
|
||||
)))
|
||||
val call: Single<Application> = PixelfedAPI.create("http://localhost:8089")
|
||||
val call: Single<Application> = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow")
|
||||
|
||||
val application: Application = call.toFuture().get()
|
||||
|
@ -144,7 +140,7 @@ class APIUnitTest {
|
|||
val OAUTH_SCHEME = "oauth2redirect"
|
||||
val SCOPE = "read write follow"
|
||||
val PACKAGE_ID = "com.h.pixeldroid"
|
||||
val call: Single<Token> = PixelfedAPI.create("http://localhost:8089")
|
||||
val call: Single<Token> = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc",
|
||||
"authorization_code")
|
||||
val token: Token = call.toFuture().get()
|
||||
|
|
Loading…
Reference in New Issue