You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/auth/AuthStateManager.kt

138 lines
4.5 KiB
Kotlin

/*
* Copyright 2017 The AppAuth for Android Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.tasks.auth
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import dagger.hilt.android.qualifiers.ApplicationContext
import net.openid.appauth.*
import org.json.JSONException
import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.locks.ReentrantLock
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AuthStateManager @Inject constructor(@ApplicationContext private val context: Context) {
private val prefs = EncryptedSharedPreferences.create(
context,
STORE_NAME,
MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
private val prefsLock = ReentrantLock()
private val currentAuthState = AtomicReference<AuthState>()
fun signOut() {
// discard the authorization and token state, but retain the configuration and
// dynamic client registration (if applicable), to save from retrieving them again.
val currentState = current
val clearedState = currentState.authorizationServiceConfiguration
?.let { AuthState(it) }
?: return
if (currentState.lastRegistrationResponse != null) {
clearedState.update(currentState.lastRegistrationResponse)
}
replace(clearedState)
}
val current: AuthState
get() {
if (currentAuthState.get() != null) {
return currentAuthState.get()
}
val state = readState()
return if (currentAuthState.compareAndSet(null, state)) {
state
} else {
currentAuthState.get()
}
}
fun replace(state: AuthState): AuthState {
writeState(state)
currentAuthState.set(state)
return state
}
fun updateAfterAuthorization(
response: AuthorizationResponse?,
ex: AuthorizationException?
): AuthState {
val current = current
current.update(response, ex)
return replace(current)
}
fun updateAfterTokenResponse(
response: TokenResponse?,
ex: AuthorizationException?
): AuthState {
val current = current
current.update(response, ex)
return replace(current)
}
fun updateAfterRegistration(
response: RegistrationResponse?,
ex: AuthorizationException?
): AuthState {
val current = current
if (ex != null) {
return current
}
current.update(response)
return replace(current)
}
private fun readState(): AuthState {
prefsLock.lock()
return try {
val currentState = prefs.getString(KEY_STATE, null)
?: return AuthState()
try {
AuthState.jsonDeserialize(currentState)
} catch (ex: JSONException) {
Timber.w("Failed to deserialize stored auth state - discarding")
AuthState()
}
} finally {
prefsLock.unlock()
}
}
private fun writeState(state: AuthState?) {
prefsLock.lock()
try {
val editor = prefs.edit()
if (state == null) {
editor.remove(KEY_STATE)
} else {
editor.putString(KEY_STATE, state.jsonSerializeString())
}
check(editor.commit()) { "Failed to write state to shared prefs" }
} finally {
prefsLock.unlock()
}
}
companion object {
private const val STORE_NAME = "AuthState"
private const val KEY_STATE = "state"
}
}