Skip to content

Commit dc63785

Browse files
committed
Fix: Critical login state and authentication issues
1 parent f093c8c commit dc63785

5 files changed

Lines changed: 90 additions & 15 deletions

File tree

opencloudApp/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@
236236
android:name=".presentation.authentication.LoginActivity"
237237
android:exported="true"
238238
android:label="@string/login_label"
239-
android:launchMode="singleTask"
239+
android:launchMode="singleTop"
240240
android:theme="@style/Theme.openCloud.Toolbar">
241241
<intent-filter>
242242
<action android:name="android.intent.action.VIEW" />

opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import android.widget.CheckBox
3939
import androidx.appcompat.app.AlertDialog
4040
import androidx.core.content.pm.PackageInfoCompat
4141
import eu.opencloud.android.data.providers.implementation.OCSharedPreferencesProvider
42-
import eu.opencloud.android.datamodel.ThumbnailsCacheManager
42+
4343
import eu.opencloud.android.db.PreferenceManager
4444
import eu.opencloud.android.dependecyinjection.commonModule
4545
import eu.opencloud.android.dependecyinjection.localDataSourceModule
@@ -99,6 +99,15 @@ class MainApp : Application() {
9999

100100
appContext = applicationContext
101101

102+
// Ensure Logcat shows Timber logs in debug builds
103+
if (BuildConfig.DEBUG) {
104+
try {
105+
Timber.plant(Timber.DebugTree())
106+
} catch (_: Throwable) {
107+
// ignore if already planted
108+
}
109+
}
110+
102111
startLogsIfEnabled()
103112

104113
DebugInjector.injectDebugTools(appContext)
@@ -108,7 +117,9 @@ class MainApp : Application() {
108117
SingleSessionManager.setUserAgent(userAgent)
109118

110119
// initialise thumbnails cache on background thread
111-
ThumbnailsCacheManager.InitDiskCacheTask().execute()
120+
// initialise thumbnails cache on background thread
121+
122+
112123

113124
initDependencyInjection()
114125

opencloudApp/src/main/java/eu/opencloud/android/operations/SyncProfileOperation.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ import android.accounts.Account
2323
import android.accounts.AccountManager
2424
import eu.opencloud.android.MainApp.Companion.appContext
2525
import eu.opencloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
26-
import eu.opencloud.android.domain.user.usecases.GetUserAvatarAsyncUseCase
2726
import eu.opencloud.android.domain.user.usecases.GetUserInfoAsyncUseCase
2827
import eu.opencloud.android.domain.user.usecases.RefreshUserQuotaFromServerAsyncUseCase
2928
import eu.opencloud.android.lib.common.accounts.AccountUtils
30-
import eu.opencloud.android.presentation.avatar.AvatarManager
29+
3130
import kotlinx.coroutines.CoroutineScope
3231
import kotlinx.coroutines.Dispatchers
3332
import kotlinx.coroutines.launch
@@ -79,12 +78,8 @@ class SyncProfileOperation(
7978
}
8079
val shouldFetchAvatar = storedCapabilities?.isFetchingAvatarAllowed() ?: true
8180
if (shouldFetchAvatar) {
82-
val getUserAvatarAsyncUseCase: GetUserAvatarAsyncUseCase by inject()
83-
val userAvatarResult = getUserAvatarAsyncUseCase(GetUserAvatarAsyncUseCase.Params(account.name))
84-
AvatarManager().handleAvatarUseCaseResult(account, userAvatarResult)
85-
if (userAvatarResult.isSuccess) {
86-
Timber.d("Avatar synchronized for account ${account.name}")
87-
}
81+
// Avatar fetching is now handled by Coil on demand
82+
Timber.d("Avatar sync handled by Coil for account ${account.name}")
8883
} else {
8984
Timber.d("Avatar for this account: ${account.name} won't be synced due to capabilities ")
9085
}

opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AuthenticationViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ class AuthenticationViewModel(
7272
private val contextProvider: ContextProvider,
7373
) : ViewModel() {
7474

75-
val codeVerifier: String = OAuthUtils().generateRandomCodeVerifier()
76-
val codeChallenge: String = OAuthUtils().generateCodeChallenge(codeVerifier)
77-
val oidcState: String = OAuthUtils().generateRandomState()
75+
var codeVerifier: String = OAuthUtils().generateRandomCodeVerifier()
76+
var codeChallenge: String = OAuthUtils().generateCodeChallenge(codeVerifier)
77+
var oidcState: String = OAuthUtils().generateRandomState()
7878

7979
private val _legacyWebfingerHost = MediatorLiveData<Event<UIResult<String>>>()
8080
val legacyWebfingerHost: LiveData<Event<UIResult<String>>> = _legacyWebfingerHost

opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
9393
import timber.log.Timber
9494
import java.io.File
9595

96+
97+
private const val KEY_SERVER_BASE_URL = "KEY_SERVER_BASE_URL"
98+
private const val KEY_OIDC_SUPPORTED = "KEY_OIDC_SUPPORTED"
99+
private const val KEY_CODE_VERIFIER = "KEY_CODE_VERIFIER"
100+
private const val KEY_CODE_CHALLENGE = "KEY_CODE_CHALLENGE"
101+
private const val KEY_OIDC_STATE = "KEY_OIDC_STATE"
102+
96103
class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrustedCertListener, SecurityEnforced {
97104

98105
private val authenticationViewModel by viewModel<AuthenticationViewModel>()
@@ -114,6 +121,16 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
114121
private var resultBundle: Bundle? = null
115122

116123
override fun onCreate(savedInstanceState: Bundle?) {
124+
if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) {
125+
if (!isTaskRoot) {
126+
val newIntent = Intent(this, LoginActivity::class.java)
127+
newIntent.data = intent.data
128+
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
129+
startActivity(newIntent)
130+
finish()
131+
return
132+
}
133+
}
117134
super.onCreate(savedInstanceState)
118135

119136
checkPasscodeEnforced(this)
@@ -136,6 +153,11 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
136153
}
137154
} else {
138155
authTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE)
156+
savedInstanceState.getString(KEY_SERVER_BASE_URL)?.let { serverBaseUrl = it }
157+
oidcSupported = savedInstanceState.getBoolean(KEY_OIDC_SUPPORTED)
158+
savedInstanceState.getString(KEY_CODE_VERIFIER)?.let { authenticationViewModel.codeVerifier = it }
159+
savedInstanceState.getString(KEY_CODE_CHALLENGE)?.let { authenticationViewModel.codeChallenge = it }
160+
savedInstanceState.getString(KEY_OIDC_STATE)?.let { authenticationViewModel.oidcState = it }
139161
}
140162

141163
// UI initialization
@@ -164,6 +186,17 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
164186
binding.accountUsername.setText(username)
165187
}
166188
}
189+
} else {
190+
// Restore UI state
191+
if (::serverBaseUrl.isInitialized && serverBaseUrl.isNotEmpty()) {
192+
binding.hostUrlInput.setText(serverBaseUrl)
193+
194+
if (authTokenType == BASIC_TOKEN_TYPE) {
195+
showOrHideBasicAuthFields(shouldBeVisible = true)
196+
} else if (authTokenType == OAUTH_TOKEN_TYPE) {
197+
showOrHideBasicAuthFields(shouldBeVisible = false)
198+
}
199+
}
167200
}
168201

169202
binding.root.filterTouchesWhenObscured =
@@ -194,10 +227,17 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
194227
accountAuthenticatorResponse?.onRequestContinued()
195228

196229
initLiveDataObservers()
230+
231+
if (intent.data != null && (intent.data?.getQueryParameter("code") != null || intent.data?.getQueryParameter("error") != null)) {
232+
if (savedInstanceState == null) {
233+
restoreAuthState()
234+
}
235+
handleGetAuthorizationCodeResponse(intent)
236+
}
197237
}
198238

199239
private fun handleDeepLink() {
200-
if (intent.data != null) {
240+
if (intent.data != null && intent.data?.getQueryParameter("code") == null && intent.data?.getQueryParameter("error") == null) {
201241
authenticationViewModel.launchedFromDeepLink = true
202242
if (getAccounts(baseContext).isNotEmpty()) {
203243
launchFileDisplayActivity()
@@ -469,6 +509,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
469509
setResult(Activity.RESULT_OK, intent)
470510

471511
authenticationViewModel.discoverAccount(accountName = accountName, discoveryNeeded = loginAction == ACTION_CREATE)
512+
clearAuthState()
472513
}
473514

474515
private fun loginIsLoading() {
@@ -498,6 +539,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
498539
}
499540
}
500541
}
542+
clearAuthState()
501543
}
502544

503545
/**
@@ -553,6 +595,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
553595
)
554596

555597
try {
598+
saveAuthState()
556599
customTabsIntent.launchUrl(
557600
this,
558601
authorizationEndpointUri
@@ -853,6 +896,10 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
853896
override fun onSaveInstanceState(outState: Bundle) {
854897
super.onSaveInstanceState(outState)
855898
outState.putString(KEY_AUTH_TOKEN_TYPE, authTokenType)
899+
if (::serverBaseUrl.isInitialized) {
900+
outState.putString(KEY_SERVER_BASE_URL, serverBaseUrl)
901+
}
902+
outState.putBoolean(KEY_OIDC_SUPPORTED, oidcSupported)
856903
}
857904

858905
override fun finish() {
@@ -873,4 +920,26 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
873920
override fun optionLockSelected(type: LockType) {
874921
manageOptionLockSelected(type)
875922
}
923+
924+
private fun saveAuthState() {
925+
val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE)
926+
prefs.edit().apply {
927+
putString(KEY_CODE_VERIFIER, authenticationViewModel.codeVerifier)
928+
putString(KEY_CODE_CHALLENGE, authenticationViewModel.codeChallenge)
929+
putString(KEY_OIDC_STATE, authenticationViewModel.oidcState)
930+
apply()
931+
}
932+
}
933+
934+
private fun restoreAuthState() {
935+
val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE)
936+
prefs.getString(KEY_CODE_VERIFIER, null)?.let { authenticationViewModel.codeVerifier = it }
937+
prefs.getString(KEY_CODE_CHALLENGE, null)?.let { authenticationViewModel.codeChallenge = it }
938+
prefs.getString(KEY_OIDC_STATE, null)?.let { authenticationViewModel.oidcState = it }
939+
}
940+
941+
private fun clearAuthState() {
942+
val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE)
943+
prefs.edit().clear().apply()
944+
}
876945
}

0 commit comments

Comments
 (0)