From: David A. Velasco Date: Thu, 17 Jan 2013 13:25:49 +0000 (+0100) Subject: Merge branch 'master' into oauth_login X-Git-Tag: oc-android-1.4.3~29^2~22 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/2b5785556e7f7c3cefc5f084949e091f80abed6d?hp=c4cd9ef44c48f39767828a97e1cf6bbddd71c855 Merge branch 'master' into oauth_login Conflicts: AndroidManifest.xml res/values-de-rDE/strings.xml res/values-de/strings.xml res/values/strings.xml src/com/owncloud/android/Uploader.java src/com/owncloud/android/datamodel/FileDataStorageManager.java src/com/owncloud/android/files/OwnCloudFileObserver.java src/com/owncloud/android/files/services/FileDownloader.java src/com/owncloud/android/files/services/FileUploader.java src/com/owncloud/android/operations/RemoteOperationResult.java src/com/owncloud/android/syncadapter/FileSyncAdapter.java src/com/owncloud/android/ui/activity/AuthenticatorActivity.java src/com/owncloud/android/ui/activity/FileDisplayActivity.java src/com/owncloud/android/ui/activity/UploadFilesActivity.java src/com/owncloud/android/ui/fragment/FileDetailFragment.java src/com/owncloud/android/ui/fragment/OCFileListFragment.java --- diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 994efb76..3cb25781 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -120,7 +120,16 @@ + android:theme="@style/Theme.ownCloud.noActionBar" + android:launchMode="singleTask"> + + + + + + + + @@ -137,7 +146,6 @@ - @@ -152,7 +160,11 @@ - + + + + + diff --git a/res/layout-land/account_setup.xml b/res/layout-land/account_setup.xml index 24b3e8ad..d68feea3 100644 --- a/res/layout-land/account_setup.xml +++ b/res/layout-land/account_setup.xml @@ -99,6 +99,15 @@ android:visibility="invisible" /> + + + + + + + + + + + + + + - + + + + + + + + + + + + + "Unexpected problem ; please select the file from a different app" No file was selected + oAuth2 URL + Login with oAuth2. + Connecting to oAuth2 server… + Please, open a web browser and go to:\n%1$s.\nValidate this code there:\n%2$s + Connection to this URL not available. + Warning The identity of the site could not be verified - The server certificate is not trusted diff --git a/res/values/styles.xml b/res/values/styles.xml index 666bcee2..6cbed998 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -83,6 +83,10 @@ true + + #777777 #000000 diff --git a/src/com/owncloud/android/AccountUtils.java b/src/com/owncloud/android/AccountUtils.java index b35fe30a..997633aa 100644 --- a/src/com/owncloud/android/AccountUtils.java +++ b/src/com/owncloud/android/AccountUtils.java @@ -31,6 +31,7 @@ public class AccountUtils { public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; + private static final String ODAV_PATH = "/remote.php/odav"; public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php"; public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php"; public static final String STATUS_PATH = "/status.php"; @@ -95,7 +96,10 @@ public class AccountUtils { * @param version version of owncloud * @return webdav path for given OC version, null if OC version unknown */ - public static String getWebdavPath(OwnCloudVersion version) { + public static String getWebdavPath(OwnCloudVersion version, boolean supportsOAuth) { + if (supportsOAuth) { + return ODAV_PATH; + } if (version != null) { if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) return WEBDAV_PATH_4_0; @@ -119,8 +123,9 @@ public class AccountUtils { AccountManager ama = AccountManager.get(context); String baseurl = ama.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL); String strver = ama.getUserData(account, AccountAuthenticator.KEY_OC_VERSION); + boolean supportsOAuth = (ama.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); OwnCloudVersion ver = new OwnCloudVersion(strver); - String webdavpath = getWebdavPath(ver); + String webdavpath = getWebdavPath(ver, supportsOAuth); if (webdavpath == null) return null; return baseurl + webdavpath; diff --git a/src/com/owncloud/android/Uploader.java b/src/com/owncloud/android/Uploader.java index e19a9724..6ec8c4fd 100644 --- a/src/com/owncloud/android/Uploader.java +++ b/src/com/owncloud/android/Uploader.java @@ -30,7 +30,6 @@ import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.network.OwnCloudClientUtils; import android.accounts.Account; import android.accounts.AccountManager; @@ -60,7 +59,6 @@ import android.widget.SimpleAdapter; import android.widget.Toast; import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; /** * This can be used to upload things to an ownCloud instance. @@ -357,12 +355,13 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro public void uploadFiles() { try { + /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - // create last directory in path if necessary if (mCreateDir) { wdc.createDirectory(mUploadPath); } + */ String[] local = new String[mStreamsToUpload.size()], remote = new String[mStreamsToUpload.size()]; diff --git a/src/com/owncloud/android/authenticator/AccountAuthenticator.java b/src/com/owncloud/android/authenticator/AccountAuthenticator.java index 58919ec2..391e9e89 100644 --- a/src/com/owncloud/android/authenticator/AccountAuthenticator.java +++ b/src/com/owncloud/android/authenticator/AccountAuthenticator.java @@ -33,6 +33,9 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { */ public static final String ACCOUNT_TYPE = "owncloud"; public static final String AUTH_TOKEN_TYPE = "org.owncloud"; + public static final String AUTH_TOKEN_TYPE_PASSWORD = "owncloud.password"; + public static final String AUTH_TOKEN_TYPE_ACCESS_TOKEN = "owncloud.oauth2.access_token"; + public static final String AUTH_TOKEN_TYPE_REFRESH_TOKEN = "owncloud.oauth2.refresh_token"; public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; @@ -57,8 +60,13 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { * http://server/path or https://owncloud.server */ public static final String KEY_OC_BASE_URL = "oc_base_url"; - - private static final String TAG = "AccountAuthenticator"; + /** + * Flag signaling if the ownCloud server can be accessed with OAuth2 access tokens. + */ + public static final String KEY_SUPPORTS_OAUTH2 = "oc_supports_oauth2"; + + private static final String TAG = AccountAuthenticator.class.getSimpleName(); + private Context mContext; public AccountAuthenticator(Context context) { @@ -144,13 +152,25 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { return e.getFailureBundle(); } final AccountManager am = AccountManager.get(mContext); - final String password = am.getPassword(account); - if (password != null) { - final Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); - result.putString(AccountManager.KEY_AUTHTOKEN, password); - return result; + if (authTokenType.equals(AUTH_TOKEN_TYPE_ACCESS_TOKEN)) { + final String accessToken = am.peekAuthToken(account, authTokenType); + if (accessToken != null) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); + result.putString(AccountManager.KEY_AUTHTOKEN, accessToken); + return result; + } + + } else if (authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD)) { + final String password = am.getPassword(account); + if (password != null) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); + result.putString(AccountManager.KEY_AUTHTOKEN, password); + return result; + } } final Intent intent = new Intent(mContext, AuthenticatorActivity.class); @@ -158,7 +178,7 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { response); intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name); + intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name); // TODO fix, this will pass the accountName, not the username final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); @@ -204,8 +224,8 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { private void setIntentFlags(Intent intent) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + //intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + //intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); // incompatible with the authorization code grant in OAuth intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.addFlags(Intent.FLAG_FROM_BACKGROUND); } @@ -219,7 +239,10 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { private void validateAuthTokenType(String authTokenType) throws UnsupportedAuthTokenTypeException { - if (!authTokenType.equals(AUTH_TOKEN_TYPE)) { + if (!authTokenType.equals(AUTH_TOKEN_TYPE) && + !authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD) && + !authTokenType.equals(AUTH_TOKEN_TYPE_ACCESS_TOKEN) && + !authTokenType.equals(AUTH_TOKEN_TYPE_REFRESH_TOKEN) ) { throw new UnsupportedAuthTokenTypeException(); } } diff --git a/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java b/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java new file mode 100644 index 00000000..fbc509d9 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java @@ -0,0 +1,45 @@ +package com.owncloud.android.authenticator.oauth2; + +/** + * Class used to store data from the app registration in oAuth2 server. + * THIS VALUES ARE ORIENTATIVE. + * MUST BE CHANGED WITH THE CORRECT ONES. + * + * @author SolidGear S.L. + * + */ + +public class OAuth2Context { + + public static final String OAUTH2_G_DEVICE_CLIENT_ID = "1044165972576.apps.googleusercontent.com"; + public static final String OAUTH2_G_DEVICE_CLIENT_SECRET = "rwrA86fnIRCC3bZm0tWnKOkV"; + public static final String OAUTH_G_DEVICE_GETTOKEN_GRANT_TYPE = "http://oauth.net/grant_type/device/1.0"; + public static final String OAUTH2_G_DEVICE_GETCODE_URL = "https://accounts.google.com/o/oauth2/device/code"; + public static final String OAUTH2_G_DEVICE_GETTOKEN_URL = "https://accounts.google.com/o/oauth2/token"; + public static final String OAUTH2_G_DEVICE_GETCODE_SCOPES = "https://www.googleapis.com/auth/userinfo.email"; + + public static final String OAUTH2_F_AUTHORIZATION_ENDPOINT_URL = "https://frko.surfnetlabs.nl/workshop/php-oauth/authorize.php"; + public static final String OAUTH2_F_TOKEN_ENDPOINT_URL = "https://frko.surfnetlabs.nl/workshop/php-oauth/token.php"; + public static final String OAUTH2_F_CLIENT_ID = "oc-android-test"; + public static final String OAUTH2_F_SCOPE = "grades"; + + public static final String OAUTH2_AUTH_CODE_GRANT_TYPE = "authorization_code"; + public static final String OAUTH2_CODE_RESPONSE_TYPE = "code"; + + public static final String OAUTH2_TOKEN_RECEIVED_ERROR = "error"; + + public static final String MY_REDIRECT_URI = "oauth-mobile-app://callback"; // THIS CAN'T BE READ DYNAMICALLY; MUST BE DEFINED IN INSTALLATION TIME + + 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"; + public static final String KEY_SCOPE = "scope"; + 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 KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + public static final String KEY_CLIENT_ID = "client_id"; +} diff --git a/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java b/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java new file mode 100644 index 00000000..62085b2d --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java @@ -0,0 +1,147 @@ +package com.owncloud.android.authenticator.oauth2; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.message.BasicNameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.os.Handler; +import android.util.Log; + +import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener.ResultOAuthType; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; + +/** + * Implements the communication with oAuth2 server to get User Code and other useful values. + * + * @author SolidGear S.L. + * + */ +public class OAuth2GetCodeRunnable implements Runnable { + + public static final String CODE_USER_CODE = "user_code"; + public static final String CODE_CLIENT_ID = "client_id"; + public static final String CODE_SCOPE = "scope"; + public static final String CODE_VERIFICATION_URL = "verification_url"; + public static final String CODE_EXPIRES_IN = "expires_in"; + public static final String CODE_DEVICE_CODE = "device_code"; + public static final String CODE_INTERVAL = "interval"; + + private static final String CODE_RESPONSE_TYPE = "response_type"; + private static final String CODE_REDIRECT_URI = "redirect_uri"; + + private String mGrantType = OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE; + + private static final String TAG = "OAuth2GetCodeRunnable"; + private OnOAuth2GetCodeResultListener mListener; + private String mUrl; + private Handler mHandler; + private Context mContext; + private JSONObject codeResponseJson = null; + ResultOAuthType mLatestResult; + + + public void setListener(OnOAuth2GetCodeResultListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public OAuth2GetCodeRunnable(String url, Context context) { + mListener = null; + mHandler = null; + mUrl = url; + mContext = context; + } + + @Override + public void run() { + + if (!isOnline()) { + postResult(ResultOAuthType.NO_NETWORK_CONNECTION,null); + return; + } + + if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) { + mLatestResult = (mUrl.startsWith("https://"))? ResultOAuthType.OK_SSL : ResultOAuthType.OK_NO_SSL; + } else { + mUrl = "https://" + mUrl; + mLatestResult = ResultOAuthType.OK_SSL; + } + + if (mGrantType.equals(OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE)) { + requestBrowserToGetAuthorizationCode(); + + } else if (mGrantType.equals(OAuth2Context.OAUTH_G_DEVICE_GETTOKEN_GRANT_TYPE)) { + getAuthorizationCode(); + } + } + + /// open the authorization endpoint in a web browser! + private void requestBrowserToGetAuthorizationCode() { + Uri uri = Uri.parse(mUrl); + Uri.Builder uriBuilder = uri.buildUpon(); + uriBuilder.appendQueryParameter(CODE_RESPONSE_TYPE, OAuth2Context.OAUTH2_CODE_RESPONSE_TYPE); + uriBuilder.appendQueryParameter(CODE_REDIRECT_URI, OAuth2Context.MY_REDIRECT_URI); + uriBuilder.appendQueryParameter(CODE_CLIENT_ID, OAuth2Context.OAUTH2_F_CLIENT_ID); + uriBuilder.appendQueryParameter(CODE_SCOPE, OAuth2Context.OAUTH2_F_SCOPE); + //uriBuilder.appendQueryParameter(CODE_STATE, whateverwewant); + + uri = uriBuilder.build(); + Log.d(TAG, "Starting browser to view " + uri.toString()); + + Intent i = new Intent(Intent.ACTION_VIEW, uri); + mContext.startActivity(i); + + postResult(mLatestResult, null); + } + + + private void getAuthorizationCode() { + ConnectorOAuth2 connectorOAuth2 = new ConnectorOAuth2(mUrl); + try { + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair(CODE_CLIENT_ID, OAuth2Context.OAUTH2_G_DEVICE_CLIENT_ID)); + nameValuePairs.add(new BasicNameValuePair(CODE_SCOPE,OAuth2Context.OAUTH2_G_DEVICE_GETCODE_SCOPES)); + UrlEncodedFormEntity params = new UrlEncodedFormEntity(nameValuePairs); + codeResponseJson = new JSONObject(connectorOAuth2.connPost(params)); + } catch (JSONException e) { + Log.e(TAG, "JSONException converting to Json: " + e.toString()); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "UnsupportedEncodingException encoding URL values: " + e.toString()); + } catch (Exception e) { + Log.e(TAG, "Exception : " + e.toString()); + } + + if (codeResponseJson == null) { + mLatestResult = ResultOAuthType.HOST_NOT_AVAILABLE; + } + postResult(mLatestResult, codeResponseJson); + } + + + private boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnectedOrConnecting(); + } + + private void postResult(final ResultOAuthType result,final JSONObject codeResponseJson) { + if (mHandler != null && mListener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onOAuth2GetCodeResult(result, codeResponseJson); + } + }); + } + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java b/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java new file mode 100644 index 00000000..463c95db --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java @@ -0,0 +1,19 @@ +package com.owncloud.android.authenticator.oauth2; + +import org.json.JSONObject; + +/** + * Listener that expects results from OAuth2GetCodeRunnable class. + * + * @author SolidGear S.L. + * + */ +public interface OnOAuth2GetCodeResultListener { + + enum ResultOAuthType { + OK_SSL, OK_NO_SSL, SSL_INIT_ERROR, HOST_NOT_AVAILABLE, TIMEOUT, NO_NETWORK_CONNECTION, INCORRECT_ADDRESS, INSTANCE_NOT_CONFIGURED, FILE_NOT_FOUND, UNKNOWN_ERROR, WRONG_CONNECTION, SSL_UNVERIFIED_SERVER, BAD_OC_VERSION + } + + public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject code); + +} diff --git a/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java b/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java new file mode 100644 index 00000000..789b5851 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java @@ -0,0 +1,113 @@ +package com.owncloud.android.authenticator.oauth2.connection; + +import org.apache.http.HttpResponse; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; + +import android.util.Log; + +/** + * Implements HTTP POST communications with an oAuth2 server. + * + * @author SolidGear S.L. + * + */ +public class ConnectorOAuth2 { + + private static final String TAG = "ConnectorOAuth2"; + /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */ + private static final int TRY_CONNECTION_TIMEOUT = 5000; + + private DefaultHttpClient httpClient; + private HttpContext localContext; + private String ConnectorOAuth2Url; + + public ConnectorOAuth2 (String destUrl) { + prepareConn(); + setConnectorOAuth2Url(destUrl); + } + + public ConnectorOAuth2 () { + prepareConn(); + } + + public String getConnectorOAuth2Url() { + return ConnectorOAuth2Url; + } + + public void setConnectorOAuth2Url(String connectorOAuth2Url) { + ConnectorOAuth2Url = connectorOAuth2Url; + } + + /** + * Starts the communication with the server. + * + * @param UrlEncodedFormEntity : parameters included in the POST call. + * @return String : data returned from the server in String format. + */ + + public String connPost(UrlEncodedFormEntity data) { + String dataOut = null; + HttpPost httpPost = null; + int responseCode = -1; + HttpResponse response = null; + + httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109); + + if (ConnectorOAuth2Url == null) { + Log.e(TAG, "connPost error: destination URI could not be null"); + return null; + } + + if (data == null){ + Log.e(TAG, "connPost error: data to send to URI " + ConnectorOAuth2Url + "could not be null"); + return null; + } + + httpPost = new HttpPost(ConnectorOAuth2Url); + + httpPost.setHeader("Accept","text/html,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); + httpPost.setEntity((UrlEncodedFormEntity) data); + + try { + response = httpClient.execute(httpPost,localContext); + + if (response == null) { + Log.e(TAG, "connPost error: response from uri " + ConnectorOAuth2Url + " is null"); + return null; + } + + responseCode = response.getStatusLine().getStatusCode(); + + if ((responseCode != 200)) { + Log.e(TAG, "connPost error: response from uri "+ ConnectorOAuth2Url + " returns status " + responseCode); + return null; + } + + dataOut = EntityUtils.toString(response.getEntity()); + + } catch (Exception e) { + Log.e(TAG, "connPost Exception: " + e); + } + + return dataOut; + } + + private void prepareConn () { + HttpParams localParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(localParams, TRY_CONNECTION_TIMEOUT); + HttpConnectionParams.setSoTimeout(localParams, TRY_CONNECTION_TIMEOUT); + httpClient = new DefaultHttpClient(localParams); + localContext = new BasicHttpContext(); + } +} diff --git a/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java b/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java new file mode 100644 index 00000000..7bfbbfa4 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java @@ -0,0 +1,200 @@ +package com.owncloud.android.authenticator.oauth2.services; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.message.BasicNameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import com.owncloud.android.authenticator.oauth2.OAuth2Context; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; + +/** + * Service class that implements the second communication with the oAuth2 server: + * pooling for the token in an interval. It send a broadcast with the results when are positive; + * otherwise, it continues asking to the server. + * + * @author Solid Gear S.L. + * + */ +public class OAuth2GetTokenService extends Service { + + public static final String TOKEN_RECEIVED_MESSAGE = "TOKEN_RECEIVED"; + public static final String TOKEN_RECEIVED_DATA = "TOKEN_DATA"; + public static final String TOKEN_URI = "TOKEN_URI"; + public static final String TOKEN_DEVICE_CODE = "device_code"; + public static final String TOKEN_INTERVAL = "interval"; + public static final String TOKEN_RECEIVED_ERROR = "error"; + public static final String TOKEN_RECEIVED_ERROR_AUTH_TOKEN = "authorization_pending"; + public static final String TOKEN_RECEIVED_ERROR_SLOW_DOWN = "slow_down"; + public static final String TOKEN_ACCESS_TOKEN = "access_token"; + public static final String TOKEN_TOKEN_TYPE = "token_type"; + public static final String TOKEN_EXPIRES_IN = "expires_in"; + public static final String TOKEN_REFRESH_TOKEN = "refresh_token"; + + private String requestDeviceCode; + private int requestInterval = -1; + private String requestBaseURI; + private ConnectorOAuth2 connectorOAuth2; + private static final String TAG = "OAuth2GetTokenService"; + private Timer timer = new Timer(); + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle param = intent.getExtras(); + + if (param != null) { + String mUrl = param.getString(TOKEN_URI); + if (!mUrl.startsWith("http://") || !mUrl.startsWith("https://")) { + requestBaseURI = "https://" + mUrl; + } + requestDeviceCode = param.getString(TOKEN_DEVICE_CODE); + requestInterval = param.getInt(TOKEN_INTERVAL); + + Log.d(TAG, "onStartCommand -> requestDeviceCode=" + requestDeviceCode); + Log.d(TAG, "onStartCommand -> requestInterval=" + requestInterval); + } else { + Log.e(TAG, "onStartCommand -> params could not be null"); + } + startService(); + return Service.START_NOT_STICKY; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + shutdownService(); + } + + private void startService() { + final UrlEncodedFormEntity params = prepareComm(); + timer.scheduleAtFixedRate( + new TimerTask() { + public void run() { + requestToken(params); + } + }, 0, requestInterval * 1000); + Log.d(TAG, "startService -> Timer started"); + } + + private void shutdownService() { + if (timer != null) timer.cancel(); + Log.d(TAG, "shutdownService -> Timer stopped"); + } + + + private UrlEncodedFormEntity prepareComm() { + + UrlEncodedFormEntity params = null; + connectorOAuth2 = new ConnectorOAuth2(); + + if (requestBaseURI == null || requestBaseURI.trim().equals("")) { + Log.e(TAG, "run -> request URI could not be null"); + postResult(null); + } + + if (requestInterval == -1) { + Log.e(TAG, "run -> request Interval must have valid positive value"); + postResult(null); + } + + if (requestDeviceCode == null || requestDeviceCode.trim().equals("")) { + Log.e(TAG, "run -> request DeviceCode could not be null"); + postResult(null); + } + + try{ + connectorOAuth2.setConnectorOAuth2Url(requestBaseURI + OAuth2Context.OAUTH2_G_DEVICE_GETTOKEN_URL); + + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair("client_id", OAuth2Context.OAUTH2_G_DEVICE_CLIENT_ID)); + nameValuePairs.add(new BasicNameValuePair("client_secret", OAuth2Context.OAUTH2_G_DEVICE_CLIENT_SECRET)); + nameValuePairs.add(new BasicNameValuePair("code",requestDeviceCode)); + nameValuePairs.add(new BasicNameValuePair("grant_type",OAuth2Context.OAUTH_G_DEVICE_GETTOKEN_GRANT_TYPE)); + + params = new UrlEncodedFormEntity(nameValuePairs); + } + catch (Exception ex1){ + Log.w(TAG, ex1.toString()); + postResult(null); + } + + return params; + } + + protected void requestToken(UrlEncodedFormEntity params){ + JSONObject tokenJson = null; + String error = null; + HashMap resultTokenMap; + + String tokenResponse = connectorOAuth2.connPost(params); + + try { + tokenJson = new JSONObject(tokenResponse); + } catch (JSONException e) { + Log.e(TAG, "Exception converting to Json " + e.toString()); + } + + try { + // We try to get error string. + if (tokenJson.has(TOKEN_RECEIVED_ERROR)) { + error = tokenJson.getString(TOKEN_RECEIVED_ERROR); + Log.d(TAG, "requestToken -> Obtained error "+ error); + } else { + //We have got the token. Parse the answer. + resultTokenMap = parseResult(tokenJson); + postResult(resultTokenMap); + } + } catch (JSONException e) { + Log.e(TAG, "Exception converting to Json " + e.toString()); + } + } + + private HashMap parseResult (JSONObject tokenJson) { + HashMap resultTokenMap=new HashMap(); + + try { + resultTokenMap.put(TOKEN_ACCESS_TOKEN, tokenJson.getString(TOKEN_ACCESS_TOKEN)); + resultTokenMap.put(TOKEN_TOKEN_TYPE, tokenJson.getString(TOKEN_TOKEN_TYPE)); + resultTokenMap.put(TOKEN_EXPIRES_IN, tokenJson.getString(TOKEN_EXPIRES_IN)); + resultTokenMap.put(TOKEN_REFRESH_TOKEN, tokenJson.getString(TOKEN_REFRESH_TOKEN)); + } catch (JSONException e) { + Log.e(TAG, "parseResult: Exception converting to Json " + e.toString()); + } + return resultTokenMap; + } + + /** + * Returns obtained values with a broadcast. + * + * @param tokenResponse : obtained values. + */ + private void postResult(HashMap tokenResponse) { + Intent intent = new Intent(TOKEN_RECEIVED_MESSAGE); + intent.putExtra(TOKEN_RECEIVED_DATA,tokenResponse); + sendBroadcast(intent); + shutdownService(); + } +} diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java index 8a03fee6..d79a07e2 100644 --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -22,13 +22,11 @@ import java.io.File; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFileOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ConflictsResolveActivity; -import eu.alefzero.webdav.WebdavClient; import android.accounts.Account; import android.content.Context; @@ -77,7 +75,6 @@ public class OwnCloudFileObserver extends FileObserver { mPath); return; } - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mOCAccount, mContext); FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver()); OCFile file = storageManager.getFileByLocalPath(mPath); // a fresh object is needed; many things could have occurred to the file since it was registered to observe // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; @@ -88,7 +85,7 @@ public class OwnCloudFileObserver extends FileObserver { true, true, mContext); - RemoteOperationResult result = sfo.execute(wc); + RemoteOperationResult result = sfo.execute(mOCAccount, mContext); if (result.getCode() == ResultCode.SYNC_CONFLICT) { // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred Intent i = new Intent(mContext, ConflictsResolveActivity.class); diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 7872c367..ed83d863 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -19,6 +19,7 @@ package com.owncloud.android.files.services; import java.io.File; +import java.io.IOException; import java.util.AbstractList; import java.util.Iterator; import java.util.Vector; @@ -36,6 +37,7 @@ import com.owncloud.android.ui.activity.FileDetailActivity; import com.owncloud.android.ui.fragment.FileDetailFragment; import android.accounts.Account; +import android.accounts.AccountsException; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -263,21 +265,30 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis notifyDownloadStart(mCurrentDownload); - /// prepare client object to send the request to the ownCloud server - if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { - mLastAccount = mCurrentDownload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - /// perform the download RemoteOperationResult downloadResult = null; try { - downloadResult = mCurrentDownload.execute(mDownloadClient); + /// prepare client object to send the request to the ownCloud server + if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) { + mLastAccount = mCurrentDownload.getAccount(); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); + mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + } + + /// perform the download + if (downloadResult == null) { + downloadResult = mCurrentDownload.execute(mDownloadClient); + } if (downloadResult.isSuccess()) { saveDownloadedFile(); } + } catch (AccountsException e) { + Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + downloadResult = new RemoteOperationResult(e); + } catch (IOException e) { + Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + downloadResult = new RemoteOperationResult(e); + } finally { synchronized(mPendingDownloads) { mPendingDownloads.remove(downloadKey); diff --git a/src/com/owncloud/android/files/services/FileOperation.java b/src/com/owncloud/android/files/services/FileOperation.java deleted file mode 100644 index 4215a1fc..00000000 --- a/src/com/owncloud/android/files/services/FileOperation.java +++ /dev/null @@ -1,55 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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.files.services; - -import java.io.File; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; - -import android.accounts.Account; -import android.content.Context; -import eu.alefzero.webdav.WebdavClient; - -public class FileOperation { - - Context mContext; - - public FileOperation(Context contex){ - this.mContext = contex; - } - - /** - * Deletes a file from ownCloud - locally and remote. - * @param file The file to delete - * @return True on success, otherwise false - */ - public boolean delete(OCFile file){ - - Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(account, mContext); - if(client.deleteFile(file.getRemotePath())){ - File localFile = new File(file.getStoragePath()); - return localFile.delete(); - } - - return false; - } - -} diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 0a033469..524f4c10 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -19,6 +19,7 @@ package com.owncloud.android.files.services; import java.io.File; +import java.io.IOException; import java.util.AbstractList; import java.util.Iterator; import java.util.Vector; @@ -34,6 +35,8 @@ import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.InstantUploadBroadcastReceiver; import com.owncloud.android.operations.ChunkedUploadFileOperation; +import com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.operations.RemoteOperation; import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; @@ -49,6 +52,7 @@ import com.owncloud.android.network.OwnCloudClientUtils; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountsException; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -392,34 +396,45 @@ public class FileUploader extends Service implements OnDatatransferProgressListe notifyUploadStart(mCurrentUpload); + RemoteOperationResult uploadResult = null; - /// prepare client object to send requests to the ownCloud server - if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { - mLastAccount = mCurrentUpload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } + try { + /// prepare client object to send requests to the ownCloud server + if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { + mLastAccount = mCurrentUpload.getAccount(); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); + mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); + } - /// create remote folder for instant uploads - if (mCurrentUpload.isRemoteFolderToBeCreated()) { - mUploadClient.createDirectory(InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR); // ignoring result; fail could just mean that it already exists, but local database is not synchronized; the upload will be tried anyway - } + /// create remote folder for instant uploads + if (mCurrentUpload.isRemoteFolderToBeCreated()) { + RemoteOperation operation = new CreateFolderOperation( InstantUploadBroadcastReceiver.INSTANT_UPLOAD_DIR, + mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR).getFileId(), // TODO generalize this : INSTANT_UPLOAD_DIR could not be a child of root + mStorageManager); + operation.execute(mUploadClient); // ignoring result; fail could just mean that it already exists, but local database is not synchronized; the upload will be tried anyway + } - /// perform the upload - RemoteOperationResult uploadResult = null; - try { + /// perform the upload uploadResult = mCurrentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { saveUploadedFile(); } + } catch (AccountsException e) { + Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + uploadResult = new RemoteOperationResult(e); + + } catch (IOException e) { + Log.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + uploadResult = new RemoteOperationResult(e); + } finally { synchronized(mPendingUploads) { mPendingUploads.remove(uploadKey); } } - + /// notify result notifyUploadResult(uploadResult, mCurrentUpload); diff --git a/src/com/owncloud/android/files/services/InstantUploadService.java b/src/com/owncloud/android/files/services/InstantUploadService.java deleted file mode 100644 index e7d28ffe..00000000 --- a/src/com/owncloud/android/files/services/InstantUploadService.java +++ /dev/null @@ -1,140 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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.files.services; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -import com.owncloud.android.network.OwnCloudClientUtils; - -import eu.alefzero.webdav.WebdavClient; - -import android.accounts.Account; -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; - -public class InstantUploadService extends Service { - - public static String KEY_FILE_PATH = "KEY_FILEPATH"; - public static String KEY_FILE_SIZE = "KEY_FILESIZE"; - public static String KEY_MIME_TYPE = "KEY_MIMETYPE"; - public static String KEY_DISPLAY_NAME = "KEY_FILENAME"; - public static String KEY_ACCOUNT = "KEY_ACCOUNT"; - - private static String TAG = "InstantUploadService"; - private static String INSTANT_UPLOAD_DIR = "/InstantUpload"; - private UploaderRunnable mUploaderRunnable; - - @Override - public IBinder onBind(Intent arg0) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent == null || - !intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_DISPLAY_NAME) || - !intent.hasExtra(KEY_FILE_PATH) || !intent.hasExtra(KEY_FILE_SIZE) || - !intent.hasExtra(KEY_MIME_TYPE)) { - Log.w(TAG, "Not all required information was provided, abording"); - return Service.START_NOT_STICKY; - } - - if (mUploaderRunnable == null) { - mUploaderRunnable = new UploaderRunnable(); - } - - String filename = intent.getStringExtra(KEY_DISPLAY_NAME); - String filepath = intent.getStringExtra(KEY_FILE_PATH); - String mimetype = intent.getStringExtra(KEY_MIME_TYPE); - Account account = intent.getParcelableExtra(KEY_ACCOUNT); - long filesize = intent.getLongExtra(KEY_FILE_SIZE, -1); - - mUploaderRunnable.addElementToQueue(filename, filepath, mimetype, filesize, account); - - // starting new thread for new download doesnt seems like a good idea - // maybe some thread pool or single background thread would be better - Log.d(TAG, "Starting instant upload thread"); - new Thread(mUploaderRunnable).start(); - - return Service.START_STICKY; - } - - private class UploaderRunnable implements Runnable { - - Object mLock; - List> mHashMapList; - - public UploaderRunnable() { - mHashMapList = new LinkedList>(); - mLock = new Object(); - } - - public void addElementToQueue(String filename, - String filepath, - String mimetype, - long length, - Account account) { - HashMap new_map = new HashMap(); - new_map.put(KEY_ACCOUNT, account); - new_map.put(KEY_DISPLAY_NAME, filename); - new_map.put(KEY_FILE_PATH, filepath); - new_map.put(KEY_MIME_TYPE, mimetype); - new_map.put(KEY_FILE_SIZE, length); - - synchronized (mLock) { - mHashMapList.add(new_map); - } - } - - private HashMap getFirstObject() { - synchronized (mLock) { - if (mHashMapList.size() == 0) - return null; - HashMap ret = mHashMapList.get(0); - mHashMapList.remove(0); - return ret; - } - } - - public void run() { - HashMap working_map; - - while ((working_map = getFirstObject()) != null) { - Account account = (Account) working_map.get(KEY_ACCOUNT); - String filename = (String) working_map.get(KEY_DISPLAY_NAME); - String filepath = (String) working_map.get(KEY_FILE_PATH); - String mimetype = (String) working_map.get(KEY_MIME_TYPE); - - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(account, getApplicationContext()); - - wdc.createDirectory(INSTANT_UPLOAD_DIR); // fail could just mean that it already exists; put will be tried anyway - try { - wdc.putFile(filepath, INSTANT_UPLOAD_DIR + "/" + filename, mimetype); - } catch (Exception e) { - // nothing to do; this service is deprecated, indeed - } - } - } - } - -} diff --git a/src/com/owncloud/android/network/BearerAuthScheme.java b/src/com/owncloud/android/network/BearerAuthScheme.java new file mode 100644 index 00000000..7739822f --- /dev/null +++ b/src/com/owncloud/android/network/BearerAuthScheme.java @@ -0,0 +1,269 @@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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.network; + +import java.util.Map; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.auth.AuthChallengeParser; +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 android.util.Log; + +/** + * Bearer authentication scheme as defined in RFC 6750. + * + * @author David A. Velasco + */ + +public class BearerAuthScheme implements AuthScheme /*extends RFC2617Scheme*/ { + + private static final String TAG = BearerAuthScheme.class.getSimpleName(); + + public static final String AUTH_POLICY = "Bearer"; + + /** Whether the bearer authentication process is complete */ + private boolean mComplete; + + /** Authentication parameter map */ + private Map mParams = null; + + + /** + * Default constructor for the bearer authentication scheme. + */ + public BearerAuthScheme() { + mComplete = false; + } + + /** + * Constructor for the basic authentication scheme. + * + * @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); + mComplete = true; + } + + /** + * Returns textual designation of the bearer authentication scheme. + * + * @return "Bearer" + */ + public String getSchemeName() { + return "bearer"; + } + + /** + * Processes the Bearer challenge. + * + * @param challenge The challenge string + * + * @throws MalformedChallengeException Thrown if the authentication challenge is malformed + */ + public void processChallenge(String challenge) throws MalformedChallengeException { + String s = AuthChallengeParser.extractScheme(challenge); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException( + "Invalid " + getSchemeName() + " challenge: " + challenge); + } + mParams = AuthChallengeParser.extractParams(challenge); + mComplete = true; + } + + /** + * Tests if the Bearer authentication process has been completed. + * + * @return 'true' if Bearer authorization has been processed, 'false' otherwise. + */ + public boolean isComplete() { + return this.mComplete; + } + + /** + * Produces bearer authorization string for the given set of + * {@link Credentials}. + * + * @param credentials The set of credentials to be used for authentication + * @param method Method name is ignored by the bearer authentication scheme + * @param uri URI is ignored by the bearer authentication scheme + * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable + * 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.d(TAG, "enter BearerScheme.authenticate(Credentials, String, String)"); + + BearerCredentials bearer = null; + try { + bearer = (BearerCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for bearer authentication: " + + credentials.getClass().getName()); + } + return BearerAuthScheme.authenticate(bearer); + } + + + /** + * Returns 'false'. Bearer authentication scheme is request based. + * + * @return 'false'. + */ + public boolean isConnectionBased() { + return false; + } + + /** + * Produces bearer authorization string for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for authentication + * @param method The method being authenticated + * @throws InvalidCredentialsException If authentication credentials are not valid or not applicable for this authentication + * scheme. + * @throws AuthenticationException If authorization string cannot be generated due to an authentication failure. + * + * @return a basic authorization string + */ + public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { + Log.d(TAG, "enter BearerScheme.authenticate(Credentials, HttpMethod)"); + + if (method == null) { + throw new IllegalArgumentException("Method may not be null"); + } + BearerCredentials bearer = null; + try { + bearer = (BearerCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for bearer authentication: " + + credentials.getClass().getName()); + } + return BearerAuthScheme.authenticate( + bearer, + method.getParams().getCredentialCharset()); + } + + /** + * @deprecated Use {@link #authenticate(BearerCredentials, String)} + * + * Returns a bearer Authorization header value for the given + * {@link BearerCredentials}. + * + * @param credentials The credentials to encode. + * + * @return A bearer authorization string + */ + public static String authenticate(BearerCredentials credentials) { + return authenticate(credentials, "ISO-8859-1"); + } + + /** + * Returns a bearer Authorization header value for the given + * {@link BearerCredentials} and charset. + * + * @param credentials The credentials to encode. + * @param charset The charset to use for encoding the credentials + * + * @return A bearer authorization string + * + * @since 3.0 + */ + public static String authenticate(BearerCredentials credentials, String charset) { + Log.d(TAG, "enter BearerAuthScheme.authenticate(BearerCredentials, String)"); + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (charset == null || charset.length() == 0) { + throw new IllegalArgumentException("charset may not be null or empty"); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(credentials.getAccessToken()); + + //return "Bearer " + EncodingUtil.getAsciiString(EncodingUtil.getBytes(buffer.toString(), charset)); + return "Bearer " + buffer.toString(); + } + + /** + * Returns a String identifying the authentication challenge. This is + * used, in combination with the host and port to determine if + * authorization has already been attempted or not. Schemes which + * require multiple requests to complete the authentication should + * return a different value for each stage in the request. + * + * Additionally, the ID should take into account any changes to the + * authentication challenge and return a different value when appropriate. + * For example when the realm changes in basic authentication it should be + * considered a different authentication attempt and a different value should + * be returned. + * + * 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() { + return getRealm(); + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return The parameter with the given name + */ + @Override + public String getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (mParams == null) { + return null; + } + return (String) mParams.get(name.toLowerCase()); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return The authentication realm + */ + @Override + public String getRealm() { + return getParameter("realm"); + } + +} diff --git a/src/com/owncloud/android/network/BearerCredentials.java b/src/com/owncloud/android/network/BearerCredentials.java new file mode 100644 index 00000000..35e2b4ed --- /dev/null +++ b/src/com/owncloud/android/network/BearerCredentials.java @@ -0,0 +1,98 @@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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.network; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.util.LangUtils; + +/** + * Bearer token {@link Credentials} + * + * @author David A. Velasco + */ +public class BearerCredentials implements Credentials { + + + private String mAccessToken; + + + /** + * The constructor with the bearer token + * + * @param token The bearer token + */ + public BearerCredentials(String token) { + if (token == null) { + throw new IllegalArgumentException("Bearer token may not be null"); + } + mAccessToken = token; + } + + + /** + * Returns the access token + * + * @return The access token + */ + public String getAccessToken() { + return mAccessToken; + } + + + /** + * Get this object string. + * + * @return The access token + */ + public String toString() { + return mAccessToken; + } + + /** + * Does a hash of the access token. + * + * @return The hash code of the access token + */ + public int hashCode() { + int hash = LangUtils.HASH_SEED; + hash = LangUtils.hashCode(hash, mAccessToken); + return hash; + } + + /** + * These credentials are assumed equal if accessToken is the same. + * + * @param o The other object to compare with. + * + * @return 'True' if the object is equivalent. + */ + public boolean equals(Object o) { + if (o == null) return false; + if (this == o) return true; + if (this.getClass().equals(o.getClass())) { + BearerCredentials that = (BearerCredentials) o; + if (LangUtils.equals(mAccessToken, that.mAccessToken)) { + return true; + } + } + return false; + } + +} + diff --git a/src/com/owncloud/android/network/OwnCloudClientUtils.java b/src/com/owncloud/android/network/OwnCloudClientUtils.java index 5cc7a9fb..b334fdff 100644 --- a/src/com/owncloud/android/network/OwnCloudClientUtils.java +++ b/src/com/owncloud/android/network/OwnCloudClientUtils.java @@ -38,18 +38,24 @@ import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import org.apache.http.conn.ssl.X509HostnameVerifier; import com.owncloud.android.AccountUtils; +import com.owncloud.android.authenticator.AccountAuthenticator; import eu.alefzero.webdav.WebdavClient; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; import android.content.Context; import android.net.Uri; +import android.os.Bundle; import android.util.Log; public class OwnCloudClientUtils { - final private static String TAG = "OwnCloudClientFactory"; + final private static String TAG = OwnCloudClientUtils.class.getSimpleName(); /** Default timeout for waiting data from the server */ public static final int DEFAULT_DATA_TIMEOUT = 60000; @@ -70,26 +76,61 @@ public class OwnCloudClientUtils { /** * Creates a WebdavClient setup for an ownCloud account * - * @param account The ownCloud account - * @param context The application context - * @return A WebdavClient object ready to be used + * Do not call this method from the main thread. + * + * @param account The ownCloud account + * @param appContext Android application context + * @return A WebdavClient object ready to be used + * @throws AuthenticatorException If the authenticator failed to get the authorization token for the account. + * @throws OperationCanceledException If the authenticator operation was cancelled while getting the authorization token for the account. + * @throws IOException If there was some I/O error while getting the authorization token for the account. */ - public static WebdavClient createOwnCloudClient (Account account, Context context) { - Log.d(TAG, "Creating WebdavClient associated to " + account.name); + public static WebdavClient createOwnCloudClient (Account account, Context appContext) throws OperationCanceledException, AuthenticatorException, IOException { + //Log.d(TAG, "Creating WebdavClient associated to " + account.name); - Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(context, account)); - WebdavClient client = createOwnCloudClient(uri, context); + Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); + WebdavClient client = createOwnCloudClient(uri, appContext); + AccountManager am = AccountManager.get(appContext); + if (am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null) { // TODO avoid a call to getUserData here + String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, false); + client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token - String username = account.name.substring(0, account.name.lastIndexOf('@')); - String password = AccountManager.get(context).getPassword(account); - //String password = am.blockingGetAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, true); - - client.setCredentials(username, password); + } else { + String username = account.name.substring(0, account.name.lastIndexOf('@')); + //String password = am.getPassword(account); + String password = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_PASSWORD, false); + client.setBasicCredentials(username, password); + } return client; } + public static WebdavClient createOwnCloudClient (Account account, Context appContext, Activity currentActivity) throws OperationCanceledException, AuthenticatorException, IOException { + Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(appContext, account)); + WebdavClient client = createOwnCloudClient(uri, appContext); + AccountManager am = AccountManager.get(appContext); + if (am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null) { // TODO avoid a call to getUserData here + AccountManagerFuture future = am.getAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, null, currentActivity, null, null); + Bundle result = future.getResult(); + String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN); + //String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, false); + if (accessToken == null) throw new AuthenticatorException("WTF!"); + client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token + + } else { + String username = account.name.substring(0, account.name.lastIndexOf('@')); + //String password = am.getPassword(account); + //String password = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_PASSWORD, false); + AccountManagerFuture future = am.getAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_PASSWORD, null, currentActivity, null, null); + Bundle result = future.getResult(); + String password = result.getString(AccountManager.KEY_AUTHTOKEN); + client.setBasicCredentials(username, password); + } + + return client; + } + /** * Creates a WebdavClient to try a new account before saving it * @@ -100,11 +141,11 @@ public class OwnCloudClientUtils { * @return A WebdavClient object ready to be used */ public static WebdavClient createOwnCloudClient(Uri uri, String username, String password, Context context) { - Log.d(TAG, "Creating WebdavClient for " + username + "@" + uri); + //Log.d(TAG, "Creating WebdavClient for " + username + "@" + uri); WebdavClient client = createOwnCloudClient(uri, context); - client.setCredentials(username, password); + client.setBasicCredentials(username, password); return client; } @@ -118,7 +159,7 @@ public class OwnCloudClientUtils { * @return A WebdavClient object ready to be used */ public static WebdavClient createOwnCloudClient(Uri uri, Context context) { - Log.d(TAG, "Creating WebdavClient for " + uri); + //Log.d(TAG, "Creating WebdavClient for " + uri); //allowSelfsignedCertificates(true); try { @@ -270,4 +311,5 @@ public class OwnCloudClientUtils { return mConnManager; } + } diff --git a/src/com/owncloud/android/operations/CreateFolderOperation.java b/src/com/owncloud/android/operations/CreateFolderOperation.java new file mode 100644 index 00000000..4d42478a --- /dev/null +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@ -0,0 +1,96 @@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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.operations; + +import org.apache.jackrabbit.webdav.client.methods.MkColMethod; + +import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.OCFile; + +import android.util.Log; + +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * Remote operation performing the creation of a new folder in the ownCloud server. + * + * @author David A. Velasco + */ +public class CreateFolderOperation extends RemoteOperation { + + private static final String TAG = CreateFolderOperation.class.getSimpleName(); + + private static final int READ_TIMEOUT = 10000; + private static final int CONNECTION_TIMEOUT = 5000; + + protected String mRemotePath; + protected long mParentDirId; + protected DataStorageManager mStorageManager; + + /** + * Constructor + * + * @param remoetPath Full path to the new directory to create in the remote server. + * @param parentDirId Local database id for the parent folder. + * @param storageManager Reference to the local database corresponding to the account where the file is contained. + */ + public CreateFolderOperation(String remotePath, long parentDirId, DataStorageManager storageManager) { + mRemotePath = remotePath; + mParentDirId = parentDirId; + mStorageManager = storageManager; + } + + + /** + * Performs the remove operation + * + * @param client Client object to communicate with the remote ownCloud server. + */ + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + MkColMethod mkcol = null; + try { + mkcol = new MkColMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); + int status = client.executeMethod(mkcol, READ_TIMEOUT, CONNECTION_TIMEOUT); + if (mkcol.succeeded()) { + // Save new directory in local database + OCFile newDir = new OCFile(mRemotePath); + newDir.setMimetype("DIR"); + newDir.setParentId(mParentDirId); + mStorageManager.saveFile(newDir); + } + + result = new RemoteOperationResult(mkcol.succeeded(), status); + Log.d(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage()); + client.exhaustResponse(mkcol.getResponseBodyAsStream()); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log.e(TAG, "Create directory " + mRemotePath + ": " + result.getLogMessage(), e); + + } finally { + if (mkcol != null) + mkcol.releaseConnection(); + } + return result; + } + +} diff --git a/src/com/owncloud/android/operations/ExistenceCheckOperation.java b/src/com/owncloud/android/operations/ExistenceCheckOperation.java new file mode 100644 index 00000000..e5f29bfd --- /dev/null +++ b/src/com/owncloud/android/operations/ExistenceCheckOperation.java @@ -0,0 +1,114 @@ +/* ownCloud Android client application + * Copyright (C) 2012 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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.operations; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.HeadMethod; + +import eu.alefzero.webdav.WebdavClient; +import android.content.Context; +import android.net.ConnectivityManager; +import android.util.Log; + +/** + * Operation to check the existence or absence of a path in a remote server. + * + * @author David A. Velasco + */ +public class ExistenceCheckOperation extends RemoteOperation { + + /** Maximum time to wait for a response from the server in MILLISECONDs. */ + public static final int TIMEOUT = 10000; + + private static final String TAG = ExistenceCheckOperation.class.getSimpleName(); + + private String mPath; + private Context mContext; + private boolean mSuccessIfAbsent; + private String mAccessToken; + + + /** + * Simple constructor. Success when the path in the server exists. + * + * @param path Path to append to the URL owned by the client instance. + * @param context Android application context. + * @param accessToken Access token for Bearer Authentication -> TODO: move to other place + */ + public ExistenceCheckOperation(String path, Context context, String accessToken) { + this(path, context, false); + mAccessToken = accessToken; + } + + + /** + * Full constructor. Success of the operation will depend upon the value of successIfAbsent. + * + * @param path 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). + */ + public ExistenceCheckOperation(String path, Context context, boolean successIfAbsent) { + mPath = (path != null) ? path : ""; + mContext = context; + mSuccessIfAbsent = successIfAbsent; + } + + + @Override + protected RemoteOperationResult run(WebdavClient client) { + if (!isOnline()) { + return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); + } + RemoteOperationResult result = null; + HeadMethod head = null; + try { + head = new HeadMethod(client.getBaseUri() + mPath); + head.addRequestHeader("Authorization", "Bearer " + mAccessToken); // TODO put in some general place + + int status = client.executeMethod(head, TIMEOUT, TIMEOUT); + client.exhaustResponse(head.getResponseBodyAsStream()); + boolean success = (status == HttpStatus.SC_OK && !mSuccessIfAbsent) || (status == HttpStatus.SC_NOT_FOUND && mSuccessIfAbsent); + result = new RemoteOperationResult(success, status); + Log.d(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + "finished with HTTP status " + status + (!success?"(FAIL)":"")); + + } catch (Exception e) { + result = new RemoteOperationResult(e); + Log.e(TAG, "Existence check for " + client.getBaseUri() + mPath + " targeting for " + (mSuccessIfAbsent ? " absence " : " existence ") + ": " + result.getLogMessage(), result.getException()); + + } finally { + if (head != null) + head.releaseConnection(); + } + return result; + } + + private boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) mContext + .getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().isConnectedOrConnecting(); + } + + + public String getAccessToken() { + return mAccessToken; + } + +} diff --git a/src/com/owncloud/android/operations/GetOAuth2AccessToken.java b/src/com/owncloud/android/operations/GetOAuth2AccessToken.java new file mode 100644 index 00000000..82de8441 --- /dev/null +++ b/src/com/owncloud/android/operations/GetOAuth2AccessToken.java @@ -0,0 +1,125 @@ +package com.owncloud.android.operations; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.NameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.authenticator.oauth2.OAuth2Context; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + +import android.util.Log; + +import eu.alefzero.webdav.WebdavClient; + +public class GetOAuth2AccessToken extends RemoteOperation { + + private static final String TAG = GetOAuth2AccessToken.class.getSimpleName(); + + private Map mOAuth2AuthorizationResponse; + private Map mResultTokenMap; + + + public GetOAuth2AccessToken(Map oAuth2AuthorizationResponse) { + mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; + mResultTokenMap = null; + } + + + public Map getOauth2AutorizationResponse() { + return mOAuth2AuthorizationResponse; + } + + public Map getResultTokenMap() { + return mResultTokenMap; + } + + @Override + protected RemoteOperationResult run(WebdavClient client) { + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + NameValuePair[] nameValuePairs = new NameValuePair[5]; + nameValuePairs[0] = new NameValuePair(OAuth2Context.KEY_CLIENT_ID, OAuth2Context.OAUTH2_F_CLIENT_ID); + nameValuePairs[1] = new NameValuePair(OAuth2Context.KEY_CODE, mOAuth2AuthorizationResponse.get(OAuth2Context.KEY_CODE)); + nameValuePairs[2] = new NameValuePair(OAuth2Context.KEY_SCOPE, mOAuth2AuthorizationResponse.get(OAuth2Context.KEY_SCOPE)); + nameValuePairs[3] = new NameValuePair(OAuth2Context.KEY_REDIRECT_URI, OAuth2Context.MY_REDIRECT_URI); + nameValuePairs[4] = new NameValuePair(OAuth2Context.KEY_GRANT_TYPE, OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE); + + postMethod = new PostMethod(client.getBaseUri().toString()); + postMethod.setRequestBody(nameValuePairs); + int status = client.executeMethod(postMethod); + if (status >= 300) { + client.exhaustResponse(postMethod.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status); + + } else { + JSONObject tokenJson = new JSONObject(postMethod.getResponseBodyAsString()); + parseResult(tokenJson); + if (mResultTokenMap.get(OAuth2Context.OAUTH2_TOKEN_RECEIVED_ERROR) != null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, status); + } + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + } finally { + if (postMethod != null) + postMethod.releaseConnection(); // let the connection available for other methods + + if (result.isSuccess()) { + Log.i(TAG, "OAuth2 TOKEN REQUEST with code " + mOAuth2AuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); + + } else if (result.getException() != null) { + Log.e(TAG, "OAuth2 TOKEN REQUEST with code " + mOAuth2AuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage(), result.getException()); + + } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { + Log.e(TAG, "OAuth2 TOKEN REQUEST with code " + mOAuth2AuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + mResultTokenMap.get(OAuth2Context.OAUTH2_TOKEN_RECEIVED_ERROR)); + + } else { + Log.e(TAG, "OAuth2 TOKEN REQUEST with code " + mOAuth2AuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); + } + } + + return result; + } + + + private void parseResult (JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap(); + + if (tokenJson.has(OAuth2Context.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Context.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Context.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Context.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Context.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Context.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Context.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Context.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Context.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Context.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Context.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Context.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Context.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Context.KEY_SCOPE, tokenJson.getString(OAuth2Context.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Context.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Context.KEY_ERROR, tokenJson.getString(OAuth2Context.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Context.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Context.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Context.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Context.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Context.KEY_ERROR_URI, tokenJson.getString(OAuth2Context.KEY_ERROR_URI)); + } + } + +} diff --git a/src/com/owncloud/android/operations/RemoteOperation.java b/src/com/owncloud/android/operations/RemoteOperation.java index 5b58e0ca..6917cb14 100644 --- a/src/com/owncloud/android/operations/RemoteOperation.java +++ b/src/com/owncloud/android/operations/RemoteOperation.java @@ -17,7 +17,16 @@ */ package com.owncloud.android.operations; +import java.io.IOException; + +import com.owncloud.android.network.OwnCloudClientUtils; + +import android.accounts.Account; +import android.accounts.AccountsException; +import android.app.Activity; +import android.content.Context; import android.os.Handler; +import android.util.Log; import eu.alefzero.webdav.WebdavClient; @@ -30,7 +39,15 @@ import eu.alefzero.webdav.WebdavClient; */ public abstract class RemoteOperation implements Runnable { - /** Object to interact with the ownCloud server */ + private static final String TAG = RemoteOperation.class.getSimpleName(); + + /** 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 WebdavClient mClient = null; /** Callback object to notify about the execution of the remote operation */ @@ -39,16 +56,49 @@ public abstract class RemoteOperation implements Runnable { /** 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(WebdavClient 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(WebdavClient)}. + * + * @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 final RemoteOperationResult execute(Account account, Context context) { + 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(); + try { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); + } catch (Exception e) { + Log.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. */ @@ -60,6 +110,43 @@ 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(WebdavClient)}. + * + * @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 final 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 + + 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) { + throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); + } + mListenerHandler = listenerHandler; + + Thread runnerThread = new Thread(this); + runnerThread.start(); + return runnerThread; + } + + /** * Asynchronously executes the remote operation * @@ -119,17 +206,51 @@ public abstract class RemoteOperation implements Runnable { */ @Override public final void run() { - final RemoteOperationResult result = execute(mClient); + RemoteOperationResult result = null; + try{ + if (mClient == null) { + if (mAccount != null && mContext != null) { + if (mCallerActivity != null) { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext, mCallerActivity); + } else { + mClient = OwnCloudClientUtils.createOwnCloudClient(mAccount, mContext); + } + } else { + throw new IllegalStateException("Trying to run a remote operation asynchronously with no client instance or account"); + } + } + + } catch (IOException e) { + Log.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.e(TAG, "Error while trying to access to " + mAccount.name, e); + result = new RemoteOperationResult(e); + } + if (result == null) + result = run(mClient); + + final RemoteOperationResult resultToSend = result; if (mListenerHandler != null && mListener != null) { mListenerHandler.post(new Runnable() { @Override public void run() { - mListener.onRemoteOperationFinish(RemoteOperation.this, result); + mListener.onRemoteOperationFinish(RemoteOperation.this, resultToSend); } }); } } - - + + + /** + * Returns the current client instance to access the remote server. + * + * @return Current client instance to access the remote server. + */ + public final WebdavClient getClient() { + return mClient; + } + } diff --git a/src/com/owncloud/android/operations/RemoteOperationResult.java b/src/com/owncloud/android/operations/RemoteOperationResult.java index 64703386..5a14fb87 100644 --- a/src/com/owncloud/android/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/operations/RemoteOperationResult.java @@ -46,7 +46,6 @@ public class RemoteOperationResult implements Serializable { /** Generated - should be refreshed every time the class changes!! */ private static final long serialVersionUID = -7805531062432602444L; - public enum ResultCode { OK, @@ -69,6 +68,7 @@ public class RemoteOperationResult implements Serializable { INVALID_LOCAL_FILE_NAME, INVALID_OVERWRITE, CONFLICT, + OAUTH2_ERROR, SYNC_CONFLICT, LOCAL_STORAGE_FULL, LOCAL_STORAGE_NOT_MOVED, diff --git a/src/com/owncloud/android/operations/RenameFileOperation.java b/src/com/owncloud/android/operations/RenameFileOperation.java index f4ec7b0a..cc1e324c 100644 --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@ -42,7 +42,7 @@ import eu.alefzero.webdav.WebdavUtils; */ public class RenameFileOperation extends RemoteOperation { - private static final String TAG = RemoveFileOperation.class.getSimpleName(); + private static final String TAG = RenameFileOperation.class.getSimpleName(); private static final int RENAME_READ_TIMEOUT = 10000; private static final int RENAME_CONNECTION_TIMEOUT = 5000; diff --git a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java index 92c0b720..8309fab4 100644 --- a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@ -142,7 +142,7 @@ public abstract class AbstractOwnCloudSyncAdapter extends return null; } - protected void initClientForCurrentAccount() throws UnknownHostException { + protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException { if (AccountUtils.constructFullURLForAccount(getContext(), account) == null) { throw new UnknownHostException(); } diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index f66980cb..46c088b4 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -37,6 +37,9 @@ import com.owncloud.android.operations.UpdateOCVersionOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; import android.accounts.Account; +import android.accounts.AccountsException; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -102,7 +105,12 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); try { this.initClientForCurrentAccount(); - } catch (UnknownHostException e) { + } catch (IOException e) { + /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + mSyncResult.tooManyRetries = true; + notifyFailedSynchronization(); + return; + } catch (AccountsException e) { /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java index bd36ed1c..fc84df60 100644 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java @@ -20,16 +20,29 @@ package com.owncloud.android.ui.activity; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONException; +import org.json.JSONObject; import com.owncloud.android.AccountUtils; import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.authenticator.AuthenticationRunnable; import com.owncloud.android.authenticator.OnAuthenticationResultListener; import com.owncloud.android.authenticator.OnConnectCheckListener; +import com.owncloud.android.authenticator.oauth2.OAuth2Context; +import com.owncloud.android.authenticator.oauth2.OAuth2GetCodeRunnable; +import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; +import com.owncloud.android.authenticator.oauth2.services.OAuth2GetTokenService; import com.owncloud.android.ui.dialog.SslValidatorDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.utils.OwnCloudVersion; import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.ConnectionCheckOperation; +import com.owncloud.android.operations.ExistenceCheckOperation; +import com.owncloud.android.operations.GetOAuth2AccessToken; import com.owncloud.android.operations.OnRemoteOperationListener; import com.owncloud.android.operations.RemoteOperation; import com.owncloud.android.operations.RemoteOperationResult; @@ -40,9 +53,12 @@ import android.accounts.AccountManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.ContentResolver; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; @@ -54,8 +70,9 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.Window; -import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; +import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.owncloud.android.R; @@ -70,7 +87,7 @@ import eu.alefzero.webdav.WebdavClient; */ public class AuthenticatorActivity extends AccountAuthenticatorActivity implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, - OnFocusChangeListener, OnClickListener { + OnFocusChangeListener, OnClickListener, OnOAuth2GetCodeResultListener { private static final int DIALOG_LOGIN_PROGRESS = 0; private static final int DIALOG_SSL_VALIDATOR = 1; @@ -80,22 +97,50 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity private Thread mAuthThread; private AuthenticationRunnable mAuthRunnable; - //private ConnectionCheckerRunnable mConnChkRunnable = null; private ConnectionCheckOperation mConnChkRunnable; + private ExistenceCheckOperation mAuthChkOperation; private final Handler mHandler = new Handler(); private String mBaseUrl; + private OwnCloudVersion mDiscoveredVersion; private static final String STATUS_TEXT = "STATUS_TEXT"; private static final String STATUS_ICON = "STATUS_ICON"; private static final String STATUS_CORRECT = "STATUS_CORRECT"; private static final String IS_SSL_CONN = "IS_SSL_CONN"; + private static final String OC_VERSION = "OC_VERSION"; private int mStatusText, mStatusIcon; private boolean mStatusCorrect, mIsSslConn; private RemoteOperationResult mLastSslUntrustedServerResult; + public static final String PARAM_ACCOUNTNAME = "param_Accountname"; + public static final String PARAM_USERNAME = "param_Username"; public static final String PARAM_HOSTNAME = "param_Hostname"; + // oAuth2 variables. + private static final int OAUTH2_LOGIN_PROGRESS = 3; + private static final String OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT"; + private static final String OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON"; + private static final String OAUTH2_CODE_RESULT = "CODE_RESULT"; + private static final String OAUTH2_IS_CHECKED = "OAUTH2_IS_CHECKED"; + private Thread mOAuth2GetCodeThread; + private OAuth2GetCodeRunnable mOAuth2GetCodeRunnable; + private TokenReceiver tokenReceiver; + private JSONObject codeResponseJson; + private int mOAuth2StatusText, mOAuth2StatusIcon; + + public ConnectorOAuth2 connectorOAuth2; + + // Variables used to save the on the state the contents of all fields. + private static final String HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String ACCOUNT_USERNAME = "ACCOUNT_USERNAME"; + private static final String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; + + //private boolean mNewRedirectUriCaptured; + private Uri mNewCapturedUriFromOAuth2Redirection; + + // END of oAuth2 variables. + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -105,6 +150,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity ImageView iv2 = (ImageView) findViewById(R.id.viewPassword); TextView tv = (TextView) findViewById(R.id.host_URL); TextView tv2 = (TextView) findViewById(R.id.account_password); + EditText oauth2Url = (EditText)findViewById(R.id.oAuth_URL); + oauth2Url.setText("OWNCLOUD AUTHORIZATION PROVIDER IN TEST"); if (savedInstanceState != null) { mStatusIcon = savedInstanceState.getInt(STATUS_ICON); @@ -116,29 +163,104 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity if (!mStatusCorrect) iv.setVisibility(View.VISIBLE); else - iv.setVisibility(View.INVISIBLE); + iv.setVisibility(View.INVISIBLE); + + String ocVersion = savedInstanceState.getString(OC_VERSION, null); + if (ocVersion != null) + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + + // Getting the state of oAuth2 components. + mOAuth2StatusIcon = savedInstanceState.getInt(OAUTH2_STATUS_ICON); + mOAuth2StatusText = savedInstanceState.getInt(OAUTH2_STATUS_TEXT); + // We set this to true if the rotation happens when the user is validating oAuth2 user_code. + changeViewByOAuth2Check(savedInstanceState.getBoolean(OAUTH2_IS_CHECKED)); + // We store a JSon object with all the data returned from oAuth2 server when we get user_code. + // Is better than store variable by variable. We use String object to serialize from/to it. + try { + if (savedInstanceState.containsKey(OAUTH2_CODE_RESULT)) { + codeResponseJson = new JSONObject(savedInstanceState.getString(OAUTH2_CODE_RESULT)); + } + } catch (JSONException e) { + Log.e(TAG, "onCreate->JSONException: " + e.toString()); + } + // END of getting the state of oAuth2 components. + + // Getting contents of each field. + EditText hostUrl = (EditText)findViewById(R.id.host_URL); + hostUrl.setText(savedInstanceState.getString(HOST_URL_TEXT), TextView.BufferType.EDITABLE); + EditText accountUsername = (EditText)findViewById(R.id.account_username); + accountUsername.setText(savedInstanceState.getString(ACCOUNT_USERNAME), TextView.BufferType.EDITABLE); + EditText accountPassword = (EditText)findViewById(R.id.account_password); + accountPassword.setText(savedInstanceState.getString(ACCOUNT_PASSWORD), TextView.BufferType.EDITABLE); + // END of getting contents of each field } else { mStatusText = mStatusIcon = 0; mStatusCorrect = false; mIsSslConn = false; + + String accountName = getIntent().getExtras().getString(PARAM_ACCOUNTNAME); + String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + if (AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType)) { + CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + oAuth2Check.setChecked(true); + changeViewByOAuth2Check(true); + } + + if (accountName != null) { + ((TextView) findViewById(R.id.account_username)).setText(accountName.substring(0, accountName.lastIndexOf('@'))); + tv.setText(accountName.substring(accountName.lastIndexOf('@') + 1)); + } } iv.setOnClickListener(this); iv2.setOnClickListener(this); tv.setOnFocusChangeListener(this); tv2.setOnFocusChangeListener(this); - + Button b = (Button) findViewById(R.id.account_register); if (b != null) { b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); } + + mNewCapturedUriFromOAuth2Redirection = null; } + + @Override + protected void onNewIntent (Intent intent) { + Uri data = intent.getData(); + //mNewRedirectUriCaptured = (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI)); + if (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI)) { + mNewCapturedUriFromOAuth2Redirection = data; + } + Log.d(TAG, "onNewIntent()"); + + } + + @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(STATUS_ICON, mStatusIcon); outState.putInt(STATUS_TEXT, mStatusText); outState.putBoolean(STATUS_CORRECT, mStatusCorrect); + if (mDiscoveredVersion != null) + outState.putString(OC_VERSION, mDiscoveredVersion.toString()); + + // Saving the state of oAuth2 components. + outState.putInt(OAUTH2_STATUS_ICON, mOAuth2StatusIcon); + outState.putInt(OAUTH2_STATUS_TEXT, mOAuth2StatusText); + CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + outState.putBoolean(OAUTH2_IS_CHECKED, oAuth2Check.isChecked()); + if (codeResponseJson != null){ + outState.putString(OAUTH2_CODE_RESULT, codeResponseJson.toString()); + } + // END of saving the state of oAuth2 components. + + // Saving contents of each field. + outState.putString(HOST_URL_TEXT,((TextView) findViewById(R.id.host_URL)).getText().toString().trim()); + outState.putString(ACCOUNT_USERNAME,((TextView) findViewById(R.id.account_username)).getText().toString().trim()); + outState.putString(ACCOUNT_PASSWORD,((TextView) findViewById(R.id.account_password)).getText().toString().trim()); + super.onSaveInstanceState(outState); } @@ -166,6 +288,41 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity dialog = working_dialog; break; } + // oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser. + case OAUTH2_LOGIN_PROGRESS: { + ProgressDialog working_dialog = new ProgressDialog(this); + try { + if (codeResponseJson != null && codeResponseJson.has(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL)) { + working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message), + codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL), + codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE))); + } else { + working_dialog.setMessage(String.format("Getting authorization")); + } + } catch (JSONException e) { + Log.e(TAG, "onCreateDialog->JSONException: " + e.toString()); + } + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(true); + working_dialog + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + Log.i(TAG, "Login canceled"); + if (mOAuth2GetCodeThread != null) { + mOAuth2GetCodeThread.interrupt(); + finish(); + } + if (tokenReceiver != null) { + unregisterReceiver(tokenReceiver); + tokenReceiver = null; + finish(); + } + } + }); + dialog = working_dialog; + break; + } case DIALOG_SSL_VALIDATOR: { dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); break; @@ -194,6 +351,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity switch (id) { case DIALOG_LOGIN_PROGRESS: case DIALOG_CERT_NOT_SAVED: + case OAUTH2_LOGIN_PROGRESS: break; case DIALOG_SSL_VALIDATOR: { ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); @@ -203,6 +361,32 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity Log.e(TAG, "Incorrect dialog called with id = " + id); } } + + @Override + protected void onResume() { + Log.d(TAG, "onResume() start"); + // (old oauth code) Registering token receiver. We must listening to the service that is pooling to the oAuth server for a token. + if (tokenReceiver == null) { + IntentFilter tokenFilter = new IntentFilter(OAuth2GetTokenService.TOKEN_RECEIVED_MESSAGE); + tokenReceiver = new TokenReceiver(); + this.registerReceiver(tokenReceiver,tokenFilter); + } + // (new oauth code) + /*if (mNewRedirectUriCaptured) { + mNewRedirectUriCaptured = false;*/ + if (mNewCapturedUriFromOAuth2Redirection != null) { + getOAuth2AccessTokenFromCapturedRedirection(); + + } + super.onResume(); + } + + @Override + protected void onPause() { + Log.d(TAG, "onPause() start"); + super.onPause(); + } + public void onAuthenticationResult(boolean success, String message) { if (success) { @@ -247,9 +431,10 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity AccountAuthenticator.ACCOUNT_TYPE); intent.putExtra(AccountManager.KEY_USERDATA, username); + accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL, + url.toString()); accManager.setUserData(account, - AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable - .getDiscoveredVersion().toString()); + AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); accManager.setUserData(account, AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl); @@ -302,9 +487,25 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity || url.toLowerCase().startsWith("https://")) { prefix = ""; } - continueConnection(prefix); + CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + if (oAuth2Check != null && oAuth2Check.isChecked()) { + startOauthorization(); + + } else { + continueConnection(prefix); + } } + private void startOauthorization() { + // We start a thread to get an authorization code from the oAuth2 server. + setOAuth2ResultIconAndText(R.drawable.progress_small, R.string.oauth_login_connection); + mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_F_AUTHORIZATION_ENDPOINT_URL, this); + //mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_G_DEVICE_GETCODE_URL, this); + mOAuth2GetCodeRunnable.setListener(this, mHandler); + mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable); + mOAuth2GetCodeThread.start(); + } + public void onRegisterClick(View view) { Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register))); setResult(RESULT_CANCELED); @@ -322,8 +523,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity url = url.substring(0, url.length() - 1); URL uri = null; - String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable - .getDiscoveredVersion()); + mDiscoveredVersion = mConnChkRunnable.getDiscoveredVersion(); + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false); if (webdav_path == null) { onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title)); @@ -427,7 +628,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); } - @Override public void onFocusChange(View view, boolean hasFocus) { if (view.getId() == R.id.host_URL) { if (!hasFocus) { @@ -437,11 +637,12 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity setResultIconAndText(R.drawable.progress_small, R.string.auth_testing_connection); //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this); - mConnChkRunnable = new ConnectionCheckOperation(uri, this); + mConnChkRunnable = new ConnectionCheckOperation(uri, this); //mConnChkRunnable.setListener(this, mHandler); //mAuthThread = new Thread(mConnChkRunnable); //mAuthThread.start(); WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); + mDiscoveredVersion = null; mAuthThread = mConnChkRunnable.execute(client, this, mHandler); } else { findViewById(R.id.refreshButton).setVisibility( @@ -501,10 +702,212 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity view.setSelection(selectionStart, selectionEnd); } } + + @Override protected void onDestroy() { + // We must stop the service thats it's pooling to oAuth2 server for a token. + Intent tokenService = new Intent(this, OAuth2GetTokenService.class); + stopService(tokenService); + + // We stop listening the result of the pooling service. + if (tokenReceiver != null) { + unregisterReceiver(tokenReceiver); + tokenReceiver = null; + finish(); + } + + super.onDestroy(); + } + + // Controlling the oAuth2 checkbox on the activity: hide and show widgets. + public void onOff_check_Click(View view) { + CheckBox oAuth2Check = (CheckBox)view; + changeViewByOAuth2Check(oAuth2Check.isChecked()); + + } + + public void changeViewByOAuth2Check(Boolean checked) { + + EditText oAuth2Url = (EditText) findViewById(R.id.oAuth_URL); + EditText accountUsername = (EditText) findViewById(R.id.account_username); + EditText accountPassword = (EditText) findViewById(R.id.account_password); + ImageView viewPassword = (ImageView) findViewById(R.id.viewPassword); + ImageView auth2ActionIndicator = (ImageView) findViewById(R.id.auth2_action_indicator); + TextView oauth2StatusText = (TextView) findViewById(R.id.oauth2_status_text); + + if (checked) { + oAuth2Url.setVisibility(View.VISIBLE); + accountUsername.setVisibility(View.GONE); + accountPassword.setVisibility(View.GONE); + viewPassword.setVisibility(View.GONE); + auth2ActionIndicator.setVisibility(View.INVISIBLE); + oauth2StatusText.setVisibility(View.INVISIBLE); + } else { + oAuth2Url.setVisibility(View.GONE); + accountUsername.setVisibility(View.VISIBLE); + accountPassword.setVisibility(View.VISIBLE); + viewPassword.setVisibility(View.INVISIBLE); + auth2ActionIndicator.setVisibility(View.GONE); + oauth2StatusText.setVisibility(View.GONE); + } + + } + + // Controlling the oAuth2 result of server connection. + private void setOAuth2ResultIconAndText(int drawable_id, int text_id) { + ImageView iv = (ImageView) findViewById(R.id.auth2_action_indicator); + TextView tv = (TextView) findViewById(R.id.oauth2_status_text); + + if (drawable_id == 0 && text_id == 0) { + iv.setVisibility(View.INVISIBLE); + tv.setVisibility(View.INVISIBLE); + } else { + iv.setImageResource(drawable_id); + tv.setText(text_id); + iv.setVisibility(View.VISIBLE); + tv.setVisibility(View.VISIBLE); + } + } + + // Results from the first call to oAuth2 server : getting the user_code and verification_url. + @Override + public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject responseJson) { + if ((type == ResultOAuthType.OK_SSL)||(type == ResultOAuthType.OK_NO_SSL)) { + codeResponseJson = responseJson; + if (codeResponseJson != null) { + getOAuth2AccessTokenFromJsonResponse(); + } // else - nothing to do here - wait for callback !!! + + } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) { + setOAuth2ResultIconAndText(R.drawable.common_error, R.string.oauth_connection_url_unavailable); + } + } + + // If the results of getting the user_code and verification_url are OK, we get the received data and we start + // the polling service to oAuth2 server to get a valid token. + private void getOAuth2AccessTokenFromJsonResponse() { + String deviceCode = null; + String verificationUrl = null; + String userCode = null; + int expiresIn = -1; + int interval = -1; + + Log.d(TAG, "ResponseOAuth2->" + codeResponseJson.toString()); + + try { + // We get data that we must show to the user or we will use internally. + verificationUrl = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL); + userCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE); + expiresIn = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_EXPIRES_IN); + + // And we get data that we must use to get a token. + deviceCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_DEVICE_CODE); + interval = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_INTERVAL); + + } catch (JSONException e) { + Log.e(TAG, "Exception accesing data in Json object" + e.toString()); + } + + // Updating status widget to OK. + setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established); + + // Showing the dialog with instructions for the user. + showDialog(OAUTH2_LOGIN_PROGRESS); + + // Loggin all the data. + Log.d(TAG, "verificationUrl->" + verificationUrl); + Log.d(TAG, "userCode->" + userCode); + Log.d(TAG, "deviceCode->" + deviceCode); + Log.d(TAG, "expiresIn->" + expiresIn); + Log.d(TAG, "interval->" + interval); + + // Starting the pooling service. + try { + Intent tokenService = new Intent(this, OAuth2GetTokenService.class); + tokenService.putExtra(OAuth2GetTokenService.TOKEN_URI, OAuth2Context.OAUTH2_G_DEVICE_GETTOKEN_URL); + tokenService.putExtra(OAuth2GetTokenService.TOKEN_DEVICE_CODE, deviceCode); + tokenService.putExtra(OAuth2GetTokenService.TOKEN_INTERVAL, interval); + + startService(tokenService); + } + catch (Exception e) { + Log.e(TAG, "tokenService creation problem :", e); + } + + } + + private void getOAuth2AccessTokenFromCapturedRedirection() { + Map responseValues = new HashMap(); + //String queryParameters = getIntent().getData().getQuery(); + String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); + mNewCapturedUriFromOAuth2Redirection = null; + + Log.v(TAG, "Queryparameters (Code) = " + queryParameters); + + String[] pairs = queryParameters.split("&"); + Log.v(TAG, "Pairs (Code) = " + pairs.toString()); + + 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; + responseValues.put(key, value); + sb.append(value + "\n"); + } + + Log.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + + + // Updating status widget to OK. + setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established); + + // Showing the dialog with instructions for the user. + showDialog(OAUTH2_LOGIN_PROGRESS); + + // + RemoteOperation operation = new GetOAuth2AccessToken(responseValues); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(OAuth2Context.OAUTH2_F_TOKEN_ENDPOINT_URL), getApplicationContext()); + operation.execute(client, this, mHandler); + } + + + + // We get data from the oAuth2 token service with this broadcast receiver. + private class TokenReceiver extends BroadcastReceiver { + /** + * The token is received. + * @author + * {@link BroadcastReceiver} to enable oAuth2 token receiving. + */ + @Override + public void onReceive(Context context, Intent intent) { + @SuppressWarnings("unchecked") + HashMap tokenResponse = (HashMap)intent.getExtras().get(OAuth2GetTokenService.TOKEN_RECEIVED_DATA); + Log.d(TAG, "TokenReceiver->" + tokenResponse.get(OAuth2GetTokenService.TOKEN_ACCESS_TOKEN)); + dismissDialog(OAUTH2_LOGIN_PROGRESS); + + } + } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mConnChkRunnable)) { + if (operation instanceof ConnectionCheckOperation) { mStatusText = mStatusIcon = 0; mStatusCorrect = false; @@ -592,6 +995,126 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity else findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE); findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); + + } else if (operation instanceof GetOAuth2AccessToken) { + + try { + dismissDialog(OAUTH2_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + if (result.isSuccess()) { + + /// time to test the retrieved access token on the ownCloud server + String url = ((TextView) findViewById(R.id.host_URL)).getText() + .toString().trim(); + if (url.endsWith("/")) + url = url.substring(0, url.length() - 1); + + Uri uri = null; + /*String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion); + + if (webdav_path == null) { + onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title)); + return; + }*/ + + String prefix = ""; + if (mIsSslConn) { + prefix = "https://"; + } else { + prefix = "http://"; + } + if (url.toLowerCase().startsWith("http://") + || url.toLowerCase().startsWith("https://")) { + prefix = ""; + } + + try { + mBaseUrl = prefix + url; + //String url_str = prefix + url + webdav_path; + String url_str = prefix + url + "/remote.php/odav"; + uri = Uri.parse(url_str); + + } catch (Exception e) { + // should never happen + onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title)); + return; + } + + showDialog(DIALOG_LOGIN_PROGRESS); + String accessToken = ((GetOAuth2AccessToken)operation).getResultTokenMap().get(OAuth2Context.KEY_ACCESS_TOKEN); + Log.d(TAG, "Got ACCESS TOKEN: " + accessToken); + mAuthChkOperation = new ExistenceCheckOperation("", this, accessToken); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(uri, getApplicationContext()); + mAuthChkOperation.execute(client, this, mHandler); + + + } else { + TextView tv = (TextView) findViewById(R.id.oAuth_URL); + tv.setError("A valid authorization could not be obtained"); + + } + + } else if (operation instanceof ExistenceCheckOperation) { + + try { + dismissDialog(DIALOG_LOGIN_PROGRESS); + } catch (IllegalArgumentException e) { + // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens + } + + if (result.isSuccess()) { + TextView tv = (TextView) findViewById(R.id.oAuth_URL); + Log.d(TAG, "Checked access - time to save the account"); + + Uri uri = Uri.parse(mBaseUrl); + String username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); + String accountName = username + "@" + uri.getHost(); + if (uri.getPort() >= 0) { + accountName += ":" + uri.getPort(); + } + // TODO - check that accountName does not exist + Account account = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE); + AccountManager accManager = AccountManager.get(this); + accManager.addAccountExplicitly(account, "", null); // with our implementation, the password is never input in the app + + // Add this account as default in the preferences, if there is none + Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); + if (defaultAccount == null) { + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.putString("select_oc_account", accountName); + editor.commit(); + } + + /// account data to save by the AccountManager + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); + intent.putExtra(AccountManager.KEY_USERDATA, username); + + accManager.setAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, ((ExistenceCheckOperation) operation).getAccessToken()); + + accManager.setUserData(account, AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable.getDiscoveredVersion().toString()); + accManager.setUserData(account, AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl); + accManager.setUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); + + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + + /// enforce the first account synchronization + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(account, "org.owncloud", bundle); + + finish(); + + } else { + TextView tv = (TextView) findViewById(R.id.oAuth_URL); + tv.setError(result.getLogMessage()); + Log.d(TAG, "Access failed: " + result.getLogMessage()); + } } } @@ -604,5 +1127,5 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity public void onFailedSavingCertificate() { showDialog(DIALOG_CERT_NOT_SAVED); } - + } diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index cfee79d4..a717f0c6 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -73,7 +73,7 @@ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileObserverService; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.CreateFolderOperation; import com.owncloud.android.operations.OnRemoteOperationListener; import com.owncloud.android.operations.RemoteOperation; import com.owncloud.android.operations.RemoteOperationResult; @@ -88,7 +88,6 @@ import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; /** * Displays, what files the user has available in his ownCloud. @@ -116,6 +115,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements private OCFileListFragment mFileList; private boolean mDualPane; + private Handler mHandler; private static final int DIALOG_SETUP_ACCOUNT = 0; private static final int DIALOG_CREATE_DIR = 1; @@ -137,6 +137,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements public void onCreate(Bundle savedInstanceState) { Log.d(getClass().toString(), "onCreate() start"); super.onCreate(savedInstanceState); + + mHandler = new Handler(); /// Load of parameters from received intent mCurrentDir = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); // no check necessary, mCurrenDir == null if the parameter is not in the intent @@ -652,8 +654,12 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements // Create directory path += directoryName + OCFile.PATH_SEPARATOR; - Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler())); - thread.start(); + RemoteOperation operation = new CreateFolderOperation(path, mCurrentDir.getFileId(), mStorageManager); + operation.execute( AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), + FileDisplayActivity.this, + FileDisplayActivity.this, + mHandler, + FileDisplayActivity.this); dialog.dismiss(); @@ -771,56 +777,6 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements return !mDirectories.isEmpty(); } - private class DirectoryCreator implements Runnable { - private String mTargetPath; - private Account mAccount; - private Handler mHandler; - - public DirectoryCreator(String targetPath, Account account, Handler handler) { - mTargetPath = targetPath; - mAccount = account; - mHandler = handler; - } - - @Override - public void run() { - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - boolean created = wdc.createDirectory(mTargetPath); - if (created) { - mHandler.post(new Runnable() { - @Override - public void run() { - dismissDialog(DIALOG_SHORT_WAIT); - - // Save new directory in local database - OCFile newDir = new OCFile(mTargetPath); - newDir.setMimetype("DIR"); - newDir.setParentId(mCurrentDir.getFileId()); - mStorageManager.saveFile(newDir); - - // Display the new folder right away - mFileList.listDirectory(); - } - }); - - } else { - mHandler.post(new Runnable() { - @Override - public void run() { - dismissDialog(DIALOG_SHORT_WAIT); - try { - Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); - msg.show(); - - } catch (NotFoundException e) { - Log.e(TAG, "Error while trying to show fail message " , e); - } - } - }); - } - } - - } // Custom array adapter to override text colors private class CustomArrayAdapter extends ArrayAdapter { @@ -1121,6 +1077,32 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } else if (operation instanceof SynchronizeFileOperation) { onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); + + } else if (operation instanceof CreateFolderOperation) { + onCreateFolderOperationFinish((CreateFolderOperation)operation, result); + } + } + + /** + * Updates the view associated to the activity after the finish of an operation trying create a new folder + * + * @param operation Creation operation performed. + * @param result Result of the creation. + */ + private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + dismissDialog(DIALOG_SHORT_WAIT); + mFileList.listDirectory(); + + } else { + dismissDialog(DIALOG_SHORT_WAIT); + try { + Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); + msg.show(); + + } catch (NotFoundException e) { + Log.e(TAG, "Error while trying to show fail message " , e); + } } } diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 2c43278f..f6c25e2c 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -18,20 +18,8 @@ package com.owncloud.android.ui.fragment; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.methods.StringRequestEntity; -import org.apache.commons.httpclient.params.HttpConnectionManagerParams; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; -import org.json.JSONObject; +import org.apache.commons.httpclient.Credentials; import android.accounts.Account; import android.accounts.AccountManager; @@ -66,7 +54,6 @@ import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; -import com.owncloud.android.AccountUtils; import com.owncloud.android.DisplayUtils; import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.datamodel.FileDataStorageManager; @@ -76,7 +63,7 @@ import com.owncloud.android.files.services.FileObserverService; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.network.BearerCredentials; import com.owncloud.android.operations.OnRemoteOperationListener; import com.owncloud.android.operations.RemoteOperation; import com.owncloud.android.operations.RemoteOperationResult; @@ -90,10 +77,8 @@ import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.TransferServiceGetter; import com.owncloud.android.ui.dialog.EditNameDialog; import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import com.owncloud.android.utils.OwnCloudVersion; import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavUtils; /** @@ -308,8 +293,7 @@ public class FileDetailFragment extends SherlockFragment implements } else { mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); // update ui boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; @@ -423,9 +407,7 @@ public class FileDetailFragment extends SherlockFragment implements mLastRemoteOperation = new RemoveFileOperation( mFile, true, mStorageManager); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); - + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); } @@ -736,7 +718,7 @@ public class FileDetailFragment extends SherlockFragment implements if (mFile.getRemotePath().equals(uploadRemotePath) || renamedInUpload) { if (uploadWasFine) { - mFile = mStorageManager.getFileByPath(uploadRemotePath); + mFile = mStorageManager.getFileByPath(uploadRemotePath); } if (renamedInUpload) { String newName = (new File(uploadRemotePath)).getName(); @@ -752,6 +734,7 @@ public class FileDetailFragment extends SherlockFragment implements // this is a temporary class for sharing purposes, it need to be replaced in transfer service + /* @SuppressWarnings("unused") private class ShareRunnable implements Runnable { private String mPath; @@ -854,6 +837,7 @@ public class FileDetailFragment extends SherlockFragment implements } } } + */ public void onDismiss(EditNameDialog dialog) { if (dialog.getResult()) { @@ -863,8 +847,7 @@ public class FileDetailFragment extends SherlockFragment implements mAccount, newFilename, new FileDataStorageManager(mAccount, getActivity().getContentResolver())); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); } @@ -950,7 +933,19 @@ public class FileDetailFragment extends SherlockFragment implements */ @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mLastRemoteOperation)) { + if (!result.isSuccess() && result.getCode() == ResultCode.UNAUTHORIZED) { + AccountManager am = AccountManager.get(getSherlockActivity()); + //am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, OwnCloudClientUtils.getAuthorizationTokenType(operation.getClient().getCredentials())); + Credentials cred = operation.getClient().getCredentials(); + if (cred instanceof BearerCredentials) { + am.invalidateAuthToken(AccountAuthenticator.ACCOUNT_TYPE, ((BearerCredentials)cred).getAccessToken()); + } else { + am.clearPassword(mAccount); + } + operation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); // need a new client instance, so avoid retry() + // TODO si el usuario no se autoriza de nuevo, esto genera un bucle infinito; o un error en la creación del objecto cliente + + } else { if (operation instanceof RemoveFileOperation) { onRemoveFileOperationFinish((RemoveFileOperation)operation, result); diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java index 117b0b2d..751de9ee 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -316,8 +316,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial case R.id.download_file_item: { Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()); RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); + operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); return true; } @@ -479,8 +478,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial AccountUtils.getCurrentOwnCloudAccount(getActivity()), newFilename, mContainerActivity.getStorageManager()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); } } @@ -493,8 +491,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial RemoteOperation operation = new RemoveFileOperation( mTargetFile, true, mContainerActivity.getStorageManager()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); } diff --git a/src/eu/alefzero/webdav/WebdavClient.java b/src/eu/alefzero/webdav/WebdavClient.java index 61f16605..312b8287 100644 --- a/src/eu/alefzero/webdav/WebdavClient.java +++ b/src/eu/alefzero/webdav/WebdavClient.java @@ -22,14 +22,20 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.httpclient.Credentials; +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.HttpState; import org.apache.commons.httpclient.HttpVersion; 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.methods.GetMethod; import org.apache.commons.httpclient.methods.HeadMethod; @@ -39,7 +45,9 @@ import org.apache.http.HttpStatus; import org.apache.http.params.CoreProtocolPNames; import org.apache.jackrabbit.webdav.client.methods.DavMethod; import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; -import org.apache.jackrabbit.webdav.client.methods.MkColMethod; + +import com.owncloud.android.network.BearerAuthScheme; +import com.owncloud.android.network.BearerCredentials; import android.net.Uri; import android.util.Log; @@ -63,18 +71,28 @@ public class WebdavClient extends HttpClient { getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); } - public void setCredentials(String username, String password) { - getParams().setAuthenticationPreemptive(true); - getState().setCredentials(AuthScope.ANY, - getCredentials(username, password)); + public void setBearerCredentials(String accessToken) { + AuthPolicy.registerAuthScheme(BearerAuthScheme.AUTH_POLICY, BearerAuthScheme.class); + + List authPrefs = new ArrayList(1); + authPrefs.add(BearerAuthScheme.AUTH_POLICY); + getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); + + mCredentials = new BearerCredentials(accessToken); + getState().setCredentials(AuthScope.ANY, mCredentials); } - private Credentials getCredentials(String username, String password) { - if (mCredentials == null) - mCredentials = new UsernamePasswordCredentials(username, password); - return mCredentials; + public void setBasicCredentials(String username, String password) { + List authPrefs = new ArrayList(1); + authPrefs.add(AuthPolicy.BASIC); + getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs); + + getParams().setAuthenticationPreemptive(true); + mCredentials = new UsernamePasswordCredentials(username, password); + getState().setCredentials(AuthScope.ANY, mCredentials); } + /** * Downloads a file in remoteFilepath to the local targetPath. * @@ -200,34 +218,6 @@ public class WebdavClient extends HttpClient { return status; } - /** - * Creates a remote directory with the received path. - * - * @param path Path of the directory to create, URL DECODED - * @return 'True' when the directory is successfully created - */ - public boolean createDirectory(String path) { - boolean result = false; - int status = -1; - MkColMethod mkcol = new MkColMethod(mUri.toString() + WebdavUtils.encodePath(path)); - try { - Log.d(TAG, "Creating directory " + path); - status = executeMethod(mkcol); - Log.d(TAG, "Status returned: " + status); - result = mkcol.succeeded(); - - Log.d(TAG, "MKCOL to " + path + " finished with HTTP status " + status + (!result?"(FAIL)":"")); - exhaustResponse(mkcol.getResponseBodyAsStream()); - - } catch (Exception e) { - logException(e, "creating directory " + path); - - } finally { - mkcol.releaseConnection(); // let the connection available for other methods - } - return result; - } - /** * Check if a file exists in the OC server @@ -336,5 +326,19 @@ public class WebdavClient extends HttpClient { public Uri getBaseUri() { return mUri; } + + + @Override + public int executeMethod(HostConfiguration hostconfig, final HttpMethod method, final HttpState state) throws IOException, HttpException { + if (mCredentials instanceof BearerAuthScheme) { + method.getHostAuthState().setAuthScheme(AuthPolicy.getAuthScheme(BearerAuthScheme.AUTH_POLICY)); + } + return super.executeMethod(hostconfig, method, state); + } + + + public final Credentials getCredentials() { + return mCredentials; + } }