diff --git a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java index 627a729f..da2f383c 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java @@ -81,4 +81,9 @@ public class OwnCloudBasicCredentials implements OwnCloudCredentials { return false; } + @Override + public boolean authTokenCanBeRefreshed() { + return false; + } + } diff --git a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java index 4667747b..66115b6f 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java @@ -76,4 +76,9 @@ public class OwnCloudBearerCredentials implements OwnCloudCredentials { return true; } + @Override + public boolean authTokenCanBeRefreshed() { + return true; + } + } diff --git a/src/com/owncloud/android/lib/common/OwnCloudCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudCredentials.java index 0a148682..4c2d9979 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudCredentials.java @@ -26,12 +26,13 @@ package com.owncloud.android.lib.common; public interface OwnCloudCredentials { - public void applyTo(OwnCloudClient ownCloudClient); + void applyTo(OwnCloudClient ownCloudClient); - public String getUsername(); - - public String getAuthToken(); - - public boolean authTokenExpires(); - + String getUsername(); + + String getAuthToken(); + + boolean authTokenExpires(); + + boolean authTokenCanBeRefreshed(); } diff --git a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java b/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java index 289b68b2..65d017b7 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java @@ -76,6 +76,11 @@ public class OwnCloudCredentialsFactory { return false; } + @Override + public boolean authTokenCanBeRefreshed() { + return false; + } + @Override public String getUsername() { // no user name diff --git a/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java index 5a5b28b7..41968bbb 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java @@ -30,54 +30,59 @@ import android.net.Uri; public class OwnCloudSamlSsoCredentials implements OwnCloudCredentials { - private String mUsername; - private String mSessionCookie; + private String mUsername; + private String mSessionCookie; - public OwnCloudSamlSsoCredentials(String username, String sessionCookie) { - mUsername = username != null ? username : ""; - mSessionCookie = sessionCookie != null ? sessionCookie : ""; - } + public OwnCloudSamlSsoCredentials(String username, String sessionCookie) { + mUsername = username != null ? username : ""; + mSessionCookie = sessionCookie != null ? sessionCookie : ""; + } - @Override - public void applyTo(OwnCloudClient client) { + @Override + public void applyTo(OwnCloudClient client) { client.getParams().setAuthenticationPreemptive(false); client.getParams().setCredentialCharset(OwnCloudCredentialsFactory.CREDENTIAL_CHARSET); client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); client.setFollowRedirects(false); - - Uri serverUri = client.getBaseUri(); - + + Uri serverUri = client.getBaseUri(); + String[] cookies = mSessionCookie.split(";"); if (cookies.length > 0) { - Cookie cookie = null; - for (int i=0; i= 0) { - cookie = new Cookie(); - cookie.setName(cookies[i].substring(0, equalPos)); - cookie.setValue(cookies[i].substring(equalPos + 1)); - cookie.setDomain(serverUri.getHost()); // VERY IMPORTANT - cookie.setPath(serverUri.getPath()); // VERY IMPORTANT - client.getState().addCookie(cookie); - } + Cookie cookie = null; + for (int i = 0; i < cookies.length; i++) { + int equalPos = cookies[i].indexOf('='); + if (equalPos >= 0) { + cookie = new Cookie(); + cookie.setName(cookies[i].substring(0, equalPos)); + cookie.setValue(cookies[i].substring(equalPos + 1)); + cookie.setDomain(serverUri.getHost()); // VERY IMPORTANT + cookie.setPath(serverUri.getPath()); // VERY IMPORTANT + client.getState().addCookie(cookie); + } } } - } + } - @Override - public String getUsername() { - // not relevant for authentication, but relevant for informational purposes - return mUsername; - } - - @Override - public String getAuthToken() { - return mSessionCookie; - } + @Override + public String getUsername() { + // not relevant for authentication, but relevant for informational purposes + return mUsername; + } - @Override - public boolean authTokenExpires() { - return true; - } + @Override + public String getAuthToken() { + return mSessionCookie; + } + + @Override + public boolean authTokenExpires() { + return true; + } + + @Override + public boolean authTokenCanBeRefreshed() { + return false; + } } diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index 4e4fb773..2a5a206c 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -27,6 +27,8 @@ package com.owncloud.android.lib.common.operations; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountsException; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.Context; import android.os.Handler; @@ -36,6 +38,7 @@ import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudCredentials; +import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; 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; @@ -90,11 +93,6 @@ public abstract class RemoteOperation implements Runnable { */ private Handler mListenerHandler = null; - /** - * Activity - */ - private Activity mCallerActivity; - /** * Abstract method to implement the operation in derived classes. @@ -154,47 +152,6 @@ public abstract class RemoteOperation implements Runnable { } - /** - * Asynchronously executes the remote operation - * - * This method should be used whenever an ownCloud account is available, instead of - * {@link #execute(OwnCloudClient)}. - * - * @param account ownCloud account in remote ownCloud server to reach during - * the execution of the operation. - * @param context Android context for the component calling the method. - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener - * objects must be called. - * @return Thread were the remote operation is executed. - * @deprecated This method will be removed in version 1.0. - * Use {@link #execute(Account, Context, OnRemoteOperationListener, - * Handler)} instead. - */ - @Deprecated - public Thread execute(Account account, Context context, OnRemoteOperationListener listener, - Handler listenerHandler, Activity callerActivity) { - if (account == null) - throw new IllegalArgumentException - ("Trying to execute a remote operation with a NULL Account"); - if (context == null) - throw new IllegalArgumentException - ("Trying to execute a remote operation with a NULL Context"); - mAccount = account; - mContext = context.getApplicationContext(); - mCallerActivity = callerActivity; - mClient = null; // the client instance will be created from mAccount - // and mContext in the runnerThread to create below - mListener = listener; - - mListenerHandler = listenerHandler; - - Thread runnerThread = new Thread(this); - runnerThread.start(); - return runnerThread; - } - - /** * Asynchronously executes the remote operation * @@ -220,7 +177,6 @@ public abstract class RemoteOperation implements Runnable { ("Trying to execute a remote operation with a NULL Context"); mAccount = account; mContext = context.getApplicationContext(); - mCallerActivity = null; mClient = null; // the client instance will be created from // mAccount and mContext in the runnerThread to create below @@ -275,8 +231,6 @@ public abstract class RemoteOperation implements Runnable { * started by {@link RemoteOperation#execute(OwnCloudClient, * OnRemoteOperationListener, Handler)}, * and result posting. - * - * TODO refactor && clean the code; now it's a mess */ @Override public final void run() { @@ -284,107 +238,27 @@ public abstract class RemoteOperation implements Runnable { boolean repeat = false; do { try { - if (mClient == null) { - if (mAccount != null && mContext != null) { - /** DEPRECATED BLOCK - will be removed at version 1.0 */ - if (mCallerActivity != null) { - mClient = OwnCloudClientFactory.createOwnCloudClient( - mAccount, mContext, mCallerActivity); - } else { - /** EOF DEPRECATED */ - OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); - mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, mContext); - } + grantOwnCloudClient(); + result = run(mClient); - } else { - throw new IllegalStateException("Trying to run a remote operation " + - "asynchronously with no client instance or account"); - } - } - - } catch (IOException e) { - Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, - new AccountsException("I/O exception while trying to authorize the account", - e)); - result = new RemoteOperationResult(e); - - } catch (AccountsException e) { + } catch (AccountsException | IOException e) { Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); result = new RemoteOperationResult(e); } - if (result == null) - result = run(mClient); - - repeat = false; - - - AccountManager mAccountManager = AccountManager.get(mContext); - - String isOAuthStr = mAccountManager.getUserData(mAccount, - AccountUtils.Constants.KEY_SUPPORTS_OAUTH2); - - Boolean isOAuth = Boolean.valueOf(isOAuthStr); - - /** DEPRECATED BLOCK - will be removed at version 1.0 ; don't trust in this code - * to trigger authentication update */ - if (mAccount != null && mContext != null && - !result.isSuccess() && - ResultCode.UNAUTHORIZED.equals(result.getCode()) - ) { - /// possible fail due to lack of authorization - // in an operation performed in foreground - OwnCloudCredentials cred = mClient.getCredentials(); - if (cred != null) { - /// confirmed : unauthorized operation - - OwnCloudClient client; - OwnCloudAccount ocAccount; - - try { - /// Step 1: Invalidate credentials of current account - ocAccount = new OwnCloudAccount(mAccount, mContext); - client = (OwnCloudClientManagerFactory.getDefaultSingleton(). - removeClientFor(ocAccount)); - if (client != null) { - AccountManager am = AccountManager.get(mContext); - if (cred.authTokenExpires()) { - am.invalidateAuthToken( // SAML & OAuth - mAccount.type, - cred.getAuthToken() - ); - } else { //Basic - am.clearPassword(mAccount); - } - } - - /// Step 2: Get new access token using refresh token - // Params needed: clientId, clientSecret (switch credentials), grantype and - // refresh token - String refreshToken = mAccountManager.getUserData(mAccount, - AccountUtils.Constants.KEY_OAUTH2_REFRESH_TOKEN); - -// mClient.executeMethod(postMethod); - - /// Step 3: Save access token in account manager - - } catch (AccountUtils.AccountNotFoundException e) { - e.printStackTrace(); - } - - mClient = null; - - // when repeated, the creation of a new OwnCloudClient after erasing the saved - // credentials will trigger the login activity - if (isOAuth) { + if (shouldInvalidateAccountCredentials(result)) { + boolean invalidated = invalidateAccountCredentials(); + if (invalidated && + mClient.getCredentials().authTokenCanBeRefreshed()) { + mClient = null; repeat = true; - } - - result = null; + // this will result in a new loop, and grantOwnCloudClient() will + // create a new instance for mClient, refreshing the token via the account + // manager } + // else: operation will finish with ResultCode.UNAUTHORIZED } - /** EOF DEPRECATED BLOCK **/ + } while (repeat); if (mAccount != null && mContext != null) { @@ -405,6 +279,50 @@ public abstract class RemoteOperation implements Runnable { } } + private void grantOwnCloudClient() throws AccountUtils.AccountNotFoundException, OperationCanceledException, AuthenticatorException, IOException { + if (mClient == null) { + if (mAccount != null && mContext != null) { + OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); + mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, mContext); + + } else { + throw new IllegalStateException("Trying to run a remote operation " + + "asynchronously with no client and no chance to create one (no account)"); + } + } + } + + 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.