/* * 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() 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" } }