mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-10-30 18:07:38 +00:00 
			
		
		
		
	Move responsibility for refreshing expired credentials from RemoteOperation to OwnCloudClient
This commit is contained in:
		
							parent
							
								
									dd2d3b8996
								
							
						
					
					
						commit
						e466bac6b1
					
				| @ -124,10 +124,10 @@ public class OwnCloudAccount { | ||||
|             throw new IllegalArgumentException("Parameter 'context' cannot be null"); | ||||
|         } | ||||
| 
 | ||||
| 		if (mSavedAccount != null) { | ||||
| 			mCredentials = AccountUtils.getCredentialsForAccount(context, mSavedAccount); | ||||
| 		} | ||||
| 	} | ||||
|         if (mSavedAccount != null) { | ||||
|             mCredentials = AccountUtils.getCredentialsForAccount(context, mSavedAccount); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public Uri getBaseUri() { | ||||
|         return mBaseUri; | ||||
|  | ||||
| @ -43,6 +43,8 @@ import org.apache.commons.httpclient.cookie.CookiePolicy; | ||||
| import org.apache.commons.httpclient.params.HttpMethodParams; | ||||
| import org.apache.commons.httpclient.params.HttpParams; | ||||
| 
 | ||||
| import android.accounts.AccountManager; | ||||
| import android.accounts.AccountsException; | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| 
 | ||||
| @ -57,7 +59,8 @@ import com.owncloud.android.lib.resources.status.OwnCloudVersion; | ||||
| public class OwnCloudClient extends HttpClient { | ||||
| 
 | ||||
|     private static final String TAG = OwnCloudClient.class.getSimpleName(); | ||||
|     public static final int MAX_REDIRECTIONS_COUNT = 3; | ||||
|     private static final int MAX_REDIRECTIONS_COUNT = 3; | ||||
|     private static final int MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS = 1; | ||||
|     private static final String PARAM_SINGLE_COOKIE_HEADER = "http.protocol.single-cookie-header"; | ||||
|     private static final boolean PARAM_SINGLE_COOKIE_HEADER_VALUE = true; | ||||
|     private static final String PARAM_PROTOCOL_VERSION = "http.protocol.version"; | ||||
| @ -74,10 +77,24 @@ public class OwnCloudClient extends HttpClient { | ||||
|     private OwnCloudVersion mVersion = null; | ||||
| 
 | ||||
|     /// next too attributes are a very ugly dependency, added to grant silent retry of OAuth token when needed ; | ||||
|     /// see {} for more details | ||||
|     /// see #shouldInvalidateCredentials and #invalidateCredentials for more details | ||||
|     private Context mContext; | ||||
|     private OwnCloudAccount mAccount; | ||||
| 
 | ||||
|     /** | ||||
|      * {@link @OwnCloudClientManager} holding a reference to this object and delivering it to callers; might be | ||||
|      * NULL | ||||
|      */ | ||||
|     private OwnCloudClientManager mOwnCloudClientManager = null; | ||||
| 
 | ||||
|     /** | ||||
|      * When 'true', the method {@link #executeMethod(HttpMethod)}  tries to silently refresh credentials | ||||
|      * if fails due to lack of authorization, if credentials support authorization refresh. | ||||
|      */ | ||||
|     private boolean mSilentRefreshOfAccountCredentials = true; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Constructor | ||||
|      */ | ||||
| @ -190,25 +207,38 @@ public class OwnCloudClient extends HttpClient { | ||||
|      */ | ||||
|     @Override | ||||
|     public int executeMethod(HttpMethod method) throws IOException { | ||||
|         // Update User Agent | ||||
|         HttpParams params = method.getParams(); | ||||
|         String userAgent = OwnCloudClientManagerFactory.getUserAgent(); | ||||
|         params.setParameter(HttpMethodParams.USER_AGENT, userAgent); | ||||
| 
 | ||||
|         preventCrashDueToInvalidPort(method); | ||||
|         boolean repeatWithFreshCredentials; | ||||
|         int repeatCounter = 0; | ||||
|         int status; | ||||
| 
 | ||||
|         Log_OC.d(TAG + " #" + mInstanceNumber, "REQUEST " + | ||||
|         do { | ||||
|             // Update User Agent | ||||
|             HttpParams params = method.getParams(); | ||||
|             String userAgent = OwnCloudClientManagerFactory.getUserAgent(); | ||||
|             params.setParameter(HttpMethodParams.USER_AGENT, userAgent); | ||||
| 
 | ||||
|             preventCrashDueToInvalidPort(method); | ||||
| 
 | ||||
|             Log_OC.d(TAG + " #" + mInstanceNumber, "REQUEST " + | ||||
|             method.getName() + " " + method.getPath()); | ||||
| 
 | ||||
|         //logCookiesAtRequest(method.getRequestHeaders(), "before"); | ||||
|         //logCookiesAtState("before"); | ||||
|         method.setFollowRedirects(false); | ||||
|             //logCookiesAtRequest(method.getRequestHeaders(), "before"); | ||||
|             //logCookiesAtState("before"); | ||||
|             method.setFollowRedirects(false); | ||||
| 
 | ||||
|         int status = super.executeMethod(method); | ||||
|             status = super.executeMethod(method); | ||||
| 
 | ||||
|         if (mFollowRedirects) { | ||||
|             status = followRedirection(method).getLastStatus(); | ||||
|         } | ||||
|             if (mFollowRedirects) { | ||||
|                 status = followRedirection(method).getLastStatus(); | ||||
|             } | ||||
| 
 | ||||
|             repeatWithFreshCredentials = checkUnauthorizedAccess(status, repeatCounter); | ||||
|             if (repeatWithFreshCredentials) { | ||||
|                 repeatCounter++; | ||||
|             } | ||||
| 
 | ||||
|         } while (repeatWithFreshCredentials); | ||||
| 
 | ||||
|         //logCookiesAtRequest(method.getRequestHeaders(), "after"); | ||||
|         //logCookiesAtState("after"); | ||||
| @ -217,7 +247,6 @@ public class OwnCloudClient extends HttpClient { | ||||
|         return status; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Fix for https://github.com/owncloud/android/issues/1847#issuecomment-267558274 | ||||
|      * | ||||
| @ -468,4 +497,112 @@ public class OwnCloudClient extends HttpClient { | ||||
|         return mAccount; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Enables or disables silent refresh of credentials, if supported by credentials themselves. | ||||
|      */ | ||||
|     public void setSilentRefreshOfAccountCredentials(boolean silentRefreshOfAccountCredentials) { | ||||
|         mSilentRefreshOfAccountCredentials = silentRefreshOfAccountCredentials; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getSilentRefreshOfAccountCredentials() { | ||||
|         return mSilentRefreshOfAccountCredentials; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 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 boolean checkUnauthorizedAccess(int status, int repeatCounter) { | ||||
|         boolean credentialsWereRefreshed = false; | ||||
| 
 | ||||
|         if (shouldInvalidateAccountCredentials(status)) { | ||||
|             boolean invalidated = invalidateAccountCredentials(); | ||||
| 
 | ||||
|             if (invalidated) { | ||||
|                 if (getCredentials().authTokenCanBeRefreshed() && | ||||
|                     repeatCounter < MAX_REPEAT_COUNT_WITH_FRESH_CREDENTIALS) { | ||||
| 
 | ||||
|                     try { | ||||
|                         mAccount.loadCredentials(mContext); | ||||
|                         // if mAccount.getCredentials().length() == 0 --> refresh failed | ||||
|                         setCredentials(mAccount.getCredentials()); | ||||
|                         credentialsWereRefreshed = true; | ||||
| 
 | ||||
|                     } catch (AccountsException | IOException e) { | ||||
|                         Log_OC.e( | ||||
|                             TAG, | ||||
|                             "Error while trying to refresh auth token for " + mAccount.getSavedAccount().name, | ||||
|                             e | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (!credentialsWereRefreshed && mOwnCloudClientManager != null) { | ||||
|                     // if credentials are not refreshed, client must be removed | ||||
|                     // from the OwnCloudClientManager to prevent it is reused once and again | ||||
|                     mOwnCloudClientManager.removeClientFor(mAccount); | ||||
|                 } | ||||
|             } | ||||
|             // else: execute will finish with status 401 | ||||
|         } | ||||
| 
 | ||||
|         return credentialsWereRefreshed; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 boolean shouldInvalidateAccountCredentials(int httpStatusCode) { | ||||
| 
 | ||||
|         boolean should = (httpStatusCode == HttpStatus.SC_UNAUTHORIZED);   // invalid credentials | ||||
| 
 | ||||
|         should &= (mCredentials != null &&         // real credentials | ||||
|             !(mCredentials instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); | ||||
| 
 | ||||
|         // test if have all the needed to effectively invalidate ... | ||||
|         should &= (mAccount != null && mAccount.getSavedAccount() != null && mContext != null); | ||||
| 
 | ||||
|         return should; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates credentials stored for the given account in the system  {@link AccountManager} and in | ||||
|      * current {@link OwnCloudClientManagerFactory#getDefaultSingleton()} instance. | ||||
|      * | ||||
|      * {@link #shouldInvalidateAccountCredentials(int)} should be called first. | ||||
|      * | ||||
|      * @return                  'True' if invalidation was successful, 'false' otherwise. | ||||
|      */ | ||||
|     private boolean invalidateAccountCredentials() { | ||||
|         AccountManager am = AccountManager.get(mContext); | ||||
|         am.invalidateAuthToken( | ||||
|             mAccount.getSavedAccount().type, | ||||
|             mCredentials.getAuthToken() | ||||
|         ); | ||||
|         am.clearPassword(mAccount.getSavedAccount()); // being strict, only needed for Basic Auth credentials | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public OwnCloudClientManager getOwnCloudClientManager() { | ||||
|         return mOwnCloudClientManager; | ||||
|     } | ||||
| 
 | ||||
|     void setOwnCloudClientManager(OwnCloudClientManager clientManager) { | ||||
|         mOwnCloudClientManager = clientManager; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -63,6 +63,8 @@ public class SimpleFactoryManager implements OwnCloudClientManager { | ||||
|         } | ||||
|         client.setCredentials(account.getCredentials()); | ||||
|         client.setAccount(account); | ||||
|         client.setContext(context); | ||||
|         client.setOwnCloudClientManager(this); | ||||
|         return client; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -117,8 +117,10 @@ public class SingleSessionManager implements OwnCloudClientManager { | ||||
|                 true);    // TODO remove dependency on OwnCloudClientFactory | ||||
|             client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); | ||||
|             client.setAccount(account); | ||||
|             // enable cookie tracking | ||||
|             client.setContext(context); | ||||
|             client.setOwnCloudClientManager(this); | ||||
| 
 | ||||
|             // enable cookie tracking | ||||
|             AccountUtils.restoreCookies(account.getSavedAccount(), client, context); | ||||
| 
 | ||||
|             account.loadCredentials(context); | ||||
|  | ||||
| @ -28,6 +28,7 @@ package com.owncloud.android.lib.common.accounts; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.apache.commons.httpclient.Cookie; | ||||
| import org.apache.commons.httpclient.HttpStatus; | ||||
| 
 | ||||
| import android.accounts.Account; | ||||
| import android.accounts.AccountManager; | ||||
| @ -262,83 +263,6 @@ 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)); | ||||
| 
 | ||||
|         // test if have all the needed to effectively invalidate ... | ||||
|         should &= ((account != null && context != null) || // ... either directly, or ... | ||||
|                     client.getAccount() != null && client.getContext() != null)  ;   // ... indirectly | ||||
| 
 | ||||
|         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 | ||||
|     ) { | ||||
|         Account checkedAccount = (account == null) ? client.getAccount().getSavedAccount() : account; | ||||
|         if (checkedAccount == null) { | ||||
|             throw new IllegalArgumentException("Account cannot be null both in parameter and in client"); | ||||
|         } | ||||
|         Context checkedContext = (context == null) ? client.getContext() : context; | ||||
|         if (checkedContext == null) { | ||||
|             throw new IllegalArgumentException("Context cannot be null both in parameter and in client"); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             OwnCloudAccount ocAccount = new OwnCloudAccount(checkedAccount, checkedContext); | ||||
|             OwnCloudClientManagerFactory.getDefaultSingleton(). | ||||
|                 removeClientFor(ocAccount);    // to prevent nobody else is provided this client | ||||
|             AccountManager am = AccountManager.get(checkedContext); | ||||
|             am.invalidateAuthToken( | ||||
|                 checkedAccount.type, | ||||
|                 client.getCredentials().getAuthToken() | ||||
|             ); | ||||
|             am.clearPassword(checkedAccount); // 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 { | ||||
| 
 | ||||
|         /** | ||||
|  | ||||
| @ -87,28 +87,6 @@ public abstract class RemoteOperation implements Runnable { | ||||
|      */ | ||||
|     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, out of the box: | ||||
|      *  -- {@link RemoteOperation#execute(Account, Context)} | ||||
|      *  -- {@link RemoteOperation#execute(Account, Context, OnRemoteOperationListener, Handler)} | ||||
|      * | ||||
|      * Valid for 'execute methods' receiving an {@link OwnCloudClient}, as long as it returns non null values | ||||
|      * to its methods {@link OwnCloudClient#getContext()} && {@link OwnCloudClient#getAccount()}: | ||||
|      *  -- {@link RemoteOperation#execute(OwnCloudClient)} | ||||
|      *  -- {@link RemoteOperation#execute(OwnCloudClient, OnRemoteOperationListener, Handler)} | ||||
|      */ | ||||
|     private boolean mSilentRefreshOfAccountCredentials = false; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Counter to establish the number of times a failed operation will be repeated due to | ||||
|      * an authorization error | ||||
|      */ | ||||
|     private int MAX_REPEAT_COUNTER = 1; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Abstract method to implement the operation in derived classes. | ||||
|      */ | ||||
| @ -137,14 +115,6 @@ public abstract class RemoteOperation implements Runnable { | ||||
|                     "Context"); | ||||
|         mAccount = account; | ||||
|         mContext = context.getApplicationContext(); | ||||
|         try { | ||||
|             OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); | ||||
|             mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). | ||||
|                     getClientFor(ocAccount, mContext); | ||||
|         } catch (Exception e) { | ||||
|             Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); | ||||
|             return new RemoteOperationResult(e); | ||||
|         } | ||||
| 
 | ||||
|         return runOperation(); | ||||
|     } | ||||
| @ -290,51 +260,15 @@ public abstract class RemoteOperation implements Runnable { | ||||
|     private RemoteOperationResult runOperation() { | ||||
| 
 | ||||
|         RemoteOperationResult result; | ||||
|         boolean repeat; | ||||
|         int repeatCounter = 0; | ||||
| 
 | ||||
|         do { | ||||
|             repeat = false; | ||||
|         try { | ||||
|             grantOwnCloudClient(); | ||||
|             result = run(mClient); | ||||
| 
 | ||||
|             try { | ||||
|                 grantOwnCloudClient(); | ||||
|                 result = run(mClient); | ||||
| 
 | ||||
|             } catch (AccountsException | IOException e) { | ||||
|                 Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); | ||||
|                 result = new RemoteOperationResult(e); | ||||
|             } | ||||
| 
 | ||||
|             if (mSilentRefreshOfAccountCredentials && | ||||
|                 AccountUtils.shouldInvalidateAccountCredentials( | ||||
|                     result, | ||||
|                     mClient, | ||||
|                     mAccount, | ||||
|                     mContext) | ||||
|                 ) { | ||||
|                 boolean invalidated = AccountUtils.invalidateAccountCredentials( | ||||
|                     mClient, | ||||
|                     mAccount, | ||||
|                     mContext | ||||
|                 ); | ||||
|                 if (invalidated && | ||||
|                         mClient.getCredentials().authTokenCanBeRefreshed() && | ||||
|                         repeatCounter < MAX_REPEAT_COUNTER) { | ||||
| 
 | ||||
|                     mClient = null; | ||||
|                     repeat = true; | ||||
|                     repeatCounter++; | ||||
| 
 | ||||
|                     // this will result in a new loop, and grantOwnCloudClient() will | ||||
|                     // create a new instance for mClient, getting a new fresh token in the | ||||
|                     // way, in the AccountAuthenticator * ; | ||||
|                     // this, unfortunately, is a hidden runtime dependency back to the app; | ||||
|                     // we should fix it ASAP | ||||
|                 } | ||||
|                 // else: operation will finish with ResultCode.UNAUTHORIZED | ||||
|             } | ||||
| 
 | ||||
|         } while (repeat); | ||||
|         } catch (AccountsException | IOException e) { | ||||
|             Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); | ||||
|             result = new RemoteOperationResult(e); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| @ -364,26 +298,4 @@ public abstract class RemoteOperation implements Runnable { | ||||
|         return mClient; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Enables or disables silent refresh of credentials, if supported by credentials itself. | ||||
|      * | ||||
|      * Will have effect if called before in all cases: | ||||
|      *  -- {@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)} | ||||
|      * | ||||
|      *  ... UNLESS the passed {@link OwnCloudClient} returns non-NULL values for | ||||
|      *  {@link OwnCloudClient#getAccount()} && {@link OwnCloudClient#getContext()} | ||||
|      */ | ||||
|     public void setSilentRefreshOfAccountCredentials(boolean silentRefreshOfAccountCredentials) { | ||||
|         mSilentRefreshOfAccountCredentials = silentRefreshOfAccountCredentials; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getSilentRefreshOfAccountCredentials() { | ||||
|         return mSilentRefreshOfAccountCredentials; | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user