From: David A. Velasco Date: Thu, 21 Mar 2013 11:21:48 +0000 (+0100) Subject: OAuth clean-up and refactoring X-Git-Tag: oc-android-1.4.3~29^2~9 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/69d6d821ec1311e6804d1398140ccf9e2d8d0e5c?hp=--cc OAuth clean-up and refactoring --- 69d6d821ec1311e6804d1398140ccf9e2d8d0e5c diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f031ad47..ea0c1aad 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -128,7 +128,7 @@ - + diff --git a/res/layout-land/account_setup.xml b/res/layout-land/account_setup.xml index b8a8fcd0..ab380ba4 100644 --- a/res/layout-land/account_setup.xml +++ b/res/layout-land/account_setup.xml @@ -125,7 +125,7 @@ android:layout_weight="1" android:ems="10" android:enabled="false" - android:text="@string/oauth_url_endpoint_auth" + android:text="@string/oauth2_url_endpoint_auth" android:singleLine="true" android:visibility="gone" > @@ -139,7 +139,7 @@ android:layout_weight="1" android:ems="10" android:enabled="false" - android:text="@string/oauth_url_endpoint_access" + android:text="@string/oauth2_url_endpoint_access" android:singleLine="true" android:visibility="gone" > diff --git a/res/layout/account_setup.xml b/res/layout/account_setup.xml index a4868a4e..e74ddf70 100644 --- a/res/layout/account_setup.xml +++ b/res/layout/account_setup.xml @@ -120,7 +120,7 @@ android:layout_weight="1" android:ems="10" android:enabled="false" - android:text="@string/oauth_url_endpoint_auth" + android:text="@string/oauth2_url_endpoint_auth" android:singleLine="true" android:visibility="gone" > @@ -134,7 +134,7 @@ android:layout_weight="1" android:ems="10" android:enabled="false" - android:text="@string/oauth_url_endpoint_access" + android:text="@string/oauth2_url_endpoint_access" android:singleLine="true" android:visibility="gone" > diff --git a/res/values/oauth.xml b/res/values/oauth.xml deleted file mode 100644 index 66bb2255..00000000 --- a/res/values/oauth.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - http://owncloud.tuxed.net/oauth/php-oauth/authorize.php - http://owncloud.tuxed.net/oauth/php-oauth/token.php - diff --git a/res/values/oauth2_configuration.xml b/res/values/oauth2_configuration.xml new file mode 100644 index 00000000..04efddc9 --- /dev/null +++ b/res/values/oauth2_configuration.xml @@ -0,0 +1,18 @@ + + + + oauth-mobile-app + oauth-mobile-app://callback + + + http://owncloud.tuxed.net/oauth/php-oauth/authorize.php + http://owncloud.tuxed.net/oauth/php-oauth/token.php + grades + authorization_code + code + + + oc-android-test + + + diff --git a/src/com/owncloud/android/AccountUtils.java b/src/com/owncloud/android/AccountUtils.java index fba0c368..ea47f153 100644 --- a/src/com/owncloud/android/AccountUtils.java +++ b/src/com/owncloud/android/AccountUtils.java @@ -19,7 +19,7 @@ package com.owncloud.android; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.utils.OwnCloudVersion; import android.accounts.Account; diff --git a/src/com/owncloud/android/Uploader.java b/src/com/owncloud/android/Uploader.java index aadf4d9e..862ede81 100644 --- a/src/com/owncloud/android/Uploader.java +++ b/src/com/owncloud/android/Uploader.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Stack; import java.util.Vector; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; diff --git a/src/com/owncloud/android/authentication/AccountAuthenticator.java b/src/com/owncloud/android/authentication/AccountAuthenticator.java new file mode 100644 index 00000000..d6acbbb1 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticator.java @@ -0,0 +1,318 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 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 2 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.authentication; + + +import android.accounts.*; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + + +/** + * Authenticator for ownCloud accounts. + * + * Controller class accessed from the system AccountManager, providing integration of ownCloud accounts with the Android system. + * + * TODO - better separation in operations for OAuth-capable and regular ownCloud accounts. + * TODO - review completeness + * + * @author David A. Velasco + */ +public class AccountAuthenticator extends AbstractAccountAuthenticator { + /** + * Is used by android system to assign accounts to authenticators. Should be + * used by application and all extensions. + */ + public static final String ACCOUNT_TYPE = "owncloud"; + public static final String AUTHORITY = "org.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"; + public static final String KEY_LOGIN_OPTIONS = "loginOptions"; + public static final String KEY_ACCOUNT = "account"; + /** + * Value under this key should handle path to webdav php script. Will be + * removed and usage should be replaced by combining + * {@link com.owncloud.android.authentication.AuthenticatorActivity.KEY_OC_BASE_URL} and + * {@link com.owncloud.android.utils.OwnCloudVersion} + * + * @deprecated + */ + public static final String KEY_OC_URL = "oc_url"; + /** + * Version should be 3 numbers separated by dot so it can be parsed by + * {@link com.owncloud.android.utils.OwnCloudVersion} + */ + public static final String KEY_OC_VERSION = "oc_version"; + /** + * Base url should point to owncloud installation without trailing / ie: + * http://server/path or https://owncloud.server + */ + public static final String KEY_OC_BASE_URL = "oc_base_url"; + /** + * 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) { + super(context); + mContext = context; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, + String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) + throws NetworkErrorException { + Log.i(TAG, "Adding account with type " + accountType + + " and auth token " + authTokenType); + try { + validateAccountType(accountType); + } catch (AuthenticatorException e) { + Log.e(TAG, "Failed to validate account type " + accountType + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE); + + setIntentFlags(intent); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + try { + validateAccountType(account.type); + } catch (AuthenticatorException e) { + Log.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + + Bundle resultBundle = new Bundle(); + resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); + return resultBundle; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + /// validate parameters + try { + validateAccountType(account.type); + validateAuthTokenType(authTokenType); + } catch (AuthenticatorException e) { + Log.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + + /// check if required token is stored + final AccountManager am = AccountManager.get(mContext); + String accessToken; + if (authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD)) { + accessToken = am.getPassword(account); + } else { + 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; + } + + /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, + Account account, String[] features) throws NetworkErrorException { + final Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + return result; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + setIntentFlags(intent); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, Account account) + throws NetworkErrorException { + return super.getAccountRemovalAllowed(response, account); + } + + 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); // incompatible with the authorization code grant in OAuth + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + } + + private void validateAccountType(String type) + throws UnsupportedAccountTypeException { + if (!type.equals(ACCOUNT_TYPE)) { + throw new UnsupportedAccountTypeException(); + } + } + + private void validateAuthTokenType(String authTokenType) + throws UnsupportedAuthTokenTypeException { + 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(); + } + } + + public static class AuthenticatorException extends Exception { + private static final long serialVersionUID = 1L; + private Bundle mFailureBundle; + + public AuthenticatorException(int code, String errorMsg) { + mFailureBundle = new Bundle(); + mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); + mFailureBundle + .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); + } + + public Bundle getFailureBundle() { + return mFailureBundle; + } + } + + public static class UnsupportedAccountTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAccountTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported account type"); + } + } + + public static class UnsupportedAuthTokenTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAuthTokenTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported auth token type"); + } + } + + public static class UnsupportedFeaturesException extends + AuthenticatorException { + public static final long serialVersionUID = 1L; + + public UnsupportedFeaturesException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported features"); + } + } + + public static class AccessDeniedException extends AuthenticatorException { + public AccessDeniedException(int code, String errorMsg) { + super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); + } + + private static final long serialVersionUID = 1L; + + } +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticatorService.java b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java new file mode 100644 index 00000000..971d6f01 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java @@ -0,0 +1,42 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 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 2 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.authentication; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + static final public String ACCOUNT_TYPE = "owncloud"; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + +} diff --git a/src/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/com/owncloud/android/authentication/AuthenticatorActivity.java new file mode 100644 index 00000000..9b2dd851 --- /dev/null +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -0,0 +1,1077 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 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 2 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.authentication; + +import com.owncloud.android.AccountUtils; +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.OwnCloudServerCheckOperation; +import com.owncloud.android.operations.ExistenceCheckOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + +import android.accounts.Account; +import android.accounts.AccountAuthenticatorActivity; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.text.InputType; +import android.util.Log; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.Window; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.owncloud.android.R; + +import eu.alefzero.webdav.WebdavClient; + +/** + * This Activity is used to add an ownCloud account to the App + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class AuthenticatorActivity extends AccountAuthenticatorActivity + implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener { + + private static final String TAG = AuthenticatorActivity.class.getSimpleName(); + + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + public static final String EXTRA_USER_NAME = "USER_NAME"; + public static final String EXTRA_HOST_NAME = "HOST_NAME"; + public static final String EXTRA_ACTION = "ACTION"; + + private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String KEY_OC_VERSION = "OC_VERSION"; + private static final String KEY_ACCOUNT = "ACCOUNT"; + private static final String KEY_STATUS_TEXT = "STATUS_TEXT"; + private static final String KEY_STATUS_ICON = "STATUS_ICON"; + private static final String KEY_STATUS_CORRECT = "STATUS_CORRECT"; + private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; + private static final String KEY_OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT"; + private static final String KEY_OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON"; + + private static final int DIALOG_LOGIN_PROGRESS = 0; + private static final int DIALOG_SSL_VALIDATOR = 1; + private static final int DIALOG_CERT_NOT_SAVED = 2; + private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3; + + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_UPDATE_TOKEN = 1; + + + private String mHostBaseUrl; + private OwnCloudVersion mDiscoveredVersion; + + private int mStatusText, mStatusIcon; + private boolean mStatusCorrect, mIsSslConn; + private int mOAuth2StatusText, mOAuth2StatusIcon; + + private final Handler mHandler = new Handler(); + private Thread mOperationThread; + private OwnCloudServerCheckOperation mOcServerChkOperation; + private ExistenceCheckOperation mAuthCheckOperation; + private RemoteOperationResult mLastSslUntrustedServerResult; + + private Uri mNewCapturedUriFromOAuth2Redirection; + + private AccountManager mAccountMgr; + private boolean mJustCreated; + private byte mAction; + private Account mAccount; + + private ImageView mRefreshButton; + private ImageView mViewPasswordButton; + private EditText mHostUrlInput; + private EditText mUsernameInput; + private EditText mPasswordInput; + private CheckBox mOAuth2Check; + private String mOAuthAccessToken; + private View mOkButton; + private TextView mAuthStatusLayout; + + private TextView mOAuthAuthEndpointText; + private TextView mOAuthTokenEndpointText; + + + /** + * {@inheritDoc} + * + * IMPORTANT ENTRY POINT 1: activity is shown to the user + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + + /// set view and get references to view elements + setContentView(R.layout.account_setup); + mRefreshButton = (ImageView) findViewById(R.id.refreshButton); + mViewPasswordButton = (ImageView) findViewById(R.id.viewPasswordButton); + mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); + mUsernameInput = (EditText) findViewById(R.id.account_username); + mPasswordInput = (EditText) findViewById(R.id.account_password); + mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); + mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); + mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + mOkButton = findViewById(R.id.buttonOK); + mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); + + + /// complete label for 'register account' button + 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))); + } + + /// bind view elements to listeners + mHostUrlInput.setOnFocusChangeListener(this); + mPasswordInput.setOnFocusChangeListener(this); + + /// initialization + mAccountMgr = AccountManager.get(this); + mNewCapturedUriFromOAuth2Redirection = null; + mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); + mAccount = null; + + if (savedInstanceState == null) { + /// connection state and info + mStatusText = mStatusIcon = 0; + mStatusCorrect = false; + mIsSslConn = false; + + /// retrieve extras from intent + String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + boolean oAuthRequired = AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType); + + mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + if (mAccount != null) { + String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); + if (ocVersion != null) { + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + } + mHostBaseUrl = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); + mHostUrlInput.setText(mHostBaseUrl); + String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); + mUsernameInput.setText(userName); + oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); + } + mOAuth2Check.setChecked(oAuthRequired); + changeViewByOAuth2Check(oAuthRequired); + + + } else { + loadSavedInstanceState(savedInstanceState); + } + + if (mAction == ACTION_UPDATE_TOKEN) { + /// lock things that should not change + mHostUrlInput.setEnabled(false); + mUsernameInput.setEnabled(false); + mOAuth2Check.setVisibility(View.GONE); + checkOcServer(); + } + + mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) + mJustCreated = true; + } + + + /** + * Saves relevant state before {@link #onPause()} + * + * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the + * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} + * + * See {@link #loadSavedInstanceState(Bundle)} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + /// connection state and info + outState.putInt(KEY_STATUS_TEXT, mStatusText); + outState.putInt(KEY_STATUS_ICON, mStatusIcon); + outState.putBoolean(KEY_STATUS_CORRECT, mStatusCorrect); + outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn); + + /// server data + if (mDiscoveredVersion != null) + outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString()); + outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl); + + /// account data, if updating + if (mAccount != null) + outState.putParcelable(KEY_ACCOUNT, mAccount); + + // Saving the state of oAuth2 components. + outState.putInt(KEY_OAUTH2_STATUS_ICON, mOAuth2StatusIcon); + outState.putInt(KEY_OAUTH2_STATUS_TEXT, mOAuth2StatusText); + + } + + + /** + * Loads saved state + * + * See {@link #onSaveInstanceState(Bundle)}. + * + * @param savedInstanceState Saved state, as received in {@link #onCreate(Bundle)}. + */ + private void loadSavedInstanceState(Bundle savedInstanceState) { + /// connection state and info + mStatusCorrect = savedInstanceState.getBoolean(KEY_STATUS_CORRECT); + mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); + mStatusText = savedInstanceState.getInt(KEY_STATUS_TEXT); + mStatusIcon = savedInstanceState.getInt(KEY_STATUS_ICON); + updateConnStatus(); + + /// UI settings depending upon connection + mOkButton.setEnabled(mStatusCorrect); // TODO really necessary? + if (!mStatusCorrect) + mRefreshButton.setVisibility(View.VISIBLE); // seems that setting visibility is necessary + else + mRefreshButton.setVisibility(View.INVISIBLE); + + /// server data + String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); + if (ocVersion != null) + mDiscoveredVersion = new OwnCloudVersion(ocVersion); + mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); + + // account data, if updating + mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); + + // state of oAuth2 components + mOAuth2StatusIcon = savedInstanceState.getInt(KEY_OAUTH2_STATUS_ICON); + mOAuth2StatusText = savedInstanceState.getInt(KEY_OAUTH2_STATUS_TEXT); + // END of getting the state of oAuth2 components. + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request + * is caught here. + * + * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the + * AndroidManifest.xml file. + */ + @Override + protected void onNewIntent (Intent intent) { + Log.d(TAG, "onNewIntent()"); + Uri data = intent.getData(); + if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) { + mNewCapturedUriFromOAuth2Redirection = data; + } + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and + * deferred in {@link #onNewIntent(Intent)}, is processed here. + */ + @Override + protected void onResume() { + super.onResume(); + // the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes; so keep the next lines here + changeViewByOAuth2Check(mOAuth2Check.isChecked()); + if (mAction == ACTION_UPDATE_TOKEN && mJustCreated) { + if (mOAuth2Check.isChecked()) + Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); + else + Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show(); + } + + if (mNewCapturedUriFromOAuth2Redirection != null) { + getOAuth2AccessTokenFromCapturedRedirection(); + } + + mJustCreated = false; + } + + + /** + * Parses the redirection with the response to the GET AUTHORIZATION request to the + * oAuth server and requests for the access token (GET ACCESS TOKEN) + */ + private void getOAuth2AccessTokenFromCapturedRedirection() { + /// Parse data from OAuth redirection + String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); + mNewCapturedUriFromOAuth2Redirection = null; + + /// Showing the dialog with instructions for the user. + showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); + + /// GET ACCESS TOKEN to the oAuth server + RemoteOperation operation = new OAuth2GetAccessToken( getString(R.string.oauth2_client_id), + getString(R.string.oauth2_redirect_uri), // TODO check - necessary here? + getString(R.string.oauth2_grant_type), + queryParameters); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth2_url_endpoint_access)), getApplicationContext()); + operation.execute(client, this, mHandler); + } + + + + /** + * Handles the change of focus on the text inputs for the server URL and the password + */ + public void onFocusChange(View view, boolean hasFocus) { + if (view.getId() == R.id.hostUrlInput) { + onUrlInputFocusChanged((TextView) view, hasFocus); + + } else if (view.getId() == R.id.account_password) { + onPasswordFocusChanged((TextView) view, hasFocus); + } + } + + + /** + * Handles changes in focus on the text input for the server URL. + * + * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to + * other field. The operation to check the existence of the server in the entered URL is + * started. + * + * When hasFocus: user 'comes back' to write again the server URL. + * + * @param hostInput TextView with the URL input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onUrlInputFocusChanged(TextView hostInput, boolean hasFocus) { + if (!hasFocus) { + checkOcServer(); + + } else { + // avoids that the 'connect' button can be clicked if the test was previously passed + mOkButton.setEnabled(false); + } + } + + + private void checkOcServer() { + String uri = mHostUrlInput.getText().toString().trim(); + if (uri.length() != 0) { + mStatusText = R.string.auth_testing_connection; + mStatusIcon = R.drawable.progress_small; + updateConnStatus(); + /** TODO cancel previous connection check if the user tries to ammend a wrong URL + if(mConnChkOperation != null) { + mConnChkOperation.cancel(); + } */ + mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); + mHostBaseUrl = ""; + mDiscoveredVersion = null; + mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); + } else { + mRefreshButton.setVisibility(View.INVISIBLE); + mStatusText = 0; + mStatusIcon = 0; + updateConnStatus(); + } + } + + + /** + * Handles changes in focus on the text input for the password (basic authorization). + * + * When (hasFocus), the button to toggle password visibility is shown. + * + * When (!hasFocus), the button is made invisible and the password is hidden. + * + * @param passwordInput TextView with the password input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { + if (hasFocus) { + mViewPasswordButton.setVisibility(View.VISIBLE); + } else { + int input_type = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + passwordInput.setInputType(input_type); + mViewPasswordButton.setVisibility(View.INVISIBLE); + } + } + + + + /** + * Cancels the authenticator activity + * + * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation + * + * This method is bound in the layout/acceoun_setup.xml resource file. + * + * @param view Cancel button + */ + public void onCancelClick(View view) { + setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator (debugging) + finish(); + } + + + + /** + * Checks the credentials of the user in the root of the ownCloud server + * before creating a new local account. + * + * For basic authorization, a check of existence of the root folder is + * performed. + * + * For OAuth, starts the flow to get an access token; the credentials test + * is postponed until it is available. + * + * IMPORTANT ENTRY POINT 4 + * + * @param view OK button + */ + public void onOkClick(View view) { + // this check should be unnecessary + if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) { + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_wtf_reenter_URL; + updateConnStatus(); + mOkButton.setEnabled(false); + Log.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); + return; + } + + if (mOAuth2Check.isChecked()) { + startOauthorization(); + + } else { + checkBasicAuthorization(); + } + } + + + /** + * Tests the credentials entered by the user performing a check of existence on + * the root folder of the ownCloud server. + */ + private void checkBasicAuthorization() { + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false); + + /// get basic credentials entered by user + String username = mUsernameInput.getText().toString(); + String password = mPasswordInput.getText().toString(); + + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// test credentials accessing the root folder + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); + client.setBasicCredentials(username, password); + mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); + } + + + /** + * Starts the OAuth 'grant type' flow to get an access token, with + * a GET AUTHORIZATION request to the BUILT-IN authorization server. + */ + private void startOauthorization() { + // be gentle with the user + mStatusIcon = R.drawable.progress_small; + mStatusText = R.string.oauth_login_connection; + updateAuthStatus(); + + // GET AUTHORIZATION request + Uri uri = Uri.parse(getString(R.string.oauth2_url_endpoint_auth)); + Uri.Builder uriBuilder = uri.buildUpon(); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); + //uriBuilder.appendQueryParameter(OAuth2Constants.KEY_STATE, whateverwewant); + uri = uriBuilder.build(); + Log.d(TAG, "Starting browser to view " + uri.toString()); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + startActivity(i); + } + + + /** + * Callback method invoked when a RemoteOperation executed by this Activity finishes. + * + * Dispatches the operation flow to the right method. + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + + if (operation instanceof OwnCloudServerCheckOperation) { + onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result); + + } else if (operation instanceof OAuth2GetAccessToken) { + onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); + + } else if (operation instanceof ExistenceCheckOperation) { + onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result); + + } + } + + + /** + * Processes the result of the server check performed when the user finishes the enter of the + * server URL. + * + * @param operation Server check performed. + * @param result Result of the check. + */ + private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) { + /// update status icon and text + updateStatusIconAndText(result); + updateConnStatus(); + + /// save result state + mStatusCorrect = result.isSuccess(); + mIsSslConn = (result.getCode() == ResultCode.OK_SSL); + + /// very special case (TODO: move to a common place for all the remote operations) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + mLastSslUntrustedServerResult = result; + showDialog(DIALOG_SSL_VALIDATOR); + } + + /// update the visibility of the 'retry connection' button + if (!mStatusCorrect) + mRefreshButton.setVisibility(View.VISIBLE); + else + mRefreshButton.setVisibility(View.INVISIBLE); + + /// retrieve discovered version and normalize server URL + mDiscoveredVersion = operation.getDiscoveredVersion(); + mHostBaseUrl = mHostUrlInput.getText().toString().trim(); + if (!mHostBaseUrl.toLowerCase().startsWith("http://") && + !mHostBaseUrl.toLowerCase().startsWith("https://")) { + + if (mIsSslConn) { + mHostBaseUrl = "https://" + mHostBaseUrl; + } else { + mHostBaseUrl = "http://" + mHostBaseUrl; + } + + } + if (mHostBaseUrl.endsWith("/")) + mHostBaseUrl = mHostBaseUrl.substring(0, mHostBaseUrl.length() - 1); + + /// allow or not the user try to access the server + mOkButton.setEnabled(mStatusCorrect); + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateStatusIconAndText(RemoteOperationResult result) { + mStatusText = mStatusIcon = 0; + + switch (result.getCode()) { + case OK_SSL: + mStatusIcon = android.R.drawable.ic_secure; + mStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mStatusText = R.string.auth_connection_established; + mStatusIcon = R.drawable.ic_ok; + } else { + mStatusText = R.string.auth_nossl_plain_ok_title; + mStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_ssl_unverified_server_title; + break; + + case BAD_OC_VERSION: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_incorrect_address_title; + break; + + case SSL_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_ssl_general_error_title; + break; + + case UNAUTHORIZED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unknown_host_title; + break; + case NO_NETWORK_CONNECTION: + mStatusIcon = R.drawable.no_network; + mStatusText = R.string.auth_no_net_conn_title; + break; + case INSTANCE_NOT_CONFIGURED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_oauth_error_access_denied; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mStatusIcon = R.drawable.common_error; + mStatusText = R.string.auth_unknown_error_title; + break; + + default: + break; + } + } + + + /** + * Processes the result of the request for and access token send + * to an OAuth authorization server. + * + * @param operation Operation performed requesting the access token. + * @param result Result of the operation. + */ + private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) { + try { + dismissDialog(DIALOG_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 + } + + String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, true); + if (result.isSuccess() && webdav_path != null) { + /// be gentle with the user + showDialog(DIALOG_LOGIN_PROGRESS); + + /// time to test the retrieved access token on the ownCloud server + mOAuthAccessToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); + Log.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken); + mAuthCheckOperation = new ExistenceCheckOperation("", this, false); + WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); + client.setBearerCredentials(mOAuthAccessToken); + mAuthCheckOperation.execute(client, this, mHandler); + + } else { + updateStatusIconAndText(result); + updateAuthStatus(); + Log.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Processes the result of the access check performed to try the user credentials. + * + * Creates a new account through the AccountManager. + * + * @param operation Access check performed. + * @param result Result of the operation. + */ + private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) { + 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()) { + Log.d(TAG, "Successful access - time to save the account"); + + if (mAction == ACTION_CREATE) { + createAccount(); + + } else { + updateToken(); + } + + finish(); + + } else { + updateStatusIconAndText(result); + updateAuthStatus(); + Log.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Sets the proper response to get that the Account Authenticator that started this activity saves + * a new authorization token for mAccount. + */ + private void updateToken() { + Bundle response = new Bundle(); + response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); + boolean isOAuth = mOAuth2Check.isChecked(); + if (isOAuth) { + response.putString(AccountManager.KEY_AUTHTOKEN, mOAuthAccessToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); + } else { + response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); + mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); + } + setAccountAuthenticatorResult(response); + } + + + /** + * Creates a new account through the Account Authenticator that started this activity. + * + * This makes the account permanent. + * + * TODO Decide how to name the OAuth accounts + */ + private void createAccount() { + /// create and save new ownCloud account + boolean isOAuth = mOAuth2Check.isChecked(); + + Uri uri = Uri.parse(mHostBaseUrl); + String username = mUsernameInput.getText().toString().trim(); + if (isOAuth) { + username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); + } + String accountName = username + "@" + uri.getHost(); + if (uri.getPort() >= 0) { + accountName += ":" + uri.getPort(); + } + mAccount = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE); + if (isOAuth) { + mAccountMgr.addAccountExplicitly(mAccount, "", null); // with our implementation, the password is never input in the app + } else { + mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); + } + + /// add the new account as default in preferences, if there is none already + Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); + if (defaultAccount == null) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(this).edit(); + editor.putString("select_oc_account", accountName); + editor.commit(); + } + + /// prepare result to return to the Authenticator + // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + if (!isOAuth) + intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); // TODO check this; not sure it's right; maybe + intent.putExtra(AccountManager.KEY_USERDATA, username); + if (isOAuth) { + mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); + } + /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); + if (isOAuth) + mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); // TODO this flag should be unnecessary + + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + + /// immediately request for the synchronization of the new account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTHORITY, bundle); + } + + + /** + * {@inheritDoc} + * + * Necessary to update the contents of the SSL Dialog + * + * TODO move to some common place for all possible untrusted SSL failures + */ + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + switch (id) { + case DIALOG_LOGIN_PROGRESS: + case DIALOG_CERT_NOT_SAVED: + case DIALOG_OAUTH2_LOGIN_PROGRESS: + break; + case DIALOG_SSL_VALIDATOR: { + ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); + break; + } + default: + Log.e(TAG, "Incorrect dialog called with id = " + id); + } + } + + + /** + * {@inheritDoc} + */ + @Override + protected Dialog onCreateDialog(int id) { + Dialog dialog = null; + switch (id) { + case DIALOG_LOGIN_PROGRESS: { + /// simple progress dialog + ProgressDialog working_dialog = new ProgressDialog(this); + working_dialog.setMessage(getResources().getString(R.string.auth_trying_to_login)); + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(true); + working_dialog + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + /// TODO study if this is enough + Log.i(TAG, "Login canceled"); + if (mOperationThread != null) { + mOperationThread.interrupt(); + finish(); + } + } + }); + dialog = working_dialog; + break; + } + case DIALOG_OAUTH2_LOGIN_PROGRESS: { + ProgressDialog working_dialog = new ProgressDialog(this); + working_dialog.setMessage(String.format("Getting authorization")); + 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"); + finish(); + } + }); + dialog = working_dialog; + break; + } + case DIALOG_SSL_VALIDATOR: { + /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already) + dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); + break; + } + case DIALOG_CERT_NOT_SAVED: { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); + builder.setCancelable(false); + builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + }; + }); + dialog = builder.create(); + break; + } + default: + Log.e(TAG, "Incorrect dialog called with id = " + id); + } + return dialog; + } + + + /** + * Starts and activity to open the 'new account' page in the ownCloud web site + * + * @param view 'Account register' button + */ + public void onRegisterClick(View view) { + Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register))); + setResult(RESULT_CANCELED); + startActivity(register); + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the last check on the ownCloud server. + */ + private void updateConnStatus() { + ImageView iv = (ImageView) findViewById(R.id.action_indicator); + TextView tv = (TextView) findViewById(R.id.status_text); + + if (mStatusIcon == 0 && mStatusText == 0) { + iv.setVisibility(View.INVISIBLE); + tv.setVisibility(View.INVISIBLE); + } else { + iv.setImageResource(mStatusIcon); + tv.setText(mStatusText); + iv.setVisibility(View.VISIBLE); + tv.setVisibility(View.VISIBLE); + } + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the interactions with the OAuth authorization server. + */ + private void updateAuthStatus() { + if (mStatusIcon == 0 && mStatusText == 0) { + mAuthStatusLayout.setVisibility(View.INVISIBLE); + } else { + mAuthStatusLayout.setText(mStatusText); + mAuthStatusLayout.setCompoundDrawablesWithIntrinsicBounds(mStatusIcon, 0, 0, 0); + mAuthStatusLayout.setVisibility(View.VISIBLE); + } + } + + + /** + * Called when the refresh button in the input field for ownCloud host is clicked. + * + * Performs a new check on the URL in the input field. + * + * @param view Refresh 'button' + */ + public void onRefreshClick(View view) { + onFocusChange(mRefreshButton, false); + } + + + /** + * Called when the eye icon in the password field is clicked. + * + * Toggles the visibility of the password in the field. + * + * @param view 'View password' 'button' + */ + public void onViewPasswordClick(View view) { + int selectionStart = mPasswordInput.getSelectionStart(); + int selectionEnd = mPasswordInput.getSelectionEnd(); + int input_type = mPasswordInput.getInputType(); + if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD; + } else { + input_type = InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + } + mPasswordInput.setInputType(input_type); + mPasswordInput.setSelection(selectionStart, selectionEnd); + } + + + /** + * Called when the checkbox for OAuth authorization is clicked. + * + * Hides or shows the input fields for user & password. + * + * @param view 'View password' 'button' + */ + public void onCheckClick(View view) { + CheckBox oAuth2Check = (CheckBox)view; + changeViewByOAuth2Check(oAuth2Check.isChecked()); + + } + + /** + * Changes the visibility of input elements depending upon the kind of authorization + * chosen by the user: basic or OAuth + * + * @param checked 'True' when OAuth is selected. + */ + public void changeViewByOAuth2Check(Boolean checked) { + + if (checked) { + mOAuthAuthEndpointText.setVisibility(View.VISIBLE); + mOAuthTokenEndpointText.setVisibility(View.VISIBLE); + mUsernameInput.setVisibility(View.GONE); + mPasswordInput.setVisibility(View.GONE); + mViewPasswordButton.setVisibility(View.GONE); + } else { + mOAuthAuthEndpointText.setVisibility(View.GONE); + mOAuthTokenEndpointText.setVisibility(View.GONE); + mUsernameInput.setVisibility(View.VISIBLE); + mPasswordInput.setVisibility(View.VISIBLE); + mViewPasswordButton.setVisibility(View.INVISIBLE); + } + + } + + /** + * Called from SslValidatorDialog when a new server certificate was correctly saved. + */ + public void onSavedCertificate() { + mOperationThread = mOcServerChkOperation.retry(this, mHandler); + } + + /** + * Called from SslValidatorDialog when a new server certificate could not be saved + * when the user requested it. + */ + @Override + public void onFailedSavingCertificate() { + showDialog(DIALOG_CERT_NOT_SAVED); + } + +} diff --git a/src/com/owncloud/android/authentication/OAuth2Constants.java b/src/com/owncloud/android/authentication/OAuth2Constants.java new file mode 100644 index 00000000..227accbc --- /dev/null +++ b/src/com/owncloud/android/authentication/OAuth2Constants.java @@ -0,0 +1,54 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2013 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 2 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.authentication; + +/** + * Constant values for OAuth 2 protocol. + * + * Includes required and optional parameter NAMES used in the 'authorization code' grant type. + * + * @author David A. Velasco + */ + +public class OAuth2Constants { + + /// Parameters to send to the Authorization Endpoint + public static final String KEY_RESPONSE_TYPE = "response_type"; + public static final String KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_CLIENT_ID = "client_id"; + public static final String KEY_SCOPE = "scope"; + public static final String KEY_STATE = "state"; + + /// Additional parameters to send to the Token Endpoint + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + + /// Parameters received in an OK response from the Token Endpoint + public static final String KEY_ACCESS_TOKEN = "access_token"; + public static final String KEY_TOKEN_TYPE = "token_type"; + public static final String KEY_EXPIRES_IN = "expires_in"; + public static final String KEY_REFRESH_TOKEN = "refresh_token"; + + /// Parameters in an ERROR response + public static final String KEY_ERROR = "error"; + public static final String KEY_ERROR_DESCRIPTION = "error_description"; + public static final String KEY_ERROR_URI = "error_uri"; + public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; + +} diff --git a/src/com/owncloud/android/authenticator/AccountAuthenticator.java b/src/com/owncloud/android/authenticator/AccountAuthenticator.java deleted file mode 100644 index a42c1f20..00000000 --- a/src/com/owncloud/android/authenticator/AccountAuthenticator.java +++ /dev/null @@ -1,308 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2012-2013 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 2 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.authenticator; - -import com.owncloud.android.ui.activity.AuthenticatorActivity; - -import android.accounts.*; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -public class AccountAuthenticator extends AbstractAccountAuthenticator { - /** - * Is used by android system to assign accounts to authenticators. Should be - * used by application and all extensions. - */ - public static final String ACCOUNT_TYPE = "owncloud"; - public static final String AUTHORITY = "org.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"; - public static final String KEY_LOGIN_OPTIONS = "loginOptions"; - public static final String KEY_ACCOUNT = "account"; - /** - * Value under this key should handle path to webdav php script. Will be - * removed and usage should be replaced by combining - * {@link com.owncloud.android.authenticator.AuthenticatorActivity.KEY_OC_BASE_URL} and - * {@link com.owncloud.android.utils.OwnCloudVersion} - * - * @deprecated - */ - public static final String KEY_OC_URL = "oc_url"; - /** - * Version should be 3 numbers separated by dot so it can be parsed by - * {@link com.owncloud.android.utils.OwnCloudVersion} - */ - public static final String KEY_OC_VERSION = "oc_version"; - /** - * Base url should point to owncloud installation without trailing / ie: - * http://server/path or https://owncloud.server - */ - public static final String KEY_OC_BASE_URL = "oc_base_url"; - /** - * 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) { - super(context); - mContext = context; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, - String accountType, String authTokenType, - String[] requiredFeatures, Bundle options) - throws NetworkErrorException { - Log.i(TAG, "Adding account with type " + accountType - + " and auth token " + authTokenType); - try { - validateAccountType(accountType); - } catch (AuthenticatorException e) { - Log.e(TAG, "Failed to validate account type " + accountType + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE); - - setIntentFlags(intent); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, - Account account, Bundle options) throws NetworkErrorException { - try { - validateAccountType(account.type); - } catch (AuthenticatorException e) { - Log.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - - setIntentFlags(intent); - - Bundle resultBundle = new Bundle(); - resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); - return resultBundle; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, - String accountType) { - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - /// validate parameters - try { - validateAccountType(account.type); - validateAuthTokenType(authTokenType); - } catch (AuthenticatorException e) { - Log.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - - /// check if required token is stored - final AccountManager am = AccountManager.get(mContext); - String accessToken; - if (authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD)) { - accessToken = am.getPassword(account); - } else { - 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; - } - - /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account); - intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); - - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, - Account account, String[] features) throws NetworkErrorException { - final Bundle result = new Bundle(); - result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); - return result; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - setIntentFlags(intent); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public Bundle getAccountRemovalAllowed( - AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - return super.getAccountRemovalAllowed(response, account); - } - - 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); // incompatible with the authorization code grant in OAuth - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.addFlags(Intent.FLAG_FROM_BACKGROUND); - } - - private void validateAccountType(String type) - throws UnsupportedAccountTypeException { - if (!type.equals(ACCOUNT_TYPE)) { - throw new UnsupportedAccountTypeException(); - } - } - - private void validateAuthTokenType(String authTokenType) - throws UnsupportedAuthTokenTypeException { - 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(); - } - } - - public static class AuthenticatorException extends Exception { - private static final long serialVersionUID = 1L; - private Bundle mFailureBundle; - - public AuthenticatorException(int code, String errorMsg) { - mFailureBundle = new Bundle(); - mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); - mFailureBundle - .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); - } - - public Bundle getFailureBundle() { - return mFailureBundle; - } - } - - public static class UnsupportedAccountTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAccountTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported account type"); - } - } - - public static class UnsupportedAuthTokenTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAuthTokenTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported auth token type"); - } - } - - public static class UnsupportedFeaturesException extends - AuthenticatorException { - public static final long serialVersionUID = 1L; - - public UnsupportedFeaturesException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported features"); - } - } - - public static class AccessDeniedException extends AuthenticatorException { - public AccessDeniedException(int code, String errorMsg) { - super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); - } - - private static final long serialVersionUID = 1L; - - } -} diff --git a/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java b/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java deleted file mode 100644 index e3972b65..00000000 --- a/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2012-2013 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 2 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.authenticator; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -public class AccountAuthenticatorService extends Service { - - private AccountAuthenticator mAuthenticator; - static final public String ACCOUNT_TYPE = "owncloud"; - - @Override - public void onCreate() { - super.onCreate(); - mAuthenticator = new AccountAuthenticator(this); - } - - @Override - public IBinder onBind(Intent intent) { - return mAuthenticator.getIBinder(); - } - -} diff --git a/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java b/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java deleted file mode 100644 index 7d36da7b..00000000 --- a/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java +++ /dev/null @@ -1,58 +0,0 @@ -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"; - - 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"; - public static final String CODE_RESPONSE_TYPE = "response_type"; - public static final String CODE_REDIRECT_URI = "redirect_uri"; - - public static final String ERROR_ACCESS_DENIED = "access_denied"; - -} diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index c3ca6c87..781402d9 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -22,7 +22,7 @@ package com.owncloud.android.files; import java.io.File; import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.db.DbHandler; import com.owncloud.android.files.services.FileUploader; diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 0d7a26b1..00d5f199 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -27,6 +27,7 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import eu.alefzero.webdav.OnDatatransferProgressListener; @@ -35,7 +36,6 @@ import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.DownloadFileOperation; import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.ui.activity.AuthenticatorActivity; import com.owncloud.android.ui.activity.FileDetailActivity; import com.owncloud.android.ui.fragment.FileDetailFragment; diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 09988fba..04044e3e 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -31,7 +31,8 @@ import org.apache.http.HttpStatus; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.InstantUploadBroadcastReceiver; @@ -41,7 +42,6 @@ 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; -import com.owncloud.android.ui.activity.AuthenticatorActivity; import com.owncloud.android.ui.activity.FileDetailActivity; import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.utils.OwnCloudVersion; diff --git a/src/com/owncloud/android/network/OwnCloudClientUtils.java b/src/com/owncloud/android/network/OwnCloudClientUtils.java index a3f322b9..7b2b64fd 100644 --- a/src/com/owncloud/android/network/OwnCloudClientUtils.java +++ b/src/com/owncloud/android/network/OwnCloudClientUtils.java @@ -38,7 +38,7 @@ 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 com.owncloud.android.authentication.AccountAuthenticator; import eu.alefzero.webdav.WebdavClient; diff --git a/src/com/owncloud/android/operations/OAuth2GetAccessToken.java b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java index 12e611c4..fbbe2544 100644 --- a/src/com/owncloud/android/operations/OAuth2GetAccessToken.java +++ b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java @@ -8,7 +8,7 @@ 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.authentication.OAuth2Constants; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import android.util.Log; @@ -19,13 +19,19 @@ public class OAuth2GetAccessToken extends RemoteOperation { private static final String TAG = OAuth2GetAccessToken.class.getSimpleName(); + private String mClientId; + private String mRedirectUri; + private String mGrantType; + private String mOAuth2AuthorizationResponse; private Map mOAuth2ParsedAuthorizationResponse; private Map mResultTokenMap; - public OAuth2GetAccessToken(String oAuth2AuthorizationResponse) { - + public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) { + mClientId = clientId; + mRedirectUri = redirectUri; + mGrantType = grantType; mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; mOAuth2ParsedAuthorizationResponse = new HashMap(); mResultTokenMap = null; @@ -47,8 +53,8 @@ public class OAuth2GetAccessToken extends RemoteOperation { try { parseAuthorizationResponse(); - if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Context.KEY_ERROR)) { - if (OAuth2Context.ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Context.KEY_ERROR))) { + if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Constants.KEY_ERROR)) { + if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_ERROR))) { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); } else { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); @@ -57,11 +63,11 @@ public class OAuth2GetAccessToken extends RemoteOperation { if (result == null) { 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, mOAuth2ParsedAuthorizationResponse.get(OAuth2Context.KEY_CODE)); - nameValuePairs[2] = new NameValuePair(OAuth2Context.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.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); + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_CODE)); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); + nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + //nameValuePairs[4] = new NameValuePair(OAuth2Constants.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_SCOPE)); postMethod = new PostMethod(client.getBaseUri().toString()); postMethod.setRequestBody(nameValuePairs); @@ -71,7 +77,7 @@ public class OAuth2GetAccessToken extends RemoteOperation { if (response != null && response.length() > 0) { JSONObject tokenJson = new JSONObject(response); parseAccessTokenResult(tokenJson); - if (mResultTokenMap.get(OAuth2Context.OAUTH2_TOKEN_RECEIVED_ERROR) != null || mResultTokenMap.get(OAuth2Context.KEY_ACCESS_TOKEN) == null) { + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); } else { @@ -98,7 +104,7 @@ public class OAuth2GetAccessToken extends RemoteOperation { Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage(), result.getException()); } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Context.OAUTH2_TOKEN_RECEIVED_ERROR) : "NULL")); + Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "NULL")); } else { Log.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getBaseUri() + ": " + result.getLogMessage()); @@ -140,29 +146,29 @@ public class OAuth2GetAccessToken extends RemoteOperation { private void parseAccessTokenResult (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(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Constants.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(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Constants.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(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Constants.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(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Constants.KEY_REFRESH_TOKEN)); } - if (tokenJson.has(OAuth2Context.KEY_SCOPE)) { - mResultTokenMap.put(OAuth2Context.KEY_SCOPE, tokenJson.getString(OAuth2Context.KEY_SCOPE)); + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson.getString(OAuth2Constants.KEY_SCOPE)); } - if (tokenJson.has(OAuth2Context.KEY_ERROR)) { - mResultTokenMap.put(OAuth2Context.KEY_ERROR, tokenJson.getString(OAuth2Context.KEY_ERROR)); + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson.getString(OAuth2Constants.KEY_ERROR)); } - if (tokenJson.has(OAuth2Context.KEY_ERROR_DESCRIPTION)) { - mResultTokenMap.put(OAuth2Context.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Context.KEY_ERROR_DESCRIPTION)); + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); } - if (tokenJson.has(OAuth2Context.KEY_ERROR_URI)) { - mResultTokenMap.put(OAuth2Context.KEY_ERROR_URI, tokenJson.getString(OAuth2Context.KEY_ERROR_URI)); + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson.getString(OAuth2Constants.KEY_ERROR_URI)); } } diff --git a/src/com/owncloud/android/operations/RemoteOperation.java b/src/com/owncloud/android/operations/RemoteOperation.java index 19b67af0..e7d40347 100644 --- a/src/com/owncloud/android/operations/RemoteOperation.java +++ b/src/com/owncloud/android/operations/RemoteOperation.java @@ -21,7 +21,7 @@ import java.io.IOException; import org.apache.commons.httpclient.Credentials; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.network.BearerCredentials; import com.owncloud.android.network.OwnCloudClientUtils; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; diff --git a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java index 08a9c15f..02f79c95 100644 --- a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@ -29,7 +29,7 @@ import android.content.Context; import android.util.Log; import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.utils.OwnCloudVersion; diff --git a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java index 1f307c94..0b0adae2 100644 --- a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java @@ -26,7 +26,7 @@ import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import android.accounts.Account; import android.accounts.AccountManager; diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index f78ed242..c3370f2d 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -28,6 +28,7 @@ import java.util.Map; import org.apache.jackrabbit.webdav.DavException; import com.owncloud.android.R; +import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -35,7 +36,6 @@ import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UpdateOCVersionOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.ui.activity.AuthenticatorActivity; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; import android.accounts.Account; diff --git a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java index 9a04501c..2c29e621 100644 --- a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java +++ b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java @@ -50,7 +50,7 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.R; diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java deleted file mode 100644 index 011abfc7..00000000 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ /dev/null @@ -1,1255 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2012-2013 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 2 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.ui.activity; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.authenticator.oauth2.OAuth2Context; -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.OwnCloudServerCheckOperation; -import com.owncloud.android.operations.ExistenceCheckOperation; -import com.owncloud.android.operations.OAuth2GetAccessToken; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; - -import android.accounts.Account; -import android.accounts.AccountAuthenticatorActivity; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.text.InputType; -import android.util.Log; -import android.view.View; -import android.view.View.OnFocusChangeListener; -import android.view.Window; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.owncloud.android.R; - -import eu.alefzero.webdav.WebdavClient; - -/** - * This Activity is used to add an ownCloud account to the App - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class AuthenticatorActivity extends AccountAuthenticatorActivity - implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener { - - private static final String TAG = AuthenticatorActivity.class.getSimpleName(); - - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - public static final String EXTRA_USER_NAME = "USER_NAME"; - public static final String EXTRA_HOST_NAME = "HOST_NAME"; - public static final String EXTRA_ACTION = "ACTION"; - - private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; - private static final String KEY_OC_VERSION = "OC_VERSION"; - private static final String KEY_ACCOUNT = "ACCOUNT"; - private static final String KEY_STATUS_TEXT = "STATUS_TEXT"; - private static final String KEY_STATUS_ICON = "STATUS_ICON"; - private static final String KEY_STATUS_CORRECT = "STATUS_CORRECT"; - private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; - private static final String KEY_OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT"; - private static final String KEY_OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON"; - - private static final int DIALOG_LOGIN_PROGRESS = 0; - private static final int DIALOG_SSL_VALIDATOR = 1; - private static final int DIALOG_CERT_NOT_SAVED = 2; - private static final int DIALOG_OAUTH2_LOGIN_PROGRESS = 3; - - public static final byte ACTION_CREATE = 0; - public static final byte ACTION_UPDATE_TOKEN = 1; - - - private String mHostBaseUrl; - private OwnCloudVersion mDiscoveredVersion; - - private int mStatusText, mStatusIcon; - private boolean mStatusCorrect, mIsSslConn; - private int mOAuth2StatusText, mOAuth2StatusIcon; - - private final Handler mHandler = new Handler(); - private Thread mOperationThread; - private OwnCloudServerCheckOperation mOcServerChkOperation; - private ExistenceCheckOperation mAuthCheckOperation; - private RemoteOperationResult mLastSslUntrustedServerResult; - - //private Thread mOAuth2GetCodeThread; - //private OAuth2GetAuthorizationToken mOAuth2GetCodeRunnable; - //private TokenReceiver tokenReceiver; - //private JSONObject mCodeResponseJson; - private Uri mNewCapturedUriFromOAuth2Redirection; - - private AccountManager mAccountMgr; - private boolean mJustCreated; - private byte mAction; - private Account mAccount; - - private ImageView mRefreshButton; - private ImageView mViewPasswordButton; - private EditText mHostUrlInput; - private EditText mUsernameInput; - private EditText mPasswordInput; - private CheckBox mOAuth2Check; - private String mOAuthAccessToken; - private View mOkButton; - private TextView mAuthStatusLayout; - - private TextView mOAuthAuthEndpointText; - private TextView mOAuthTokenEndpointText; - - - /** - * {@inheritDoc} - * - * IMPORTANT ENTRY POINT 1: activity is shown to the user - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - - /// set view and get references to view elements - setContentView(R.layout.account_setup); - mRefreshButton = (ImageView) findViewById(R.id.refreshButton); - mViewPasswordButton = (ImageView) findViewById(R.id.viewPasswordButton); - mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); - mUsernameInput = (EditText) findViewById(R.id.account_username); - mPasswordInput = (EditText) findViewById(R.id.account_password); - mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); - mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); - mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); - mOkButton = findViewById(R.id.buttonOK); - mAuthStatusLayout = (TextView) findViewById(R.id.auth_status_text); - - - /// complete label for 'register account' button - 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))); - } - - /// bind view elements to listeners - mHostUrlInput.setOnFocusChangeListener(this); - mPasswordInput.setOnFocusChangeListener(this); - - /// initialization - mAccountMgr = AccountManager.get(this); - mNewCapturedUriFromOAuth2Redirection = null; // TODO save? - mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); - mAccount = null; - - if (savedInstanceState == null) { - /// connection state and info - mStatusText = mStatusIcon = 0; - mStatusCorrect = false; - mIsSslConn = false; - - /// retrieve extras from intent - String tokenType = getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); - boolean oAuthRequired = AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(tokenType); - - mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); - if (mAccount != null) { - String ocVersion = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION); - if (ocVersion != null) { - mDiscoveredVersion = new OwnCloudVersion(ocVersion); - } - mHostBaseUrl = mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); - mHostUrlInput.setText(mHostBaseUrl); - String userName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); - mUsernameInput.setText(userName); - oAuthRequired = (mAccountMgr.getUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2) != null); - } - mOAuth2Check.setChecked(oAuthRequired); - changeViewByOAuth2Check(oAuthRequired); - - - } else { - loadSavedInstanceState(savedInstanceState); - } - - if (mAction == ACTION_UPDATE_TOKEN) { - /// lock things that should not change - mHostUrlInput.setEnabled(false); - mUsernameInput.setEnabled(false); - mOAuth2Check.setVisibility(View.GONE); - checkOcServer(); - } - - mPasswordInput.setText(""); // clean password to avoid social hacking (disadvantage: password in removed if the device is turned aside) - mJustCreated = true; - } - - - /** - * Saves relevant state before {@link #onPause()} - * - * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the - * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} - * - * See {@link #loadSavedInstanceState(Bundle)} - */ - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - /// connection state and info - outState.putInt(KEY_STATUS_TEXT, mStatusText); - outState.putInt(KEY_STATUS_ICON, mStatusIcon); - outState.putBoolean(KEY_STATUS_CORRECT, mStatusCorrect); - outState.putBoolean(KEY_IS_SSL_CONN, mIsSslConn); - - /// server data - if (mDiscoveredVersion != null) - outState.putString(KEY_OC_VERSION, mDiscoveredVersion.toString()); - outState.putString(KEY_HOST_URL_TEXT, mHostBaseUrl); - - /// account data, if updating - if (mAccount != null) - outState.putParcelable(KEY_ACCOUNT, mAccount); - - // Saving the state of oAuth2 components. - outState.putInt(KEY_OAUTH2_STATUS_ICON, mOAuth2StatusIcon); - outState.putInt(KEY_OAUTH2_STATUS_TEXT, mOAuth2StatusText); - - /* Leave old OAuth flow - if (codeResponseJson != null){ - outState.putString(KEY_OAUTH2_CODE_RESULT, codeResponseJson.toString()); - } - */ - } - - - /** - * Loads saved state - * - * See {@link #onSaveInstanceState(Bundle)}. - * - * @param savedInstanceState Saved state, as received in {@link #onCreate(Bundle)}. - */ - private void loadSavedInstanceState(Bundle savedInstanceState) { - /// connection state and info - mStatusCorrect = savedInstanceState.getBoolean(KEY_STATUS_CORRECT); - mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); - mStatusText = savedInstanceState.getInt(KEY_STATUS_TEXT); - mStatusIcon = savedInstanceState.getInt(KEY_STATUS_ICON); - updateConnStatus(); - - /// UI settings depending upon connection - mOkButton.setEnabled(mStatusCorrect); // TODO really necessary? - if (!mStatusCorrect) - mRefreshButton.setVisibility(View.VISIBLE); // seems that setting visibility is necessary - else - mRefreshButton.setVisibility(View.INVISIBLE); - - /// server data - String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); - if (ocVersion != null) - mDiscoveredVersion = new OwnCloudVersion(ocVersion); - mHostBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); - - // account data, if updating - mAccount = savedInstanceState.getParcelable(KEY_ACCOUNT); - - // state of oAuth2 components - mOAuth2StatusIcon = savedInstanceState.getInt(KEY_OAUTH2_STATUS_ICON); - mOAuth2StatusText = savedInstanceState.getInt(KEY_OAUTH2_STATUS_TEXT); - - /* Leave old OAuth flow - // 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(KEY_OAUTH2_CODE_RESULT)) { - codeResponseJson = new JSONObject(savedInstanceState.getString(KEY_OAUTH2_CODE_RESULT)); - } - } catch (JSONException e) { - Log.e(TAG, "onCreate->JSONException: " + e.toString()); - }*/ - // END of getting the state of oAuth2 components. - - } - - - /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request - * is caught here. - * - * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the - * AndroidManifest.xml file. - */ - @Override - protected void onNewIntent (Intent intent) { - Log.d(TAG, "onNewIntent()"); - Uri data = intent.getData(); - if (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI)) { - mNewCapturedUriFromOAuth2Redirection = data; - } - } - - - /** - * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and - * deferred in {@link #onNewIntent(Intent)}, is processed here. - */ - @Override - protected void onResume() { - super.onResume(); - // the state of mOAuth2Check is automatically recovered between configuration changes, but not before onCreate() finishes; so keep the next lines here - changeViewByOAuth2Check(mOAuth2Check.isChecked()); - if (mAction == ACTION_UPDATE_TOKEN && mJustCreated) { - if (mOAuth2Check.isChecked()) - Toast.makeText(this, R.string.auth_expired_oauth_token_toast, Toast.LENGTH_LONG).show(); - else - Toast.makeText(this, R.string.auth_expired_basic_auth_toast, Toast.LENGTH_LONG).show(); - } - - - /* LEAVE OLD OAUTH FLOW ; - // (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 (mNewCapturedUriFromOAuth2Redirection != null) { - getOAuth2AccessTokenFromCapturedRedirection(); - } - - mJustCreated = false; - } - - - @Override protected void onDestroy() { - super.onDestroy(); - - /* LEAVE OLD OAUTH FLOW - // 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; - }*/ - - } - - - /** - * Parses the redirection with the response to the GET AUTHORIZATION request to the - * oAuth server and requests for the access token (GET ACCESS TOKEN) - */ - private void getOAuth2AccessTokenFromCapturedRedirection() { - /// Parse data from OAuth redirection - String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); - mNewCapturedUriFromOAuth2Redirection = null; - - /// Showing the dialog with instructions for the user. - showDialog(DIALOG_OAUTH2_LOGIN_PROGRESS); - - /// GET ACCESS TOKEN to the oAuth server - RemoteOperation operation = new OAuth2GetAccessToken(queryParameters); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(getString(R.string.oauth_url_endpoint_access)), getApplicationContext()); - operation.execute(client, this, mHandler); - } - - - - /** - * Handles the change of focus on the text inputs for the server URL and the password - */ - public void onFocusChange(View view, boolean hasFocus) { - if (view.getId() == R.id.hostUrlInput) { - onUrlInputFocusChanged((TextView) view, hasFocus); - - } else if (view.getId() == R.id.account_password) { - onPasswordFocusChanged((TextView) view, hasFocus); - } - } - - - /** - * Handles changes in focus on the text input for the server URL. - * - * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to - * other field. The operation to check the existence of the server in the entered URL is - * started. - * - * When hasFocus: user 'comes back' to write again the server URL. - * - * @param hostInput TextView with the URL input field receiving the change of focus. - * @param hasFocus 'True' if focus is received, 'false' if is lost - */ - private void onUrlInputFocusChanged(TextView hostInput, boolean hasFocus) { - if (!hasFocus) { - checkOcServer(); - - } else { - // avoids that the 'connect' button can be clicked if the test was previously passed - mOkButton.setEnabled(false); - } - } - - - private void checkOcServer() { - String uri = mHostUrlInput.getText().toString().trim(); - if (uri.length() != 0) { - mStatusText = R.string.auth_testing_connection; - mStatusIcon = R.drawable.progress_small; - updateConnStatus(); - /** TODO cancel previous connection check if the user tries to ammend a wrong URL - if(mConnChkOperation != null) { - mConnChkOperation.cancel(); - } */ - mOcServerChkOperation = new OwnCloudServerCheckOperation(uri, this); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); - mHostBaseUrl = ""; - mDiscoveredVersion = null; - mOperationThread = mOcServerChkOperation.execute(client, this, mHandler); - } else { - mRefreshButton.setVisibility(View.INVISIBLE); - mStatusText = 0; - mStatusIcon = 0; - updateConnStatus(); - } - } - - - /** - * Handles changes in focus on the text input for the password (basic authorization). - * - * When (hasFocus), the button to toggle password visibility is shown. - * - * When (!hasFocus), the button is made invisible and the password is hidden. - * - * @param passwordInput TextView with the password input field receiving the change of focus. - * @param hasFocus 'True' if focus is received, 'false' if is lost - */ - private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { - if (hasFocus) { - mViewPasswordButton.setVisibility(View.VISIBLE); - } else { - int input_type = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; - passwordInput.setInputType(input_type); - mViewPasswordButton.setVisibility(View.INVISIBLE); - } - } - - - - /** - * Cancels the authenticator activity - * - * IMPORTANT ENTRY POINT 3: Never underestimate the importance of cancellation - * - * This method is bound in the layout/acceoun_setup.xml resource file. - * - * @param view Cancel button - */ - public void onCancelClick(View view) { - setResult(RESULT_CANCELED); // TODO review how is this related to AccountAuthenticator - finish(); - } - - - - /** - * Checks the credentials of the user in the root of the ownCloud server - * before creating a new local account. - * - * For basic authorization, a check of existence of the root folder is - * performed. - * - * For OAuth, starts the flow to get an access token; the credentials test - * is postponed until it is available. - * - * IMPORTANT ENTRY POINT 4 - * - * @param view OK button - */ - public void onOkClick(View view) { - // this check should be unnecessary - if (mDiscoveredVersion == null || !mDiscoveredVersion.isVersionValid() || mHostBaseUrl == null || mHostBaseUrl.length() == 0) { - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_wtf_reenter_URL; - updateConnStatus(); - mOkButton.setEnabled(false); - Log.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); - return; - } - - if (mOAuth2Check.isChecked()) { - startOauthorization(); - - } else { - checkBasicAuthorization(); - } - } - - - /** - * Tests the credentials entered by the user performing a check of existence on - * the root folder of the ownCloud server. - */ - private void checkBasicAuthorization() { - /// get the path to the root folder through WebDAV from the version server - String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false); - - /// get basic credentials entered by user - String username = mUsernameInput.getText().toString(); - String password = mPasswordInput.getText().toString(); - - /// be gentle with the user - showDialog(DIALOG_LOGIN_PROGRESS); - - /// test credentials accessing the root folder - mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); - client.setBasicCredentials(username, password); - mOperationThread = mAuthCheckOperation.execute(client, this, mHandler); - } - - - /** - * Starts the OAuth 'grant type' flow to get an access token, with - * a GET AUTHORIZATION request to the BUILT-IN authorization server. - */ - private void startOauthorization() { - // be gentle with the user - mStatusIcon = R.drawable.progress_small; - mStatusText = R.string.oauth_login_connection; - updateAuthStatus(); - - // GET AUTHORIZATION request - /* - mOAuth2GetCodeRunnable = new OAuth2GetAuthorizationToken(, this); - mOAuth2GetCodeRunnable.setListener(this, mHandler); - mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable); - mOAuth2GetCodeThread.start(); - */ - - //if (mGrantType.equals(OAuth2Context.OAUTH2_AUTH_CODE_GRANT_TYPE)) { - Uri uri = Uri.parse(getString(R.string.oauth_url_endpoint_auth)); - Uri.Builder uriBuilder = uri.buildUpon(); - uriBuilder.appendQueryParameter(OAuth2Context.CODE_RESPONSE_TYPE, OAuth2Context.OAUTH2_CODE_RESPONSE_TYPE); - uriBuilder.appendQueryParameter(OAuth2Context.CODE_REDIRECT_URI, OAuth2Context.MY_REDIRECT_URI); - uriBuilder.appendQueryParameter(OAuth2Context.CODE_CLIENT_ID, OAuth2Context.OAUTH2_F_CLIENT_ID); - uriBuilder.appendQueryParameter(OAuth2Context.CODE_SCOPE, OAuth2Context.OAUTH2_F_SCOPE); - //uriBuilder.appendQueryParameter(OAuth2Context.CODE_STATE, whateverwewant); - uri = uriBuilder.build(); - Log.d(TAG, "Starting browser to view " + uri.toString()); - Intent i = new Intent(Intent.ACTION_VIEW, uri); - startActivity(i); - //} - } - - - /** - * Callback method invoked when a RemoteOperation executed by this Activity finishes. - * - * Dispatches the operation flow to the right method. - */ - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - - if (operation instanceof OwnCloudServerCheckOperation) { - onOcServerCheckFinish((OwnCloudServerCheckOperation) operation, result); - - } else if (operation instanceof OAuth2GetAccessToken) { - onGetOAuthAccessTokenFinish((OAuth2GetAccessToken)operation, result); - - } else if (operation instanceof ExistenceCheckOperation) { - onAuthorizationCheckFinish((ExistenceCheckOperation)operation, result); - - } - } - - - /** - * Processes the result of the server check performed when the user finishes the enter of the - * server URL. - * - * @param operation Server check performed. - * @param result Result of the check. - */ - private void onOcServerCheckFinish(OwnCloudServerCheckOperation operation, RemoteOperationResult result) { - /// update status icon and text - updateStatusIconAndText(result); - updateConnStatus(); - - /// save result state - mStatusCorrect = result.isSuccess(); - mIsSslConn = (result.getCode() == ResultCode.OK_SSL); - - /// very special case (TODO: move to a common place for all the remote operations) - if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - } - - /// update the visibility of the 'retry connection' button - if (!mStatusCorrect) - mRefreshButton.setVisibility(View.VISIBLE); - else - mRefreshButton.setVisibility(View.INVISIBLE); - - /// retrieve discovered version and normalize server URL - mDiscoveredVersion = operation.getDiscoveredVersion(); - mHostBaseUrl = mHostUrlInput.getText().toString().trim(); - if (!mHostBaseUrl.toLowerCase().startsWith("http://") && - !mHostBaseUrl.toLowerCase().startsWith("https://")) { - - if (mIsSslConn) { - mHostBaseUrl = "https://" + mHostBaseUrl; - } else { - mHostBaseUrl = "http://" + mHostBaseUrl; - } - - } - if (mHostBaseUrl.endsWith("/")) - mHostBaseUrl = mHostBaseUrl.substring(0, mHostBaseUrl.length() - 1); - - /// allow or not the user try to access the server - mOkButton.setEnabled(mStatusCorrect); - } - - - /** - * Chooses the right icon and text to show to the user for the received operation result. - * - * @param result Result of a remote operation performed in this activity - */ - private void updateStatusIconAndText(RemoteOperationResult result) { - mStatusText = mStatusIcon = 0; - - switch (result.getCode()) { - case OK_SSL: - mStatusIcon = android.R.drawable.ic_secure; - mStatusText = R.string.auth_secure_connection; - break; - - case OK_NO_SSL: - case OK: - if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { - mStatusText = R.string.auth_connection_established; - mStatusIcon = R.drawable.ic_ok; - } else { - mStatusText = R.string.auth_nossl_plain_ok_title; - mStatusIcon = android.R.drawable.ic_partial_secure; - } - break; - - case SSL_RECOVERABLE_PEER_UNVERIFIED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_unverified_server_title; - break; - - case BAD_OC_VERSION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_bad_oc_version_title; - break; - case WRONG_CONNECTION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_wrong_connection_title; - break; - case TIMEOUT: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_timeout_title; - break; - case INCORRECT_ADDRESS: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_address_title; - break; - - case SSL_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_general_error_title; - break; - - case UNAUTHORIZED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unauthorized; - break; - case HOST_NOT_AVAILABLE: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_host_title; - break; - case NO_NETWORK_CONNECTION: - mStatusIcon = R.drawable.no_network; - mStatusText = R.string.auth_no_net_conn_title; - break; - case INSTANCE_NOT_CONFIGURED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_not_configured_title; - break; - case FILE_NOT_FOUND: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_path_title; - break; - case OAUTH2_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_oauth_error; - break; - case OAUTH2_ERROR_ACCESS_DENIED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_oauth_error_access_denied; - break; - case UNHANDLED_HTTP_CODE: - case UNKNOWN_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_error_title; - break; - - default: - break; - } - } - - - /** - * Processes the result of the request for and access token send - * to an OAuth authorization server. - * - * @param operation Operation performed requesting the access token. - * @param result Result of the operation. - */ - private void onGetOAuthAccessTokenFinish(OAuth2GetAccessToken operation, RemoteOperationResult result) { - try { - dismissDialog(DIALOG_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 - } - - String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, true); - if (result.isSuccess() && webdav_path != null) { - /// be gentle with the user - showDialog(DIALOG_LOGIN_PROGRESS); - - /// time to test the retrieved access token on the ownCloud server - mOAuthAccessToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Context.KEY_ACCESS_TOKEN); - Log.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken); - mAuthCheckOperation = new ExistenceCheckOperation("", this, false); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this); - client.setBearerCredentials(mOAuthAccessToken); - mAuthCheckOperation.execute(client, this, mHandler); - - } else { - updateStatusIconAndText(result); - updateAuthStatus(); - Log.d(TAG, "Access failed: " + result.getLogMessage()); - } - } - - - /** - * Processes the result of the access check performed to try the user credentials. - * - * Creates a new account through the AccountManager. - * - * @param operation Access check performed. - * @param result Result of the operation. - */ - private void onAuthorizationCheckFinish(ExistenceCheckOperation operation, RemoteOperationResult result) { - 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()) { - Log.d(TAG, "Successful access - time to save the account"); - - if (mAction == ACTION_CREATE) { - createAccount(); - - } else { - updateToken(); - } - - finish(); - - } else { - updateStatusIconAndText(result); - updateAuthStatus(); - Log.d(TAG, "Access failed: " + result.getLogMessage()); - } - } - - - /** - * Sets the proper response to get that the Account Authenticator that started this activity saves - * a new authorization token for mAccount. - */ - private void updateToken() { - Bundle response = new Bundle(); - response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); - response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); - boolean isOAuth = mOAuth2Check.isChecked(); - if (isOAuth) { - response.putString(AccountManager.KEY_AUTHTOKEN, mOAuthAccessToken); - // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention - mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); - } else { - response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); - mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); - } - setAccountAuthenticatorResult(response); - } - - - /** - * Creates a new account through the Account Authenticator that started this activity. - * - * This makes the account permanent. - * - * TODO Decide how to name the OAuth accounts - * TODO Minimize the direct interactions with the account manager; seems that not all the operations - * in the current code are really necessary, provided that right extras are returned to the Account - * Authenticator through setAccountAuthenticatorResult - */ - private void createAccount() { - /// create and save new ownCloud account - boolean isOAuth = mOAuth2Check.isChecked(); - - Uri uri = Uri.parse(mHostBaseUrl); - String username = mUsernameInput.getText().toString().trim(); - if (isOAuth) { - username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); // TODO change this to something readable - } - String accountName = username + "@" + uri.getHost(); - if (uri.getPort() >= 0) { - accountName += ":" + uri.getPort(); - } - mAccount = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE); - if (isOAuth) { - mAccountMgr.addAccountExplicitly(mAccount, "", null); // with our implementation, the password is never input in the app - } else { - mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); - } - - /// add the new account as default in preferences, if there is none already - Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); - if (defaultAccount == null) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(this).edit(); - editor.putString("select_oc_account", accountName); - editor.commit(); - } - - /// prepare result to return to the Authenticator - // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); - if (!isOAuth) - intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); // TODO check this; not sure it's right; maybe - intent.putExtra(AccountManager.KEY_USERDATA, username); - if (isOAuth) { - mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken); - } - /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString()); - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl); - if (isOAuth) - mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); // TODO this flag should be unnecessary - - setAccountAuthenticatorResult(intent.getExtras()); - setResult(RESULT_OK, intent); - - /// immediately request for the synchronization of the new account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTHORITY, bundle); - } - - - /** - * {@inheritDoc} - * - * Necessary to update the contents of the SSL Dialog - * - * TODO move to some common place for all possible untrusted SSL failures - */ - @Override - protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { - switch (id) { - case DIALOG_LOGIN_PROGRESS: - case DIALOG_CERT_NOT_SAVED: - case DIALOG_OAUTH2_LOGIN_PROGRESS: - break; - case DIALOG_SSL_VALIDATOR: { - ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); - break; - } - default: - Log.e(TAG, "Incorrect dialog called with id = " + id); - } - } - - - /** - * {@inheritDoc} - */ - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DIALOG_LOGIN_PROGRESS: { - /// simple progress dialog - ProgressDialog working_dialog = new ProgressDialog(this); - working_dialog.setMessage(getResources().getString(R.string.auth_trying_to_login)); - working_dialog.setIndeterminate(true); - working_dialog.setCancelable(true); - working_dialog - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - /// TODO study if this is enough - Log.i(TAG, "Login canceled"); - if (mOperationThread != null) { - mOperationThread.interrupt(); - finish(); - } - } - }); - dialog = working_dialog; - break; - } - case DIALOG_OAUTH2_LOGIN_PROGRESS: { - /// oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser. - OLD! - // TODO optimize this dialog - ProgressDialog working_dialog = new ProgressDialog(this); - /* Leave the old OAuth flow - try { - if (mCodeResponseJson != null && mCodeResponseJson.has(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL)) { - working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message), - mCodeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL), - mCodeResponseJson.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(); - }*/ - finish(); - } - }); - dialog = working_dialog; - break; - } - case DIALOG_SSL_VALIDATOR: { - /// TODO start to use new dialog interface, at least for this (it is a FragmentDialog already) - dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); - break; - } - case DIALOG_CERT_NOT_SAVED: { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); - builder.setCancelable(false); - builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }; - }); - dialog = builder.create(); - break; - } - default: - Log.e(TAG, "Incorrect dialog called with id = " + id); - } - return dialog; - } - - - /** - * Starts and activity to open the 'new account' page in the ownCloud web site - * - * @param view 'Account register' button - */ - public void onRegisterClick(View view) { - Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register))); - setResult(RESULT_CANCELED); - startActivity(register); - } - - - /** - * Updates the content and visibility state of the icon and text associated - * to the last check on the ownCloud server. - */ - private void updateConnStatus() { - ImageView iv = (ImageView) findViewById(R.id.action_indicator); - TextView tv = (TextView) findViewById(R.id.status_text); - - if (mStatusIcon == 0 && mStatusText == 0) { - iv.setVisibility(View.INVISIBLE); - tv.setVisibility(View.INVISIBLE); - } else { - iv.setImageResource(mStatusIcon); - tv.setText(mStatusText); - iv.setVisibility(View.VISIBLE); - tv.setVisibility(View.VISIBLE); - } - } - - - /** - * Updates the content and visibility state of the icon and text associated - * to the interactions with the OAuth authorization server. - */ - private void updateAuthStatus() { - /*ImageView iv = (ImageView) findViewById(R.id.auth_status_icon); - TextView tv = (TextView) findViewById(R.id.auth_status_text);*/ - - if (mStatusIcon == 0 && mStatusText == 0) { - mAuthStatusLayout.setVisibility(View.INVISIBLE); - /*iv.setVisibility(View.INVISIBLE); - tv.setVisibility(View.INVISIBLE);*/ - } else { - mAuthStatusLayout.setText(mStatusText); - mAuthStatusLayout.setCompoundDrawablesWithIntrinsicBounds(mStatusIcon, 0, 0, 0); - /*iv.setImageResource(mStatusIcon); - tv.setText(mStatusText); - /*iv.setVisibility(View.VISIBLE); - tv.setVisibility(View.VISIBLE);^*/ - mAuthStatusLayout.setVisibility(View.VISIBLE); - } - } - - - /** - * Called when the refresh button in the input field for ownCloud host is clicked. - * - * Performs a new check on the URL in the input field. - * - * @param view Refresh 'button' - */ - public void onRefreshClick(View view) { - onFocusChange(mRefreshButton, false); - } - - - /** - * Called when the eye icon in the password field is clicked. - * - * Toggles the visibility of the password in the field. - * - * @param view 'View password' 'button' - */ - public void onViewPasswordClick(View view) { - int selectionStart = mPasswordInput.getSelectionStart(); - int selectionEnd = mPasswordInput.getSelectionEnd(); - int input_type = mPasswordInput.getInputType(); - if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { - input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD; - } else { - input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - } - mPasswordInput.setInputType(input_type); - mPasswordInput.setSelection(selectionStart, selectionEnd); - } - - - /** - * Called when the checkbox for OAuth authorization is clicked. - * - * Hides or shows the input fields for user & password. - * - * @param view 'View password' 'button' - */ - public void onCheckClick(View view) { - CheckBox oAuth2Check = (CheckBox)view; - changeViewByOAuth2Check(oAuth2Check.isChecked()); - - } - - /** - * Changes the visibility of input elements depending upon the kind of authorization - * chosen by the user: basic or OAuth - * - * @param checked 'True' when OAuth is selected. - */ - public void changeViewByOAuth2Check(Boolean checked) { - - if (checked) { - mOAuthAuthEndpointText.setVisibility(View.VISIBLE); - mOAuthTokenEndpointText.setVisibility(View.VISIBLE); - mUsernameInput.setVisibility(View.GONE); - mPasswordInput.setVisibility(View.GONE); - mViewPasswordButton.setVisibility(View.GONE); - } else { - mOAuthAuthEndpointText.setVisibility(View.GONE); - mOAuthTokenEndpointText.setVisibility(View.GONE); - mUsernameInput.setVisibility(View.VISIBLE); - mPasswordInput.setVisibility(View.VISIBLE); - mViewPasswordButton.setVisibility(View.INVISIBLE); - } - - } - - /* Leave the old OAuth flow - // 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)) { - mCodeResponseJson = responseJson; - if (mCodeResponseJson != null) { - getOAuth2AccessTokenFromJsonResponse(); - } // else - nothing to do here - wait for callback !!! - - } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) { - updateOAuth2IconAndText(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->" + mCodeResponseJson.toString()); - - try { - // We get data that we must show to the user or we will use internally. - verificationUrl = mCodeResponseJson.getString(OAuth2GetAuthorizationToken.CODE_VERIFICATION_URL); - userCode = mCodeResponseJson.getString(OAuth2GetAuthorizationToken.CODE_USER_CODE); - expiresIn = mCodeResponseJson.getInt(OAuth2GetAuthorizationToken.CODE_EXPIRES_IN); - - // And we get data that we must use to get a token. - deviceCode = mCodeResponseJson.getString(OAuth2GetAuthorizationToken.CODE_DEVICE_CODE); - interval = mCodeResponseJson.getInt(OAuth2GetAuthorizationToken.CODE_INTERVAL); - - } catch (JSONException e) { - Log.e(TAG, "Exception accesing data in Json object" + e.toString()); - } - - // Updating status widget to OK. - updateOAuth2IconAndText(R.drawable.ic_ok, R.string.auth_connection_established); - - // Showing the dialog with instructions for the user. - showDialog(DIALOG_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); - } - - } - */ - - /* Leave the old OAuth flow - // 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(DIALOG_OAUTH2_LOGIN_PROGRESS); - - } - } - */ - - - /** - * Called from SslValidatorDialog when a new server certificate was correctly saved. - */ - public void onSavedCertificate() { - mOperationThread = mOcServerChkOperation.retry(this, mHandler); - } - - /** - * Called from SslValidatorDialog when a new server certificate could not be saved - * when the user requested it. - */ - @Override - 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 eb218d08..c4cbe978 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -66,7 +66,7 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; diff --git a/src/com/owncloud/android/ui/activity/LandingActivity.java b/src/com/owncloud/android/ui/activity/LandingActivity.java index e63a0fe0..e3937be1 100644 --- a/src/com/owncloud/android/ui/activity/LandingActivity.java +++ b/src/com/owncloud/android/ui/activity/LandingActivity.java @@ -18,7 +18,7 @@ package com.owncloud.android.ui.activity; import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.ui.adapter.LandingScreenAdapter; import android.accounts.Account; diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 3668ff38..4be1fbbf 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -56,7 +56,7 @@ import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; import com.owncloud.android.DisplayUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader;