1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-07 16:06:08 +00:00

Allow client objects to decide if RemoteOperations should silently refresh access token when expired

This commit is contained in:
David A. Velasco 2017-08-02 18:43:07 +02:00
parent c04f6e3463
commit a4386bb4fe
25 changed files with 170 additions and 73 deletions

View File

@ -29,6 +29,8 @@ import java.io.IOException;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;

View File

@ -47,7 +47,9 @@ import org.apache.commons.httpclient.params.HttpParams;
import android.net.Uri; import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials; 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.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.network.RedirectionPath; import com.owncloud.android.lib.common.network.RedirectionPath;
import com.owncloud.android.lib.common.network.WebdavUtils; import com.owncloud.android.lib.common.network.WebdavUtils;

View File

@ -41,6 +41,7 @@ import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.network.NetworkUtils;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.lib.resources.status.OwnCloudVersion;

View File

@ -40,6 +40,7 @@ import android.util.Log;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
/** /**

View File

@ -37,9 +37,12 @@ import android.accounts.OperationCanceledException;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.lib.resources.status.OwnCloudVersion;
@ -280,6 +283,72 @@ public class AccountUtils {
} }
} }
/**
* Determines if credentials should be invalidated for the given account, according the to the result
* of an operation just run through the given client.
*
* @param result Result of the last remote operation ran through 'client' below.
* @param client {@link OwnCloudClient} that run last remote operation, resulting in 'result' above.
* @param account {@link Account} storing credentials used to run the last operation.
* @param context Android context, needed to access {@link AccountManager}; method will return false
* if NULL, since invalidation of credentials is just not possible if no context is
* available.
* @return 'True' if credentials should and might be invalidated, 'false' if shouldn't or
* cannot be invalidated with the given arguments.
*/
public static boolean shouldInvalidateAccountCredentials(
RemoteOperationResult result,
OwnCloudClient client,
Account account,
Context context) {
boolean should = RemoteOperationResult.ResultCode.UNAUTHORIZED.equals(result.getCode());
// invalid credentials
should &= (client.getCredentials() != null && // real credentials
!(client.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials));
should &= (account != null && context != null); // have all the needed to effectively invalidate
return should;
}
/**
* Invalidates credentials stored for the given account in the system {@link AccountManager} and in
* current {@link OwnCloudClientManagerFactory#getDefaultSingleton()} instance.
*
* {@link AccountUtils#shouldInvalidateAccountCredentials(RemoteOperationResult, OwnCloudClient, Account, Context)}
* should be called first.
*
* @param client {@link OwnCloudClient} that run last remote operation.
* @param account {@link Account} storing credentials to invalidate.
* @param context Android context, needed to access {@link AccountManager}.
*
* @return 'True' if invalidation was successful, 'false' otherwise.
*/
public static boolean invalidateAccountCredentials(
OwnCloudClient client,
Account account,
Context context
) {
try {
OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
OwnCloudClientManagerFactory.getDefaultSingleton().
removeClientFor(ocAccount); // to prevent nobody else is provided this client
AccountManager am = AccountManager.get(context);
am.invalidateAuthToken(
account.type,
client.getCredentials().getAuthToken()
);
am.clearPassword(account); // being strict, only needed for Basic Auth credentials
return true;
} catch (AccountUtils.AccountNotFoundException e) {
Log_OC.e(TAG, "Account was deleted from AccountManager, cannot invalidate its token", e);
return false;
}
}
public static class AccountNotFoundException extends AccountsException { public static class AccountNotFoundException extends AccountsException {
/** /**

View File

@ -21,7 +21,9 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common.authentication;
import com.owncloud.android.lib.common.OwnCloudClient;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -21,7 +21,7 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common.authentication;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -30,8 +30,9 @@ import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.AuthState; import org.apache.commons.httpclient.auth.AuthState;
import com.owncloud.android.lib.common.network.BearerAuthScheme; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.network.BearerCredentials; import com.owncloud.android.lib.common.authentication.oauth.BearerAuthScheme;
import com.owncloud.android.lib.common.authentication.oauth.BearerCredentials;
public class OwnCloudBearerCredentials implements OwnCloudCredentials { public class OwnCloudBearerCredentials implements OwnCloudCredentials {

View File

@ -22,7 +22,9 @@
* *
*/ */
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common.authentication;
import com.owncloud.android.lib.common.OwnCloudClient;
public interface OwnCloudCredentials { public interface OwnCloudCredentials {

View File

@ -22,7 +22,9 @@
* *
*/ */
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common.authentication;
import com.owncloud.android.lib.common.OwnCloudClient;
public class OwnCloudCredentialsFactory { public class OwnCloudCredentialsFactory {

View File

@ -21,13 +21,15 @@
* THE SOFTWARE. * THE SOFTWARE.
* *
*/ */
package com.owncloud.android.lib.common; package com.owncloud.android.lib.common.authentication;
import org.apache.commons.httpclient.Cookie; import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.cookie.CookiePolicy;
import android.net.Uri; import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudClient;
public class OwnCloudSamlSsoCredentials implements OwnCloudCredentials { public class OwnCloudSamlSsoCredentials implements OwnCloudCredentials {
private String mUsername; private String mUsername;

View File

@ -22,11 +22,10 @@
* *
*/ */
package com.owncloud.android.lib.common.network; package com.owncloud.android.lib.common.authentication.oauth;
import java.util.Map; import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.auth.AuthChallengeParser; import org.apache.commons.httpclient.auth.AuthChallengeParser;
@ -34,7 +33,6 @@ import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.auth.InvalidCredentialsException; import org.apache.commons.httpclient.auth.InvalidCredentialsException;
import org.apache.commons.httpclient.auth.MalformedChallengeException; import org.apache.commons.httpclient.auth.MalformedChallengeException;
import org.apache.commons.httpclient.util.EncodingUtil;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;

View File

@ -22,7 +22,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network; package com.owncloud.android.lib.common.authentication.oauth;
import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.util.LangUtils; import org.apache.commons.httpclient.util.LangUtils;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
public class OAuth2ClientConfiguration { public class OAuth2ClientConfiguration {

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
/** /**
* Constant values for OAuth 2 protocol. * Constant values for OAuth 2 protocol.

View File

@ -24,13 +24,13 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import android.net.Uri; import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudBasicCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudBasicCredentials;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;

View File

@ -19,13 +19,13 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import android.net.Uri; import android.net.Uri;
import com.owncloud.android.lib.common.OwnCloudBasicCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudBasicCredentials;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
public enum OAuth2GrantType { public enum OAuth2GrantType {
AUTHORIZATION_CODE("authorization_code"), AUTHORIZATION_CODE("authorization_code"),

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
public interface OAuth2Provider { public interface OAuth2Provider {

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import java.util.HashMap; import java.util.HashMap;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperation;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;

View File

@ -24,7 +24,7 @@
* *
*/ */
package com.owncloud.android.lib.common.network.authentication.oauth; package com.owncloud.android.lib.common.authentication.oauth;
import android.net.Uri; import android.net.Uri;

View File

@ -25,7 +25,6 @@
package com.owncloud.android.lib.common.operations; package com.owncloud.android.lib.common.operations;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountsException; import android.accounts.AccountsException;
import android.accounts.AuthenticatorException; import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException; import android.accounts.OperationCanceledException;
@ -35,9 +34,7 @@ import android.os.Handler;
import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;
import java.io.IOException; import java.io.IOException;
@ -90,6 +87,16 @@ public abstract class RemoteOperation implements Runnable {
*/ */
private Handler mListenerHandler = null; private Handler mListenerHandler = null;
/**
* When 'true', the operation tries to silently refresh credentials if fails due to lack of authorization.
*
* Valid for 'execute methods' receiving an {@link Account} instance as parameter, but not for those
* receiving an {@link OwnCloudClient}. This is, valid for:
* -- {@link RemoteOperation#execute(Account, Context)}
* -- {@link RemoteOperation#execute(Account, Context, OnRemoteOperationListener, Handler)}
*/
private boolean mSilentRefreshOfAccountCredentials = false;
/** /**
* Counter to establish the number of times a failed operation will be repeated due to * Counter to establish the number of times a failed operation will be repeated due to
@ -135,7 +142,7 @@ public abstract class RemoteOperation implements Runnable {
return new RemoteOperationResult(e); return new RemoteOperationResult(e);
} }
return runOperationRetryingItIfNeeded(); return runOperation();
} }
@ -153,8 +160,9 @@ public abstract class RemoteOperation implements Runnable {
throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " +
"OwnCloudClient"); "OwnCloudClient");
mClient = client; mClient = client;
mSilentRefreshOfAccountCredentials = false;
return runOperationRetryingItIfNeeded(); return runOperation();
} }
@ -225,6 +233,8 @@ public abstract class RemoteOperation implements Runnable {
mListenerHandler = listenerHandler; mListenerHandler = listenerHandler;
} }
mSilentRefreshOfAccountCredentials = false;
Thread runnerThread = new Thread(this); Thread runnerThread = new Thread(this);
runnerThread.start(); runnerThread.start();
return runnerThread; return runnerThread;
@ -240,12 +250,13 @@ public abstract class RemoteOperation implements Runnable {
@Override @Override
public final void run() { public final void run() {
final RemoteOperationResult resultToSend = runOperation();
if (mAccount != null && mContext != null) { if (mAccount != null && mContext != null) {
// Save Client Cookies // Save Client Cookies
AccountUtils.saveClient(mClient, mAccount, mContext); AccountUtils.saveClient(mClient, mAccount, mContext);
} }
final RemoteOperationResult resultToSend = runOperationRetryingItIfNeeded();;
if (mListenerHandler != null && mListener != null) { if (mListenerHandler != null && mListener != null) {
mListenerHandler.post(new Runnable() { mListenerHandler.post(new Runnable() {
@Override @Override
@ -259,12 +270,15 @@ public abstract class RemoteOperation implements Runnable {
} }
/** /**
* Run operation after asynchronous or synchronous executions. If the account credentials are * Run operation for asynchronous or synchronous 'execute' method.
* invalidated, the operation will be retried with new valid credentials
* *
* @return remote operation result * Considers and performs silent refresh of account credentials if possible, and if
* {@link RemoteOperation#setSilentRefreshOfAccountCredentials(boolean)} was called with
* parameter 'true' before the execution.
*
* @return Remote operation result
*/ */
private RemoteOperationResult runOperationRetryingItIfNeeded () { private RemoteOperationResult runOperation() {
RemoteOperationResult result; RemoteOperationResult result;
boolean repeat; boolean repeat;
@ -282,8 +296,18 @@ public abstract class RemoteOperation implements Runnable {
result = new RemoteOperationResult(e); result = new RemoteOperationResult(e);
} }
if (shouldInvalidateAccountCredentials(result)) { if (mSilentRefreshOfAccountCredentials &&
boolean invalidated = invalidateAccountCredentials(); AccountUtils.shouldInvalidateAccountCredentials(
result,
mClient,
mAccount,
mContext)
) {
boolean invalidated = AccountUtils.invalidateAccountCredentials(
mClient,
mAccount,
mContext
);
if (invalidated && if (invalidated &&
mClient.getCredentials().authTokenCanBeRefreshed() && mClient.getCredentials().authTokenCanBeRefreshed() &&
repeatCounter < MAX_REPEAT_COUNTER) { repeatCounter < MAX_REPEAT_COUNTER) {
@ -322,37 +346,6 @@ public abstract class RemoteOperation implements Runnable {
} }
} }
private boolean shouldInvalidateAccountCredentials(RemoteOperationResult result) {
boolean should = ResultCode.UNAUTHORIZED.equals(result.getCode()); // invalid credentials
should &= (mClient.getCredentials() != null && // real credentials
!(mClient.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials));
should &= (mAccount != null && mContext != null); // have all the needed to effectively invalidate
return should;
}
private boolean invalidateAccountCredentials() {
try {
OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
OwnCloudClientManagerFactory.getDefaultSingleton().
removeClientFor(ocAccount); // to prevent nobody else is provided this client
AccountManager am = AccountManager.get(mContext);
am.invalidateAuthToken(
mAccount.type,
mClient.getCredentials().getAuthToken()
);
am.clearPassword(mAccount); // being strict, only needed for Basic Auth credentials
return true;
} catch (AccountUtils.AccountNotFoundException e) {
Log_OC.e(TAG, "Account was deleted from AccountManager, cannot invalidate its token", e);
return false;
}
}
/** /**
* Returns the current client instance to access the remote server. * Returns the current client instance to access the remote server.
* *
@ -361,4 +354,26 @@ public abstract class RemoteOperation implements Runnable {
public final OwnCloudClient getClient() { public final OwnCloudClient getClient() {
return mClient; return mClient;
} }
/**
* Enables or disables silent refresh of credentials, if supported by credentials itself.
*
* Will have effect if called before:
* -- {@link RemoteOperation#execute(Account, Context)}
* -- {@link RemoteOperation#execute(Account, Context, OnRemoteOperationListener, Handler)}
*
* Will have NO effect if called before:
* -- {@link RemoteOperation#execute(OwnCloudClient)}
* -- {@link RemoteOperation#execute(OwnCloudClient, OnRemoteOperationListener, Handler)}
*
* @param silentRefreshOfAccountCredentials 'True' enables silent refresh, 'false' disables it.
*/
public void setSilentRefreshOfAccountCredentials(boolean silentRefreshOfAccountCredentials) {
mSilentRefreshOfAccountCredentials = silentRefreshOfAccountCredentials;
}
public boolean getSilentRefreshOfAccountCredentials() {
return mSilentRefreshOfAccountCredentials;
}
} }

View File

@ -28,7 +28,7 @@
package com.owncloud.android.lib.resources.users; package com.owncloud.android.lib.resources.users;
import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudCredentials; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials;
import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.common.utils.Log_OC;