From e4b57e8063bf164ad23d40496f73cceb045cb456 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Tue, 28 Mar 2017 18:53:57 +0200 Subject: [PATCH 01/23] WIP --- .../lib/common/OwnCloudBasicCredentials.java | 4 ++ .../lib/common/OwnCloudBearerCredentials.java | 70 ++++++++++--------- .../lib/common/OwnCloudClientFactory.java | 4 +- .../common/OwnCloudCredentialsFactory.java | 4 +- .../lib/common/accounts/AccountUtils.java | 3 +- .../lib/common/network/BearerAuthScheme.java | 12 +++- 6 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java index 4f7434a2..627a729f 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java @@ -29,6 +29,8 @@ import java.util.List; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthPolicy; import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.auth.AuthState; +import org.apache.commons.httpclient.auth.BasicScheme; public class OwnCloudBasicCredentials implements OwnCloudCredentials { @@ -50,6 +52,8 @@ public class OwnCloudBasicCredentials implements OwnCloudCredentials { @Override public void applyTo(OwnCloudClient client) { + AuthPolicy.registerAuthScheme(AuthState.PREEMPTIVE_AUTH_SCHEME, BasicScheme.class); + List authPrefs = new ArrayList(1); authPrefs.add(AuthPolicy.BASIC); client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); diff --git a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java b/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java index f0bb13a7..4667747b 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java +++ b/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java @@ -28,48 +28,52 @@ import java.util.List; import org.apache.commons.httpclient.auth.AuthPolicy; import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.auth.AuthState; import com.owncloud.android.lib.common.network.BearerAuthScheme; import com.owncloud.android.lib.common.network.BearerCredentials; public class OwnCloudBearerCredentials implements OwnCloudCredentials { - private String mAccessToken; - - public OwnCloudBearerCredentials(String accessToken) { - mAccessToken = accessToken != null ? accessToken : ""; - } + private String mUsername; + private String mAccessToken; - @Override - public void applyTo(OwnCloudClient client) { - AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class); - - List authPrefs = new ArrayList(1); - authPrefs.add(BearerAuthScheme.AUTH_POLICY); - client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); - - client.getParams().setAuthenticationPreemptive(true); + public OwnCloudBearerCredentials(String username, String accessToken) { + mUsername = username != null ? username : ""; + mAccessToken = accessToken != null ? accessToken : ""; + } + + @Override + public void applyTo(OwnCloudClient client) { + AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class); + AuthPolicy.registerAuthScheme(AuthState.PREEMPTIVE_AUTH_SCHEME, BearerAuthScheme.class); + + List authPrefs = new ArrayList<>(1); + authPrefs.add(BearerAuthScheme.AUTH_POLICY); + client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); + + client.getParams().setAuthenticationPreemptive(true); // true enforces BASIC AUTH ; library is stupid client.getParams().setCredentialCharset(OwnCloudCredentialsFactory.CREDENTIAL_CHARSET); - client.getState().setCredentials( - AuthScope.ANY, - new BearerCredentials(mAccessToken) - ); - } + client.getState().setCredentials( + AuthScope.ANY, + new BearerCredentials(mAccessToken) + ); + } - @Override - public String getUsername() { - // its unknown - return null; - } - - @Override - public String getAuthToken() { - return mAccessToken; - } + @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 mAccessToken; + } + + @Override + public boolean authTokenExpires() { + return true; + } } diff --git a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java index 5d01e487..6109324e 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -96,7 +96,7 @@ public class OwnCloudClientFactory { false); client.setCredentials( - OwnCloudCredentialsFactory.newBearerCredentials(accessToken) + OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken) ); } else if (isSamlSso) { // TODO avoid a call to getUserData here @@ -161,7 +161,7 @@ public class OwnCloudClientFactory { String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); if (accessToken == null) throw new AuthenticatorException("WTF!"); client.setCredentials( - OwnCloudCredentialsFactory.newBearerCredentials(accessToken) + OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken) ); } else if (isSamlSso) { // TODO avoid a call to getUserData here diff --git a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java b/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java index e7cf12fd..289b68b2 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java @@ -40,8 +40,8 @@ public class OwnCloudCredentialsFactory { return new OwnCloudBasicCredentials(username, password, preemptiveMode); } - public static OwnCloudCredentials newBearerCredentials(String authToken) { - return new OwnCloudBearerCredentials(authToken); + public static OwnCloudCredentials newBearerCredentials(String username, String authToken) { + return new OwnCloudBearerCredentials(username, authToken); } public static OwnCloudCredentials newSamlSsoCredentials(String username, String sessionCookie) { diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index b575d31d..a4dad289 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -48,6 +48,7 @@ public class AccountUtils { private static final String TAG = AccountUtils.class.getSimpleName(); public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; + public static final String ODAV_PATH = "/remote.php/webdav"; public static final String STATUS_PATH = "/status.php"; /** @@ -171,7 +172,7 @@ public class AccountUtils { AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), false); - credentials = OwnCloudCredentialsFactory.newBearerCredentials(accessToken); + credentials = OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken); } else if (isSamlSso) { String accessToken = am.blockingGetAuthToken( diff --git a/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java b/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java index 75a5cc75..c87b19d3 100644 --- a/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java +++ b/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java @@ -26,6 +26,7 @@ package com.owncloud.android.lib.common.network; import java.util.Map; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.auth.AuthChallengeParser; @@ -33,6 +34,7 @@ import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.httpclient.auth.InvalidCredentialsException; import org.apache.commons.httpclient.auth.MalformedChallengeException; +import org.apache.commons.httpclient.util.EncodingUtil; import com.owncloud.android.lib.common.utils.Log_OC; @@ -218,9 +220,15 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { } StringBuffer buffer = new StringBuffer(); buffer.append(credentials.getAccessToken()); - - //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); + + Log_OC.v(TAG, "OAUTH2: string to authorize: " + "Bearer " + buffer.toString()); return "Bearer " + buffer.toString(); + //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); + /*return "Bearer " + EncodingUtil.getAsciiString( + Base64.encodeBase64( + EncodingUtil.getBytes(buffer.toString(), charset) + ) + );*/ } /** From f6ee1a15cb8da13b89a92f3d04624e999f68780f Mon Sep 17 00:00:00 2001 From: davigonz Date: Fri, 30 Jun 2017 12:35:19 +0200 Subject: [PATCH 02/23] Reading several authenticate headers from http response --- .../lib/common/operations/RemoteOperationResult.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index ea60f379..37cdb74f 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -129,7 +129,7 @@ public class RemoteOperationResult implements Serializable { private Exception mException = null; private ResultCode mCode = ResultCode.UNKNOWN_ERROR; private String mRedirectedLocation; - private String mAuthenticate; + private ArrayList mAuthenticate = new ArrayList<>(); private String mLastPermanentLocation = null; private ArrayList mData; @@ -321,8 +321,7 @@ public class RemoteOperationResult implements Serializable { continue; } if ("www-authenticate".equals(current.getName().toLowerCase())) { - mAuthenticate = current.getValue(); - break; + mAuthenticate.add(current.getValue().toLowerCase()); } } } @@ -562,7 +561,7 @@ public class RemoteOperationResult implements Serializable { return (mRedirectedLocation != null && !(mRedirectedLocation.toLowerCase().startsWith("https://"))); } - public String getAuthenticateHeader() { + public ArrayList getAuthenticateHeader() { return mAuthenticate; } From 2df2cde466b4cc83783fc734f274dea0a7fdaa47 Mon Sep 17 00:00:00 2001 From: davigonz Date: Fri, 30 Jun 2017 13:02:28 +0200 Subject: [PATCH 03/23] Rename getAuthernticateHeader method --- .../android/lib/common/operations/RemoteOperationResult.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index 37cdb74f..c04a9391 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -561,7 +561,7 @@ public class RemoteOperationResult implements Serializable { return (mRedirectedLocation != null && !(mRedirectedLocation.toLowerCase().startsWith("https://"))); } - public ArrayList getAuthenticateHeader() { + public ArrayList getAuthenticateHeaders() { return mAuthenticate; } From fe947194c5ef86b1618000afd6b62bc17a95b535 Mon Sep 17 00:00:00 2001 From: davigonz Date: Mon, 10 Jul 2017 13:55:00 +0200 Subject: [PATCH 04/23] Add constant to save refresh token in account --- .../owncloud/android/lib/common/accounts/AccountUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index a4dad289..e77a868d 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -349,6 +349,11 @@ public class AccountUtils { */ public static final String KEY_DISPLAY_NAME = "oc_display_name"; + /** + * OAuth2 refresh token + **/ + public static final String KEY_OAUTH2_REFRESH_TOKEN = "oc_oauth2_refresh_token"; + } } From a1b1c2f9edb83520e596d2e929788f8825d18d52 Mon Sep 17 00:00:00 2001 From: davigonz Date: Fri, 14 Jul 2017 13:06:08 +0200 Subject: [PATCH 05/23] Invalidate the client token when access token expires and try the last operation [WIP] --- .../common/operations/RemoteOperation.java | 373 ++++++++++-------- 1 file changed, 213 insertions(+), 160 deletions(-) diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index bb8ea421..4e4fb773 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. + * Copyright (C) 2017 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -45,58 +45,75 @@ import java.io.IOException; /** * Operation which execution involves one or several interactions with an ownCloud server. - * + * * Provides methods to execute the operation both synchronously or asynchronously. - * - * @author David A. Velasco + * + * @author David A. Velasco + * @author David González Verdugo */ public abstract class RemoteOperation implements Runnable { - + private static final String TAG = RemoteOperation.class.getSimpleName(); - /** OCS API header name */ + /** + * OCS API header name + */ public static final String OCS_API_HEADER = "OCS-APIREQUEST"; - /** OCS API header value */ + /** + * OCS API header value + */ public static final String OCS_API_HEADER_VALUE = "true"; - /** ownCloud account in the remote ownCloud server to operate */ + /** + * ownCloud account in the remote ownCloud server to operate + */ private Account mAccount = null; - - /** Android Application context */ - private Context mContext = null; - - /** Object to interact with the remote server */ - private OwnCloudClient mClient = null; - - /** Callback object to notify about the execution of the remote operation */ - private OnRemoteOperationListener mListener = null; - - /** Handler to the thread where mListener methods will be called */ - private Handler mListenerHandler = null; - /** Activity */ + /** + * Android Application context + */ + private Context mContext = null; + + /** + * Object to interact with the remote server + */ + private OwnCloudClient mClient = null; + + /** + * Callback object to notify about the execution of the remote operation + */ + private OnRemoteOperationListener mListener = null; + + /** + * Handler to the thread where mListener methods will be called + */ + private Handler mListenerHandler = null; + + /** + * Activity + */ private Activity mCallerActivity; - - /** - * Abstract method to implement the operation in derived classes. - */ - protected abstract RemoteOperationResult run(OwnCloudClient client); - + + /** + * Abstract method to implement the operation in derived classes. + */ + protected abstract RemoteOperationResult run(OwnCloudClient client); + /** * Synchronously executes the remote operation on the received ownCloud account. - * + * * Do not call this method from the main thread. - * + * * 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. - * @return Result of the operation. + * + * @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. + * @return Result of the operation. */ public RemoteOperationResult execute(Account account, Context context) { if (account == null) @@ -108,54 +125,53 @@ public abstract class RemoteOperation implements Runnable { mAccount = account; mContext = context.getApplicationContext(); try { - OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); + OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, mContext); + getClientFor(ocAccount, mContext); } catch (Exception e) { Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); return new RemoteOperationResult(e); } return run(mClient); } - - - /** - * Synchronously executes the remote operation - * - * Do not call this method from the main thread. - * - * @param client Client object to reach an ownCloud server during the execution of - * the operation. - * @return Result of the operation. - */ - public RemoteOperationResult execute(OwnCloudClient client) { - if (client == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " + - "OwnCloudClient"); - mClient = client; - return run(client); - } - + + /** + * Synchronously executes the remote operation + * + * Do not call this method from the main thread. + * + * @param client Client object to reach an ownCloud server during the execution of + * the operation. + * @return Result of the operation. + */ + public RemoteOperationResult execute(OwnCloudClient client) { + if (client == null) + throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " + + "OwnCloudClient"); + mClient = client; + return run(client); + } + + /** * Asynchronously executes the remote operation - * + * * This method should be used whenever an ownCloud account is available, instead of * {@link #execute(OwnCloudClient)}. - * - * @deprecated This method will be removed in version 1.0. - * Use {@link #execute(Account, Context, OnRemoteOperationListener, - * Handler)} instead. - * - * @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. + * + * @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 + @Deprecated public Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) { if (account == null) @@ -168,34 +184,34 @@ public abstract class RemoteOperation implements Runnable { mContext = context.getApplicationContext(); mCallerActivity = callerActivity; mClient = null; // the client instance will be created from mAccount - // and mContext in the runnerThread to create below + // 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 - * - * This method should be used whenever an ownCloud account is available, + * + * This method should be used whenever an ownCloud account is available, * instead of {@link #execute(OwnCloudClient, OnRemoteOperationListener, Handler))}. - * - * @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. + * + * @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. */ public Thread execute(Account account, Context context, OnRemoteOperationListener listener, Handler listenerHandler) { - + if (account == null) throw new IllegalArgumentException ("Trying to execute a remote operation with a NULL Account"); @@ -206,147 +222,185 @@ public abstract class RemoteOperation implements Runnable { mContext = context.getApplicationContext(); mCallerActivity = null; mClient = null; // the client instance will be created from - // mAccount and mContext in the runnerThread to create below - + // 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 - * - * @param client Client object to reach an ownCloud server - * during the execution of the operation. - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler, if passed in, associated to the thread where the methods of - * the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public Thread execute(OwnCloudClient client, + + /** + * Asynchronously executes the remote operation + * + * @param client Client object to reach an ownCloud server + * during the execution of the operation. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler, if passed in, associated to the thread where the methods of + * the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public Thread execute(OwnCloudClient client, OnRemoteOperationListener listener, Handler listenerHandler) { - if (client == null) { - throw new IllegalArgumentException + if (client == null) { + throw new IllegalArgumentException ("Trying to execute a remote operation with a NULL OwnCloudClient"); - } - mClient = client; - - if (listener == null) { - throw new IllegalArgumentException + } + mClient = client; + + if (listener == null) { + throw new IllegalArgumentException ("Trying to execute a remote operation asynchronously " + "without a listener to notiy the result"); - } - mListener = listener; - - if (listenerHandler != null) { + } + mListener = listener; + + if (listenerHandler != null) { mListenerHandler = listenerHandler; - } - - - Thread runnerThread = new Thread(this); - runnerThread.start(); - return runnerThread; - } + } - /** - * Asynchronous execution of the operation - * started by {@link RemoteOperation#execute(OwnCloudClient, - * OnRemoteOperationListener, Handler)}, - * and result posting. - * - * TODO refactor && clean the code; now it's a mess - */ + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + + /** + * Asynchronous execution of the operation + * 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() { RemoteOperationResult result = null; boolean repeat = false; do { - try{ + try { if (mClient == null) { if (mAccount != null && mContext != null) { - /** DEPRECATED BLOCK - will be removed at version 1.0 */ + /** DEPRECATED BLOCK - will be removed at version 1.0 */ if (mCallerActivity != null) { mClient = OwnCloudClientFactory.createOwnCloudClient( - mAccount, mContext, mCallerActivity); + mAccount, mContext, mCallerActivity); } else { - /** EOF DEPRECATED */ - OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); + /** EOF DEPRECATED */ + OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); mClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, mContext); + getClientFor(ocAccount, mContext); } - + } 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) { 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; - /** DEPRECATED BLOCK - will be removed at version 1.0 ; don't trust in this code - * to trigger authentication update */ - if (mCallerActivity != null && mAccount != null && mContext != null && + + + 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 - AccountManager am = AccountManager.get(mContext); - if (cred.authTokenExpires()) { - am.invalidateAuthToken( - mAccount.type, - cred.getAuthToken() - ); - } else { - am.clearPassword(mAccount); + + 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 - repeat = true; + if (isOAuth) { + repeat = true; + } + result = null; } } /** EOF DEPRECATED BLOCK **/ } while (repeat); - + if (mAccount != null && mContext != null) { - // Save Client Cookies + // Save Client Cookies AccountUtils.saveClient(mClient, mAccount, mContext); } - + final RemoteOperationResult resultToSend = result; if (mListenerHandler != null && mListener != null) { - mListenerHandler.post(new Runnable() { + mListenerHandler.post(new Runnable() { @Override public void run() { mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); } }); - } - else if(mListener != null) { + } else if (mListener != null) { mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); } } @@ -354,11 +408,10 @@ public abstract class RemoteOperation implements Runnable { /** * Returns the current client instance to access the remote server. - * - * @return Current client instance to access the remote server. + * + * @return Current client instance to access the remote server. */ public final OwnCloudClient getClient() { return mClient; } - -} +} \ No newline at end of file From 0dc859c071e0e004b4855024ee77855eec3937f5 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Fri, 21 Jul 2017 11:06:58 +0200 Subject: [PATCH 06/23] Move OAuth2 support classes from app to library --- .../oauth/OAuth2ClientConfiguration.java | 66 ++++++ .../authentication/oauth/OAuth2Constants.java | 68 ++++++ .../oauth/OAuth2GetAccessToken.java | 219 ++++++++++++++++++ .../authentication/oauth/OAuth2GrantType.java | 44 ++++ .../authentication/oauth/OAuth2Provider.java | 66 ++++++ .../oauth/OAuth2ProvidersRegistry.java | 122 ++++++++++ .../oauth/OAuth2QueryParser.java | 73 ++++++ .../oauth/OAuth2RequestBuilder.java | 47 ++++ .../oauth/OwnCloudOAuth2Provider.java | 94 ++++++++ .../oauth/OwnCloudOAuth2RequestBuilder.java | 139 +++++++++++ 10 files changed, 938 insertions(+) create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java new file mode 100644 index 00000000..43db97e0 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java @@ -0,0 +1,66 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +public class OAuth2ClientConfiguration { + + private String mClientId; + + private String mClientSecret; + + private String mRedirectUri; + + public OAuth2ClientConfiguration(String clientId, String clientSecret, String redirectUri) { + mClientId = (clientId == null) ? "" : clientId; + mClientSecret = (clientSecret == null) ? "" : clientSecret; + mRedirectUri = (redirectUri == null) ? "" : redirectUri; + } + + public String getClientId() { + return mClientId; + } + + public void setClientId(String clientId) { + mClientId = clientId; + } + + public String getClientSecret() { + return mClientSecret; + } + + public void setClientSecret(String clientSecret) { + mClientSecret = clientSecret; + } + + public String getRedirectUri() { + return mRedirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.mRedirectUri = redirectUri; + } +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java new file mode 100644 index 00000000..d10c88d6 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java @@ -0,0 +1,68 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +/** + * Constant values for OAuth 2 protocol. + * + * Includes required and optional parameter NAMES used in the 'authorization code' grant type. + */ + +public class OAuth2Constants { + + /// Parameters to send to the Authorization Endpoint + public static final String KEY_RESPONSE_TYPE = "response_type"; + public static final String KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_CLIENT_ID = "client_id"; + public static final String KEY_SCOPE = "scope"; + public static final String KEY_STATE = "state"; + + /// Additional parameters to send to the Token Endpoint + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + + // Used to get the Access Token using Refresh Token + public static final String OAUTH2_REFRESH_TOKEN_GRANT_TYPE = "refresh_token"; + + /// Parameters received in an OK response from the Token Endpoint + public static final String KEY_ACCESS_TOKEN = "access_token"; + public static final String KEY_TOKEN_TYPE = "token_type"; + public static final String KEY_EXPIRES_IN = "expires_in"; + public static final String KEY_REFRESH_TOKEN = "refresh_token"; + + /// Parameters in an ERROR response + public static final String KEY_ERROR = "error"; + public static final String KEY_ERROR_DESCRIPTION = "error_description"; + public static final String KEY_ERROR_URI = "error_uri"; + public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; + + /// Extra not standard + public static final String KEY_USER_ID = "user_id"; + + /// Depends on oauth2 grant type + public static final String OAUTH2_RESPONSE_TYPE_CODE = "code"; +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java new file mode 100644 index 00000000..f22b3ceb --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java @@ -0,0 +1,219 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import android.net.Uri; + +import com.owncloud.android.lib.common.OwnCloudBasicCredentials; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudCredentials; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; + +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.PostMethod; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + + +public class OAuth2GetAccessToken extends RemoteOperation { + + private String mGrantType; + private String mCode; + private String mClientId; + private String mClientSecret; + private String mRedirectUri; + private final String mAccessTokenEndpointPath; + + private Map mResultTokenMap; + + + public OAuth2GetAccessToken( + String grantType, + String code, + String clientId, + String secretId, + String redirectUri, + String accessTokenEndpointPath + ) { + mClientId = clientId; + mClientSecret = secretId; + mRedirectUri = redirectUri; + mGrantType = grantType; + mCode = code; + + mAccessTokenEndpointPath = + accessTokenEndpointPath != null ? + accessTokenEndpointPath : + OwnCloudOAuth2Provider.ACCESS_TOKEN_ENDPOINT_PATH + ; + mResultTokenMap = null; + } + + /* + public Map getResultTokenMap() { + return mResultTokenMap; + } + */ + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + NameValuePair[] nameValuePairs = new NameValuePair[4]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mCode); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); + nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + + Uri.Builder uriBuilder = client.getBaseUri().buildUpon(); + uriBuilder.appendEncodedPath(mAccessTokenEndpointPath); + + postMethod = new PostMethod(uriBuilder.build().toString()); + postMethod.setRequestBody(nameValuePairs); + + OwnCloudCredentials oauthCredentials = new OwnCloudBasicCredentials( + mClientId, + mClientSecret + ); + OwnCloudCredentials oldCredentials = switchClientCredentials(oauthCredentials); + + client.executeMethod(postMethod); + switchClientCredentials(oldCredentials); + + String response = postMethod.getResponseBodyAsString(); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || + mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, postMethod); + ArrayList data = new ArrayList<>(); + data.add(mResultTokenMap); + result.setData(data); + } + + } else { + result = new RemoteOperationResult(false, postMethod); + client.exhaustResponse(postMethod.getResponseBodyAsStream()); + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + } finally { + if (postMethod != null) + postMethod.releaseConnection(); // let the connection available for other methods + + /* + if (result.isSuccess()) { + Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mCode + " to " + + client.getWebdavUri() + ": " + result.getLogMessage()); + + } else if (result.getException() != null) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mCode + " to " + client. + getWebdavUri() + ": " + result.getLogMessage(), result.getException()); + + } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mCode + " to " + client. + getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap. + get(OAuth2Constants.KEY_ERROR) : "NULL")); + + } else { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mCode + " to " + client. + getWebdavUri() + ": " + result.getLogMessage()); + } + */ + } + + return result; + } + + private OwnCloudCredentials switchClientCredentials(OwnCloudCredentials newCredentials) { + // work-around for POC with owncloud/oauth2 app, that doesn't allow client + OwnCloudCredentials previousCredentials = getClient().getCredentials(); + getClient().setCredentials(newCredentials); + return previousCredentials; + } + + + private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap<>(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson. + getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson. + getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson. + getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson. + getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson. + getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson. + getString(OAuth2Constants.KEY_ERROR_URI)); + } + + if (tokenJson.has(OAuth2Constants.KEY_USER_ID)) { // not standard + mResultTokenMap.put(OAuth2Constants.KEY_USER_ID, tokenJson. + getString(OAuth2Constants.KEY_USER_ID)); + } + } +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java new file mode 100644 index 00000000..26c53dba --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java @@ -0,0 +1,44 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +public enum OAuth2GrantType { + AUTHORIZATION_CODE("authorization_code"), + IMPLICIT("implicit"), + PASSWORD("password"), + CLIENT_CREDENTIAL("client_credentials"); + + private String mValue; + + OAuth2GrantType(String value) { + mValue = value; + } + + public String getValue() { + return mValue; + } +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java new file mode 100644 index 00000000..b750048c --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java @@ -0,0 +1,66 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +public interface OAuth2Provider { + + /** + * {@link OAuth2RequestBuilder} implementation for this provider. + * + * @return {@link OAuth2RequestBuilder} implementation. + */ + OAuth2RequestBuilder getOperationBuilder(); + + + /** + * Set configuration of the client that will use this {@link OAuth2Provider} + * @param oAuth2ClientConfiguration Configuration of the client that will use this {@link OAuth2Provider} + */ + void setClientConfiguration(OAuth2ClientConfiguration oAuth2ClientConfiguration); + + /** + * Configuration of the client that is using this {@link OAuth2Provider} + * return Configuration of the client that is usinng this {@link OAuth2Provider} + */ + OAuth2ClientConfiguration getClientConfiguration(); + + + /** + * Set base URI to authorization server. + * + * @param authorizationServerUri Set base URL to authorization server. + */ + void setAuthorizationServerUri(String authorizationServerUri); + + /** + * base URI to authorization server. + * + * @return Base URL to authorization server. + */ + String getAuthorizationServerUri(); + +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java new file mode 100644 index 00000000..b827215f --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java @@ -0,0 +1,122 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + + +import java.util.HashMap; +import java.util.Map; + +public class OAuth2ProvidersRegistry { + + private Map mProviders = new HashMap<>(); + + private OAuth2Provider mDefaultProvider = null; + + private OAuth2ProvidersRegistry () { + } + + /** + * See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom + */ + private static class LazyHolder { + private static final OAuth2ProvidersRegistry INSTANCE = new OAuth2ProvidersRegistry(); + } + + /** + * Singleton accesor. + * + * @return Singleton isntance of {@link OAuth2ProvidersRegistry} + */ + public static OAuth2ProvidersRegistry getInstance() { + return LazyHolder.INSTANCE; + } + + /** + * Register an {@link OAuth2Provider} with the name passed as parameter. + * + * @param name Name to bind 'oAuthProvider' in the registry. + * @param oAuth2Provider An {@link OAuth2Provider} instance to keep in the registry. + * @throws IllegalArgumentException if 'name' or 'oAuthProvider' are null. + */ + public void registerProvider(String name, OAuth2Provider oAuth2Provider) { + if (name == null) { + throw new IllegalArgumentException("Name must not be NULL"); + } + if (oAuth2Provider == null) { + throw new IllegalArgumentException("oAuth2Provider must not be NULL"); + } + + mProviders.put(name, oAuth2Provider); + if (mProviders.size() == 1) { + mDefaultProvider = oAuth2Provider; + } + } + + public OAuth2Provider unregisterProvider(String name) { + OAuth2Provider unregisteredProvider = mProviders.remove(name); + if (mProviders.size() == 0) { + mDefaultProvider = null; + } else if (unregisteredProvider != null && unregisteredProvider == mDefaultProvider) { + mDefaultProvider = mProviders.values().iterator().next(); + } + return unregisteredProvider; + } + + /** + * Get default {@link OAuth2Provider}. + * + * @return Default provider, or NULL if there is no provider. + */ + public OAuth2Provider getProvider() { + return mDefaultProvider; + } + + /** + * Get {@link OAuth2Provider} registered with the name passed as parameter. + * + * @param name Name used to register the desired {@link OAuth2Provider} + * @return {@link OAuth2Provider} registered with the name 'name' + */ + public OAuth2Provider getProvider(String name) { + return mProviders.get(name); + } + + /** + * Sets the {@link OAuth2Provider} registered with the name passed as parameter as the default provider + * + * @param name Name used to register the {@link OAuth2Provider} to set as default. + * @return {@link OAuth2Provider} set as default, or NULL if no provider was registered with 'name'. + */ + public OAuth2Provider setDefaultProvider(String name) { + OAuth2Provider toDefault = mProviders.get(name); + if (toDefault != null) { + mDefaultProvider = toDefault; + } + return toDefault; + } + +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java new file mode 100644 index 00000000..98b89fe8 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java @@ -0,0 +1,73 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import com.owncloud.android.lib.common.utils.Log_OC; + +import java.util.HashMap; +import java.util.Map; + + +public class OAuth2QueryParser { + + private static final String TAG = OAuth2QueryParser.class.getName(); + + private Map mOAuth2ParsedAuthorizationResponse; + + public OAuth2QueryParser() { + mOAuth2ParsedAuthorizationResponse = new HashMap<>(); + } + + public Map parse(String query) { + mOAuth2ParsedAuthorizationResponse.clear(); + + String[] pairs = query.split("&"); + int i = 0; + String key = ""; + String value; + while (pairs.length > i) { + int j = 0; + String[] part = pairs[i].split("="); + while (part.length > j) { + String p = part[j]; + if (j == 0) { + key = p; + } else if (j == 1) { + value = p; + mOAuth2ParsedAuthorizationResponse.put(key, value); + } + + Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + + return mOAuth2ParsedAuthorizationResponse; + } + +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java new file mode 100644 index 00000000..9d3008e0 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java @@ -0,0 +1,47 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import com.owncloud.android.lib.common.operations.RemoteOperation; + +public interface OAuth2RequestBuilder { + + enum OAuthRequest { + GET_AUTHORIZATION_CODE, CREATE_ACCESS_TOKEN + } + + void setRequest(OAuthRequest operation); + + void setGrantType(OAuth2GrantType grantType); + + void setAuthorizationCode(String code); + + RemoteOperation buildOperation(); + + String buildUri(); + +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java new file mode 100644 index 00000000..75ab493b --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java @@ -0,0 +1,94 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import com.owncloud.android.lib.common.utils.Log_OC; + +public class OwnCloudOAuth2Provider implements OAuth2Provider { + + public static final String NAME = OAuth2Provider.class.getName(); + + public static final String ACCESS_TOKEN_ENDPOINT_PATH = "index.php/apps/oauth2/api/v1/token"; + private static final String AUTHORIZATION_CODE_ENDPOINT_PATH = "index.php/apps/oauth2/authorize"; + + private String mAuthorizationServerUrl = ""; + private String mAccessTokenEndpointPath = ACCESS_TOKEN_ENDPOINT_PATH; + private String mAuthorizationCodeEndpointPath = AUTHORIZATION_CODE_ENDPOINT_PATH; + + private OAuth2ClientConfiguration mClientConfiguration; + + @Override + public OAuth2RequestBuilder getOperationBuilder() { + return new OwnCloudOAuth2RequestBuilder(this); + } + + @Override + public void setClientConfiguration(OAuth2ClientConfiguration oAuth2ClientConfiguration) { + mClientConfiguration = oAuth2ClientConfiguration; + } + + @Override + public OAuth2ClientConfiguration getClientConfiguration() { + return mClientConfiguration; + } + + @Override + public void setAuthorizationServerUri(String authorizationServerUri) { + mAuthorizationServerUrl = authorizationServerUri; + } + + @Override + public String getAuthorizationServerUri() { + return mAuthorizationServerUrl; + } + + public String getAccessTokenEndpointPath() { + return mAccessTokenEndpointPath; + } + + public void setAccessTokenEndpointPath(String accessTokenEndpointPath) { + if (accessTokenEndpointPath == null || accessTokenEndpointPath.length() <= 0) { + Log_OC.w(NAME, "Setting invalid access token endpoint path, going on with default"); + mAccessTokenEndpointPath = ACCESS_TOKEN_ENDPOINT_PATH; + } else { + mAccessTokenEndpointPath = accessTokenEndpointPath; + } + } + + public String getAuthorizationCodeEndpointPath() { + return mAuthorizationCodeEndpointPath; + } + + public void setAuthorizationCodeEndpointPath(String authorizationCodeEndpointPath) { + if (authorizationCodeEndpointPath == null || authorizationCodeEndpointPath.length() <= 0) { + Log_OC.w(NAME, "Setting invalid authorization code endpoint path, going on with default"); + mAuthorizationCodeEndpointPath = AUTHORIZATION_CODE_ENDPOINT_PATH; + } else { + mAuthorizationCodeEndpointPath = authorizationCodeEndpointPath; + } + } +} diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java new file mode 100644 index 00000000..83549879 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -0,0 +1,139 @@ +/* ownCloud Android Library is available under MIT license + * + * @author David A. Velasco + * Copyright (C) 2017 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import android.net.Uri; + +import com.owncloud.android.lib.common.operations.RemoteOperation; + +public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { + + private OwnCloudOAuth2Provider mOAuth2Provider; + + private OAuthRequest mRequest; + private OAuth2GrantType mGrantType = OAuth2GrantType.AUTHORIZATION_CODE; + private String mCode; + + public OwnCloudOAuth2RequestBuilder(OwnCloudOAuth2Provider ownCloudOAuth2Provider) { + mOAuth2Provider = ownCloudOAuth2Provider; + } + + @Override + public void setRequest(OAuthRequest request) { + mRequest = request; + } + + @Override + public void setGrantType(OAuth2GrantType grantType) { + mGrantType = grantType; + } + + @Override + public void setAuthorizationCode(String code) { + mCode = code; + } + + @Override + public RemoteOperation buildOperation() { + if (OAuth2GrantType.AUTHORIZATION_CODE != mGrantType) { + throw new UnsupportedOperationException( + "Unsupported grant type. Only " + + OAuth2GrantType.AUTHORIZATION_CODE.getValue() + " is supported" + ); + } + switch(mRequest) { + case CREATE_ACCESS_TOKEN: + OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); + return new OAuth2GetAccessToken( + mGrantType.getValue(), + mCode, + clientConfiguration.getClientId(), + clientConfiguration.getClientSecret(), + clientConfiguration.getRedirectUri(), + mOAuth2Provider.getAccessTokenEndpointPath() + ); + default: + throw new UnsupportedOperationException( + "Unsupported request" + ); + } + } + + @Override + public String buildUri() { + if (OAuth2GrantType.AUTHORIZATION_CODE != mGrantType) { + throw new UnsupportedOperationException( + "Unsupported grant type. Only " + + OAuth2GrantType.AUTHORIZATION_CODE.getValue() + " is supported by this provider" + ); + } + OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); + Uri uri; + Uri.Builder uriBuilder; + switch(mRequest) { + case GET_AUTHORIZATION_CODE: + uri = Uri.parse(mOAuth2Provider.getAuthorizationServerUri()); + uriBuilder = uri.buildUpon(); + uriBuilder.appendEncodedPath(mOAuth2Provider.getAuthorizationCodeEndpointPath()); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_RESPONSE_TYPE, OAuth2Constants.OAUTH2_RESPONSE_TYPE_CODE + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_REDIRECT_URI, clientConfiguration.getRedirectUri() + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_CLIENT_ID, clientConfiguration.getClientId() + ); + + uri = uriBuilder.build(); + return uri.toString(); + + case CREATE_ACCESS_TOKEN: + uri = Uri.parse(mOAuth2Provider.getAuthorizationServerUri()); + uriBuilder = uri.buildUpon(); + uriBuilder.appendEncodedPath(mOAuth2Provider.getAccessTokenEndpointPath()); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_RESPONSE_TYPE, OAuth2Constants.OAUTH2_RESPONSE_TYPE_CODE + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_REDIRECT_URI, clientConfiguration.getRedirectUri() + ); + uriBuilder.appendQueryParameter( + OAuth2Constants.KEY_CLIENT_ID, clientConfiguration.getClientId() + ); + + uri = uriBuilder.build(); + return uri.toString(); + + default: + throw new UnsupportedOperationException( + "Unsupported request" + ); + } + } + +} From a06af810d07359116dbd84a5e417a5ab9223355c Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Tue, 25 Jul 2017 09:35:16 +0200 Subject: [PATCH 07/23] Invalidate stored auth token directly from RemoteOperation when request results UNAUTHORIZED --- .../lib/common/OwnCloudBasicCredentials.java | 5 + .../lib/common/OwnCloudBearerCredentials.java | 5 + .../lib/common/OwnCloudCredentials.java | 15 +- .../common/OwnCloudCredentialsFactory.java | 5 + .../common/OwnCloudSamlSsoCredentials.java | 79 +++---- .../common/operations/RemoteOperation.java | 202 ++++++------------ 6 files changed, 125 insertions(+), 186 deletions(-) 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. From c6f843087658a0776227af95b6ea9c7da9f08157 Mon Sep 17 00:00:00 2001 From: davigonz Date: Tue, 25 Jul 2017 12:14:22 +0200 Subject: [PATCH 08/23] Move OAuth2GetRefreshedAccessTokenOperation to the library and prepare RemoteOperation to retry the last failed operation when access token expires --- ...ava => OAuth2GetAccessTokenOperation.java} | 4 +- ...Auth2GetRefreshedAccessTokenOperation.java | 243 ++++++++++++++++++ .../oauth/OwnCloudOAuth2RequestBuilder.java | 2 +- .../common/operations/RemoteOperation.java | 32 ++- 4 files changed, 269 insertions(+), 12 deletions(-) rename src/com/owncloud/android/lib/common/network/authentication/oauth/{OAuth2GetAccessToken.java => OAuth2GetAccessTokenOperation.java} (98%) create mode 100644 src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java similarity index 98% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java rename to src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java index f22b3ceb..23ef9876 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessToken.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java @@ -45,7 +45,7 @@ import java.util.HashMap; import java.util.Map; -public class OAuth2GetAccessToken extends RemoteOperation { +public class OAuth2GetAccessTokenOperation extends RemoteOperation { private String mGrantType; private String mCode; @@ -57,7 +57,7 @@ public class OAuth2GetAccessToken extends RemoteOperation { private Map mResultTokenMap; - public OAuth2GetAccessToken( + public OAuth2GetAccessTokenOperation( String grantType, String code, String clientId, diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java new file mode 100644 index 00000000..c2c36fd5 --- /dev/null +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -0,0 +1,243 @@ +/** + * ownCloud Android client application + * + * @author David González Verdugo + * + * Copyright (C) 2017 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.lib.common.network.authentication.oauth; + +import android.net.Uri; + +import com.owncloud.android.lib.common.OwnCloudBasicCredentials; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudCredentials; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.utils.Log_OC; + +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.PostMethod; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class OAuth2GetRefreshedAccessToken extends RemoteOperation { + + private static final String TAG = OAuth2GetRefreshedAccessToken.class.getSimpleName(); + + private String mClientId; + private String mClientSecret; + private String mGrantType; + + private String mOAuth2RefreshAccessTokenQueryParams; + + private Map mOAuth2ParsedRefreshAccessTokenQueryParams; + + private Map mResultTokenMap; + + public OAuth2GetRefreshedAccessToken( + String clientId, + String secretId, + String grantType, + String oAuth2RefreshAccessTokenQueryParams + ) { + + mClientId = clientId; + mClientSecret = secretId; + mGrantType = grantType; + mOAuth2RefreshAccessTokenQueryParams = oAuth2RefreshAccessTokenQueryParams; + mOAuth2ParsedRefreshAccessTokenQueryParams = new HashMap<>(); + mResultTokenMap = null; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + parseAuthorizationResponse(); + + if (mOAuth2ParsedRefreshAccessTokenQueryParams.keySet().contains(OAuth2Constants. + KEY_ERROR)) { + if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals( + mOAuth2ParsedRefreshAccessTokenQueryParams.get(OAuth2Constants.KEY_ERROR))) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); + } else { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + } + } + + if (result == null) { + NameValuePair[] nameValuePairs = new NameValuePair[3]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REFRESH_TOKEN, + mOAuth2ParsedRefreshAccessTokenQueryParams.get(OAuth2Constants.KEY_REFRESH_TOKEN)); + + + Uri.Builder uriBuilder = client.getBaseUri().buildUpon(); + + postMethod = new PostMethod(uriBuilder.build().toString()); + postMethod.setRequestBody(nameValuePairs); + + OwnCloudCredentials oauthCredentials = new OwnCloudBasicCredentials( + mClientId, + mClientSecret + ); + OwnCloudCredentials oldCredentials = switchClientCredentials(oauthCredentials); + + client.executeMethod(postMethod); + switchClientCredentials(oldCredentials); + + String response = postMethod.getResponseBodyAsString(); + Log_OC.d(TAG, "OAUTH2: raw response from POST TOKEN: " + response); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseNewAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || + mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, postMethod); + ArrayList data = new ArrayList<>(); + data.add(mResultTokenMap); + result.setData(data); + } + + } else { + result = new RemoteOperationResult(false, postMethod); + client.exhaustResponse(postMethod.getResponseBodyAsStream()); + } + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + } finally { + if (postMethod != null) + postMethod.releaseConnection(); // let the connection available for other methods + + if (result.isSuccess()) { + Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + + client.getWebdavUri() + ": " + result.getLogMessage()); + + } else if (result.getException() != null) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + getWebdavUri() + ": " + result.getLogMessage(), result.getException()); + + } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap. + get(OAuth2Constants.KEY_ERROR) : "NULL")); + + } else { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + + mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + getWebdavUri() + ": " + result.getLogMessage()); + } + } + + return result; + } + + private OwnCloudCredentials switchClientCredentials(OwnCloudCredentials newCredentials) { + // work-around for POC with owncloud/oauth2 app, that doesn't allow client + OwnCloudCredentials previousCredentials = getClient().getCredentials(); + getClient().setCredentials(newCredentials); + return previousCredentials; + } + + private void parseAuthorizationResponse() { + String[] pairs = mOAuth2RefreshAccessTokenQueryParams.split("&"); + int i = 0; + String key = ""; + String value = ""; + StringBuilder sb = new StringBuilder(); + while (pairs.length > i) { + int j = 0; + String[] part = pairs[i].split("="); + while (part.length > j) { + String p = part[j]; + if (j == 0) { + key = p; + sb.append(key + " = "); + } else if (j == 1) { + value = p; + mOAuth2ParsedRefreshAccessTokenQueryParams.put(key, value); + sb.append(value + "\n"); + } + + Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + } + + private void parseNewAccessTokenResult(JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap<>(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson. + getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson. + getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson. + getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson. + getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson. + getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson. + getString(OAuth2Constants.KEY_ERROR_URI)); + } + + if (tokenJson.has(OAuth2Constants.KEY_USER_ID)) { // not standard + mResultTokenMap.put(OAuth2Constants.KEY_USER_ID, tokenJson. + getString(OAuth2Constants.KEY_USER_ID)); + } + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java index 83549879..91266b6d 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -68,7 +68,7 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { switch(mRequest) { case CREATE_ACCESS_TOKEN: OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); - return new OAuth2GetAccessToken( + return new OAuth2GetAccessTokenOperation( mGrantType.getValue(), mCode, clientConfiguration.getClientId(), diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index 2a5a206c..5af81bfe 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -94,6 +94,13 @@ public abstract class RemoteOperation implements Runnable { private Handler mListenerHandler = null; + /** + * 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. */ @@ -235,8 +242,11 @@ public abstract class RemoteOperation implements Runnable { @Override public final void run() { RemoteOperationResult result = null; - boolean repeat = false; + boolean repeat; + int repeatCounter = 0; do { + repeat = false; + try { grantOwnCloudClient(); result = run(mClient); @@ -249,12 +259,16 @@ public abstract class RemoteOperation implements Runnable { if (shouldInvalidateAccountCredentials(result)) { boolean invalidated = invalidateAccountCredentials(); if (invalidated && - mClient.getCredentials().authTokenCanBeRefreshed()) { - mClient = null; - repeat = true; - // this will result in a new loop, and grantOwnCloudClient() will - // create a new instance for mClient, refreshing the token via the account - // manager + 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, refreshing the token via the account + // manager } // else: operation will finish with ResultCode.UNAUTHORIZED } @@ -297,8 +311,8 @@ public abstract class RemoteOperation implements Runnable { boolean should = ResultCode.UNAUTHORIZED.equals(result.getCode()); // invalid credentials - should &= (mClient.getCredentials() != null && ! // real credentials - (mClient.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); + should &= (mClient.getCredentials() != null && // real credentials + !(mClient.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); should &= (mAccount != null && mContext != null); // have all the needed to effectively invalidate From 95fdf75d3845e2054a122b11ae895a4c44e8fd63 Mon Sep 17 00:00:00 2001 From: davigonz Date: Tue, 25 Jul 2017 12:17:32 +0200 Subject: [PATCH 09/23] Rename get refreshed access token operation --- .../oauth/OAuth2GetRefreshedAccessTokenOperation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java index c2c36fd5..02f32880 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -40,9 +40,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -public class OAuth2GetRefreshedAccessToken extends RemoteOperation { +public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { - private static final String TAG = OAuth2GetRefreshedAccessToken.class.getSimpleName(); + private static final String TAG = OAuth2GetRefreshedAccessTokenOperation.class.getSimpleName(); private String mClientId; private String mClientSecret; @@ -54,7 +54,7 @@ public class OAuth2GetRefreshedAccessToken extends RemoteOperation { private Map mResultTokenMap; - public OAuth2GetRefreshedAccessToken( + public OAuth2GetRefreshedAccessTokenOperation( String clientId, String secretId, String grantType, From ae8e3ca904124cafb0e8ae9cab33492702aee861 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Tue, 25 Jul 2017 19:04:52 +0200 Subject: [PATCH 10/23] Simplify operation to refresh OAuth2 access token --- ...Auth2GetRefreshedAccessTokenOperation.java | 136 ++++++------------ .../common/operations/RemoteOperation.java | 14 +- 2 files changed, 53 insertions(+), 97 deletions(-) diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java index 02f32880..c1257b82 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -48,9 +48,7 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { private String mClientSecret; private String mGrantType; - private String mOAuth2RefreshAccessTokenQueryParams; - - private Map mOAuth2ParsedRefreshAccessTokenQueryParams; + private String mRefreshToken; private Map mResultTokenMap; @@ -58,14 +56,13 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { String clientId, String secretId, String grantType, - String oAuth2RefreshAccessTokenQueryParams + String refreshToken ) { mClientId = clientId; mClientSecret = secretId; mGrantType = grantType; - mOAuth2RefreshAccessTokenQueryParams = oAuth2RefreshAccessTokenQueryParams; - mOAuth2ParsedRefreshAccessTokenQueryParams = new HashMap<>(); + mRefreshToken = refreshToken; mResultTokenMap = null; } @@ -76,60 +73,44 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { PostMethod postMethod = null; try { - parseAuthorizationResponse(); + NameValuePair[] nameValuePairs = new NameValuePair[3]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REFRESH_TOKEN, mRefreshToken); - if (mOAuth2ParsedRefreshAccessTokenQueryParams.keySet().contains(OAuth2Constants. - KEY_ERROR)) { - if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals( - mOAuth2ParsedRefreshAccessTokenQueryParams.get(OAuth2Constants.KEY_ERROR))) { - result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); - } else { + Uri.Builder uriBuilder = client.getBaseUri().buildUpon(); + + postMethod = new PostMethod(uriBuilder.build().toString()); + postMethod.setRequestBody(nameValuePairs); + + OwnCloudCredentials oauthCredentials = new OwnCloudBasicCredentials( + mClientId, + mClientSecret + ); + OwnCloudCredentials oldCredentials = switchClientCredentials(oauthCredentials); + + client.executeMethod(postMethod); + switchClientCredentials(oldCredentials); + + String response = postMethod.getResponseBodyAsString(); + Log_OC.d(TAG, "OAUTH2: raw response from POST TOKEN: " + response); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseNewAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || + mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); - } - } - - if (result == null) { - NameValuePair[] nameValuePairs = new NameValuePair[3]; - nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); - nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); - nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REFRESH_TOKEN, - mOAuth2ParsedRefreshAccessTokenQueryParams.get(OAuth2Constants.KEY_REFRESH_TOKEN)); - - - Uri.Builder uriBuilder = client.getBaseUri().buildUpon(); - - postMethod = new PostMethod(uriBuilder.build().toString()); - postMethod.setRequestBody(nameValuePairs); - - OwnCloudCredentials oauthCredentials = new OwnCloudBasicCredentials( - mClientId, - mClientSecret - ); - OwnCloudCredentials oldCredentials = switchClientCredentials(oauthCredentials); - - client.executeMethod(postMethod); - switchClientCredentials(oldCredentials); - - String response = postMethod.getResponseBodyAsString(); - Log_OC.d(TAG, "OAUTH2: raw response from POST TOKEN: " + response); - if (response != null && response.length() > 0) { - JSONObject tokenJson = new JSONObject(response); - parseNewAccessTokenResult(tokenJson); - if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || - mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { - result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); - - } else { - result = new RemoteOperationResult(true, postMethod); - ArrayList data = new ArrayList<>(); - data.add(mResultTokenMap); - result.setData(data); - } } else { - result = new RemoteOperationResult(false, postMethod); - client.exhaustResponse(postMethod.getResponseBodyAsStream()); + result = new RemoteOperationResult(true, postMethod); + ArrayList data = new ArrayList<>(); + data.add(mResultTokenMap); + result.setData(data); } + + } else { + result = new RemoteOperationResult(false, postMethod); + client.exhaustResponse(postMethod.getResponseBodyAsStream()); } } catch (Exception e) { @@ -139,27 +120,29 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { if (postMethod != null) postMethod.releaseConnection(); // let the connection available for other methods + /* if (result.isSuccess()) { - Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + + Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with refresh token " + + mRefreshToken + " to " + client.getWebdavUri() + ": " + result.getLogMessage()); } else if (result.getException() != null) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + + mRefreshToken + " to " + client. getWebdavUri() + ": " + result.getLogMessage(), result.getException()); } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + + mRefreshToken + " to " + client. getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap. get(OAuth2Constants.KEY_ERROR) : "NULL")); } else { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mOAuth2ParsedRefreshAccessTokenQueryParams.get("code") + " to " + client. + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + + mRefreshToken + " to " + client. getWebdavUri() + ": " + result.getLogMessage()); } + */ } return result; @@ -172,33 +155,6 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { return previousCredentials; } - private void parseAuthorizationResponse() { - String[] pairs = mOAuth2RefreshAccessTokenQueryParams.split("&"); - int i = 0; - String key = ""; - String value = ""; - StringBuilder sb = new StringBuilder(); - while (pairs.length > i) { - int j = 0; - String[] part = pairs[i].split("="); - while (part.length > j) { - String p = part[j]; - if (j == 0) { - key = p; - sb.append(key + " = "); - } else if (j == 1) { - value = p; - mOAuth2ParsedRefreshAccessTokenQueryParams.put(key, value); - sb.append(value + "\n"); - } - - Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); - j++; - } - i++; - } - } - private void parseNewAccessTokenResult(JSONObject tokenJson) throws JSONException { mResultTokenMap = new HashMap<>(); diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index 5af81bfe..d7f74e8e 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -29,15 +29,12 @@ 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; import com.owncloud.android.lib.common.OwnCloudAccount; 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; @@ -241,7 +238,7 @@ public abstract class RemoteOperation implements Runnable { */ @Override public final void run() { - RemoteOperationResult result = null; + RemoteOperationResult result; boolean repeat; int repeatCounter = 0; do { @@ -267,8 +264,10 @@ public abstract class RemoteOperation implements Runnable { repeatCounter++; // this will result in a new loop, and grantOwnCloudClient() will - // create a new instance for mClient, refreshing the token via the account - // manager + // 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 } @@ -293,7 +292,8 @@ public abstract class RemoteOperation implements Runnable { } } - private void grantOwnCloudClient() throws AccountUtils.AccountNotFoundException, OperationCanceledException, AuthenticatorException, IOException { + private void grantOwnCloudClient() throws + AccountUtils.AccountNotFoundException, OperationCanceledException, AuthenticatorException, IOException { if (mClient == null) { if (mAccount != null && mContext != null) { OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext); From aa717b3e7bfc616a5570238d3f46511b77ab7e2a Mon Sep 17 00:00:00 2001 From: davigonz Date: Mon, 31 Jul 2017 10:47:20 +0200 Subject: [PATCH 11/23] Use proper grant type to get new access token using refresh token --- .../oauth/OAuth2GetRefreshedAccessTokenOperation.java | 3 +++ .../common/network/authentication/oauth/OAuth2GrantType.java | 1 + 2 files changed, 4 insertions(+) diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java index c1257b82..ec95d628 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -87,13 +87,16 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { mClientId, mClientSecret ); + OwnCloudCredentials oldCredentials = switchClientCredentials(oauthCredentials); client.executeMethod(postMethod); + switchClientCredentials(oldCredentials); String response = postMethod.getResponseBodyAsString(); Log_OC.d(TAG, "OAUTH2: raw response from POST TOKEN: " + response); + if (response != null && response.length() > 0) { JSONObject tokenJson = new JSONObject(response); parseNewAccessTokenResult(tokenJson); diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java index 26c53dba..fdf98bcf 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java @@ -28,6 +28,7 @@ package com.owncloud.android.lib.common.network.authentication.oauth; public enum OAuth2GrantType { AUTHORIZATION_CODE("authorization_code"), + REFRESH_TOKEN("refresh_token"), IMPLICIT("implicit"), PASSWORD("password"), CLIENT_CREDENTIAL("client_credentials"); From 99334c2e4547a9b452a1746d14b59e03c2c8d6f6 Mon Sep 17 00:00:00 2001 From: davigonz Date: Tue, 1 Aug 2017 11:55:47 +0200 Subject: [PATCH 12/23] Include refresh token operation in library, builder --- ...Auth2GetRefreshedAccessTokenOperation.java | 21 +++++++++++++------ .../oauth/OAuth2RequestBuilder.java | 7 ++++--- .../oauth/OwnCloudOAuth2RequestBuilder.java | 18 +++++++++++++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java index ec95d628..28f072dc 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -44,26 +44,34 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { private static final String TAG = OAuth2GetRefreshedAccessTokenOperation.class.getSimpleName(); + private String mGrantType; private String mClientId; private String mClientSecret; - private String mGrantType; - private String mRefreshToken; - private Map mResultTokenMap; + private final String mAccessTokenEndpointPath; + + public OAuth2GetRefreshedAccessTokenOperation( + String grantType, String clientId, String secretId, - String grantType, - String refreshToken + String refreshToken, + String accessTokenEndpointPath ) { + mGrantType = grantType; mClientId = clientId; mClientSecret = secretId; - mGrantType = grantType; mRefreshToken = refreshToken; mResultTokenMap = null; + + mAccessTokenEndpointPath = + accessTokenEndpointPath != null ? + accessTokenEndpointPath : + OwnCloudOAuth2Provider.ACCESS_TOKEN_ENDPOINT_PATH + ; } @Override @@ -79,6 +87,7 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REFRESH_TOKEN, mRefreshToken); Uri.Builder uriBuilder = client.getBaseUri().buildUpon(); + uriBuilder.appendEncodedPath(mAccessTokenEndpointPath); postMethod = new PostMethod(uriBuilder.build().toString()); postMethod.setRequestBody(nameValuePairs); diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java index 9d3008e0..bfb4b681 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java @@ -31,7 +31,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperation; public interface OAuth2RequestBuilder { enum OAuthRequest { - GET_AUTHORIZATION_CODE, CREATE_ACCESS_TOKEN + GET_AUTHORIZATION_CODE, CREATE_ACCESS_TOKEN, REFRESH_ACCESS_TOKEN } void setRequest(OAuthRequest operation); @@ -40,8 +40,9 @@ public interface OAuth2RequestBuilder { void setAuthorizationCode(String code); + void setRefreshToken(String refreshToken); + RemoteOperation buildOperation(); String buildUri(); - -} +} \ No newline at end of file diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java index 91266b6d..7647a45c 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -37,6 +37,7 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { private OAuthRequest mRequest; private OAuth2GrantType mGrantType = OAuth2GrantType.AUTHORIZATION_CODE; private String mCode; + private String mRefreshToken; public OwnCloudOAuth2RequestBuilder(OwnCloudOAuth2Provider ownCloudOAuth2Provider) { mOAuth2Provider = ownCloudOAuth2Provider; @@ -57,6 +58,11 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { mCode = code; } + @Override + public void setRefreshToken(String refreshToken) { + mRefreshToken = refreshToken; + } + @Override public RemoteOperation buildOperation() { if (OAuth2GrantType.AUTHORIZATION_CODE != mGrantType) { @@ -65,9 +71,10 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { OAuth2GrantType.AUTHORIZATION_CODE.getValue() + " is supported" ); } + OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); + switch(mRequest) { case CREATE_ACCESS_TOKEN: - OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); return new OAuth2GetAccessTokenOperation( mGrantType.getValue(), mCode, @@ -76,6 +83,15 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { clientConfiguration.getRedirectUri(), mOAuth2Provider.getAccessTokenEndpointPath() ); + + case REFRESH_ACCESS_TOKEN: + return new OAuth2GetRefreshedAccessTokenOperation( + mGrantType.getValue(), + clientConfiguration.getClientId(), + clientConfiguration.getClientSecret(), + mRefreshToken, + mOAuth2Provider.getAccessTokenEndpointPath() + ); default: throw new UnsupportedOperationException( "Unsupported request" From 7f98b3804c06ef032c764d7c55e5cf6ba60479e5 Mon Sep 17 00:00:00 2001 From: davigonz Date: Tue, 1 Aug 2017 17:09:04 +0200 Subject: [PATCH 13/23] Include refresh token grantype before throwing an exception when building oauth2 operation --- .../authentication/oauth/OwnCloudOAuth2RequestBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java index 7647a45c..cfd05e1b 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -65,10 +65,12 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { @Override public RemoteOperation buildOperation() { - if (OAuth2GrantType.AUTHORIZATION_CODE != mGrantType) { + if (mGrantType != OAuth2GrantType.AUTHORIZATION_CODE && + mGrantType != OAuth2GrantType.REFRESH_TOKEN) { throw new UnsupportedOperationException( "Unsupported grant type. Only " + - OAuth2GrantType.AUTHORIZATION_CODE.getValue() + " is supported" + OAuth2GrantType.AUTHORIZATION_CODE.getValue() + " and " + + OAuth2GrantType.REFRESH_TOKEN + " are supported" ); } OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); From c04f6e346398748c0b9df1d1518753eed0940542 Mon Sep 17 00:00:00 2001 From: davigonz Date: Wed, 2 Aug 2017 12:25:38 +0200 Subject: [PATCH 14/23] Create new method to retry synchronous and asynchronous remote operations --- .../common/operations/RemoteOperation.java | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index d7f74e8e..8850348e 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -134,7 +134,8 @@ public abstract class RemoteOperation implements Runnable { Log_OC.e(TAG, "Error while trying to access to " + mAccount.name, e); return new RemoteOperationResult(e); } - return run(mClient); + + return runOperationRetryingItIfNeeded(); } @@ -152,7 +153,8 @@ public abstract class RemoteOperation implements Runnable { throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " + "OwnCloudClient"); mClient = client; - return run(client); + + return runOperationRetryingItIfNeeded(); } @@ -223,7 +225,6 @@ public abstract class RemoteOperation implements Runnable { mListenerHandler = listenerHandler; } - Thread runnerThread = new Thread(this); runnerThread.start(); return runnerThread; @@ -238,9 +239,37 @@ public abstract class RemoteOperation implements Runnable { */ @Override public final void run() { + + if (mAccount != null && mContext != null) { + // Save Client Cookies + AccountUtils.saveClient(mClient, mAccount, mContext); + } + + final RemoteOperationResult resultToSend = runOperationRetryingItIfNeeded();; + if (mListenerHandler != null && mListener != null) { + mListenerHandler.post(new Runnable() { + @Override + public void run() { + mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); + } + }); + } else if (mListener != null) { + mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); + } + } + + /** + * Run operation after asynchronous or synchronous executions. If the account credentials are + * invalidated, the operation will be retried with new valid credentials + * + * @return remote operation result + */ + private RemoteOperationResult runOperationRetryingItIfNeeded () { + RemoteOperationResult result; boolean repeat; int repeatCounter = 0; + do { repeat = false; @@ -256,7 +285,7 @@ public abstract class RemoteOperation implements Runnable { if (shouldInvalidateAccountCredentials(result)) { boolean invalidated = invalidateAccountCredentials(); if (invalidated && - mClient.getCredentials().authTokenCanBeRefreshed() && + mClient.getCredentials().authTokenCanBeRefreshed() && repeatCounter < MAX_REPEAT_COUNTER) { mClient = null; @@ -274,24 +303,10 @@ public abstract class RemoteOperation implements Runnable { } while (repeat); - if (mAccount != null && mContext != null) { - // Save Client Cookies - AccountUtils.saveClient(mClient, mAccount, mContext); - } - - final RemoteOperationResult resultToSend = result; - if (mListenerHandler != null && mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); - } - }); - } else if (mListener != null) { - mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); - } + return result; } + private void grantOwnCloudClient() throws AccountUtils.AccountNotFoundException, OperationCanceledException, AuthenticatorException, IOException { if (mClient == null) { From a4386bb4fe50e9d97f5c34cc88cd0ecbd106bcd1 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Wed, 2 Aug 2017 18:43:07 +0200 Subject: [PATCH 15/23] Allow client objects to decide if RemoteOperations should silently refresh access token when expired --- .../android/lib/common/OwnCloudAccount.java | 2 + .../android/lib/common/OwnCloudClient.java | 4 +- .../lib/common/OwnCloudClientFactory.java | 1 + .../lib/common/SingleSessionManager.java | 1 + .../lib/common/accounts/AccountUtils.java | 73 ++++++++++++- .../OwnCloudBasicCredentials.java | 4 +- .../OwnCloudBearerCredentials.java | 7 +- .../OwnCloudCredentials.java | 4 +- .../OwnCloudCredentialsFactory.java | 4 +- .../OwnCloudSamlSsoCredentials.java | 4 +- .../oauth}/BearerAuthScheme.java | 4 +- .../oauth}/BearerCredentials.java | 2 +- .../oauth/OAuth2ClientConfiguration.java | 2 +- .../authentication/oauth/OAuth2Constants.java | 2 +- .../oauth/OAuth2GetAccessTokenOperation.java | 6 +- ...Auth2GetRefreshedAccessTokenOperation.java | 6 +- .../authentication/oauth/OAuth2GrantType.java | 2 +- .../authentication/oauth/OAuth2Provider.java | 2 +- .../oauth/OAuth2ProvidersRegistry.java | 2 +- .../oauth/OAuth2QueryParser.java | 2 +- .../oauth/OAuth2RequestBuilder.java | 2 +- .../oauth/OwnCloudOAuth2Provider.java | 2 +- .../oauth/OwnCloudOAuth2RequestBuilder.java | 2 +- .../common/operations/RemoteOperation.java | 101 ++++++++++-------- .../users/GetRemoteUserQuotaOperation.java | 2 +- 25 files changed, 170 insertions(+), 73 deletions(-) rename src/com/owncloud/android/lib/common/{ => authentication}/OwnCloudBasicCredentials.java (96%) rename src/com/owncloud/android/lib/common/{ => authentication}/OwnCloudBearerCredentials.java (91%) rename src/com/owncloud/android/lib/common/{ => authentication}/OwnCloudCredentials.java (92%) rename src/com/owncloud/android/lib/common/{ => authentication}/OwnCloudCredentialsFactory.java (96%) rename src/com/owncloud/android/lib/common/{ => authentication}/OwnCloudSamlSsoCredentials.java (96%) rename src/com/owncloud/android/lib/common/{network => authentication/oauth}/BearerAuthScheme.java (98%) rename src/com/owncloud/android/lib/common/{network => authentication/oauth}/BearerCredentials.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2ClientConfiguration.java (96%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2Constants.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2GetAccessTokenOperation.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2GrantType.java (95%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2Provider.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2ProvidersRegistry.java (98%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2QueryParser.java (97%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OAuth2RequestBuilder.java (96%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OwnCloudOAuth2Provider.java (98%) rename src/com/owncloud/android/lib/common/{network => }/authentication/oauth/OwnCloudOAuth2RequestBuilder.java (98%) diff --git a/src/com/owncloud/android/lib/common/OwnCloudAccount.java b/src/com/owncloud/android/lib/common/OwnCloudAccount.java index af440e35..d6534e96 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudAccount.java +++ b/src/com/owncloud/android/lib/common/OwnCloudAccount.java @@ -29,6 +29,8 @@ import java.io.IOException; import com.owncloud.android.lib.common.accounts.AccountUtils; 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.AccountManager; diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index dc3ea126..378a864a 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -47,7 +47,9 @@ import org.apache.commons.httpclient.params.HttpParams; 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.network.RedirectionPath; import com.owncloud.android.lib.common.network.WebdavUtils; diff --git a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java index 6109324e..c94c5c8a 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -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.AccountNotFoundException; 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.resources.status.OwnCloudVersion; diff --git a/src/com/owncloud/android/lib/common/SingleSessionManager.java b/src/com/owncloud/android/lib/common/SingleSessionManager.java index 589b33fd..fbc08374 100644 --- a/src/com/owncloud/android/lib/common/SingleSessionManager.java +++ b/src/com/owncloud/android/lib/common/SingleSessionManager.java @@ -40,6 +40,7 @@ import android.util.Log; import com.owncloud.android.lib.common.accounts.AccountUtils; 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; /** diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index e77a868d..cfb03187 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -37,9 +37,12 @@ import android.accounts.OperationCanceledException; import android.content.Context; import android.net.Uri; +import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudCredentials; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +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.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 { /** diff --git a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java b/src/com/owncloud/android/lib/common/authentication/OwnCloudBasicCredentials.java similarity index 96% rename from src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java rename to src/com/owncloud/android/lib/common/authentication/OwnCloudBasicCredentials.java index da2f383c..96ed4568 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBasicCredentials.java +++ b/src/com/owncloud/android/lib/common/authentication/OwnCloudBasicCredentials.java @@ -21,7 +21,9 @@ * 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.List; diff --git a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java b/src/com/owncloud/android/lib/common/authentication/OwnCloudBearerCredentials.java similarity index 91% rename from src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java rename to src/com/owncloud/android/lib/common/authentication/OwnCloudBearerCredentials.java index 66115b6f..968d6703 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudBearerCredentials.java +++ b/src/com/owncloud/android/lib/common/authentication/OwnCloudBearerCredentials.java @@ -21,7 +21,7 @@ * THE SOFTWARE. * */ -package com.owncloud.android.lib.common; +package com.owncloud.android.lib.common.authentication; import java.util.ArrayList; 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.AuthState; -import com.owncloud.android.lib.common.network.BearerAuthScheme; -import com.owncloud.android.lib.common.network.BearerCredentials; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.authentication.oauth.BearerAuthScheme; +import com.owncloud.android.lib.common.authentication.oauth.BearerCredentials; public class OwnCloudBearerCredentials implements OwnCloudCredentials { diff --git a/src/com/owncloud/android/lib/common/OwnCloudCredentials.java b/src/com/owncloud/android/lib/common/authentication/OwnCloudCredentials.java similarity index 92% rename from src/com/owncloud/android/lib/common/OwnCloudCredentials.java rename to src/com/owncloud/android/lib/common/authentication/OwnCloudCredentials.java index 4c2d9979..978dde8b 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudCredentials.java +++ b/src/com/owncloud/android/lib/common/authentication/OwnCloudCredentials.java @@ -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 { diff --git a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java b/src/com/owncloud/android/lib/common/authentication/OwnCloudCredentialsFactory.java similarity index 96% rename from src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java rename to src/com/owncloud/android/lib/common/authentication/OwnCloudCredentialsFactory.java index 65d017b7..8643aaea 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudCredentialsFactory.java +++ b/src/com/owncloud/android/lib/common/authentication/OwnCloudCredentialsFactory.java @@ -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 { diff --git a/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java b/src/com/owncloud/android/lib/common/authentication/OwnCloudSamlSsoCredentials.java similarity index 96% rename from src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java rename to src/com/owncloud/android/lib/common/authentication/OwnCloudSamlSsoCredentials.java index 41968bbb..0f475428 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudSamlSsoCredentials.java +++ b/src/com/owncloud/android/lib/common/authentication/OwnCloudSamlSsoCredentials.java @@ -21,13 +21,15 @@ * 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.CookiePolicy; import android.net.Uri; +import com.owncloud.android.lib.common.OwnCloudClient; + public class OwnCloudSamlSsoCredentials implements OwnCloudCredentials { private String mUsername; diff --git a/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java similarity index 98% rename from src/com/owncloud/android/lib/common/network/BearerAuthScheme.java rename to src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java index c87b19d3..c3991e07 100644 --- a/src/com/owncloud/android/lib/common/network/BearerAuthScheme.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java @@ -22,11 +22,10 @@ * */ -package com.owncloud.android.lib.common.network; +package com.owncloud.android.lib.common.authentication.oauth; import java.util.Map; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpMethod; 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.InvalidCredentialsException; import org.apache.commons.httpclient.auth.MalformedChallengeException; -import org.apache.commons.httpclient.util.EncodingUtil; import com.owncloud.android.lib.common.utils.Log_OC; diff --git a/src/com/owncloud/android/lib/common/network/BearerCredentials.java b/src/com/owncloud/android/lib/common/authentication/oauth/BearerCredentials.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/BearerCredentials.java rename to src/com/owncloud/android/lib/common/authentication/oauth/BearerCredentials.java index 8de3ec76..c815bc4c 100644 --- a/src/com/owncloud/android/lib/common/network/BearerCredentials.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/BearerCredentials.java @@ -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.util.LangUtils; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java similarity index 96% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java index 43db97e0..244b5da8 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ClientConfiguration.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java @@ -24,7 +24,7 @@ * */ -package com.owncloud.android.lib.common.network.authentication.oauth; +package com.owncloud.android.lib.common.authentication.oauth; public class OAuth2ClientConfiguration { diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Constants.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Constants.java index d10c88d6..ad48e5b1 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Constants.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Constants.java @@ -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. diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java index 23ef9876..2e2611de 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java @@ -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 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.OwnCloudCredentials; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java index 28f072dc..962b3619 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java @@ -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 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.OwnCloudCredentials; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java similarity index 95% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java index fdf98bcf..2d146958 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2GrantType.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java @@ -24,7 +24,7 @@ * */ -package com.owncloud.android.lib.common.network.authentication.oauth; +package com.owncloud.android.lib.common.authentication.oauth; public enum OAuth2GrantType { AUTHORIZATION_CODE("authorization_code"), diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Provider.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Provider.java index b750048c..d259d80e 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2Provider.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2Provider.java @@ -24,7 +24,7 @@ * */ -package com.owncloud.android.lib.common.network.authentication.oauth; +package com.owncloud.android.lib.common.authentication.oauth; public interface OAuth2Provider { diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ProvidersRegistry.java similarity index 98% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ProvidersRegistry.java index b827215f..32aac0ad 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2ProvidersRegistry.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ProvidersRegistry.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2QueryParser.java similarity index 97% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2QueryParser.java index 98b89fe8..24825d2a 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2QueryParser.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2QueryParser.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RequestBuilder.java similarity index 96% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RequestBuilder.java index bfb4b681..23b3cdb9 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RequestBuilder.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2Provider.java similarity index 98% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2Provider.java index 75ab493b..63b43b3e 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2Provider.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2Provider.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java similarity index 98% rename from src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java index cfd05e1b..84c1bf85 100644 --- a/src/com/owncloud/android/lib/common/network/authentication/oauth/OwnCloudOAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index 8850348e..23fe299f 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -25,7 +25,6 @@ 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; @@ -35,9 +34,7 @@ import android.os.Handler; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; 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.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; import java.io.IOException; @@ -90,6 +87,16 @@ 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, 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 @@ -135,7 +142,7 @@ public abstract class RemoteOperation implements Runnable { 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 " + "OwnCloudClient"); mClient = client; + mSilentRefreshOfAccountCredentials = false; - return runOperationRetryingItIfNeeded(); + return runOperation(); } @@ -225,6 +233,8 @@ public abstract class RemoteOperation implements Runnable { mListenerHandler = listenerHandler; } + mSilentRefreshOfAccountCredentials = false; + Thread runnerThread = new Thread(this); runnerThread.start(); return runnerThread; @@ -240,12 +250,13 @@ public abstract class RemoteOperation implements Runnable { @Override public final void run() { + final RemoteOperationResult resultToSend = runOperation(); + if (mAccount != null && mContext != null) { // Save Client Cookies AccountUtils.saveClient(mClient, mAccount, mContext); } - final RemoteOperationResult resultToSend = runOperationRetryingItIfNeeded();; if (mListenerHandler != null && mListener != null) { mListenerHandler.post(new Runnable() { @Override @@ -259,12 +270,15 @@ public abstract class RemoteOperation implements Runnable { } /** - * Run operation after asynchronous or synchronous executions. If the account credentials are - * invalidated, the operation will be retried with new valid credentials + * Run operation for asynchronous or synchronous 'execute' method. * - * @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; boolean repeat; @@ -282,8 +296,18 @@ public abstract class RemoteOperation implements Runnable { result = new RemoteOperationResult(e); } - if (shouldInvalidateAccountCredentials(result)) { - boolean invalidated = invalidateAccountCredentials(); + if (mSilentRefreshOfAccountCredentials && + AccountUtils.shouldInvalidateAccountCredentials( + result, + mClient, + mAccount, + mContext) + ) { + boolean invalidated = AccountUtils.invalidateAccountCredentials( + mClient, + mAccount, + mContext + ); if (invalidated && mClient.getCredentials().authTokenCanBeRefreshed() && 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. * @@ -361,4 +354,26 @@ public abstract class RemoteOperation implements Runnable { public final OwnCloudClient getClient() { 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; + } } \ No newline at end of file diff --git a/src/com/owncloud/android/lib/resources/users/GetRemoteUserQuotaOperation.java b/src/com/owncloud/android/lib/resources/users/GetRemoteUserQuotaOperation.java index b821d3ef..a1e64aca 100644 --- a/src/com/owncloud/android/lib/resources/users/GetRemoteUserQuotaOperation.java +++ b/src/com/owncloud/android/lib/resources/users/GetRemoteUserQuotaOperation.java @@ -28,7 +28,7 @@ package com.owncloud.android.lib.resources.users; 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.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; From 02f35d5f5e9d4c4ca9b8a4d7351ac629c491689b Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Fri, 4 Aug 2017 11:11:37 +0200 Subject: [PATCH 16/23] Remove deprecated methods for update to version 1.0 --- .../android/lib/common/OwnCloudClient.java | 22 ------ .../lib/common/OwnCloudClientFactory.java | 79 +++---------------- .../lib/common/accounts/AccountUtils.java | 58 +++----------- .../oauth/BearerAuthScheme.java | 10 +-- .../files/ExistenceCheckRemoteOperation.java | 13 --- .../files/RenameRemoteFileOperation.java | 17 +++- .../lib/resources/status/OwnCloudVersion.java | 8 -- 7 files changed, 36 insertions(+), 171 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index 378a864a..fa67fd1f 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -144,28 +144,6 @@ public class OwnCloudClient extends HttpClient { mCredentials.applyTo(this); } - /** - * Check if a file exists in the OC server - * - * @return 'true' if the file exists; 'false' it doesn't exist - * @throws Exception When the existence could not be determined - * @deprecated Use ExistenceCheckOperation instead - */ - @Deprecated - public boolean existsFile(String path) throws IOException, HttpException { - HeadMethod head = new HeadMethod(getWebdavUri() + WebdavUtils.encodePath(path)); - try { - int status = executeMethod(head); - Log_OC.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + - ((status != HttpStatus.SC_OK) ? "(FAIL)" : "")); - exhaustResponse(head.getResponseBodyAsStream()); - return (status == HttpStatus.SC_OK); - - } finally { - head.releaseConnection(); // let the connection available for other methods - } - } - /** * Requests the received method with the received timeout (milliseconds). * diff --git a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java index c94c5c8a..8c7a23f8 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -58,11 +58,12 @@ public class OwnCloudClientFactory { /** * Creates a OwnCloudClient setup for an ownCloud account - * + * * Do not call this method from the main thread. - * + * * @param account The ownCloud account * @param appContext Android application context + * @param currentActivity Caller {@link Activity} * @return A OwnCloudClient object ready to be used * @throws AuthenticatorException If the authenticator failed to get the authorization * token for the account. @@ -72,69 +73,7 @@ public class OwnCloudClientFactory { * authorization token for the account. * @throws AccountNotFoundException If 'account' is unknown for the AccountManager * - * @deprecated : Will be deleted in version 1.0. - * Use {@link #createOwnCloudClient(Account, Context, Activity)} instead. */ - @Deprecated - public static OwnCloudClient createOwnCloudClient (Account account, Context appContext) - throws OperationCanceledException, AuthenticatorException, IOException, - AccountNotFoundException { - //Log_OC.d(TAG, "Creating OwnCloudClient associated to " + account.name); - Uri baseUri = Uri.parse(AccountUtils.getBaseUrlForAccount(appContext, account)); - AccountManager am = AccountManager.get(appContext); - // TODO avoid calling to getUserData here - boolean isOauth2 = - am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_OAUTH2) != null; - boolean isSamlSso = - am.getUserData(account, AccountUtils.Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null; - OwnCloudClient client = createOwnCloudClient(baseUri, appContext, !isSamlSso); - - String username = AccountUtils.getUsernameForAccount(account); - if (isOauth2) { - String accessToken = am.blockingGetAuthToken( - account, - AccountTypeUtils.getAuthTokenTypeAccessToken(account.type), - false); - - client.setCredentials( - OwnCloudCredentialsFactory.newBearerCredentials(username, accessToken) - ); - - } else if (isSamlSso) { // TODO avoid a call to getUserData here - String accessToken = am.blockingGetAuthToken( - account, - AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(account.type), - false); - - client.setCredentials( - OwnCloudCredentialsFactory.newSamlSsoCredentials(username, accessToken) - ); - - } else { - //String password = am.getPassword(account); - String password = am.blockingGetAuthToken( - account, - AccountTypeUtils.getAuthTokenTypePass(account.type), - false); - - OwnCloudVersion version = AccountUtils.getServerVersionForAccount(account, appContext); - client.setCredentials( - OwnCloudCredentialsFactory.newBasicCredentials( - username, - password, - (version != null && version.isPreemptiveAuthenticationPreferred()) - ) - ); - - } - - // Restore cookies - AccountUtils.restoreCookies(account, client, appContext); - - return client; - } - - public static OwnCloudClient createOwnCloudClient (Account account, Context appContext, Activity currentActivity) throws OperationCanceledException, AuthenticatorException, IOException, @@ -157,7 +96,7 @@ public class OwnCloudClientFactory { currentActivity, null, null); - + Bundle result = future.getResult(); String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); if (accessToken == null) throw new AuthenticatorException("WTF!"); @@ -173,7 +112,7 @@ public class OwnCloudClientFactory { currentActivity, null, null); - + Bundle result = future.getResult(); String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); if (accessToken == null) throw new AuthenticatorException("WTF!"); @@ -194,7 +133,7 @@ public class OwnCloudClientFactory { null, null ); - + Bundle result = future.getResult(); String password = result.getString(AccountManager.KEY_AUTHTOKEN); OwnCloudVersion version = AccountUtils.getServerVersionForAccount(account, appContext); @@ -206,13 +145,13 @@ public class OwnCloudClientFactory { ) ); } - + // Restore cookies AccountUtils.restoreCookies(account, client, appContext); - + return client; } - + /** * Creates a OwnCloudClient to access a URL and sets the desired parameters for ownCloud * client connections. diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index cfb03187..dad8e231 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -51,51 +51,30 @@ public class AccountUtils { private static final String TAG = AccountUtils.class.getSimpleName(); public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; - public static final String ODAV_PATH = "/remote.php/webdav"; public static final String STATUS_PATH = "/status.php"; /** * Constructs full url to host and webdav resource basing on host version * - * @param context - * @param account - * @return url or null on failure + * @param context Valid Android {@link Context}, needed to access the {@link AccountManager} + * @param account A stored ownCloud {@link Account} + * @return Full URL to WebDAV endpoint in the server corresponding to 'account'. * @throws AccountNotFoundException When 'account' is unknown for the AccountManager - * @deprecated To be removed in release 1.0. */ - @Deprecated - public static String constructFullURLForAccount(Context context, Account account) throws AccountNotFoundException { - AccountManager ama = AccountManager.get(context); - String baseurl = ama.getUserData(account, Constants.KEY_OC_BASE_URL); - if (baseurl == null) { - throw new AccountNotFoundException(account, "Account not found", null); - } - return baseurl + WEBDAV_PATH_4_0; - } - - /** - * Extracts url server from the account - * - * @param context - * @param account - * @return url server or null on failure - * @throws AccountNotFoundException When 'account' is unknown for the AccountManager - * @deprecated This method will be removed in version 1.0. - * Use {@link #getBaseUrlForAccount(Context, Account)} - * instead. - */ - @Deprecated - public static String constructBasicURLForAccount(Context context, Account account) + public static String getWebDavUrlForAccount(Context context, Account account) throws AccountNotFoundException { - return getBaseUrlForAccount(context, account); + + return getBaseUrlForAccount(context, account) + WEBDAV_PATH_4_0; } + /** * Extracts url server from the account * - * @param context - * @param account - * @return url server or null on failure + * @param context Valid Android {@link Context}, needed to access the {@link AccountManager} + * @param account A stored ownCloud {@link Account} + * @return Full URL to the server corresponding to 'account', ending in the base path + * common to all API endpoints. * @throws AccountNotFoundException When 'account' is unknown for the AccountManager */ public static String getBaseUrlForAccount(Context context, Account account) @@ -370,15 +349,6 @@ public class AccountUtils { public static class Constants { - /** - * Value under this key should handle path to webdav php script. Will be - * removed and usage should be replaced by combining - * {@link #KEY_OC_BASE_URL } and - * {@link com.owncloud.android.lib.resources.status.OwnCloudVersion} - * - * @deprecated - */ - public static final String KEY_OC_URL = "oc_url"; /** * Version should be 3 numbers separated by dot so it can be parsed by * {@link com.owncloud.android.lib.resources.status.OwnCloudVersion} @@ -397,12 +367,6 @@ public class AccountUtils { * Flag signaling if the ownCloud server can be accessed with session cookies from SAML-based web single-sign-on. */ public static final String KEY_SUPPORTS_SAML_WEB_SSO = "oc_supports_saml_web_sso"; - /** - * Flag signaling if the ownCloud server supports Share API" - * - * @deprecated - */ - public static final String KEY_SUPPORTS_SHARE_API = "oc_supports_share_api"; /** * OC account cookies */ diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java index c3991e07..df7ed6dd 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java @@ -71,8 +71,6 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * @param challenge Authentication challenge * * @throws MalformedChallengeException Thrown if the authentication challenge is malformed - * - * @deprecated Use parameterless constructor and {@link AuthScheme#processChallenge(String)} method */ public BearerAuthScheme(final String challenge) throws MalformedChallengeException { processChallenge(challenge); @@ -125,8 +123,6 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * for this authentication scheme * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure * @return A bearer authorization string - * - * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} */ public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)"); @@ -183,9 +179,7 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { } /** - * @deprecated Use {@link #authenticate(BearerCredentials, String)} - * - * Returns a bearer Authorization header value for the given + * Returns a bearer Authorization header value for the given * {@link BearerCredentials}. * * @param credentials The credentials to encode. @@ -245,8 +239,6 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * This method simply returns the realm for the challenge. * * @return String a String identifying the authentication challenge. - * - * @deprecated no longer used */ @Override public String getID() { diff --git a/src/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java b/src/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java index e5036104..63a95c3a 100644 --- a/src/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java +++ b/src/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java @@ -71,19 +71,6 @@ public class ExistenceCheckRemoteOperation extends RemoteOperation { mSuccessIfAbsent = successIfAbsent; } - /** - * Full constructor. Success of the operation will depend upon the value of successIfAbsent. - * - * @param remotePath Path to append to the URL owned by the client instance. - * @param context Android application context. - * @param successIfAbsent When 'true', the operation finishes in success if the path does - * NOT exist in the remote server (HTTP 404). - * @deprecated - */ - public ExistenceCheckRemoteOperation(String remotePath, Context context, boolean successIfAbsent) { - this(remotePath, successIfAbsent); - } - @Override protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; diff --git a/src/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java b/src/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java index e1cfd9c8..92ccae41 100644 --- a/src/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java +++ b/src/com/owncloud/android/lib/resources/files/RenameRemoteFileOperation.java @@ -24,6 +24,8 @@ package com.owncloud.android.lib.resources.files; +import android.os.RemoteException; + import java.io.File; import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; @@ -101,8 +103,7 @@ public class RenameRemoteFileOperation extends RemoteOperation { return new RemoteOperationResult(ResultCode.OK); } - // check if a file with the new name already exists - if (client.existsFile(mNewRemotePath)) { + if (targetPathIsUsed(client)) { return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE); } @@ -134,6 +135,18 @@ public class RenameRemoteFileOperation extends RemoteOperation { return result; } + /** + * Checks if a file with the new name already exists. + * + * @return 'True' if the target path is already used by an existing file. + */ + private boolean targetPathIsUsed(OwnCloudClient client) { + ExistenceCheckRemoteOperation existenceCheckRemoteOperation = + new ExistenceCheckRemoteOperation(mNewRemotePath, false); + RemoteOperationResult exists = existenceCheckRemoteOperation.run(client); + return exists.isSuccess(); + } + /** * Move operation */ diff --git a/src/com/owncloud/android/lib/resources/status/OwnCloudVersion.java b/src/com/owncloud/android/lib/resources/status/OwnCloudVersion.java index c69d761c..0bd66036 100644 --- a/src/com/owncloud/android/lib/resources/status/OwnCloudVersion.java +++ b/src/com/owncloud/android/lib/resources/status/OwnCloudVersion.java @@ -63,14 +63,6 @@ public class OwnCloudVersion implements Comparable { private int mVersion; private boolean mIsValid; - /** - * @deprecated Will be removed in version 1.0 of the library. - */ - private OwnCloudVersion(int version) { - mVersion = version; - mIsValid = true; - } - public OwnCloudVersion(String version) { mVersion = 0; mIsValid = false; From ae4b463478dfa5be717e80e676538171a1964ddb Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Fri, 4 Aug 2017 12:04:52 +0200 Subject: [PATCH 17/23] Update Sample Client and Test Project after moving OwnCloudCredentialsFactory to a different pacakge --- .../src/com/owncloud/android/lib/sampleclient/MainActivity.java | 2 +- .../src/com/owncloud/android/lib/test_project/TestActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sample_client/src/com/owncloud/android/lib/sampleclient/MainActivity.java b/sample_client/src/com/owncloud/android/lib/sampleclient/MainActivity.java index 31cf1c79..3cb43f72 100644 --- a/sample_client/src/com/owncloud/android/lib/sampleclient/MainActivity.java +++ b/sample_client/src/com/owncloud/android/lib/sampleclient/MainActivity.java @@ -35,7 +35,7 @@ import java.util.List; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.OwnCloudClientFactory; import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.resources.files.RemoteFile; import com.owncloud.android.lib.common.operations.RemoteOperation; diff --git a/test_client/src/com/owncloud/android/lib/test_project/TestActivity.java b/test_client/src/com/owncloud/android/lib/test_project/TestActivity.java index 4bc107fe..9b9ecc3d 100644 --- a/test_client/src/com/owncloud/android/lib/test_project/TestActivity.java +++ b/test_client/src/com/owncloud/android/lib/test_project/TestActivity.java @@ -42,7 +42,7 @@ import android.view.Menu; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; From 3ea5ad32f69b24211f3a6cc3c4465ac139e94a12 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Fri, 4 Aug 2017 12:35:48 +0200 Subject: [PATCH 18/23] Update imports in automated tests after moving OwnCloudCredentialsFactory to a different package --- .../owncloud/android/lib/test_project/test/CopyFileTest.java | 2 +- .../android/lib/test_project/test/GetCapabilitiesTest.java | 2 +- .../android/lib/test_project/test/GetShareesTest.java | 2 +- .../owncloud/android/lib/test_project/test/MoveFileTest.java | 2 +- .../android/lib/test_project/test/OwnCloudClientTest.java | 4 ++-- .../lib/test_project/test/SimpleFactoryManagerTest.java | 2 +- .../lib/test_project/test/SingleSessionManagerTest.java | 2 +- .../android/lib/test_project/test/UpdatePrivateShareTest.java | 2 +- .../android/lib/test_project/test/UpdatePublicShareTest.java | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/CopyFileTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/CopyFileTest.java index 013c2489..4c9e0d97 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/CopyFileTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/CopyFileTest.java @@ -31,7 +31,7 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetCapabilitiesTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetCapabilitiesTest.java index 9893ec27..bbdd37d0 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetCapabilitiesTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetCapabilitiesTest.java @@ -38,7 +38,7 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.resources.status.GetRemoteCapabilitiesOperation; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetShareesTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetShareesTest.java index 2c00d87b..e9d25fd2 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetShareesTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/GetShareesTest.java @@ -37,7 +37,7 @@ import org.json.JSONObject; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.resources.shares.GetRemoteShareesOperation; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/MoveFileTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/MoveFileTest.java index de17eb36..9ac235ce 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/MoveFileTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/MoveFileTest.java @@ -35,7 +35,7 @@ import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java index 8bc64e3c..fd74b252 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java @@ -43,9 +43,9 @@ import android.test.AndroidTestCase; import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; -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.authentication.OwnCloudCredentials; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.test_project.R; import com.owncloud.android.lib.test_project.SelfSignedConfidentSslSocketFactory; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/SimpleFactoryManagerTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/SimpleFactoryManagerTest.java index 3627e1b5..de70c7ed 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/SimpleFactoryManagerTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/SimpleFactoryManagerTest.java @@ -33,8 +33,8 @@ import android.test.AndroidTestCase; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.SimpleFactoryManager; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.test_project.R; import com.owncloud.android.lib.test_project.SelfSignedConfidentSslSocketFactory; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/SingleSessionManagerTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/SingleSessionManagerTest.java index be6d4738..bcc3fb82 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/SingleSessionManagerTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/SingleSessionManagerTest.java @@ -33,8 +33,8 @@ import android.test.AndroidTestCase; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.SingleSessionManager; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.test_project.R; import com.owncloud.android.lib.test_project.SelfSignedConfidentSslSocketFactory; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePrivateShareTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePrivateShareTest.java index fd8eae04..2f1155dd 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePrivateShareTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePrivateShareTest.java @@ -40,7 +40,7 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePublicShareTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePublicShareTest.java index bfb100ad..a7792b84 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePublicShareTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/UpdatePublicShareTest.java @@ -41,7 +41,7 @@ import android.util.Log; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudCredentialsFactory; +import com.owncloud.android.lib.common.authentication.OwnCloudCredentialsFactory; import com.owncloud.android.lib.common.network.NetworkUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; From c7ade613c6def9a5dccf980532d01df02e4cc261 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Fri, 4 Aug 2017 14:19:15 +0200 Subject: [PATCH 19/23] Clean-up and minor refactor after review --- .../oauth/BearerAuthScheme.java | 16 +-- .../oauth/OAuth2ClientConfiguration.java | 6 +- .../oauth/OAuth2GetAccessTokenOperation.java | 87 ++--------------- .../authentication/oauth/OAuth2GrantType.java | 5 +- ...=> OAuth2RefreshAccessTokenOperation.java} | 97 +++---------------- .../oauth/OAuth2ResponseParser.java | 77 +++++++++++++++ .../oauth/OwnCloudOAuth2RequestBuilder.java | 3 +- .../common/operations/RemoteOperation.java | 2 +- test_client/res/values/setup.xml | 8 +- .../test_project/test/OwnCloudClientTest.java | 4 +- 10 files changed, 116 insertions(+), 189 deletions(-) rename src/com/owncloud/android/lib/common/authentication/oauth/{OAuth2GetRefreshedAccessTokenOperation.java => OAuth2RefreshAccessTokenOperation.java} (52%) create mode 100644 src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ResponseParser.java diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java index df7ed6dd..b0104342 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/BearerAuthScheme.java @@ -34,8 +34,6 @@ import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.httpclient.auth.InvalidCredentialsException; import org.apache.commons.httpclient.auth.MalformedChallengeException; -import com.owncloud.android.lib.common.utils.Log_OC; - /** @@ -125,9 +123,7 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * @return A bearer authorization string */ public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { - Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)"); - - BearerCredentials bearer = null; + BearerCredentials bearer; try { bearer = (BearerCredentials) credentials; } catch (ClassCastException e) { @@ -160,8 +156,6 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * @return a basic authorization string */ public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { - Log_OC.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)"); - if (method == null) { throw new IllegalArgumentException("Method may not be null"); } @@ -202,7 +196,6 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { * @since 3.0 */ public static String authenticate(BearerCredentials credentials, String charset) { - Log_OC.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); if (credentials == null) { throw new IllegalArgumentException("Credentials may not be null"); @@ -213,14 +206,7 @@ public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { StringBuffer buffer = new StringBuffer(); buffer.append(credentials.getAccessToken()); - Log_OC.v(TAG, "OAUTH2: string to authorize: " + "Bearer " + buffer.toString()); return "Bearer " + buffer.toString(); - //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); - /*return "Bearer " + EncodingUtil.getAsciiString( - Base64.encodeBase64( - EncodingUtil.getBytes(buffer.toString(), charset) - ) - );*/ } /** diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java index 244b5da8..24814167 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ClientConfiguration.java @@ -45,7 +45,7 @@ public class OAuth2ClientConfiguration { } public void setClientId(String clientId) { - mClientId = clientId; + mClientId = (clientId == null) ? "" : clientId; } public String getClientSecret() { @@ -53,7 +53,7 @@ public class OAuth2ClientConfiguration { } public void setClientSecret(String clientSecret) { - mClientSecret = clientSecret; + mClientSecret = (clientSecret == null) ? "" : clientSecret; } public String getRedirectUri() { @@ -61,6 +61,6 @@ public class OAuth2ClientConfiguration { } public void setRedirectUri(String redirectUri) { - this.mRedirectUri = redirectUri; + this.mRedirectUri = (redirectUri == null) ? "" : redirectUri; } } diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java index 2e2611de..33b3c8b5 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetAccessTokenOperation.java @@ -54,7 +54,7 @@ public class OAuth2GetAccessTokenOperation extends RemoteOperation { private String mRedirectUri; private final String mAccessTokenEndpointPath; - private Map mResultTokenMap; + private final OAuth2ResponseParser mResponseParser; public OAuth2GetAccessTokenOperation( @@ -76,15 +76,10 @@ public class OAuth2GetAccessTokenOperation extends RemoteOperation { accessTokenEndpointPath : OwnCloudOAuth2Provider.ACCESS_TOKEN_ENDPOINT_PATH ; - mResultTokenMap = null; + + mResponseParser = new OAuth2ResponseParser(); } - /* - public Map getResultTokenMap() { - return mResultTokenMap; - } - */ - @Override protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; @@ -115,15 +110,16 @@ public class OAuth2GetAccessTokenOperation extends RemoteOperation { String response = postMethod.getResponseBodyAsString(); if (response != null && response.length() > 0) { JSONObject tokenJson = new JSONObject(response); - parseAccessTokenResult(tokenJson); - if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || - mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + Map accessTokenResult = + mResponseParser.parseAccessTokenResult(tokenJson); + if (accessTokenResult.get(OAuth2Constants.KEY_ERROR) != null || + accessTokenResult.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); } else { result = new RemoteOperationResult(true, postMethod); ArrayList data = new ArrayList<>(); - data.add(mResultTokenMap); + data.add(accessTokenResult); result.setData(data); } @@ -138,82 +134,15 @@ public class OAuth2GetAccessTokenOperation extends RemoteOperation { } finally { if (postMethod != null) postMethod.releaseConnection(); // let the connection available for other methods - - /* - if (result.isSuccess()) { - Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mCode + " to " + - client.getWebdavUri() + ": " + result.getLogMessage()); - - } else if (result.getException() != null) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mCode + " to " + client. - getWebdavUri() + ": " + result.getLogMessage(), result.getException()); - - } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mCode + " to " + client. - getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap. - get(OAuth2Constants.KEY_ERROR) : "NULL")); - - } else { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + - mCode + " to " + client. - getWebdavUri() + ": " + result.getLogMessage()); - } - */ } return result; } private OwnCloudCredentials switchClientCredentials(OwnCloudCredentials newCredentials) { - // work-around for POC with owncloud/oauth2 app, that doesn't allow client OwnCloudCredentials previousCredentials = getClient().getCredentials(); getClient().setCredentials(newCredentials); return previousCredentials; } - - private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { - mResultTokenMap = new HashMap<>(); - - if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson. - getString(OAuth2Constants.KEY_ACCESS_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson. - getString(OAuth2Constants.KEY_TOKEN_TYPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { - mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson. - getString(OAuth2Constants.KEY_EXPIRES_IN)); - } - if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson. - getString(OAuth2Constants.KEY_REFRESH_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson. - getString(OAuth2Constants.KEY_SCOPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson. - getString(OAuth2Constants.KEY_ERROR)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson. - getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson. - getString(OAuth2Constants.KEY_ERROR_URI)); - } - - if (tokenJson.has(OAuth2Constants.KEY_USER_ID)) { // not standard - mResultTokenMap.put(OAuth2Constants.KEY_USER_ID, tokenJson. - getString(OAuth2Constants.KEY_USER_ID)); - } - } } diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java index 2d146958..b9923994 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GrantType.java @@ -28,10 +28,11 @@ package com.owncloud.android.lib.common.authentication.oauth; public enum OAuth2GrantType { AUTHORIZATION_CODE("authorization_code"), - REFRESH_TOKEN("refresh_token"), IMPLICIT("implicit"), PASSWORD("password"), - CLIENT_CREDENTIAL("client_credentials"); + CLIENT_CREDENTIAL("client_credentials"), + REFRESH_TOKEN("refresh_token") // not a grant type conceptually, but used as such to refresh access tokens + ; private String mValue; diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RefreshAccessTokenOperation.java similarity index 52% rename from src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java rename to src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RefreshAccessTokenOperation.java index 962b3619..cbdb48b1 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2GetRefreshedAccessTokenOperation.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2RefreshAccessTokenOperation.java @@ -33,45 +33,41 @@ import com.owncloud.android.lib.common.utils.Log_OC; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.PostMethod; -import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; -public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { +public class OAuth2RefreshAccessTokenOperation extends RemoteOperation { - private static final String TAG = OAuth2GetRefreshedAccessTokenOperation.class.getSimpleName(); + private static final String TAG = OAuth2RefreshAccessTokenOperation.class.getSimpleName(); - private String mGrantType; private String mClientId; private String mClientSecret; private String mRefreshToken; - private Map mResultTokenMap; private final String mAccessTokenEndpointPath; + private final OAuth2ResponseParser mResponseParser; - public OAuth2GetRefreshedAccessTokenOperation( - String grantType, + public OAuth2RefreshAccessTokenOperation( String clientId, String secretId, String refreshToken, String accessTokenEndpointPath ) { - mGrantType = grantType; mClientId = clientId; mClientSecret = secretId; mRefreshToken = refreshToken; - mResultTokenMap = null; mAccessTokenEndpointPath = accessTokenEndpointPath != null ? accessTokenEndpointPath : OwnCloudOAuth2Provider.ACCESS_TOKEN_ENDPOINT_PATH ; + + mResponseParser = new OAuth2ResponseParser(); } @Override @@ -82,7 +78,10 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { try { NameValuePair[] nameValuePairs = new NameValuePair[3]; - nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[0] = new NameValuePair( + OAuth2Constants.KEY_GRANT_TYPE, + OAuth2GrantType.REFRESH_TOKEN.getValue() // always for this operation + ); nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REFRESH_TOKEN, mRefreshToken); @@ -108,15 +107,16 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { if (response != null && response.length() > 0) { JSONObject tokenJson = new JSONObject(response); - parseNewAccessTokenResult(tokenJson); - if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || - mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + Map accessTokenResult = + mResponseParser.parseAccessTokenResult(tokenJson); + if (accessTokenResult.get(OAuth2Constants.KEY_ERROR) != null || + accessTokenResult.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); } else { result = new RemoteOperationResult(true, postMethod); ArrayList data = new ArrayList<>(); - data.add(mResultTokenMap); + data.add(accessTokenResult); result.setData(data); } @@ -129,83 +129,18 @@ public class OAuth2GetRefreshedAccessTokenOperation extends RemoteOperation { result = new RemoteOperationResult(e); } finally { - if (postMethod != null) + if (postMethod != null) { postMethod.releaseConnection(); // let the connection available for other methods - - /* - if (result.isSuccess()) { - Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with refresh token " + - mRefreshToken + " to " + - client.getWebdavUri() + ": " + result.getLogMessage()); - - } else if (result.getException() != null) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + - mRefreshToken + " to " + client. - getWebdavUri() + ": " + result.getLogMessage(), result.getException()); - - } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + - mRefreshToken + " to " + client. - getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap. - get(OAuth2Constants.KEY_ERROR) : "NULL")); - - } else { - Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with refresh token " + - mRefreshToken + " to " + client. - getWebdavUri() + ": " + result.getLogMessage()); } - */ } return result; } private OwnCloudCredentials switchClientCredentials(OwnCloudCredentials newCredentials) { - // work-around for POC with owncloud/oauth2 app, that doesn't allow client OwnCloudCredentials previousCredentials = getClient().getCredentials(); getClient().setCredentials(newCredentials); return previousCredentials; } - private void parseNewAccessTokenResult(JSONObject tokenJson) throws JSONException { - mResultTokenMap = new HashMap<>(); - - if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson. - getString(OAuth2Constants.KEY_ACCESS_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson. - getString(OAuth2Constants.KEY_TOKEN_TYPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { - mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson. - getString(OAuth2Constants.KEY_EXPIRES_IN)); - } - if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { - mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson. - getString(OAuth2Constants.KEY_REFRESH_TOKEN)); - } - if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { - mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson. - getString(OAuth2Constants.KEY_SCOPE)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson. - getString(OAuth2Constants.KEY_ERROR)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson. - getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); - } - if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { - mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson. - getString(OAuth2Constants.KEY_ERROR_URI)); - } - - if (tokenJson.has(OAuth2Constants.KEY_USER_ID)) { // not standard - mResultTokenMap.put(OAuth2Constants.KEY_USER_ID, tokenJson. - getString(OAuth2Constants.KEY_USER_ID)); - } - } } \ No newline at end of file diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ResponseParser.java b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ResponseParser.java new file mode 100644 index 00000000..068165f2 --- /dev/null +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OAuth2ResponseParser.java @@ -0,0 +1,77 @@ +/** + * ownCloud Android client application + * + * @author David A. Velasco + * + * Copyright (C) 2017 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.lib.common.authentication.oauth; + + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +class OAuth2ResponseParser { + + Map parseAccessTokenResult(JSONObject tokenJson) throws JSONException { + Map resultTokenMap = new HashMap<>(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + resultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + resultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson. + getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + resultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson. + getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + resultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson. + getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + resultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson. + getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + resultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson. + getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + resultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson. + getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + resultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson. + getString(OAuth2Constants.KEY_ERROR_URI)); + } + + if (tokenJson.has(OAuth2Constants.KEY_USER_ID)) { // not standard + resultTokenMap.put(OAuth2Constants.KEY_USER_ID, tokenJson. + getString(OAuth2Constants.KEY_USER_ID)); + } + + return resultTokenMap; + } + +} diff --git a/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java index 84c1bf85..18933824 100644 --- a/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java +++ b/src/com/owncloud/android/lib/common/authentication/oauth/OwnCloudOAuth2RequestBuilder.java @@ -87,8 +87,7 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder { ); case REFRESH_ACCESS_TOKEN: - return new OAuth2GetRefreshedAccessTokenOperation( - mGrantType.getValue(), + return new OAuth2RefreshAccessTokenOperation( clientConfiguration.getClientId(), clientConfiguration.getClientSecret(), mRefreshToken, diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index 23fe299f..f059bfe5 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -189,10 +189,10 @@ public abstract class RemoteOperation implements Runnable { if (context == null) throw new IllegalArgumentException ("Trying to execute a remote operation with a NULL Context"); + // mAccount and mContext in the runnerThread to create below mAccount = account; mContext = context.getApplicationContext(); mClient = null; // the client instance will be created from - // mAccount and mContext in the runnerThread to create below mListener = listener; diff --git a/test_client/res/values/setup.xml b/test_client/res/values/setup.xml index 0f4c5c70..3fc22fe1 100644 --- a/test_client/res/values/setup.xml +++ b/test_client/res/values/setup.xml @@ -25,9 +25,9 @@ - - - - + https://qa.oc.solidgear.es + https://qa2.oc.solidgear.es + android-library-test + letitgo,letitgo,thatperfectappisgone Mozilla/5.0 (Android) ownCloud test project diff --git a/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java b/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java index fd74b252..8210c3de 100644 --- a/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java +++ b/test_client/tests/src/com/owncloud/android/lib/test_project/test/OwnCloudClientTest.java @@ -130,7 +130,7 @@ public class OwnCloudClientTest extends AndroidTestCase { client.setCredentials(credentials); assertEquals("Basic credentials not set", credentials, client.getCredentials()); - credentials = OwnCloudCredentialsFactory.newBearerCredentials("bearerToken"); + credentials = OwnCloudCredentialsFactory.newBearerCredentials("user", "bearerToken"); client.setCredentials(credentials); assertEquals("Bearer credentials not set", credentials, client.getCredentials()); @@ -294,7 +294,7 @@ public class OwnCloudClientTest extends AndroidTestCase { public void testGetWebdavUri() { OwnCloudClient client = new OwnCloudClient(mServerUri, NetworkUtils.getMultiThreadedConnManager()); - client.setCredentials(OwnCloudCredentialsFactory.newBearerCredentials("fakeToken")); + client.setCredentials(OwnCloudCredentialsFactory.newBearerCredentials("user", "fakeToken")); Uri webdavUri = client.getWebdavUri(); assertTrue("WebDAV URI does not point to the right entry point", webdavUri.getPath().endsWith(AccountUtils.WEBDAV_PATH_4_0)); From dd2d3b89964740806c4f78896914543d36fcf5d3 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Mon, 14 Aug 2017 14:46:08 +0200 Subject: [PATCH 20/23] Extend chance of silent refresh of auth token in RemoteOperations that are run with execute(...) methods receiving OwnCloudClients --- .../android/lib/common/OwnCloudClient.java | 26 ++++++++- .../lib/common/OwnCloudClientFactory.java | 1 + .../lib/common/SimpleFactoryManager.java | 55 ++++++++++--------- .../lib/common/SingleSessionManager.java | 1 + .../lib/common/accounts/AccountUtils.java | 21 +++++-- .../common/operations/RemoteOperation.java | 24 +++++--- 6 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index fa67fd1f..96bace13 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -33,7 +33,6 @@ import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.HttpStatus; @@ -41,10 +40,10 @@ import org.apache.commons.httpclient.HttpVersion; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.cookie.CookiePolicy; -import org.apache.commons.httpclient.methods.HeadMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.params.HttpParams; +import android.content.Context; import android.net.Uri; import com.owncloud.android.lib.common.authentication.OwnCloudCredentials; @@ -52,7 +51,6 @@ 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.network.RedirectionPath; -import com.owncloud.android.lib.common.network.WebdavUtils; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.status.OwnCloudVersion; @@ -75,6 +73,11 @@ 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 + private Context mContext; + private OwnCloudAccount mAccount; + /** * Constructor */ @@ -448,4 +451,21 @@ public class OwnCloudClient extends HttpClient { public OwnCloudVersion getOwnCloudVersion() { return mVersion; } + + public void setContext(Context context) { + this.mContext = context; + } + + public Context getContext() { + return mContext; + } + + public void setAccount(OwnCloudAccount account) { + this.mAccount = account; + } + + public OwnCloudAccount getAccount() { + return mAccount; + } + } diff --git a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java index 8c7a23f8..ff9e32b0 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClientFactory.java @@ -176,6 +176,7 @@ public class OwnCloudClientFactory { OwnCloudClient client = new OwnCloudClient(uri, NetworkUtils.getMultiThreadedConnManager()); client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); client.setFollowRedirects(followRedirects); + client.setContext(context); return client; } diff --git a/src/com/owncloud/android/lib/common/SimpleFactoryManager.java b/src/com/owncloud/android/lib/common/SimpleFactoryManager.java index 362d2a6e..81abbc78 100644 --- a/src/com/owncloud/android/lib/common/SimpleFactoryManager.java +++ b/src/com/owncloud/android/lib/common/SimpleFactoryManager.java @@ -36,44 +36,45 @@ import com.owncloud.android.lib.common.utils.Log_OC; import java.io.IOException; public class SimpleFactoryManager implements OwnCloudClientManager { - - private static final String TAG = SimpleFactoryManager.class.getSimpleName(); - @Override - public OwnCloudClient getClientFor(OwnCloudAccount account, Context context) - throws AccountNotFoundException, OperationCanceledException, AuthenticatorException, - IOException { + private static final String TAG = SimpleFactoryManager.class.getSimpleName(); - Log_OC.d(TAG, "getClientFor(OwnCloudAccount ... : "); + @Override + public OwnCloudClient getClientFor(OwnCloudAccount account, Context context) + throws AccountNotFoundException, OperationCanceledException, AuthenticatorException, + IOException { - OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient( - account.getBaseUri(), - context.getApplicationContext(), - true); + Log_OC.d(TAG, "getClientFor(OwnCloudAccount ... : "); - Log_OC.v(TAG, " new client {" + - (account.getName() != null ? - account.getName() : - AccountUtils.buildAccountName(account.getBaseUri(), "") + OwnCloudClient client = OwnCloudClientFactory.createOwnCloudClient( + account.getBaseUri(), + context.getApplicationContext(), + true); - ) + ", " + client.hashCode() + "}"); + Log_OC.v(TAG, " new client {" + + (account.getName() != null ? + account.getName() : + AccountUtils.buildAccountName(account.getBaseUri(), "") + + ) + ", " + client.hashCode() + "}"); if (account.getCredentials() == null) { account.loadCredentials(context); } client.setCredentials(account.getCredentials()); - return client; - } + client.setAccount(account); + return client; + } - @Override - public OwnCloudClient removeClientFor(OwnCloudAccount account) { - // nothing to do - not taking care of tracking instances! - return null; - } + @Override + public OwnCloudClient removeClientFor(OwnCloudAccount account) { + // nothing to do - not taking care of tracking instances! + return null; + } - @Override - public void saveAllClients(Context context, String accountType) { - // nothing to do - not taking care of tracking instances! - } + @Override + public void saveAllClients(Context context, String accountType) { + // nothing to do - not taking care of tracking instances! + } } diff --git a/src/com/owncloud/android/lib/common/SingleSessionManager.java b/src/com/owncloud/android/lib/common/SingleSessionManager.java index fbc08374..f168837d 100644 --- a/src/com/owncloud/android/lib/common/SingleSessionManager.java +++ b/src/com/owncloud/android/lib/common/SingleSessionManager.java @@ -116,6 +116,7 @@ public class SingleSessionManager implements OwnCloudClientManager { context.getApplicationContext(), true); // TODO remove dependency on OwnCloudClientFactory client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); + client.setAccount(account); // enable cookie tracking AccountUtils.restoreCookies(account.getSavedAccount(), client, context); diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index dad8e231..7f91207d 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -287,7 +287,9 @@ public class AccountUtils { should &= (client.getCredentials() != null && // real credentials !(client.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); - should &= (account != null && context != null); // have all the needed to effectively invalidate + // 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; } @@ -310,16 +312,25 @@ public class AccountUtils { 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(account, context); + OwnCloudAccount ocAccount = new OwnCloudAccount(checkedAccount, checkedContext); OwnCloudClientManagerFactory.getDefaultSingleton(). removeClientFor(ocAccount); // to prevent nobody else is provided this client - AccountManager am = AccountManager.get(context); + AccountManager am = AccountManager.get(checkedContext); am.invalidateAuthToken( - account.type, + checkedAccount.type, client.getCredentials().getAuthToken() ); - am.clearPassword(account); // being strict, only needed for Basic Auth credentials + am.clearPassword(checkedAccount); // being strict, only needed for Basic Auth credentials return true; } catch (AccountUtils.AccountNotFoundException e) { diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index f059bfe5..c2590adb 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -90,10 +90,14 @@ public abstract class RemoteOperation implements Runnable { /** * 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: + * 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; @@ -160,7 +164,10 @@ public abstract class RemoteOperation implements Runnable { throw new IllegalArgumentException("Trying to execute a remote operation with a NULL " + "OwnCloudClient"); mClient = client; - mSilentRefreshOfAccountCredentials = false; + if (client.getAccount() != null) { + mAccount = client.getAccount().getSavedAccount(); + } + mContext = client.getContext(); return runOperation(); } @@ -221,6 +228,10 @@ public abstract class RemoteOperation implements Runnable { ("Trying to execute a remote operation with a NULL OwnCloudClient"); } mClient = client; + if (client.getAccount() != null) { + mAccount = client.getAccount().getSavedAccount(); + } + mContext = client.getContext(); if (listener == null) { throw new IllegalArgumentException @@ -233,8 +244,6 @@ public abstract class RemoteOperation implements Runnable { mListenerHandler = listenerHandler; } - mSilentRefreshOfAccountCredentials = false; - Thread runnerThread = new Thread(this); runnerThread.start(); return runnerThread; @@ -359,7 +368,7 @@ public abstract class RemoteOperation implements Runnable { /** * Enables or disables silent refresh of credentials, if supported by credentials itself. * - * Will have effect if called before: + * Will have effect if called before in all cases: * -- {@link RemoteOperation#execute(Account, Context)} * -- {@link RemoteOperation#execute(Account, Context, OnRemoteOperationListener, Handler)} * @@ -367,7 +376,8 @@ public abstract class RemoteOperation implements Runnable { * -- {@link RemoteOperation#execute(OwnCloudClient)} * -- {@link RemoteOperation#execute(OwnCloudClient, OnRemoteOperationListener, Handler)} * - * @param silentRefreshOfAccountCredentials 'True' enables silent refresh, 'false' disables it. + * ... UNLESS the passed {@link OwnCloudClient} returns non-NULL values for + * {@link OwnCloudClient#getAccount()} && {@link OwnCloudClient#getContext()} */ public void setSilentRefreshOfAccountCredentials(boolean silentRefreshOfAccountCredentials) { mSilentRefreshOfAccountCredentials = silentRefreshOfAccountCredentials; From e466bac6b15336983340e8b2c552a88423a06264 Mon Sep 17 00:00:00 2001 From: "David A. Velasco" Date: Thu, 17 Aug 2017 09:22:30 +0200 Subject: [PATCH 21/23] Move responsibility for refreshing expired credentials from RemoteOperation to OwnCloudClient --- .../android/lib/common/OwnCloudAccount.java | 8 +- .../android/lib/common/OwnCloudClient.java | 169 ++++++++++++++++-- .../lib/common/SimpleFactoryManager.java | 2 + .../lib/common/SingleSessionManager.java | 4 +- .../lib/common/accounts/AccountUtils.java | 78 +------- .../common/operations/RemoteOperation.java | 102 +---------- 6 files changed, 170 insertions(+), 193 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudAccount.java b/src/com/owncloud/android/lib/common/OwnCloudAccount.java index d6534e96..533247df 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudAccount.java +++ b/src/com/owncloud/android/lib/common/OwnCloudAccount.java @@ -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; diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index 96bace13..99433d2c 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -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; + } + } diff --git a/src/com/owncloud/android/lib/common/SimpleFactoryManager.java b/src/com/owncloud/android/lib/common/SimpleFactoryManager.java index 81abbc78..b955c517 100644 --- a/src/com/owncloud/android/lib/common/SimpleFactoryManager.java +++ b/src/com/owncloud/android/lib/common/SimpleFactoryManager.java @@ -63,6 +63,8 @@ public class SimpleFactoryManager implements OwnCloudClientManager { } client.setCredentials(account.getCredentials()); client.setAccount(account); + client.setContext(context); + client.setOwnCloudClientManager(this); return client; } diff --git a/src/com/owncloud/android/lib/common/SingleSessionManager.java b/src/com/owncloud/android/lib/common/SingleSessionManager.java index f168837d..05396480 100644 --- a/src/com/owncloud/android/lib/common/SingleSessionManager.java +++ b/src/com/owncloud/android/lib/common/SingleSessionManager.java @@ -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); diff --git a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java index 7f91207d..f59cfd59 100644 --- a/src/com/owncloud/android/lib/common/accounts/AccountUtils.java +++ b/src/com/owncloud/android/lib/common/accounts/AccountUtils.java @@ -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 { /** diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java index c2590adb..a8de3938 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperation.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperation.java @@ -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; - } } \ No newline at end of file From 12d04bb63c7960469aeed51f3613adf181ab6c80 Mon Sep 17 00:00:00 2001 From: davigonz Date: Thu, 17 Aug 2017 10:45:01 +0200 Subject: [PATCH 22/23] Ensure that SAML credentials will be invalidated if appropriate --- .../android/lib/common/OwnCloudClient.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index 99433d2c..1ff53fd4 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -93,7 +93,7 @@ public class OwnCloudClient extends HttpClient { */ private boolean mSilentRefreshOfAccountCredentials = true; - + private String mRedirectedLocation; /** * Constructor @@ -278,6 +278,7 @@ public class OwnCloudClient extends HttpClient { int redirectionsCount = 0; int status = method.getStatusCode(); RedirectionPath result = new RedirectionPath(status, MAX_REDIRECTIONS_COUNT); + while (redirectionsCount < MAX_REDIRECTIONS_COUNT && (status == HttpStatus.SC_MOVED_PERMANENTLY || status == HttpStatus.SC_MOVED_TEMPORARILY || @@ -295,6 +296,8 @@ public class OwnCloudClient extends HttpClient { String locationStr = location.getValue(); result.addLocation(locationStr); + mRedirectedLocation = locationStr; + // Release the connection to avoid reach the max number of connections per host // due to it will be set a different url exhaustResponse(method.getResponseBodyAsStream()); @@ -568,7 +571,7 @@ public class OwnCloudClient extends HttpClient { */ private boolean shouldInvalidateAccountCredentials(int httpStatusCode) { - boolean should = (httpStatusCode == HttpStatus.SC_UNAUTHORIZED); // invalid credentials + boolean should = (httpStatusCode == HttpStatus.SC_UNAUTHORIZED || isIdPRedirection()); // invalid credentials should &= (mCredentials != null && // real credentials !(mCredentials instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); @@ -605,4 +608,13 @@ public class OwnCloudClient extends HttpClient { mOwnCloudClientManager = clientManager; } + /** + * Check if the redirection is to an identity provider such as SAML or wayf + * @return true if the redirection location includes SAML or wayf, false otherwise + */ + private boolean isIdPRedirection() { + return (mRedirectedLocation != null && + (mRedirectedLocation.toUpperCase().contains("SAML") || + mRedirectedLocation.toLowerCase().contains("wayf"))); + } } From 75821de7a3fbf818950a75f55d47dce58347f8c0 Mon Sep 17 00:00:00 2001 From: davigonz Date: Thu, 17 Aug 2017 11:16:32 +0200 Subject: [PATCH 23/23] Changes to invalidate SAML credentials properly --- .../android/lib/common/OwnCloudClient.java | 25 ++++++++++++++----- .../operations/RemoteOperationResult.java | 10 +++----- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/com/owncloud/android/lib/common/OwnCloudClient.java b/src/com/owncloud/android/lib/common/OwnCloudClient.java index 1ff53fd4..60c84851 100644 --- a/src/com/owncloud/android/lib/common/OwnCloudClient.java +++ b/src/com/owncloud/android/lib/common/OwnCloudClient.java @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. + * Copyright (C) 2017 ownCloud GmbH. * Copyright (C) 2012 Bartek Przybylski * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -229,6 +229,8 @@ public class OwnCloudClient extends HttpClient { status = super.executeMethod(method); + checkFirstRedirection(method); + if (mFollowRedirects) { status = followRedirection(method).getLastStatus(); } @@ -247,6 +249,18 @@ public class OwnCloudClient extends HttpClient { return status; } + private void checkFirstRedirection(HttpMethod method) { + Header[] httpHeaders = method.getResponseHeaders(); + + for (Header httpHeader : httpHeaders) { + + if ("location".equals(httpHeader.getName().toLowerCase())) { + mRedirectedLocation = httpHeader.getValue(); + break; + } + } + } + /** * Fix for https://github.com/owncloud/android/issues/1847#issuecomment-267558274 * @@ -290,13 +304,12 @@ public class OwnCloudClient extends HttpClient { location = method.getResponseHeader("location"); } if (location != null) { - Log_OC.d(TAG + " #" + mInstanceNumber, - "Location to redirect: " + location.getValue()); - String locationStr = location.getValue(); - result.addLocation(locationStr); - mRedirectedLocation = locationStr; + Log_OC.d(TAG + " #" + mInstanceNumber, + "Location to redirect: " + locationStr); + + result.addLocation(locationStr); // Release the connection to avoid reach the max number of connections per host // due to it will be set a different url diff --git a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index c04a9391..e8699e7d 100644 --- a/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -313,15 +313,13 @@ public class RemoteOperationResult implements Serializable { public RemoteOperationResult(boolean success, int httpCode, String httpPhrase, Header[] httpHeaders) { this(success, httpCode, httpPhrase); if (httpHeaders != null) { - Header current; for (Header httpHeader : httpHeaders) { - current = httpHeader; - if ("location".equals(current.getName().toLowerCase())) { - mRedirectedLocation = current.getValue(); + if ("location".equals(httpHeader.getName().toLowerCase())) { + mRedirectedLocation = httpHeader.getValue(); continue; } - if ("www-authenticate".equals(current.getName().toLowerCase())) { - mAuthenticate.add(current.getValue().toLowerCase()); + if ("www-authenticate".equals(httpHeader.getName().toLowerCase())) { + mAuthenticate.add(httpHeader.getValue().toLowerCase()); } } }