Skip to content
This repository was archived by the owner on Mar 19, 2024. It is now read-only.

Commit d8ac22d

Browse files
authored
Merge pull request #450 from owncloud/connection_validator
Connection validator
2 parents 351efb7 + 7c77c26 commit d8ac22d

File tree

20 files changed

+714
-356
lines changed

20 files changed

+714
-356
lines changed

owncloudComLibrary/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212
implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") {
1313
exclude module: "kotlin-reflect"
1414
}
15+
implementation 'org.apache.commons:commons-lang3:3.12.0'
1516
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
1617

1718
testImplementation 'junit:junit:4.13.2'
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.owncloud.android.lib.common
2+
3+
import android.accounts.AccountManager
4+
import android.accounts.AccountsException
5+
import android.content.Context
6+
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials
7+
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials
8+
import com.owncloud.android.lib.common.http.HttpConstants
9+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
10+
import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation
11+
import com.owncloud.android.lib.resources.status.GetRemoteStatusOperation
12+
import com.owncloud.android.lib.resources.status.RemoteServerInfo
13+
import org.apache.commons.lang3.exception.ExceptionUtils
14+
import timber.log.Timber
15+
import java.io.IOException
16+
import java.lang.Exception
17+
18+
class ConnectionValidator(
19+
val context: Context,
20+
val clearCookiesOnValidation: Boolean
21+
) {
22+
fun validate(baseClient: OwnCloudClient, singleSessionManager: SingleSessionManager): Boolean {
23+
try {
24+
var validationRetryCount = 0
25+
val client = OwnCloudClient(baseClient.baseUri, null, false, singleSessionManager)
26+
if (clearCookiesOnValidation) {
27+
client.clearCookies()
28+
} else {
29+
client.cookiesForBaseUri = baseClient.cookiesForBaseUri
30+
}
31+
32+
client.account = baseClient.account
33+
client.credentials = baseClient.credentials
34+
while (validationRetryCount < VALIDATION_RETRY_COUNT) {
35+
Timber.d("validationRetryCout %d", validationRetryCount)
36+
var successCounter = 0
37+
var failCounter = 0
38+
39+
client.setFollowRedirects(true)
40+
if (isOnwCloudStatusOk(client)) {
41+
successCounter++
42+
} else {
43+
failCounter++
44+
}
45+
46+
// 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
47+
if (baseClient.credentials !is OwnCloudAnonymousCredentials) {
48+
client.setFollowRedirects(false)
49+
val contentReply = canAccessRootFolder(client)
50+
if (contentReply.httpCode == HttpConstants.HTTP_OK) {
51+
if (contentReply.data == true) { //if data is true it means that the content reply was ok
52+
successCounter++
53+
} else {
54+
failCounter++
55+
}
56+
} else {
57+
failCounter++
58+
if (contentReply.httpCode == HttpConstants.HTTP_UNAUTHORIZED) {
59+
checkUnauthorizedAccess(client, singleSessionManager, contentReply.httpCode)
60+
}
61+
}
62+
}
63+
if (successCounter >= failCounter) {
64+
baseClient.credentials = client.credentials
65+
baseClient.cookiesForBaseUri = client.cookiesForBaseUri
66+
return true
67+
}
68+
validationRetryCount++
69+
}
70+
Timber.d("Could not authenticate or get valid data from owncloud")
71+
} catch (e: Exception) {
72+
Timber.d(ExceptionUtils.getStackTrace(e))
73+
}
74+
return false
75+
}
76+
77+
private fun isOnwCloudStatusOk(client: OwnCloudClient): Boolean {
78+
val reply = getOwnCloudStatus(client)
79+
// dont check status code. It currently relais on the broken redirect code of the owncloud client
80+
// TODO: Use okhttp redirect and add this check again
81+
// return reply.httpCode == HttpConstants.HTTP_OK &&
82+
return !reply.isException &&
83+
reply.data != null
84+
}
85+
86+
private fun getOwnCloudStatus(client: OwnCloudClient): RemoteOperationResult<RemoteServerInfo> {
87+
val remoteStatusOperation = GetRemoteStatusOperation()
88+
return remoteStatusOperation.execute(client)
89+
}
90+
91+
private fun canAccessRootFolder(client: OwnCloudClient): RemoteOperationResult<Boolean> {
92+
val checkPathExistenceRemoteOperation = CheckPathExistenceRemoteOperation("/", true)
93+
return checkPathExistenceRemoteOperation.execute(client)
94+
}
95+
96+
/**
97+
* Determines if credentials should be invalidated according the to the HTTPS status
98+
* of a network request just performed.
99+
*
100+
* @param httpStatusCode Result of the last request ran with the 'credentials' belows.
101+
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
102+
* cannot be invalidated with the given arguments.
103+
*/
104+
private fun shouldInvalidateAccountCredentials(credentials: OwnCloudCredentials, account: OwnCloudAccount, httpStatusCode: Int): Boolean {
105+
var shouldInvalidateAccountCredentials = httpStatusCode == HttpConstants.HTTP_UNAUTHORIZED
106+
shouldInvalidateAccountCredentials = shouldInvalidateAccountCredentials and // real credentials
107+
(credentials !is OwnCloudAnonymousCredentials)
108+
109+
// test if have all the needed to effectively invalidate ...
110+
shouldInvalidateAccountCredentials =
111+
shouldInvalidateAccountCredentials and (account.savedAccount != null)
112+
return shouldInvalidateAccountCredentials
113+
}
114+
115+
/**
116+
* Invalidates credentials stored for the given account in the system [AccountManager] and in
117+
* current [SingleSessionManager.getDefaultSingleton] instance.
118+
*
119+
*
120+
* [.shouldInvalidateAccountCredentials] should be called first.
121+
*
122+
*/
123+
private fun invalidateAccountCredentials(account: OwnCloudAccount, credentials: OwnCloudCredentials) {
124+
val am = AccountManager.get(context)
125+
am.invalidateAuthToken(
126+
account.savedAccount.type,
127+
credentials.authToken
128+
)
129+
am.clearPassword(account.savedAccount) // being strict, only needed for Basic Auth credentials
130+
}
131+
132+
/**
133+
* Checks the status code of an execution and decides if should be repeated with fresh credentials.
134+
*
135+
*
136+
* Invalidates current credentials if the request failed as anauthorized.
137+
*
138+
*
139+
* Refresh current credentials if possible, and marks a retry.
140+
*
141+
* @param status
142+
* @param repeatCounter
143+
* @return
144+
*/
145+
private fun checkUnauthorizedAccess(client: OwnCloudClient, singleSessionManager: SingleSessionManager, status: Int): Boolean {
146+
var credentialsWereRefreshed = false
147+
val account = client.account
148+
val credentials = account.credentials
149+
if (shouldInvalidateAccountCredentials(credentials, account, status)) {
150+
invalidateAccountCredentials(account, credentials)
151+
152+
if (credentials.authTokenCanBeRefreshed()) {
153+
try {
154+
// This command does the actual refresh
155+
account.loadCredentials(context)
156+
// if mAccount.getCredentials().length() == 0 --> refresh failed
157+
client.credentials = account.credentials
158+
credentialsWereRefreshed = true
159+
} catch (e: AccountsException) {
160+
Timber.e(
161+
e, "Error while trying to refresh auth token for %s\ntrace: %s",
162+
account.savedAccount.name,
163+
ExceptionUtils.getStackTrace(e)
164+
)
165+
} catch (e: IOException) {
166+
Timber.e(
167+
e, "Error while trying to refresh auth token for %s\ntrace: %s",
168+
account.savedAccount.name,
169+
ExceptionUtils.getStackTrace(e)
170+
)
171+
}
172+
if (!credentialsWereRefreshed) {
173+
// if credentials are not refreshed, client must be removed
174+
// from the OwnCloudClientManager to prevent it is reused once and again
175+
singleSessionManager.removeClientFor(account)
176+
}
177+
}
178+
// else: onExecute will finish with status 401
179+
}
180+
return credentialsWereRefreshed
181+
}
182+
183+
companion object {
184+
private val VALIDATION_RETRY_COUNT = 3
185+
}
186+
}

0 commit comments

Comments
 (0)