X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/4c50eb4d2dd1fba177d743315ae52223430db8cb..7d84fd0c9f15227bc65a2ae00a74e1cfd8f8d33b:/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java index 52714b6a..8dce3bc2 100644 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java @@ -1,5 +1,6 @@ /* 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 @@ -18,21 +19,20 @@ package com.owncloud.android.ui.activity; -import java.net.MalformedURLException; -import java.net.URL; - import com.owncloud.android.AccountUtils; import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.authenticator.AuthenticationRunnable; -import com.owncloud.android.authenticator.OnAuthenticationResultListener; -import com.owncloud.android.authenticator.OnConnectCheckListener; +import com.owncloud.android.authenticator.oauth2.OAuth2Context; import com.owncloud.android.ui.dialog.SslValidatorDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.utils.OwnCloudVersion; import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.ConnectionCheckOperation; +import com.owncloud.android.operations.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; @@ -51,11 +51,15 @@ import android.preference.PreferenceManager; import android.text.InputType; import android.util.Log; import android.view.View; -import android.view.View.OnClickListener; 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; @@ -64,296 +68,610 @@ 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 OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, - OnFocusChangeListener, OnClickListener { + 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; - private static final String TAG = "AuthActivity"; + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_UPDATE_TOKEN = 1; - private Thread mAuthThread; - private AuthenticationRunnable mAuthRunnable; - //private ConnectionCheckerRunnable mConnChkRunnable = null; - private ConnectionCheckOperation mConnChkRunnable; - private final Handler mHandler = new Handler(); - private String mBaseUrl; - private static final String STATUS_TEXT = "STATUS_TEXT"; - private static final String STATUS_ICON = "STATUS_ICON"; - private static final String STATUS_CORRECT = "STATUS_CORRECT"; - private static final String IS_SSL_CONN = "IS_SSL_CONN"; + private 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; - public static final String PARAM_USERNAME = "param_Username"; - public static final String PARAM_HOSTNAME = "param_Hostname"; - + //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); - ImageView iv = (ImageView) findViewById(R.id.refreshButton); - ImageView iv2 = (ImageView) findViewById(R.id.viewPassword); - TextView tv = (TextView) findViewById(R.id.host_URL); - TextView tv2 = (TextView) findViewById(R.id.account_password); - - if (savedInstanceState != null) { - mStatusIcon = savedInstanceState.getInt(STATUS_ICON); - mStatusText = savedInstanceState.getInt(STATUS_TEXT); - mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT); - mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN); - setResultIconAndText(mStatusIcon, mStatusText); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); - if (!mStatusCorrect) - iv.setVisibility(View.VISIBLE); - else - iv.setVisibility(View.INVISIBLE); + 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); + - } else { + /// 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(); } - iv.setOnClickListener(this); - iv2.setOnClickListener(this); - tv.setOnFocusChangeListener(this); - tv2.setOnFocusChangeListener(this); + + 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) { - outState.putInt(STATUS_ICON, mStatusIcon); - outState.putInt(STATUS_TEXT, mStatusText); - outState.putBoolean(STATUS_CORRECT, mStatusCorrect); 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 Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DIALOG_LOGIN_PROGRESS: { - 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) { - Log.i(TAG, "Login canceled"); - if (mAuthThread != null) { - mAuthThread.interrupt(); - finish(); - } - } - }); - dialog = working_dialog; - break; + 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; } - case DIALOG_SSL_VALIDATOR: { - 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; } + + /** + * 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 onPrepareDialog(int id, Dialog dialog, Bundle args) { - switch (id) { - case DIALOG_LOGIN_PROGRESS: - case DIALOG_CERT_NOT_SAVED: - break; - case DIALOG_SSL_VALIDATOR: { - ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); - break; + 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(); } - default: - Log.e(TAG, "Incorrect dialog called with id = " + id); + + + /* 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(); - public void onAuthenticationResult(boolean success, String message) { - if (success) { - TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password); + /* 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; + }*/ - URL url; - try { - url = new URL(message); - } catch (MalformedURLException e) { - // should never happen - Log.e(getClass().getName(), "Malformed URL: " + message); - return; - } + } + + + /** + * 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); - String username = username_text.getText().toString().trim(); - String accountName = username + "@" + url.getHost(); - if (url.getPort() >= 0) { - accountName += ":" + url.getPort(); - } - Account account = new Account(accountName, - AccountAuthenticator.ACCOUNT_TYPE); - AccountManager accManager = AccountManager.get(this); - accManager.addAccountExplicitly(account, password_text.getText() - .toString(), null); - - // Add this account as default in the 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(); - } + /// 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); + } + } + - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, - AccountAuthenticator.ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); - intent.putExtra(AccountManager.KEY_AUTHTOKEN, - AccountAuthenticator.ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_USERDATA, username); - - accManager.setUserData(account, - AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable - .getDiscoveredVersion().toString()); + /** + * 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(); - accManager.setUserData(account, - AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl); - - setAccountAuthenticatorResult(intent.getExtras()); - setResult(RESULT_OK, intent); - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI, - // bundle); - ContentResolver.requestSync(account, "org.owncloud", bundle); - - /* - * if - * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion - * .owncloud_v2) >= 0) { Intent i = new Intent(this, - * ExtensionsAvailableActivity.class); startActivity(i); } - */ + } else { + // avoids that the 'connect' button can be clicked if the test was previously passed + mOkButton.setEnabled(false); + } + } - finish(); + + 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 { - 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 - } - TextView tv = (TextView) findViewById(R.id.account_username); - tv.setError(message); + 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); + 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) { - String prefix = ""; - String url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim(); - if (mIsSslConn) { - prefix = "https://"; - } else { - prefix = "http://"; + // 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 (url.toLowerCase().startsWith("http://") - || url.toLowerCase().startsWith("https://")) { - prefix = ""; + + if (mOAuth2Check.isChecked()) { + startOauthorization(); + + } else { + checkBasicAuthorization(); } - continueConnection(prefix); } - public void onRegisterClick(View view) { - Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse("https://owncloud.com/mobile/new")); - setResult(RESULT_CANCELED); - startActivity(register); + + /** + * 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); } - private void continueConnection(String prefix) { - String url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim(); - String username = ((TextView) findViewById(R.id.account_username)) - .getText().toString(); - String password = ((TextView) findViewById(R.id.account_password)) - .getText().toString(); - if (url.endsWith("/")) - url = url.substring(0, url.length() - 1); - - URL uri = null; - String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable - .getDiscoveredVersion()); - - if (webdav_path == null) { - onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title)); - return; - } + + /** + * 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(); - try { - mBaseUrl = prefix + url; - String url_str = prefix + url + webdav_path; - uri = new URL(url_str); - } catch (MalformedURLException e) { - // should never happen - onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title)); - return; + // 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); + } + } + - showDialog(DIALOG_LOGIN_PROGRESS); - mAuthRunnable = new AuthenticationRunnable(uri, username, password, this); - mAuthRunnable.setOnAuthenticationResultListener(this, mHandler); - mAuthThread = new Thread(mAuthRunnable); - mAuthThread.start(); + /** + * 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); } - @Override - public void onConnectionCheckResult(ResultType type) { + + /** + * 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; - mStatusCorrect = false; - String t_url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim().toLowerCase(); - switch (type) { + switch (result.getCode()) { case OK_SSL: - mIsSslConn = true; mStatusIcon = android.R.drawable.ic_secure; mStatusText = R.string.auth_secure_connection; - mStatusCorrect = true; break; + case OK_NO_SSL: - mIsSslConn = false; - mStatusCorrect = true; - if (t_url.startsWith("http://") ) { + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { mStatusText = R.string.auth_connection_established; mStatusIcon = R.drawable.ic_ok; } else { @@ -361,6 +679,12 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity 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; @@ -377,13 +701,15 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity mStatusIcon = R.drawable.common_error; mStatusText = R.string.auth_incorrect_address_title; break; - case SSL_UNVERIFIED_SERVER: + + case SSL_ERROR: mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_unverified_server_title; + mStatusText = R.string.auth_ssl_general_error_title; break; - case SSL_INIT_ERROR: + + case UNAUTHORIZED: mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_general_error_title; + mStatusText = R.string.auth_unauthorized; break; case HOST_NOT_AVAILABLE: mStatusIcon = R.drawable.common_error; @@ -397,198 +723,533 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity 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; - case FILE_NOT_FOUND: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_path_title; + + 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 connection checker result type: " + type); + Log.e(TAG, "Incorrect dialog called with id = " + id); } - setResultIconAndText(mStatusIcon, mStatusText); - if (!mStatusCorrect) - findViewById(R.id.refreshButton).setVisibility(View.VISIBLE); - else - findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); } + + /** + * {@inheritDoc} + */ @Override - public void onFocusChange(View view, boolean hasFocus) { - if (view.getId() == R.id.host_URL) { - if (!hasFocus) { - TextView tv = ((TextView) findViewById(R.id.host_URL)); - String uri = tv.getText().toString().trim(); - if (uri.length() != 0) { - setResultIconAndText(R.drawable.progress_small, - R.string.auth_testing_connection); - //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this); - mConnChkRunnable = new ConnectionCheckOperation(uri, this); - //mConnChkRunnable.setListener(this, mHandler); - //mAuthThread = new Thread(mConnChkRunnable); - //mAuthThread.start(); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); - mAuthThread = mConnChkRunnable.execute(client, this, mHandler); - } else { - findViewById(R.id.refreshButton).setVisibility( - View.INVISIBLE); - setResultIconAndText(0, 0); + 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(); } - } else { - // avoids that the 'connect' button can be clicked if the test was previously passed - findViewById(R.id.buttonOK).setEnabled(false); - } - } else if (view.getId() == R.id.account_password) { - ImageView iv = (ImageView) findViewById(R.id.viewPassword); - if (hasFocus) { - iv.setVisibility(View.VISIBLE); - } else { - TextView v = (TextView) findViewById(R.id.account_password); - int input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD; - v.setInputType(input_type); - iv.setVisibility(View.INVISIBLE); - } + }); + 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); } - private void setResultIconAndText(int drawable_id, int text_id) { + + /** + * 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 (drawable_id == 0 && text_id == 0) { + if (mStatusIcon == 0 && mStatusText == 0) { iv.setVisibility(View.INVISIBLE); tv.setVisibility(View.INVISIBLE); } else { - iv.setImageResource(drawable_id); - tv.setText(text_id); + 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 onClick(View v) { - if (v.getId() == R.id.refreshButton) { - onFocusChange(findViewById(R.id.host_URL), false); - } else if (v.getId() == R.id.viewPassword) { - TextView view = (TextView) findViewById(R.id.account_password); - int input_type = view.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; - } - view.setInputType(input_type); + 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); } } - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mConnChkRunnable)) { - - mStatusText = mStatusIcon = 0; - mStatusCorrect = false; - String t_url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim().toLowerCase(); - - switch (result.getCode()) { - case OK_SSL: - mIsSslConn = true; - mStatusIcon = android.R.drawable.ic_secure; - mStatusText = R.string.auth_secure_connection; - mStatusCorrect = true; - break; - - case OK_NO_SSL: - case OK: - mIsSslConn = false; - mStatusCorrect = true; - if (t_url.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 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_RECOVERABLE_PEER_UNVERIFIED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_unverified_server_title; - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - break; - - case SSL_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_general_error_title; - 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 UNHANDLED_HTTP_CODE: - case UNKNOWN_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_error_title; - break; - default: - Log.e(TAG, "Incorrect connection checker result type: " + result.getHttpCode()); - } - setResultIconAndText(mStatusIcon, mStatusText); - if (!mStatusCorrect) - findViewById(R.id.refreshButton).setVisibility(View.VISIBLE); - else - findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); - } - } - - + // 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() { - mAuthThread = mConnChkRunnable.retry(this, mHandler); + 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); } - + }