mirror of
https://github.com/owncloud/android-library.git
synced 2025-06-08 08:26:10 +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:
parent
a06af810d0
commit
c6f8430876
@ -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,
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user