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

Move OAuth2GetRefreshedAccessTokenOperation to the library and prepare RemoteOperation to retry the last failed operation when access token expires

This commit is contained in:
davigonz 2017-07-25 12:14:22 +02:00
parent a06af810d0
commit c6f8430876
4 changed files with 269 additions and 12 deletions

View File

@ -45,7 +45,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class OAuth2GetAccessToken extends RemoteOperation { public class OAuth2GetAccessTokenOperation extends RemoteOperation {
private String mGrantType; private String mGrantType;
private String mCode; private String mCode;
@ -57,7 +57,7 @@ public class OAuth2GetAccessToken extends RemoteOperation {
private Map<String, String> mResultTokenMap; private Map<String, String> mResultTokenMap;
public OAuth2GetAccessToken( public OAuth2GetAccessTokenOperation(
String grantType, String grantType,
String code, String code,
String clientId, String clientId,

View File

@ -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 <http://www.gnu.org/licenses/>.
*
*/
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<String, String> mOAuth2ParsedRefreshAccessTokenQueryParams;
private Map<String, String> 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<Object> 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));
}
}
}

View File

@ -68,7 +68,7 @@ public class OwnCloudOAuth2RequestBuilder implements OAuth2RequestBuilder {
switch(mRequest) { switch(mRequest) {
case CREATE_ACCESS_TOKEN: case CREATE_ACCESS_TOKEN:
OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration(); OAuth2ClientConfiguration clientConfiguration = mOAuth2Provider.getClientConfiguration();
return new OAuth2GetAccessToken( return new OAuth2GetAccessTokenOperation(
mGrantType.getValue(), mGrantType.getValue(),
mCode, mCode,
clientConfiguration.getClientId(), clientConfiguration.getClientId(),

View File

@ -94,6 +94,13 @@ public abstract class RemoteOperation implements Runnable {
private Handler mListenerHandler = null; 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. * Abstract method to implement the operation in derived classes.
*/ */
@ -235,8 +242,11 @@ public abstract class RemoteOperation implements Runnable {
@Override @Override
public final void run() { public final void run() {
RemoteOperationResult result = null; RemoteOperationResult result = null;
boolean repeat = false; boolean repeat;
int repeatCounter = 0;
do { do {
repeat = false;
try { try {
grantOwnCloudClient(); grantOwnCloudClient();
result = run(mClient); result = run(mClient);
@ -249,12 +259,16 @@ public abstract class RemoteOperation implements Runnable {
if (shouldInvalidateAccountCredentials(result)) { if (shouldInvalidateAccountCredentials(result)) {
boolean invalidated = invalidateAccountCredentials(); boolean invalidated = invalidateAccountCredentials();
if (invalidated && if (invalidated &&
mClient.getCredentials().authTokenCanBeRefreshed()) { mClient.getCredentials().authTokenCanBeRefreshed() &&
mClient = null; repeatCounter < MAX_REPEAT_COUNTER) {
repeat = true;
// this will result in a new loop, and grantOwnCloudClient() will mClient = null;
// create a new instance for mClient, refreshing the token via the account repeat = true;
// manager 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 // 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 boolean should = ResultCode.UNAUTHORIZED.equals(result.getCode()); // invalid credentials
should &= (mClient.getCredentials() != null && ! // real credentials should &= (mClient.getCredentials() != null && // real credentials
(mClient.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials)); !(mClient.getCredentials() instanceof OwnCloudCredentialsFactory.OwnCloudAnonymousCredentials));
should &= (mAccount != null && mContext != null); // have all the needed to effectively invalidate should &= (mAccount != null && mContext != null); // have all the needed to effectively invalidate