Skip to content

Commit

Permalink
Merge branch 'release/1.1.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
sgrimault committed Apr 14, 2021
2 parents 9ec924f + dc0de39 commit b7de1bf
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 153 deletions.
4 changes: 2 additions & 2 deletions sync/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: "kotlin-kapt"

version = "1.1.6"
version = "1.1.7"

android {
compileSdkVersion 29
Expand Down Expand Up @@ -72,7 +72,7 @@ dependencies {

implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.5.0'
implementation 'com.google.android.material:material:1.4.0-alpha02'
implementation 'com.google.code.gson:gson:2.8.6'
Expand Down
65 changes: 35 additions & 30 deletions sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import fr.geonature.sync.api.model.User
import fr.geonature.sync.auth.AuthManager
import fr.geonature.sync.util.SettingsUtils.getGeoNatureServerUrl
import fr.geonature.sync.util.SettingsUtils.getTaxHubServerUrl
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody
Expand Down Expand Up @@ -48,6 +54,35 @@ class GeoNatureAPIClient private constructor(

val client = OkHttpClient
.Builder()
.cookieJar(object : CookieJar {
override fun saveFromResponse(
url: HttpUrl,
cookies: MutableList<Cookie>
) {
cookies
.firstOrNull()
?.also {
authManager.setCookie(it)
}
}

override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
return authManager
.getCookie()
?.let {
if (it.expiresAt() < System.currentTimeMillis()) {
GlobalScope.launch(IO) {
authManager.logout()
}

return@let mutableListOf()
}

mutableListOf(it)
}
?: mutableListOf()
}
})
.connectTimeout(
120,
TimeUnit.SECONDS
Expand All @@ -61,36 +96,6 @@ class GeoNatureAPIClient private constructor(
TimeUnit.SECONDS
)
.addInterceptor(loggingInterceptor)
// save cookie interceptor
.addInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())

originalResponse
.headers("Set-Cookie")
.firstOrNull()
?.also {
authManager.setCookie(it)
}

originalResponse
}
// set cookie interceptor
.addInterceptor { chain ->
val builder = chain
.request()
.newBuilder()

authManager
.getCookie()
?.also {
builder.addHeader(
"Cookie",
it
)
}

chain.proceed(builder.build())
}
.build()

geoNatureService = Retrofit
Expand Down
129 changes: 72 additions & 57 deletions sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import androidx.preference.PreferenceManager
import fr.geonature.sync.api.model.AuthLogin
import fr.geonature.sync.auth.io.AuthLoginJsonReader
import fr.geonature.sync.auth.io.AuthLoginJsonWriter
import fr.geonature.sync.auth.io.CookieHelper
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Cookie
import java.util.Calendar

/**
Expand All @@ -23,8 +25,7 @@ import java.util.Calendar
*/
class AuthManager private constructor(applicationContext: Context) {

internal val preferenceManager: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(applicationContext)
internal val preferenceManager: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
private val authLoginJsonReader = AuthLoginJsonReader()
private val authLoginJsonWriter = AuthLoginJsonWriter()

Expand All @@ -38,75 +39,86 @@ class AuthManager private constructor(applicationContext: Context) {
}
}

fun setCookie(cookie: String) {
preferenceManager.edit()
fun setCookie(cookie: Cookie) {
preferenceManager
.edit()
.putString(
KEY_PREFERENCE_COOKIE,
cookie
CookieHelper.serialize(cookie)
)
.apply()
}

fun getCookie(): String? {
return preferenceManager.getString(
KEY_PREFERENCE_COOKIE,
null
)
fun getCookie(): Cookie? {
return runCatching {
preferenceManager
.getString(
KEY_PREFERENCE_COOKIE,
null
)
?.let { CookieHelper.deserialize(it) }
}.getOrNull()
}

suspend fun getAuthLogin(): AuthLogin? = withContext(IO) {
val authLoginAsJson = preferenceManager.getString(
KEY_PREFERENCE_AUTH_LOGIN,
null
)

if (authLoginAsJson.isNullOrBlank()) {
_authLogin.postValue(null)
return@withContext null
}

authLoginJsonReader.read(authLoginAsJson)
.let {
if (it?.expires?.before(Calendar.getInstance().time) == true) {
logout()
return@let null
}
suspend fun getAuthLogin(): AuthLogin? =
withContext(IO) {
val authLoginAsJson = preferenceManager.getString(
KEY_PREFERENCE_AUTH_LOGIN,
null
)

_authLogin.postValue(it)
it
if (authLoginAsJson.isNullOrBlank()) {
_authLogin.postValue(null)
return@withContext null
}
}

suspend fun setAuthLogin(authLogin: AuthLogin): Boolean = withContext(IO) {
val authLoginAsJson = authLoginJsonWriter.write(authLogin)
authLoginJsonReader
.read(authLoginAsJson)
.let {
if (it?.expires?.before(Calendar.getInstance().time) == true) {
logout()
return@let null
}

if (authLoginAsJson.isNullOrBlank()) {
_authLogin.postValue(null)
return@withContext false
_authLogin.postValue(it)
it
}
}

preferenceManager.edit()
.putString(
KEY_PREFERENCE_AUTH_LOGIN,
authLoginAsJson
)
.commit()
.also {
_authLogin.postValue(if (it) authLogin else null)
suspend fun setAuthLogin(authLogin: AuthLogin): Boolean =
withContext(IO) {
val authLoginAsJson = authLoginJsonWriter.write(authLogin)

if (authLoginAsJson.isNullOrBlank()) {
_authLogin.postValue(null)
return@withContext false
}
}

suspend fun logout(): Boolean = withContext(IO) {
preferenceManager.edit()
.remove(KEY_PREFERENCE_COOKIE)
.remove(KEY_PREFERENCE_AUTH_LOGIN)
.commit()
.also {
if (it) {
_authLogin.postValue(null)
preferenceManager
.edit()
.putString(
KEY_PREFERENCE_AUTH_LOGIN,
authLoginAsJson
)
.commit()
.also {
_authLogin.postValue(if (it) authLogin else null)
}
}
}
}

suspend fun logout(): Boolean =
withContext(IO) {
preferenceManager
.edit()
.remove(KEY_PREFERENCE_COOKIE)
.remove(KEY_PREFERENCE_AUTH_LOGIN)
.commit()
.also {
if (it) {
_authLogin.postValue(null)
}
}
}

companion object {
private const val KEY_PREFERENCE_COOKIE = "key_preference_cookie"
Expand All @@ -122,8 +134,11 @@ class AuthManager private constructor(applicationContext: Context) {
* @return The singleton instance of [AuthManager].
*/
@Suppress("UNCHECKED_CAST")
fun getInstance(applicationContext: Context): AuthManager = INSTANCE ?: synchronized(this) {
INSTANCE ?: AuthManager(applicationContext).also { INSTANCE = it }
}
fun getInstance(applicationContext: Context): AuthManager =
INSTANCE
?: synchronized(this) {
INSTANCE
?: AuthManager(applicationContext).also { INSTANCE = it }
}
}
}
66 changes: 66 additions & 0 deletions sync/src/main/java/fr/geonature/sync/auth/io/CookieHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package fr.geonature.sync.auth.io

import fr.geonature.sync.util.hexStringToByteArray
import fr.geonature.sync.util.toHex
import okhttp3.Cookie
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream

/**
* Serialize/Deserialize Cookie.
*
* @author S. Grimault
*/
object CookieHelper {

@JvmStatic
@Throws(IOException::class)
fun serialize(cookie: Cookie): String {
return ByteArrayOutputStream()
.also {
ObjectOutputStream(it).apply {
writeObject(cookie.name())
writeObject(cookie.value())
writeObject(cookie.domain())
writeObject(cookie.path())
writeBoolean(cookie.secure())
writeBoolean(cookie.httpOnly())
writeBoolean(cookie.hostOnly())
writeLong(if (cookie.persistent()) cookie.expiresAt() else -1)
close()
}
}
.toByteArray()
.toHex()
}

@JvmStatic
@Throws(IOException::class)
fun deserialize(hexString: String): Cookie {
return ObjectInputStream(ByteArrayInputStream(hexString.hexStringToByteArray())).let {
val builder = Cookie.Builder()
builder.name(it.readObject() as String)
builder.value(it.readObject() as String)

val domain = it.readObject() as String

builder.path(it.readObject() as String)

if (it.readBoolean()) builder.secure()
if (it.readBoolean()) builder.httpOnly()
if (it.readBoolean()) builder.hostOnlyDomain(domain) else builder.domain(domain)

it
.readLong()
.takeIf { expiresAt -> expiresAt > 0 }
?.also { expiresAt ->
builder.expiresAt(expiresAt)
}

builder.build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application
)
)]

// this work info is not scheduled or not running: the current worker is done
if (workInfo.state !in arrayListOf(
WorkInfo.State.ENQUEUED,
WorkInfo.State.RUNNING
)
) {
currentSyncWorkerId = null
}

DataSyncStatus(
workInfo.state,
workInfo.progress.getString(DataSyncWorker.KEY_SYNC_MESSAGE)
Expand Down
12 changes: 12 additions & 0 deletions sync/src/main/java/fr/geonature/sync/sync/worker/DataSyncWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import fr.geonature.sync.MainApplication
import fr.geonature.sync.R
import fr.geonature.sync.api.GeoNatureAPIClient
import fr.geonature.sync.api.model.User
import fr.geonature.sync.auth.AuthManager
import fr.geonature.sync.data.LocalDatabase
import fr.geonature.sync.settings.AppSettings
import fr.geonature.sync.sync.DataSyncManager
Expand Down Expand Up @@ -57,12 +58,23 @@ class DataSyncWorker(
appContext,
workerParams
) {
private val authManager: AuthManager = AuthManager.getInstance(applicationContext)
private val dataSyncManager = DataSyncManager.getInstance(applicationContext)
private val workManager = WorkManager.getInstance(applicationContext)

override suspend fun doWork(): Result {
val startTime = Date()

// not connected: abort
if (authManager.getAuthLogin() == null) {
return Result.failure(
workData(
applicationContext.getString(R.string.sync_error_server_not_connected),
ServerStatus.UNAUTHORIZED
)
)
}

val geoNatureAPIClient = GeoNatureAPIClient.instance(applicationContext)
?: return Result.failure(
workData(applicationContext.getString(R.string.sync_error_server_url_configuration))
Expand Down
Loading

0 comments on commit b7de1bf

Please sign in to comment.