Merge branch 'refresh_tokens' into 'master'
Store refresh tokens and use them if request fails with 401 Closes #169 See merge request pixeldroid/PixelDroid!265
This commit is contained in:
commit
271b9f310d
|
@ -72,14 +72,14 @@ dependencies {
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.3.2'
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||||
implementation "androidx.browser:browser:1.2.0"
|
implementation "androidx.browser:browser:1.3.0"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha09'
|
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha10'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||||
|
@ -110,23 +110,23 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
|
|
||||||
//Dagger (dependency injection)
|
//Dagger (dependency injection)
|
||||||
implementation 'com.google.dagger:dagger-android:2.29.1'
|
implementation 'com.google.dagger:dagger-android:2.30.1'
|
||||||
implementation 'com.google.dagger:dagger-android-support:2.29.1'
|
implementation 'com.google.dagger:dagger-android-support:2.30.1'
|
||||||
// if you use the support libraries
|
// if you use the support libraries
|
||||||
kapt 'com.google.dagger:dagger-android-processor:2.29.1'
|
kapt 'com.google.dagger:dagger-android-processor:2.30.1'
|
||||||
kapt 'com.google.dagger:dagger-compiler:2.29.1'
|
kapt 'com.google.dagger:dagger-compiler:2.30.1'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.20'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
||||||
|
|
||||||
|
|
||||||
implementation 'info.androidhive:imagefilters:1.0.7'
|
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||||
implementation 'com.github.yalantis:ucrop:2.2.5-native'
|
implementation 'com.github.yalantis:ucrop:2.2.6-native'
|
||||||
|
|
||||||
implementation("com.github.bumptech.glide:glide:4.11.0") {
|
implementation("com.github.bumptech.glide:glide:4.11.0") {
|
||||||
exclude group: "com.android.support"
|
exclude group: "com.android.support"
|
||||||
|
@ -141,13 +141,13 @@ dependencies {
|
||||||
|
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
implementation "com.mikepenz:materialdrawer:8.1.5"
|
implementation "com.mikepenz:materialdrawer:8.1.8"
|
||||||
// Add for NavController support
|
// Add for NavController support
|
||||||
implementation "com.mikepenz:materialdrawer-nav:8.1.5"
|
implementation "com.mikepenz:materialdrawer-nav:8.1.5"
|
||||||
|
|
||||||
//iconics
|
//iconics
|
||||||
implementation "com.mikepenz:iconics-core:5.0.3"
|
implementation "com.mikepenz:iconics-core:5.0.3"
|
||||||
implementation "com.mikepenz:materialdrawer-iconics:8.1.5"
|
implementation "com.mikepenz:materialdrawer-iconics:8.1.8"
|
||||||
implementation "com.mikepenz:iconics-views:5.0.3"
|
implementation "com.mikepenz:iconics-views:5.0.3"
|
||||||
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
|
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,16 @@ class CameraTest {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = "http://localhost",
|
instance_uri = "http://localhost",
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -54,13 +54,16 @@ class DrawerMenuTest {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = baseUrl.toString(),
|
instance_uri = baseUrl.toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -61,13 +61,16 @@ class HomeFeedTest {
|
||||||
)
|
)
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = baseUrl.toString(),
|
instance_uri = baseUrl.toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -77,13 +77,16 @@ class IntentTest {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = baseUrl.toString(),
|
instance_uri = baseUrl.toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -117,13 +117,16 @@ class LoginActivityOnlineTest {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = server.getUrl().toString(),
|
instance_uri = server.getUrl().toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -89,13 +89,16 @@ class PostFragmentUITests {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = baseUrl.toString(),
|
instance_uri = baseUrl.toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -55,13 +55,16 @@ class PostTest {
|
||||||
|
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = "123",
|
user_id = "123",
|
||||||
instance_uri = baseUrl.toString(),
|
instance_uri = baseUrl.toString(),
|
||||||
username = "Testi",
|
username = "Testi",
|
||||||
display_name = "Testi Testo",
|
display_name = "Testi Testo",
|
||||||
avatar_static = "some_avatar_url",
|
avatar_static = "some_avatar_url",
|
||||||
isActive = true,
|
isActive = true,
|
||||||
accessToken = "token"
|
accessToken = "token",
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
@ -24,7 +24,6 @@ import io.reactivex.SingleObserver
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.functions.Function3
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
import kotlinx.android.synthetic.main.activity_login.*
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
@ -32,6 +31,7 @@ import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||||
since they do not depend on each other)
|
since they do not depend on each other)
|
||||||
|
@ -138,7 +138,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
loadingAnimation(true)
|
loadingAnimation(true)
|
||||||
|
|
||||||
pixelfedAPI = apiHolder.setDomain(normalizedDomain)
|
pixelfedAPI = PixelfedAPI.createFromUrl(normalizedDomain)
|
||||||
|
|
||||||
Single.zip(
|
Single.zip(
|
||||||
pixelfedAPI.registerApplication(
|
pixelfedAPI.registerApplication(
|
||||||
|
@ -251,12 +251,12 @@ class LoginActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Successful authorization
|
//Successful authorization
|
||||||
pixelfedAPI = apiHolder.setDomain(domain)
|
pixelfedAPI = PixelfedAPI.createFromUrl(domain)
|
||||||
|
|
||||||
//TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯
|
//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)
|
//Also, maybe find a nicer way to do this, this feels hacky (although it can work fine)
|
||||||
val nullInstance = Instance(null, null, null, null, null, null, null, null)
|
val nullInstance = Instance(null, null, null, null, null, null, null, null)
|
||||||
val nullToken = Token(null, null, null, null)
|
val nullToken = Token(null, null, null, null, null)
|
||||||
|
|
||||||
Single.zip(
|
Single.zip(
|
||||||
pixelfedAPI.instance().onErrorReturn { nullInstance },
|
pixelfedAPI.instance().onErrorReturn { nullInstance },
|
||||||
|
@ -280,7 +280,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
DBUtils.storeInstance(db, instance)
|
DBUtils.storeInstance(db, instance)
|
||||||
storeUser(token.access_token, instance.uri)
|
storeUser(token.access_token, token.refresh_token, clientId, clientSecret, instance.uri)
|
||||||
wipeSharedSettings()
|
wipeSharedSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun storeUser(accessToken: String, instance: String) {
|
private fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) {
|
||||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||||
.enqueue(object : Callback<Account> {
|
.enqueue(object : Callback<Account> {
|
||||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||||
|
@ -326,7 +326,10 @@ class LoginActivity : AppCompatActivity() {
|
||||||
user,
|
user,
|
||||||
instance,
|
instance,
|
||||||
activeUser = true,
|
activeUser = true,
|
||||||
accessToken = accessToken
|
accessToken = accessToken,
|
||||||
|
refreshToken = refreshToken,
|
||||||
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
)
|
)
|
||||||
apiHolder.setDomainToCurrentUser(db)
|
apiHolder.setDomainToCurrentUser(db)
|
||||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
@ -190,6 +189,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
if (hasInternet(applicationContext)) {
|
if (hasInternet(applicationContext)) {
|
||||||
val domain = user?.instance_uri.orEmpty()
|
val domain = user?.instance_uri.orEmpty()
|
||||||
val accessToken = user?.accessToken.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)
|
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||||
api.verifyCredentials("Bearer $accessToken")
|
api.verifyCredentials("Bearer $accessToken")
|
||||||
.enqueue(object : Callback<Account> {
|
.enqueue(object : Callback<Account> {
|
||||||
|
@ -199,7 +201,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
) {
|
) {
|
||||||
if (response.body() != null && response.isSuccessful) {
|
if (response.body() != null && response.isSuccessful) {
|
||||||
val account = response.body() as Account
|
val account = response.body() as Account
|
||||||
DBUtils.addUser(db, account, domain, accessToken = accessToken)
|
DBUtils.addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
||||||
fillDrawerAccountInfo(account.id!!)
|
fillDrawerAccountInfo(account.id!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,7 @@ interface PixelfedAPI {
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Deprecated(
|
fun createFromUrl(baseUrl: String): PixelfedAPI {
|
||||||
"Use the DI-d PixelfedAPIHolder instead",
|
|
||||||
ReplaceWith("apiHolder.api")
|
|
||||||
)
|
|
||||||
fun create(baseUrl: String): PixelfedAPI {
|
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
@ -52,11 +48,12 @@ interface PixelfedAPI {
|
||||||
fun obtainToken(
|
fun obtainToken(
|
||||||
@Field("client_id") client_id: String,
|
@Field("client_id") client_id: String,
|
||||||
@Field("client_secret") client_secret: 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("scope") scope: String? = "read",
|
||||||
@Field("code") code: String? = null,
|
@Field("code") code: String? = null,
|
||||||
@Field("grant_type") grant_type: String? = null
|
@Field("grant_type") grant_type: String? = null,
|
||||||
): Single<Token>
|
@Field("refresh_token") refresh_token: String? = null
|
||||||
|
): Single<Token>
|
||||||
|
|
||||||
// get instance configuration
|
// get instance configuration
|
||||||
@GET("/api/v1/instance")
|
@GET("/api/v1/instance")
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.h.pixeldroid.db.entities
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import com.h.pixeldroid.db.entities.InstanceDatabaseEntity
|
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "users",
|
tableName = "users",
|
||||||
|
@ -17,12 +16,15 @@ import com.h.pixeldroid.db.entities.InstanceDatabaseEntity
|
||||||
)],
|
)],
|
||||||
indices = [Index(value = ["instance_uri"])]
|
indices = [Index(value = ["instance_uri"])]
|
||||||
)
|
)
|
||||||
data class UserDatabaseEntity (
|
data class UserDatabaseEntity(
|
||||||
var user_id: String,
|
var user_id: String,
|
||||||
var instance_uri: String,
|
var instance_uri: String,
|
||||||
var username: String,
|
var username: String,
|
||||||
var display_name: String,
|
var display_name: String,
|
||||||
var avatar_static: String,
|
var avatar_static: String,
|
||||||
var isActive: Boolean,
|
var isActive: Boolean,
|
||||||
var accessToken: String
|
var accessToken: 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.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.db.AppDatabase
|
import com.h.pixeldroid.db.AppDatabase
|
||||||
|
import com.h.pixeldroid.db.entities.UserDatabaseEntity
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import okhttp3.*
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.lang.Exception
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -15,26 +18,51 @@ class APIModule{
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesAPIHolder(db: AppDatabase): PixelfedAPIHolder {
|
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()
|
private val intermediate: Retrofit.Builder = Retrofit.Builder()
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.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 {
|
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
|
val newAPI = intermediate
|
||||||
.baseUrl(domain)
|
.baseUrl(user.instance_uri)
|
||||||
.build().create(PixelfedAPI::class.java)
|
.client(OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user)).build())
|
||||||
|
.build().create(PixelfedAPI::class.java)
|
||||||
api = newAPI
|
api = newAPI
|
||||||
return newAPI
|
return newAPI
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PostFragment : BaseFragment() {
|
||||||
|
|
||||||
val user = db.userDao().getActiveUser()!!
|
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)
|
val holder = StatusViewHolder(root)
|
||||||
|
|
||||||
|
|
|
@ -89,8 +89,8 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
val uiModel = getItem(position) as Status
|
val uiModel = getItem(position) as Status
|
||||||
uiModel.let {
|
uiModel.let {
|
||||||
val instanceUri = db.userDao().getActiveUser()!!.instance_uri
|
val user = db.userDao().getActiveUser()!!
|
||||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope)
|
(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) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
val uiModel = getItem(position) as Status
|
val uiModel = getItem(position) as Status
|
||||||
uiModel.let {
|
uiModel.let {
|
||||||
val instanceUri = db.userDao().getActiveUser()!!.instance_uri
|
val user = db.userDao().getActiveUser()!!
|
||||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(instanceUri), db, lifecycleScope)
|
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.h.pixeldroid.objects
|
||||||
|
|
||||||
data class Token(
|
data class Token(
|
||||||
val access_token: String?,
|
val access_token: String?,
|
||||||
|
val refresh_token: String?,
|
||||||
val token_type: String?,
|
val token_type: String?,
|
||||||
val scope: String?,
|
val scope: String?,
|
||||||
val created_at: Int?
|
val created_at: Int?
|
||||||
|
|
|
@ -19,19 +19,23 @@ class DBUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true, accessToken: String) {
|
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||||
|
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
||||||
db.userDao().insertUser(
|
db.userDao().insertUser(
|
||||||
UserDatabaseEntity(
|
UserDatabaseEntity(
|
||||||
user_id = account.id!!,
|
user_id = account.id!!,
|
||||||
//make sure not to normalize to https when localhost, to allow testing
|
//make sure not to normalize to https when localhost, to allow testing
|
||||||
instance_uri = normalizeOrNot(instance_uri),
|
instance_uri = normalizeOrNot(instance_uri),
|
||||||
username = account.username!!,
|
username = account.username!!,
|
||||||
display_name = account.getDisplayName(),
|
display_name = account.getDisplayName(),
|
||||||
avatar_static = account.avatar_static.orEmpty(),
|
avatar_static = account.avatar_static.orEmpty(),
|
||||||
isActive = activeUser,
|
isActive = activeUser,
|
||||||
accessToken = accessToken
|
accessToken = accessToken,
|
||||||
)
|
refreshToken = refreshToken,
|
||||||
)
|
clientId = clientId,
|
||||||
|
clientSecret = clientSecret
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeInstance(db: AppDatabase, instance: Instance) {
|
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.api.PixelfedAPI
|
||||||
import com.h.pixeldroid.objects.*
|
import com.h.pixeldroid.objects.*
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import retrofit2.Call
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,9 +88,9 @@ class APIUnitTest {
|
||||||
val statusesHome: List<Status>
|
val statusesHome: List<Status>
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
statuses = PixelfedAPI.create("http://localhost:8089")
|
statuses = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||||
.timelinePublic(null, null, null, null, null)
|
.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)
|
.timelineHome("abc", null, null, null,null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +112,7 @@ class APIUnitTest {
|
||||||
.withHeader("Content-Type", "application/json")
|
.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}"""
|
.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")
|
.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow")
|
||||||
|
|
||||||
val application: Application = call.toFuture().get()
|
val application: Application = call.toFuture().get()
|
||||||
|
@ -136,6 +132,7 @@ class APIUnitTest {
|
||||||
.withHeader("Content-Type", "application/json")
|
.withHeader("Content-Type", "application/json")
|
||||||
.withBody("""{
|
.withBody("""{
|
||||||
"access_token": "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0",
|
"access_token": "ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0",
|
||||||
|
"refresh_token": "ZA-Yj3aBD8U8Cm7lKUp-sqfdsqfdqfsdfqds",
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
"scope": "read write follow push",
|
"scope": "read write follow push",
|
||||||
"created_at": 1573979017
|
"created_at": 1573979017
|
||||||
|
@ -144,7 +141,7 @@ class APIUnitTest {
|
||||||
val OAUTH_SCHEME = "oauth2redirect"
|
val OAUTH_SCHEME = "oauth2redirect"
|
||||||
val SCOPE = "read write follow"
|
val SCOPE = "read write follow"
|
||||||
val PACKAGE_ID = "com.h.pixeldroid"
|
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",
|
.obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc",
|
||||||
"authorization_code")
|
"authorization_code")
|
||||||
val token: Token = call.toFuture().get()
|
val token: Token = call.toFuture().get()
|
||||||
|
@ -152,7 +149,7 @@ class APIUnitTest {
|
||||||
assertEquals("Bearer", token.token_type)
|
assertEquals("Bearer", token.token_type)
|
||||||
assertEquals("read write follow push", token.scope)
|
assertEquals("read write follow push", token.scope)
|
||||||
assertEquals(1573979017, token.created_at)
|
assertEquals(1573979017, token.created_at)
|
||||||
assertEquals(Token("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", "Bearer", "read write follow push",1573979017), token)
|
assertEquals(Token("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", "ZA-Yj3aBD8U8Cm7lKUp-sqfdsqfdqfsdfqds","Bearer", "read write follow push",1573979017), token)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue