From e27a968ddb3b6d0e54a53d6b0a36935a4f31cc54 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Tue, 14 Sep 2021 14:36:49 +0200 Subject: [PATCH] add update authToken code to connectionValidator --- .../android/lib/common/ConnectionValidator.kt | 103 +++++++++++++++++- .../android/lib/common/OwnCloudClient.java | 19 ++-- .../lib/common/SingleSessionManager.java | 9 +- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/ConnectionValidator.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/ConnectionValidator.kt index 70ee7136..4a932058 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/ConnectionValidator.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/ConnectionValidator.kt @@ -1,31 +1,37 @@ package com.owncloud.android.lib.common +import android.accounts.AccountManager +import android.accounts.AccountsException +import android.content.Context import com.owncloud.android.lib.common.authentication.OwnCloudCredentials import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials import com.owncloud.android.lib.common.http.HttpConstants -import com.owncloud.android.lib.common.http.methods.HttpBaseMethod import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation import com.owncloud.android.lib.resources.status.RemoteServerInfo import org.apache.commons.lang3.exception.ExceptionUtils import timber.log.Timber +import java.io.IOException import java.lang.Exception class ConnectionValidator ( + val context: Context, val clearCookiesOnValidation: Boolean ){ - fun validate(baseClient: OwnCloudClient): Boolean { + fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager): Boolean { try { var validationRetryCount = 0 - val client = OwnCloudClient(baseClient.baseUri, null, false) + val client = OwnCloudClient(baseClient.baseUri, null, false, singleSessionManager) if (clearCookiesOnValidation) { client.clearCookies(); } else { client.cookiesForBaseUri = baseClient.cookiesForBaseUri } + client.account = baseClient.account client.credentials = baseClient.credentials while (validationRetryCount < 5) { Timber.d("+++++++++++++++++++++++++++++++++++++ validationRetryCout %d", validationRetryCount) @@ -40,7 +46,7 @@ class ConnectionValidator ( } // Skip the part where we try to check if we can access the parts where we have to be logged in... if we are not logged in - if(baseClient.credentials !is OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials) { + if(baseClient.credentials !is OwnCloudAnonymousCredentials) { client.setFollowRedirects(false) val contentReply = canAccessRootFolder(client) if (contentReply.httpCode == HttpConstants.HTTP_OK) { @@ -51,8 +57,8 @@ class ConnectionValidator ( } } else { failCounter++ - if (contentReply.hashCode() == HttpConstants.HTTP_UNAUTHORIZED) { - triggerAuthRefresh() + if (contentReply.httpCode == HttpConstants.HTTP_UNAUTHORIZED) { + checkUnauthorizedAccess(client, singleSessionManager, contentReply.httpCode) } } } @@ -92,4 +98,89 @@ class ConnectionValidator ( val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation("/", true) return checkPathExistenceRemoteOperation.execute(client) } + + /** + * Determines if credentials should be invalidated according the to the HTTPS status + * of a network request just performed. + * + * @param httpStatusCode Result of the last request ran with the 'credentials' belows. + * @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or + * cannot be invalidated with the given arguments. + */ + private fun shouldInvalidateAccountCredentials(credentials: OwnCloudCredentials, account: OwnCloudAccount, httpStatusCode: Int): Boolean { + var shouldInvalidateAccountCredentials = httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED + shouldInvalidateAccountCredentials = shouldInvalidateAccountCredentials and // real credentials + (credentials !is OwnCloudAnonymousCredentials) + + // test if have all the needed to effectively invalidate ... + shouldInvalidateAccountCredentials = + shouldInvalidateAccountCredentials and (account.savedAccount != null) + return shouldInvalidateAccountCredentials + } + + /** + * Invalidates credentials stored for the given account in the system [AccountManager] and in + * current [SingleSessionManager.getDefaultSingleton] instance. + * + * + * [.shouldInvalidateAccountCredentials] should be called first. + * + */ + private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) { + val am = AccountManager.get(context) + am.invalidateAuthToken( + account.savedAccount.type, + credentials.authToken + ) + am.clearPassword(account.savedAccount) // being strict, only needed for Basic Auth credentials + } + + /** + * Checks the status code of an execution and decides if should be repeated with fresh credentials. + * + * + * Invalidates current credentials if the request failed as anauthorized. + * + * + * Refresh current credentials if possible, and marks a retry. + * + * @param status + * @param repeatCounter + * @return + */ + private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean { + var credentialsWereRefreshed = false + val account = client.account + val credentials = account.credentials + if (shouldInvalidateAccountCredentials(credentials, account, status)) { + invalidateAccountCredentials(account, credentials) + if (credentials.authTokenCanBeRefreshed()) { + try { + account.loadCredentials(context) + // if mAccount.getCredentials().length() == 0 --> refresh failed + client.credentials = account.credentials + credentialsWereRefreshed = true + } catch (e: AccountsException) { + Timber.e( + e, "Error while trying to refresh auth token for %s\ntrace: %s", + account.savedAccount.name, + ExceptionUtils.getStackTrace(e) + ) + } catch (e: IOException) { + Timber.e( + e, "Error while trying to refresh auth token for %s\ntrace: %s", + account.savedAccount.name, + ExceptionUtils.getStackTrace(e) + ) + } + if (!credentialsWereRefreshed) { + // if credentials are not refreshed, client must be removed + // from the OwnCloudClientManager to prevent it is reused once and again + singleSessionManager.removeClientFor(account) + } + } + // else: onExecute will finish with status 401 + } + return credentialsWereRefreshed + } } \ No newline at end of file diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java index 330a09fa..a1bb2526 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/OwnCloudClient.java @@ -59,7 +59,6 @@ public class OwnCloudClient extends HttpClient { public static final String STATUS_PATH = "/status.php"; private static final String WEBDAV_UPLOADS_PATH_4_0 = "/remote.php/dav/uploads/"; private static final int MAX_REDIRECTIONS_COUNT = 5; - private static final int MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS = 1; private static int sIntanceCounter = 0; private OwnCloudCredentials mCredentials = null; @@ -80,12 +79,13 @@ public class OwnCloudClient extends HttpClient { private boolean mFollowRedirects = false; - public OwnCloudClient(Uri baseUri, ConnectionValidator connectionValidator, boolean synchronizeRequests) { + public OwnCloudClient(Uri baseUri, ConnectionValidator connectionValidator, boolean synchronizeRequests, SingleSessionManager singleSessionManager) { if (baseUri == null) { throw new IllegalArgumentException("Parameter 'baseUri' cannot be NULL"); } mBaseUri = baseUri; mSynchronizeRequests = synchronizeRequests; + mSingleSessionManager = singleSessionManager; mInstanceNumber = sIntanceCounter++; Timber.d("#" + mInstanceNumber + "Creating OwnCloudClient"); @@ -134,8 +134,10 @@ public class OwnCloudClient extends HttpClient { stacklog(status, method); if (mConnectionValidator != null && - status == HttpConstants.HTTP_MOVED_TEMPORARILY) { - retry = mConnectionValidator.validate(this); // retry on success fail on no success + (status == HttpConstants.HTTP_MOVED_TEMPORARILY || + (!(mCredentials instanceof OwnCloudAnonymousCredentials) && + status == HttpConstants.HTTP_UNAUTHORIZED))) { + retry = mConnectionValidator.validate(this, mSingleSessionManager); // retry on success fail on no success } else if (mFollowRedirects) { status = followRedirection(method).getLastStatus(); } @@ -145,8 +147,8 @@ public class OwnCloudClient extends HttpClient { if (repeatWithFreshCredentials) { repeatCounter++; } - */ + } while (retry); return status; @@ -163,6 +165,7 @@ public class OwnCloudClient extends HttpClient { "\nMethod: " + method.toString() + "\nUrl: " + method.getHttpUrl() + "\nCookeis: " + getCookiesForBaseUri().toString() + + "\nCredentials type: " + mCredentials.getClass().toString() + "\ntrace: " + ExceptionUtils.getStackTrace(e) + "---------------------------"); } @@ -336,8 +339,8 @@ public class OwnCloudClient extends HttpClient { } public List getCookiesForBaseUri() { - return getOkHttpClient().cookieJar().loadForRequest( - HttpUrl.parse(mBaseUri.toString())); + return getOkHttpClient().cookieJar().loadForRequest( + HttpUrl.parse(mBaseUri.toString())); } public OwnCloudVersion getOwnCloudVersion() { @@ -374,7 +377,7 @@ public class OwnCloudClient extends HttpClient { invalidateAccountCredentials(); if (getCredentials().authTokenCanBeRefreshed() && - repeatCounter < MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS) { + repeatCounter < 1) { try { mAccount.loadCredentials(getContext()); // if mAccount.getCredentials().length() == 0 --> refresh failed diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java index 225c48b0..e2c75c34 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/SingleSessionManager.java @@ -77,9 +77,8 @@ public class SingleSessionManager { sUserAgent = userAgent; } - private static OwnCloudClient createOwnCloudClient(Uri uri, Context context, boolean followRedirects, ConnectionValidator connectionValidator) { - OwnCloudClient client = new OwnCloudClient(uri, connectionValidator, true); - client.setFollowRedirects(followRedirects); + private static OwnCloudClient createOwnCloudClient(Uri uri, Context context, ConnectionValidator connectionValidator, SingleSessionManager singleSessionManager) { + OwnCloudClient client = new OwnCloudClient(uri, connectionValidator, true, singleSessionManager); HttpClient.setContext(context); return client; @@ -132,8 +131,8 @@ public class SingleSessionManager { client = createOwnCloudClient( account.getBaseUri(), context.getApplicationContext(), - true, - connectionValidator); // TODO remove dependency on OwnCloudClientFactory + connectionValidator, + this); //the next two lines are a hack because okHttpclient is used as a singleton instead of being an //injected instance that can be deleted when required