From: David A. Velasco Date: Wed, 31 Oct 2012 17:59:43 +0000 (+0100) Subject: Login page updated to get OAuth2 access token X-Git-Tag: oc-android-1.4.3~29^2~27 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/cab55a8edd27a9b470e0a0b97e228ba3852a65f1?ds=inline;hp=--cc Login page updated to get OAuth2 access token --- cab55a8edd27a9b470e0a0b97e228ba3852a65f1 diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0c15b2d8..79397e17 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -150,7 +150,11 @@ - + + + + + diff --git a/res/layout-land/account_setup.xml b/res/layout-land/account_setup.xml index 34ac31da..d61a830b 100644 --- a/res/layout-land/account_setup.xml +++ b/res/layout-land/account_setup.xml @@ -100,6 +100,15 @@ android:visibility="invisible" /> + + + + + + + + + + + + + + - + + + + + + + + + + + + + "Unexpected problem ; please, try other app to select the file" No file was selected + oAuth2 URL + Login with oAuth2. + Connecting to oAuth2 server… + Please, open a web browser and go to:\n%1$s.\nValidate this code there:\n%2$s + Connection to this URL not available. + Warning The identity of the site could not be verified - The server certificate is not trusted diff --git a/res/values/styles.xml b/res/values/styles.xml index 666bcee2..6cbed998 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -83,6 +83,10 @@ true + + #777777 #000000 diff --git a/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java b/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java new file mode 100644 index 00000000..03189fe2 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OAuth2Context.java @@ -0,0 +1,20 @@ +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_DEVICE_CLIENT_ID = "0000000000000.apps.googleusercontent.com"; + public static final String OAUTH2_DEVICE_CLIENT_SECRET = "XXXXXXXXXXXXXXXXXXXXXXXXX"; + public static final String OAUTH_DEVICE_GETTOKEN_GRANT_TYPE = "http://oauth.net/grant_type/device/1.0"; + public static final String OAUTH2_DEVICE_GETCODE_URL = "/o/oauth2/device/code"; + public static final String OAUTH2_DEVICE_GETTOKEN_URL = "/o/oauth2/token"; + public static final String OAUTH2_DEVICE_GETCODE_SCOPES = "https://www.googleapis.com/auth/userinfo.email"; +} diff --git a/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java b/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java new file mode 100644 index 00000000..a4a563c5 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OAuth2GetCodeRunnable.java @@ -0,0 +1,113 @@ +package com.owncloud.android.authenticator.oauth2; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.message.BasicNameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.os.Handler; +import android.util.Log; + +import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener.ResultOAuthType; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; + +/** + * Implements the communication with oAuth2 server to get User Code and other useful values. + * + * @author SolidGear S.L. + * + */ +public class OAuth2GetCodeRunnable implements Runnable { + + public static final String CODE_USER_CODE = "user_code"; + public static final String CODE_CLIENT_ID = "client_id"; + public static final String CODE_CLIENT_SCOPE = "scope"; + public static final String CODE_VERIFICATION_URL = "verification_url"; + public static final String CODE_EXPIRES_IN = "expires_in"; + public static final String CODE_DEVICE_CODE = "device_code"; + public static final String CODE_INTERVAL = "interval"; + + private static final String TAG = "OAuth2GetCodeRunnable"; + private OnOAuth2GetCodeResultListener mListener; + private String mUrl; + private Handler mHandler; + private Context mContext; + + public void setListener(OnOAuth2GetCodeResultListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public OAuth2GetCodeRunnable(String url, Context context) { + mListener = null; + mHandler = null; + mUrl = url; + mContext = context; + } + + @Override + public void run() { + ResultOAuthType mLatestResult; + String targetURI = null; + JSONObject codeResponseJson = null; + + if (!isOnline()) { + postResult(ResultOAuthType.NO_NETWORK_CONNECTION,null); + return; + } + + if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) { + mLatestResult = (mUrl.startsWith("https://"))? ResultOAuthType.OK_SSL : ResultOAuthType.OK_NO_SSL; + } else { + mUrl = "https://" + mUrl; + mLatestResult = ResultOAuthType.OK_SSL; + } + targetURI = mUrl + OAuth2Context.OAUTH2_DEVICE_GETCODE_URL; + + ConnectorOAuth2 connectorOAuth2 = new ConnectorOAuth2(targetURI); + + try { + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair(CODE_CLIENT_ID, OAuth2Context.OAUTH2_DEVICE_CLIENT_ID)); + nameValuePairs.add(new BasicNameValuePair(CODE_CLIENT_SCOPE,OAuth2Context.OAUTH2_DEVICE_GETCODE_SCOPES)); + UrlEncodedFormEntity params = new UrlEncodedFormEntity(nameValuePairs); + codeResponseJson = new JSONObject(connectorOAuth2.connPost(params)); + } catch (JSONException e) { + Log.e(TAG, "JSONException converting to Json: " + e.toString()); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "UnsupportedEncodingException encoding URL values: " + e.toString()); + } catch (Exception e) { + Log.e(TAG, "Exception : " + e.toString()); + } + + if (codeResponseJson == null) { + mLatestResult = ResultOAuthType.HOST_NOT_AVAILABLE; + } + + postResult(mLatestResult, codeResponseJson); + } + + private boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnectedOrConnecting(); + } + + private void postResult(final ResultOAuthType result,final JSONObject codeResponseJson) { + if (mHandler != null && mListener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onOAuth2GetCodeResult(result, codeResponseJson); + } + }); + } + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java b/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java new file mode 100644 index 00000000..463c95db --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/OnOAuth2GetCodeResultListener.java @@ -0,0 +1,19 @@ +package com.owncloud.android.authenticator.oauth2; + +import org.json.JSONObject; + +/** + * Listener that expects results from OAuth2GetCodeRunnable class. + * + * @author SolidGear S.L. + * + */ +public interface OnOAuth2GetCodeResultListener { + + enum ResultOAuthType { + OK_SSL, OK_NO_SSL, SSL_INIT_ERROR, HOST_NOT_AVAILABLE, TIMEOUT, NO_NETWORK_CONNECTION, INCORRECT_ADDRESS, INSTANCE_NOT_CONFIGURED, FILE_NOT_FOUND, UNKNOWN_ERROR, WRONG_CONNECTION, SSL_UNVERIFIED_SERVER, BAD_OC_VERSION + } + + public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject code); + +} diff --git a/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java b/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java new file mode 100644 index 00000000..789b5851 --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/connection/ConnectorOAuth2.java @@ -0,0 +1,113 @@ +package com.owncloud.android.authenticator.oauth2.connection; + +import org.apache.http.HttpResponse; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; + +import android.util.Log; + +/** + * Implements HTTP POST communications with an oAuth2 server. + * + * @author SolidGear S.L. + * + */ +public class ConnectorOAuth2 { + + private static final String TAG = "ConnectorOAuth2"; + /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */ + private static final int TRY_CONNECTION_TIMEOUT = 5000; + + private DefaultHttpClient httpClient; + private HttpContext localContext; + private String ConnectorOAuth2Url; + + public ConnectorOAuth2 (String destUrl) { + prepareConn(); + setConnectorOAuth2Url(destUrl); + } + + public ConnectorOAuth2 () { + prepareConn(); + } + + public String getConnectorOAuth2Url() { + return ConnectorOAuth2Url; + } + + public void setConnectorOAuth2Url(String connectorOAuth2Url) { + ConnectorOAuth2Url = connectorOAuth2Url; + } + + /** + * Starts the communication with the server. + * + * @param UrlEncodedFormEntity : parameters included in the POST call. + * @return String : data returned from the server in String format. + */ + + public String connPost(UrlEncodedFormEntity data) { + String dataOut = null; + HttpPost httpPost = null; + int responseCode = -1; + HttpResponse response = null; + + httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109); + + if (ConnectorOAuth2Url == null) { + Log.e(TAG, "connPost error: destination URI could not be null"); + return null; + } + + if (data == null){ + Log.e(TAG, "connPost error: data to send to URI " + ConnectorOAuth2Url + "could not be null"); + return null; + } + + httpPost = new HttpPost(ConnectorOAuth2Url); + + httpPost.setHeader("Accept","text/html,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); + httpPost.setEntity((UrlEncodedFormEntity) data); + + try { + response = httpClient.execute(httpPost,localContext); + + if (response == null) { + Log.e(TAG, "connPost error: response from uri " + ConnectorOAuth2Url + " is null"); + return null; + } + + responseCode = response.getStatusLine().getStatusCode(); + + if ((responseCode != 200)) { + Log.e(TAG, "connPost error: response from uri "+ ConnectorOAuth2Url + " returns status " + responseCode); + return null; + } + + dataOut = EntityUtils.toString(response.getEntity()); + + } catch (Exception e) { + Log.e(TAG, "connPost Exception: " + e); + } + + return dataOut; + } + + private void prepareConn () { + HttpParams localParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(localParams, TRY_CONNECTION_TIMEOUT); + HttpConnectionParams.setSoTimeout(localParams, TRY_CONNECTION_TIMEOUT); + httpClient = new DefaultHttpClient(localParams); + localContext = new BasicHttpContext(); + } +} diff --git a/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java b/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java new file mode 100644 index 00000000..584b20bd --- /dev/null +++ b/src/com/owncloud/android/authenticator/oauth2/services/OAuth2GetTokenService.java @@ -0,0 +1,201 @@ +package com.owncloud.android.authenticator.oauth2.services; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.message.BasicNameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import com.owncloud.android.authenticator.oauth2.OAuth2Context; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; + +/** + * Service class that implements the second communication with the oAuth2 server: + * pooling for the token in an interval. It send a broadcast with the results when are positive; + * otherwise, it continues asking to the server. + * + * @author Solid Gear S.L. + * + */ +public class OAuth2GetTokenService extends Service { + + public static final String TOKEN_RECEIVED_MESSAGE = "TOKEN_RECEIVED"; + public static final String TOKEN_RECEIVED_DATA = "TOKEN_DATA"; + public static final String TOKEN_BASE_URI = "baseURI"; + public static final String TOKEN_DEVICE_CODE = "device_code"; + public static final String TOKEN_INTERVAL = "interval"; + public static final String TOKEN_RECEIVED_ERROR = "error"; + public static final String TOKEN_RECEIVED_ERROR_AUTH_TOKEN = "authorization_pending"; + public static final String TOKEN_RECEIVED_ERROR_SLOW_DOWN = "slow_down"; + public static final String TOKEN_ACCESS_TOKEN = "access_token"; + public static final String TOKEN_TOKEN_TYPE = "token_type"; + public static final String TOKEN_EXPIRES_IN = "expires_in"; + public static final String TOKEN_REFRESH_TOKEN = "refresh_token"; + + private String requestDeviceCode; + private int requestInterval = -1; + private String requestBaseURI; + private ConnectorOAuth2 connectorOAuth2; + private static final String TAG = "OAuth2GetTokenService"; + private Timer timer = new Timer(); + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle param = intent.getExtras(); + + if (param != null) { + String mUrl = param.getString(TOKEN_BASE_URI); + if (!mUrl.startsWith("http://") || !mUrl.startsWith("https://")) { + requestBaseURI = "https://" + mUrl; + } + requestDeviceCode = param.getString(TOKEN_DEVICE_CODE); + requestInterval = param.getInt(TOKEN_INTERVAL); + + Log.d(TAG, "onBind -> baseURI=" + requestBaseURI); + Log.d(TAG, "onBind -> requestDeviceCode=" + requestDeviceCode); + Log.d(TAG, "onBind -> requestInterval=" + requestInterval); + } else { + Log.e(TAG, "onBind -> params could not be null"); + } + startService(); + return Service.START_NOT_STICKY; + } + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + shutdownService(); + } + + private void startService() { + final UrlEncodedFormEntity params = prepareComm(); + timer.scheduleAtFixedRate( + new TimerTask() { + public void run() { + requestToken(params); + } + }, 0, requestInterval * 1000); + Log.d(TAG, "startService -> Timer started"); + } + + private void shutdownService() { + if (timer != null) timer.cancel(); + Log.d(TAG, "shutdownService -> Timer stopped"); + } + + + private UrlEncodedFormEntity prepareComm() { + + UrlEncodedFormEntity params = null; + connectorOAuth2 = new ConnectorOAuth2(); + + if (requestBaseURI == null || requestBaseURI.trim().equals("")) { + Log.e(TAG, "run -> request URI could not be null"); + postResult(null); + } + + if (requestInterval == -1) { + Log.e(TAG, "run -> request Interval must have valid positive value"); + postResult(null); + } + + if (requestDeviceCode == null || requestDeviceCode.trim().equals("")) { + Log.e(TAG, "run -> request DeviceCode could not be null"); + postResult(null); + } + + try{ + connectorOAuth2.setConnectorOAuth2Url(requestBaseURI + OAuth2Context.OAUTH2_DEVICE_GETTOKEN_URL); + + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair("client_id", OAuth2Context.OAUTH2_DEVICE_CLIENT_ID)); + nameValuePairs.add(new BasicNameValuePair("client_secret", OAuth2Context.OAUTH2_DEVICE_CLIENT_SECRET)); + nameValuePairs.add(new BasicNameValuePair("code",requestDeviceCode)); + nameValuePairs.add(new BasicNameValuePair("grant_type",OAuth2Context.OAUTH_DEVICE_GETTOKEN_GRANT_TYPE)); + + params = new UrlEncodedFormEntity(nameValuePairs); + } + catch (Exception ex1){ + Log.w(TAG, ex1.toString()); + postResult(null); + } + + return params; + } + + protected void requestToken(UrlEncodedFormEntity params){ + JSONObject tokenJson = null; + String error = null; + HashMap resultTokenMap; + + String tokenResponse = connectorOAuth2.connPost(params); + + try { + tokenJson = new JSONObject(tokenResponse); + } catch (JSONException e) { + Log.e(TAG, "Exception converting to Json " + e.toString()); + } + + try { + // We try to get error string. + if (tokenJson.has(TOKEN_RECEIVED_ERROR)) { + error = tokenJson.getString(TOKEN_RECEIVED_ERROR); + Log.d(TAG, "requestToken -> Obtained error "+ error); + } else { + //We have got the token. Parse the answer. + resultTokenMap = parseResult(tokenJson); + postResult(resultTokenMap); + } + } catch (JSONException e) { + Log.e(TAG, "Exception converting to Json " + e.toString()); + } + } + + private HashMap parseResult (JSONObject tokenJson) { + HashMap resultTokenMap=new HashMap(); + + try { + resultTokenMap.put(TOKEN_ACCESS_TOKEN, tokenJson.getString(TOKEN_ACCESS_TOKEN)); + resultTokenMap.put(TOKEN_TOKEN_TYPE, tokenJson.getString(TOKEN_TOKEN_TYPE)); + resultTokenMap.put(TOKEN_EXPIRES_IN, tokenJson.getString(TOKEN_EXPIRES_IN)); + resultTokenMap.put(TOKEN_REFRESH_TOKEN, tokenJson.getString(TOKEN_REFRESH_TOKEN)); + } catch (JSONException e) { + Log.e(TAG, "parseResult: Exception converting to Json " + e.toString()); + } + return resultTokenMap; + } + + /** + * Returns obtained values with a broadcast. + * + * @param tokenResponse : obtained values. + */ + private void postResult(HashMap tokenResponse) { + Intent intent = new Intent(TOKEN_RECEIVED_MESSAGE); + intent.putExtra(TOKEN_RECEIVED_DATA,tokenResponse); + sendBroadcast(intent); + shutdownService(); + } +} diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java index 23ce0c8c..f3d60966 100644 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java @@ -20,12 +20,20 @@ package com.owncloud.android.ui.activity; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; + +import org.json.JSONException; +import org.json.JSONObject; import com.owncloud.android.AccountUtils; import com.owncloud.android.authenticator.AccountAuthenticator; import com.owncloud.android.authenticator.AuthenticationRunnable; import com.owncloud.android.authenticator.OnAuthenticationResultListener; import com.owncloud.android.authenticator.OnConnectCheckListener; +import com.owncloud.android.authenticator.oauth2.OAuth2GetCodeRunnable; +import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener; +import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2; +import com.owncloud.android.authenticator.oauth2.services.OAuth2GetTokenService; import com.owncloud.android.ui.dialog.SslValidatorDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; import com.owncloud.android.network.OwnCloudClientUtils; @@ -40,9 +48,12 @@ import android.accounts.AccountManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; +import android.content.BroadcastReceiver; import android.content.ContentResolver; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; @@ -54,6 +65,8 @@ 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.ImageView; import android.widget.TextView; import com.owncloud.android.R; @@ -68,7 +81,7 @@ import eu.alefzero.webdav.WebdavClient; */ public class AuthenticatorActivity extends AccountAuthenticatorActivity implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, - OnFocusChangeListener, OnClickListener { + OnFocusChangeListener, OnClickListener, OnOAuth2GetCodeResultListener { private static final int DIALOG_LOGIN_PROGRESS = 0; private static final int DIALOG_SSL_VALIDATOR = 1; @@ -94,6 +107,30 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity public static final String PARAM_USERNAME = "param_Username"; public static final String PARAM_HOSTNAME = "param_Hostname"; + // oAuth2 variables. + private static final int OAUTH2_LOGIN_PROGRESS = 3; + private static final String OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT"; + private static final String OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON"; + private static final String OAUTH2_CODE_RESULT = "CODE_RESULT"; + private static final String OAUTH2_BASE_URL = "BASE_URL"; + private static final String OAUTH2_IS_CHECKED = "OAUTH2_IS_CHECKED"; + private Thread mOAuth2GetCodeThread; + private OAuth2GetCodeRunnable mOAuth2GetCodeRunnable; + private String oAuth2BaseUrl; + private TokenReceiver tokenReceiver; + private JSONObject codeResponseJson; + private int mOAuth2StatusText, mOAuth2StatusIcon; + + public ConnectorOAuth2 connectorOAuth2; + + // Variables used to save the on the state the contents of all fields. + private static final String HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String OAUTH2_URL_TEXT = "OAUTH2_URL_TEXT"; + private static final String ACCOUNT_USERNAME = "ACCOUNT_USERNAME"; + private static final String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; + + // END of oAuth2 variables. + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -103,6 +140,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity ImageView iv2 = (ImageView) findViewById(R.id.viewPassword); TextView tv = (TextView) findViewById(R.id.host_URL); TextView tv2 = (TextView) findViewById(R.id.account_password); + // New textview to oAuth2 URL. + TextView tv3 = (TextView) findViewById(R.id.oAuth_URL); if (savedInstanceState != null) { mStatusIcon = savedInstanceState.getInt(STATUS_ICON); @@ -114,7 +153,35 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity if (!mStatusCorrect) iv.setVisibility(View.VISIBLE); else - iv.setVisibility(View.INVISIBLE); + iv.setVisibility(View.INVISIBLE); + + // Getting the state of oAuth2 components. + mOAuth2StatusIcon = savedInstanceState.getInt(OAUTH2_STATUS_ICON); + mOAuth2StatusText = savedInstanceState.getInt(OAUTH2_STATUS_TEXT); + // We set this to true if the rotation happens when the user is validating oAuth2 user_code. + changeViewByOAuth2Check(savedInstanceState.getBoolean(OAUTH2_IS_CHECKED)); + oAuth2BaseUrl = savedInstanceState.getString(OAUTH2_BASE_URL); + // We store a JSon object with all the data returned from oAuth2 server when we get user_code. + // Is better than store variable by variable. We use String object to serialize from/to it. + try { + if (savedInstanceState.containsKey(OAUTH2_CODE_RESULT)) { + codeResponseJson = new JSONObject(savedInstanceState.getString(OAUTH2_CODE_RESULT)); + } + } catch (JSONException e) { + Log.e(TAG, "onCreate->JSONException: " + e.toString()); + } + // END of getting the state of oAuth2 components. + + // Getting contents of each field. + EditText hostUrl = (EditText)findViewById(R.id.host_URL); + hostUrl.setText(savedInstanceState.getString(HOST_URL_TEXT), TextView.BufferType.EDITABLE); + EditText oauth2Url = (EditText)findViewById(R.id.oAuth_URL); + oauth2Url.setText(savedInstanceState.getString(OAUTH2_URL_TEXT), TextView.BufferType.EDITABLE); + EditText accountUsername = (EditText)findViewById(R.id.account_username); + accountUsername.setText(savedInstanceState.getString(ACCOUNT_USERNAME), TextView.BufferType.EDITABLE); + EditText accountPassword = (EditText)findViewById(R.id.account_password); + accountPassword.setText(savedInstanceState.getString(ACCOUNT_PASSWORD), TextView.BufferType.EDITABLE); + // END of getting contents of each field } else { mStatusText = mStatusIcon = 0; @@ -125,6 +192,10 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity iv2.setOnClickListener(this); tv.setOnFocusChangeListener(this); tv2.setOnFocusChangeListener(this); + // Setting the listener for oAuth2 URL TextView. + tv3.setOnFocusChangeListener(this); + + super.onCreate(savedInstanceState); } @Override @@ -132,6 +203,24 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity outState.putInt(STATUS_ICON, mStatusIcon); outState.putInt(STATUS_TEXT, mStatusText); outState.putBoolean(STATUS_CORRECT, mStatusCorrect); + + // Saving the state of oAuth2 components. + outState.putInt(OAUTH2_STATUS_ICON, mOAuth2StatusIcon); + outState.putInt(OAUTH2_STATUS_TEXT, mOAuth2StatusText); + CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + outState.putBoolean(OAUTH2_IS_CHECKED, oAuth2Check.isChecked()); + if (codeResponseJson != null){ + outState.putString(OAUTH2_CODE_RESULT, codeResponseJson.toString()); + } + outState.putString(OAUTH2_BASE_URL, oAuth2BaseUrl); + // END of saving the state of oAuth2 components. + + // Saving contents of each field. + outState.putString(HOST_URL_TEXT,((TextView) findViewById(R.id.host_URL)).getText().toString().trim()); + outState.putString(OAUTH2_URL_TEXT,((TextView) findViewById(R.id.oAuth_URL)).getText().toString().trim()); + outState.putString(ACCOUNT_USERNAME,((TextView) findViewById(R.id.account_username)).getText().toString().trim()); + outState.putString(ACCOUNT_PASSWORD,((TextView) findViewById(R.id.account_password)).getText().toString().trim()); + super.onSaveInstanceState(outState); } @@ -159,6 +248,37 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity dialog = working_dialog; break; } + // oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser. + case OAUTH2_LOGIN_PROGRESS: { + ProgressDialog working_dialog = new ProgressDialog(this); + try { + working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message), + codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL), + codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE))); + } catch (JSONException e) { + Log.e(TAG, "onCreateDialog->JSONException: " + e.toString()); + } + working_dialog.setIndeterminate(true); + working_dialog.setCancelable(true); + working_dialog + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + Log.i(TAG, "Login canceled"); + if (mOAuth2GetCodeThread != null) { + mOAuth2GetCodeThread.interrupt(); + finish(); + } + if (tokenReceiver != null) { + unregisterReceiver(tokenReceiver); + tokenReceiver = null; + finish(); + } + } + }); + dialog = working_dialog; + break; + } case DIALOG_SSL_VALIDATOR: { dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); break; @@ -196,6 +316,25 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity Log.e(TAG, "Incorrect dialog called with id = " + id); } } + + @Override + protected void onResume() { + Log.d(TAG, "onResume() start"); + // 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); + } + super.onResume(); + } + + @Override + protected void onPause() { + Log.d(TAG, "onPause() start"); + super.onPause(); + } + public void onAuthenticationResult(boolean success, String message) { if (success) { @@ -240,6 +379,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity AccountAuthenticator.ACCOUNT_TYPE); intent.putExtra(AccountManager.KEY_USERDATA, username); + accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL, + url.toString()); accManager.setUserData(account, AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable .getDiscoveredVersion().toString()); @@ -452,6 +593,28 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity v.setInputType(input_type); iv.setVisibility(View.INVISIBLE); } + // If the focusChange occurs on the oAuth2 URL field, we do this. + } else if (view.getId() == R.id.oAuth_URL) { + if (!hasFocus) { + TextView tv3 = ((TextView) findViewById(R.id.oAuth_URL)); + // We get the URL of oAuth2 server. + oAuth2BaseUrl = tv3.getText().toString().trim(); + if (oAuth2BaseUrl.length() != 0) { + // We start a thread to get user_code from the oAuth2 server. + setOAuth2ResultIconAndText(R.drawable.progress_small, R.string.oauth_login_connection); + mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(oAuth2BaseUrl, this); + mOAuth2GetCodeRunnable.setListener(this, mHandler); + mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable); + mOAuth2GetCodeThread.start(); + } else { + findViewById(R.id.refreshButton).setVisibility( + View.INVISIBLE); + setOAuth2ResultIconAndText(0, 0); + } + } else { + // avoids that the 'connect' button can be clicked if the test was previously passed + findViewById(R.id.buttonOK).setEnabled(false); + } } } @@ -481,6 +644,151 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity view.setInputType(input_type); } } + + @Override protected void onDestroy() { + // We must stop the service thats it's pooling to oAuth2 server for a token. + Intent tokenService = new Intent(this, OAuth2GetTokenService.class); + stopService(tokenService); + + // We stop listening the result of the pooling service. + if (tokenReceiver != null) { + unregisterReceiver(tokenReceiver); + tokenReceiver = null; + finish(); + } + + super.onDestroy(); + } + + // Controlling the oAuth2 checkbox on the activity: hide and show widgets. + public void onOff_check_Click(View view) { + CheckBox oAuth2Check = (CheckBox)view; + changeViewByOAuth2Check(oAuth2Check.isChecked()); + + } + + public void changeViewByOAuth2Check(Boolean checked) { + + EditText oAuth2Url = (EditText) findViewById(R.id.oAuth_URL); + EditText accountUsername = (EditText) findViewById(R.id.account_username); + EditText accountPassword = (EditText) findViewById(R.id.account_password); + ImageView viewPassword = (ImageView) findViewById(R.id.viewPassword); + ImageView auth2ActionIndicator = (ImageView) findViewById(R.id.auth2_action_indicator); + TextView oauth2StatusText = (TextView) findViewById(R.id.oauth2_status_text); + + if (checked) { + oAuth2Url.setVisibility(View.VISIBLE); + accountUsername.setVisibility(View.GONE); + accountPassword.setVisibility(View.GONE); + viewPassword.setVisibility(View.GONE); + auth2ActionIndicator.setVisibility(View.INVISIBLE); + oauth2StatusText.setVisibility(View.INVISIBLE); + } else { + oAuth2Url.setVisibility(View.GONE); + accountUsername.setVisibility(View.VISIBLE); + accountPassword.setVisibility(View.VISIBLE); + viewPassword.setVisibility(View.INVISIBLE); + auth2ActionIndicator.setVisibility(View.GONE); + oauth2StatusText.setVisibility(View.GONE); + } + + } + + // Controlling the oAuth2 result of server connection. + private void setOAuth2ResultIconAndText(int drawable_id, int text_id) { + ImageView iv = (ImageView) findViewById(R.id.auth2_action_indicator); + TextView tv = (TextView) findViewById(R.id.oauth2_status_text); + + if (drawable_id == 0 && text_id == 0) { + iv.setVisibility(View.INVISIBLE); + tv.setVisibility(View.INVISIBLE); + } else { + iv.setImageResource(drawable_id); + tv.setText(text_id); + iv.setVisibility(View.VISIBLE); + tv.setVisibility(View.VISIBLE); + } + } + + // Results from the first call to oAuth2 server : getting the user_code and verification_url. + @Override + public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject responseJson) { + if ((type == ResultOAuthType.OK_SSL)||(type == ResultOAuthType.OK_NO_SSL)) { + codeResponseJson = responseJson; + startOAuth2Authentication(); + } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) { + setOAuth2ResultIconAndText(R.drawable.common_error, R.string.oauth_connection_url_unavailable); + } + } + + // If the results of getting the user_code and verification_url are OK, we get the received data and we start + // the pooling service to oAuth2 server to get a valid token. + private void startOAuth2Authentication () { + String deviceCode = null; + String verificationUrl = null; + String userCode = null; + int expiresIn = -1; + int interval = -1; + + Log.d(TAG, "ResponseOAuth2->" + codeResponseJson.toString()); + + try { + // We get data that we must show to the user or we will use internally. + verificationUrl = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL); + userCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE); + expiresIn = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_EXPIRES_IN); + + // And we get data that we must use to get a token. + deviceCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_DEVICE_CODE); + interval = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_INTERVAL); + + } catch (JSONException e) { + Log.e(TAG, "Exception accesing data in Json object" + e.toString()); + } + + // Updating status widget to OK. + setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established); + + // Showing the dialog with instructions for the user. + showDialog(OAUTH2_LOGIN_PROGRESS); + + // Loggin all the data. + Log.d(TAG, "verificationUrl->" + verificationUrl); + Log.d(TAG, "userCode->" + userCode); + Log.d(TAG, "deviceCode->" + deviceCode); + Log.d(TAG, "expiresIn->" + expiresIn); + Log.d(TAG, "interval->" + interval); + + // Starting the pooling service. + try { + Intent tokenService = new Intent(this, OAuth2GetTokenService.class); + tokenService.putExtra(OAuth2GetTokenService.TOKEN_BASE_URI, oAuth2BaseUrl); + 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); + } + } + + // We get data from the oAuth2 token service with this broadcast receiver. + private class TokenReceiver extends BroadcastReceiver { + /** + * The token is received. + * @author + * {@link BroadcastReceiver} to enable oAuth2 token receiving. + */ + @Override + public void onReceive(Context context, Intent intent) { + @SuppressWarnings("unchecked") + HashMap tokenResponse = (HashMap)intent.getExtras().get(OAuth2GetTokenService.TOKEN_RECEIVED_DATA); + Log.d(TAG, "TokenReceiver->" + tokenResponse.get(OAuth2GetTokenService.TOKEN_ACCESS_TOKEN)); + dismissDialog(OAUTH2_LOGIN_PROGRESS); + + } + } @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { @@ -584,5 +892,5 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity public void onFailedSavingCertificate() { showDialog(DIALOG_CERT_NOT_SAVED); } - + }