<action android:name="android.intent.action.BOOT_COMPLETED"/>\r
</intent-filter>\r
</receiver>\r
- <service android:name=".files.services.FileObserverService"/>
+ <service android:name=".files.services.FileObserverService"/>\r
+ \r
+ <service android:name=".authenticator.oauth2.services.OAuth2GetTokenService" >\r
+ </service>\r
+
</application>\r
\r
</manifest>
android:visibility="invisible" />
</LinearLayout>
+ <CheckBox
+ android:id="@+id/oauth_onOff_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:onClick="onOff_check_Click"
+ android:text="@string/oauth_check_onoff"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<EditText
+ android:id="@+id/oAuth_URL"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:ems="10"
+ android:hint="@string/oauth_host_url"
+ android:singleLine="true"
+ android:visibility="gone" >
+
+ <requestFocus />
+ </EditText>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+
+ <ImageView
+ android:id="@+id/auth2_action_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp"
+ android:src="@android:drawable/stat_notify_sync"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/oauth2_status_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:visibility="gone" />
+ </LinearLayout>
+
+ <EditText
android:id="@+id/account_username"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="wrap_content"
android:text="TextView"
android:visibility="invisible" />
-
</LinearLayout>
+ <CheckBox
+ android:id="@+id/oauth_onOff_check"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="false"
+ android:onClick="onOff_check_Click"
+ android:text="@string/oauth_check_onoff"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_weight="1"
android:text="@string/auth_login_details"
android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <EditText
+ android:id="@+id/oAuth_URL"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:ems="10"
+ android:hint="@string/oauth_host_url"
+ android:singleLine="true"
+ android:visibility="gone" >
+ <requestFocus />
+ </EditText>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+
+ <ImageView
+ android:id="@+id/auth2_action_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp"
+ android:src="@android:drawable/stat_notify_sync"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/oauth2_status_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:visibility="gone" />
+ </LinearLayout>
<EditText
android:id="@+id/account_username"
android:layout_width="match_parent"
<string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please, try other app to select the file"</string>
<string name="filedisplay_no_file_selected">No file was selected</string>
+ <string name="oauth_host_url">oAuth2 URL</string>
+ <string name="oauth_check_onoff">Login with oAuth2.</string>
+ <string name="oauth_login_connection">Connecting to oAuth2 server…</string>
+ <string name="oauth_code_validation_message">Please, open a web browser and go to:\n%1$s.\nValidate this code there:\n%2$s</string>
+ <string name="oauth_connection_url_unavailable">Connection to this URL not available.</string>
+
<string name="ssl_validator_title">Warning</string>
<string name="ssl_validator_header">The identity of the site could not be verified</string>
<string name="ssl_validator_reason_cert_not_trusted">- The server certificate is not trusted</string>
<item name="android:singleLine">true</item>
</style>
+
+ <style name="OAuthDialog" parent="@android:style/Theme.Dialog">
+ <item name="android:windowNoTitle">false</item>
+ </style>
<color name="setup_text_hint">#777777</color>
<color name="setup_text_typed">#000000</color>
--- /dev/null
+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";
+}
--- /dev/null
+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<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(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
--- /dev/null
+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);
+
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(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<String, String> 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<String, String> parseResult (JSONObject tokenJson) {
+ HashMap<String, String> resultTokenMap=new HashMap<String, String>();
+
+ 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<String, String> tokenResponse) {
+ Intent intent = new Intent(TOKEN_RECEIVED_MESSAGE);
+ intent.putExtra(TOKEN_RECEIVED_DATA,tokenResponse);
+ sendBroadcast(intent);
+ shutdownService();
+ }
+}
\r
import java.net.MalformedURLException;\r
import java.net.URL;\r
+import java.util.HashMap;\r
+\r
+import org.json.JSONException;\r
+import org.json.JSONObject;\r
\r
import com.owncloud.android.AccountUtils;\r
import com.owncloud.android.authenticator.AccountAuthenticator;\r
import com.owncloud.android.authenticator.AuthenticationRunnable;\r
import com.owncloud.android.authenticator.OnAuthenticationResultListener;\r
import com.owncloud.android.authenticator.OnConnectCheckListener;\r
+import com.owncloud.android.authenticator.oauth2.OAuth2GetCodeRunnable;\r
+import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener;\r
+import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2;\r
+import com.owncloud.android.authenticator.oauth2.services.OAuth2GetTokenService;\r
import com.owncloud.android.ui.dialog.SslValidatorDialog;\r
import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;\r
import com.owncloud.android.network.OwnCloudClientUtils;\r
import android.app.AlertDialog;\r
import android.app.Dialog;\r
import android.app.ProgressDialog;\r
+import android.content.BroadcastReceiver;\r
import android.content.ContentResolver;\r
+import android.content.Context;\r
import android.content.DialogInterface;\r
import android.content.Intent;\r
+import android.content.IntentFilter;\r
import android.content.SharedPreferences;\r
import android.net.Uri;\r
import android.os.Bundle;\r
import android.view.View.OnClickListener;\r
import android.view.View.OnFocusChangeListener;\r
import android.view.Window;\r
+import android.widget.CheckBox;\r
+import android.widget.EditText;\r
import android.widget.ImageView;\r
import android.widget.TextView;\r
import com.owncloud.android.R;\r
*/\r
public class AuthenticatorActivity extends AccountAuthenticatorActivity\r
implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, \r
- OnFocusChangeListener, OnClickListener {\r
+ OnFocusChangeListener, OnClickListener, OnOAuth2GetCodeResultListener {\r
\r
private static final int DIALOG_LOGIN_PROGRESS = 0;\r
private static final int DIALOG_SSL_VALIDATOR = 1;\r
public static final String PARAM_USERNAME = "param_Username";\r
public static final String PARAM_HOSTNAME = "param_Hostname";\r
\r
+ // oAuth2 variables.\r
+ private static final int OAUTH2_LOGIN_PROGRESS = 3;\r
+ private static final String OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT";\r
+ private static final String OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON";\r
+ private static final String OAUTH2_CODE_RESULT = "CODE_RESULT";\r
+ private static final String OAUTH2_BASE_URL = "BASE_URL"; \r
+ private static final String OAUTH2_IS_CHECKED = "OAUTH2_IS_CHECKED"; \r
+ private Thread mOAuth2GetCodeThread;\r
+ private OAuth2GetCodeRunnable mOAuth2GetCodeRunnable; \r
+ private String oAuth2BaseUrl;\r
+ private TokenReceiver tokenReceiver;\r
+ private JSONObject codeResponseJson; \r
+ private int mOAuth2StatusText, mOAuth2StatusIcon; \r
+ \r
+ public ConnectorOAuth2 connectorOAuth2;\r
+ \r
+ // Variables used to save the on the state the contents of all fields.\r
+ private static final String HOST_URL_TEXT = "HOST_URL_TEXT";\r
+ private static final String OAUTH2_URL_TEXT = "OAUTH2_URL_TEXT";\r
+ private static final String ACCOUNT_USERNAME = "ACCOUNT_USERNAME";\r
+ private static final String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD";\r
+\r
+ // END of oAuth2 variables.\r
+ \r
@Override\r
protected void onCreate(Bundle savedInstanceState) {\r
super.onCreate(savedInstanceState);\r
ImageView iv2 = (ImageView) findViewById(R.id.viewPassword);\r
TextView tv = (TextView) findViewById(R.id.host_URL);\r
TextView tv2 = (TextView) findViewById(R.id.account_password);\r
+ // New textview to oAuth2 URL.\r
+ TextView tv3 = (TextView) findViewById(R.id.oAuth_URL);\r
\r
if (savedInstanceState != null) {\r
mStatusIcon = savedInstanceState.getInt(STATUS_ICON);\r
if (!mStatusCorrect)\r
iv.setVisibility(View.VISIBLE);\r
else\r
- iv.setVisibility(View.INVISIBLE);\r
+ iv.setVisibility(View.INVISIBLE); \r
+ \r
+ // Getting the state of oAuth2 components.\r
+ mOAuth2StatusIcon = savedInstanceState.getInt(OAUTH2_STATUS_ICON);\r
+ mOAuth2StatusText = savedInstanceState.getInt(OAUTH2_STATUS_TEXT);\r
+ // We set this to true if the rotation happens when the user is validating oAuth2 user_code.\r
+ changeViewByOAuth2Check(savedInstanceState.getBoolean(OAUTH2_IS_CHECKED));\r
+ oAuth2BaseUrl = savedInstanceState.getString(OAUTH2_BASE_URL);\r
+ // We store a JSon object with all the data returned from oAuth2 server when we get user_code.\r
+ // Is better than store variable by variable. We use String object to serialize from/to it.\r
+ try {\r
+ if (savedInstanceState.containsKey(OAUTH2_CODE_RESULT)) {\r
+ codeResponseJson = new JSONObject(savedInstanceState.getString(OAUTH2_CODE_RESULT));\r
+ }\r
+ } catch (JSONException e) {\r
+ Log.e(TAG, "onCreate->JSONException: " + e.toString());\r
+ }\r
+ // END of getting the state of oAuth2 components.\r
+ \r
+ // Getting contents of each field.\r
+ EditText hostUrl = (EditText)findViewById(R.id.host_URL);\r
+ hostUrl.setText(savedInstanceState.getString(HOST_URL_TEXT), TextView.BufferType.EDITABLE);\r
+ EditText oauth2Url = (EditText)findViewById(R.id.oAuth_URL);\r
+ oauth2Url.setText(savedInstanceState.getString(OAUTH2_URL_TEXT), TextView.BufferType.EDITABLE);\r
+ EditText accountUsername = (EditText)findViewById(R.id.account_username);\r
+ accountUsername.setText(savedInstanceState.getString(ACCOUNT_USERNAME), TextView.BufferType.EDITABLE);\r
+ EditText accountPassword = (EditText)findViewById(R.id.account_password);\r
+ accountPassword.setText(savedInstanceState.getString(ACCOUNT_PASSWORD), TextView.BufferType.EDITABLE);\r
+ // END of getting contents of each field\r
\r
} else {\r
mStatusText = mStatusIcon = 0;\r
iv2.setOnClickListener(this);\r
tv.setOnFocusChangeListener(this);\r
tv2.setOnFocusChangeListener(this);\r
+ // Setting the listener for oAuth2 URL TextView.\r
+ tv3.setOnFocusChangeListener(this);\r
+ \r
+ super.onCreate(savedInstanceState);\r
}\r
\r
@Override\r
outState.putInt(STATUS_ICON, mStatusIcon);\r
outState.putInt(STATUS_TEXT, mStatusText);\r
outState.putBoolean(STATUS_CORRECT, mStatusCorrect);\r
+ \r
+ // Saving the state of oAuth2 components.\r
+ outState.putInt(OAUTH2_STATUS_ICON, mOAuth2StatusIcon);\r
+ outState.putInt(OAUTH2_STATUS_TEXT, mOAuth2StatusText);\r
+ CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);\r
+ outState.putBoolean(OAUTH2_IS_CHECKED, oAuth2Check.isChecked());\r
+ if (codeResponseJson != null){\r
+ outState.putString(OAUTH2_CODE_RESULT, codeResponseJson.toString());\r
+ }\r
+ outState.putString(OAUTH2_BASE_URL, oAuth2BaseUrl);\r
+ // END of saving the state of oAuth2 components.\r
+ \r
+ // Saving contents of each field.\r
+ outState.putString(HOST_URL_TEXT,((TextView) findViewById(R.id.host_URL)).getText().toString().trim());\r
+ outState.putString(OAUTH2_URL_TEXT,((TextView) findViewById(R.id.oAuth_URL)).getText().toString().trim());\r
+ outState.putString(ACCOUNT_USERNAME,((TextView) findViewById(R.id.account_username)).getText().toString().trim());\r
+ outState.putString(ACCOUNT_PASSWORD,((TextView) findViewById(R.id.account_password)).getText().toString().trim());\r
+ \r
super.onSaveInstanceState(outState);\r
}\r
\r
dialog = working_dialog;\r
break;\r
}\r
+ // oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser.\r
+ case OAUTH2_LOGIN_PROGRESS: {\r
+ ProgressDialog working_dialog = new ProgressDialog(this);\r
+ try {\r
+ working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message), \r
+ codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL), \r
+ codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE)));\r
+ } catch (JSONException e) {\r
+ Log.e(TAG, "onCreateDialog->JSONException: " + e.toString());\r
+ }\r
+ working_dialog.setIndeterminate(true);\r
+ working_dialog.setCancelable(true);\r
+ working_dialog\r
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {\r
+ @Override\r
+ public void onCancel(DialogInterface dialog) {\r
+ Log.i(TAG, "Login canceled");\r
+ if (mOAuth2GetCodeThread != null) {\r
+ mOAuth2GetCodeThread.interrupt();\r
+ finish();\r
+ } \r
+ if (tokenReceiver != null) {\r
+ unregisterReceiver(tokenReceiver);\r
+ tokenReceiver = null;\r
+ finish();\r
+ }\r
+ }\r
+ });\r
+ dialog = working_dialog;\r
+ break;\r
+ }\r
case DIALOG_SSL_VALIDATOR: {\r
dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);\r
break;\r
Log.e(TAG, "Incorrect dialog called with id = " + id);\r
}\r
}\r
+ \r
+ @Override\r
+ protected void onResume() {\r
+ Log.d(TAG, "onResume() start");\r
+ // Registering token receiver. We must listening to the service that is pooling to the oAuth server for a token.\r
+ if (tokenReceiver == null) {\r
+ IntentFilter tokenFilter = new IntentFilter(OAuth2GetTokenService.TOKEN_RECEIVED_MESSAGE); \r
+ tokenReceiver = new TokenReceiver();\r
+ this.registerReceiver(tokenReceiver,tokenFilter);\r
+ }\r
+ super.onResume();\r
+ }\r
+\r
+ @Override\r
+ protected void onPause() {\r
+ Log.d(TAG, "onPause() start");\r
+ super.onPause();\r
+ } \r
+ \r
\r
public void onAuthenticationResult(boolean success, String message) {\r
if (success) {\r
AccountAuthenticator.ACCOUNT_TYPE);\r
intent.putExtra(AccountManager.KEY_USERDATA, username);\r
\r
+ accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL,\r
+ url.toString());\r
accManager.setUserData(account,\r
AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable\r
.getDiscoveredVersion().toString());\r
v.setInputType(input_type);\r
iv.setVisibility(View.INVISIBLE);\r
}\r
+ // If the focusChange occurs on the oAuth2 URL field, we do this.\r
+ } else if (view.getId() == R.id.oAuth_URL) {\r
+ if (!hasFocus) {\r
+ TextView tv3 = ((TextView) findViewById(R.id.oAuth_URL));\r
+ // We get the URL of oAuth2 server.\r
+ oAuth2BaseUrl = tv3.getText().toString().trim();\r
+ if (oAuth2BaseUrl.length() != 0) {\r
+ // We start a thread to get user_code from the oAuth2 server.\r
+ setOAuth2ResultIconAndText(R.drawable.progress_small, R.string.oauth_login_connection);\r
+ mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(oAuth2BaseUrl, this);\r
+ mOAuth2GetCodeRunnable.setListener(this, mHandler);\r
+ mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable);\r
+ mOAuth2GetCodeThread.start();\r
+ } else {\r
+ findViewById(R.id.refreshButton).setVisibility(\r
+ View.INVISIBLE);\r
+ setOAuth2ResultIconAndText(0, 0);\r
+ }\r
+ } else {\r
+ // avoids that the 'connect' button can be clicked if the test was previously passed\r
+ findViewById(R.id.buttonOK).setEnabled(false); \r
+ }\r
}\r
}\r
\r
view.setInputType(input_type);\r
}\r
}\r
+ \r
+ @Override protected void onDestroy() { \r
+ // We must stop the service thats it's pooling to oAuth2 server for a token.\r
+ Intent tokenService = new Intent(this, OAuth2GetTokenService.class);\r
+ stopService(tokenService);\r
+ \r
+ // We stop listening the result of the pooling service.\r
+ if (tokenReceiver != null) {\r
+ unregisterReceiver(tokenReceiver);\r
+ tokenReceiver = null;\r
+ finish();\r
+ }\r
+\r
+ super.onDestroy();\r
+ } \r
+ \r
+ // Controlling the oAuth2 checkbox on the activity: hide and show widgets.\r
+ public void onOff_check_Click(View view) {\r
+ CheckBox oAuth2Check = (CheckBox)view; \r
+ changeViewByOAuth2Check(oAuth2Check.isChecked());\r
+\r
+ }\r
+ \r
+ public void changeViewByOAuth2Check(Boolean checked) {\r
+ \r
+ EditText oAuth2Url = (EditText) findViewById(R.id.oAuth_URL);\r
+ EditText accountUsername = (EditText) findViewById(R.id.account_username);\r
+ EditText accountPassword = (EditText) findViewById(R.id.account_password);\r
+ ImageView viewPassword = (ImageView) findViewById(R.id.viewPassword); \r
+ ImageView auth2ActionIndicator = (ImageView) findViewById(R.id.auth2_action_indicator); \r
+ TextView oauth2StatusText = (TextView) findViewById(R.id.oauth2_status_text); \r
+\r
+ if (checked) {\r
+ oAuth2Url.setVisibility(View.VISIBLE);\r
+ accountUsername.setVisibility(View.GONE);\r
+ accountPassword.setVisibility(View.GONE);\r
+ viewPassword.setVisibility(View.GONE);\r
+ auth2ActionIndicator.setVisibility(View.INVISIBLE);\r
+ oauth2StatusText.setVisibility(View.INVISIBLE);\r
+ } else {\r
+ oAuth2Url.setVisibility(View.GONE);\r
+ accountUsername.setVisibility(View.VISIBLE);\r
+ accountPassword.setVisibility(View.VISIBLE);\r
+ viewPassword.setVisibility(View.INVISIBLE);\r
+ auth2ActionIndicator.setVisibility(View.GONE);\r
+ oauth2StatusText.setVisibility(View.GONE);\r
+ } \r
+\r
+ } \r
+ \r
+ // Controlling the oAuth2 result of server connection.\r
+ private void setOAuth2ResultIconAndText(int drawable_id, int text_id) {\r
+ ImageView iv = (ImageView) findViewById(R.id.auth2_action_indicator);\r
+ TextView tv = (TextView) findViewById(R.id.oauth2_status_text);\r
+\r
+ if (drawable_id == 0 && text_id == 0) {\r
+ iv.setVisibility(View.INVISIBLE);\r
+ tv.setVisibility(View.INVISIBLE);\r
+ } else {\r
+ iv.setImageResource(drawable_id);\r
+ tv.setText(text_id);\r
+ iv.setVisibility(View.VISIBLE);\r
+ tv.setVisibility(View.VISIBLE);\r
+ }\r
+ } \r
+ \r
+ // Results from the first call to oAuth2 server : getting the user_code and verification_url.\r
+ @Override\r
+ public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject responseJson) {\r
+ if ((type == ResultOAuthType.OK_SSL)||(type == ResultOAuthType.OK_NO_SSL)) {\r
+ codeResponseJson = responseJson;\r
+ startOAuth2Authentication();\r
+ } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) {\r
+ setOAuth2ResultIconAndText(R.drawable.common_error, R.string.oauth_connection_url_unavailable);\r
+ }\r
+ }\r
+\r
+ // If the results of getting the user_code and verification_url are OK, we get the received data and we start\r
+ // the pooling service to oAuth2 server to get a valid token.\r
+ private void startOAuth2Authentication () {\r
+ String deviceCode = null;\r
+ String verificationUrl = null;\r
+ String userCode = null;\r
+ int expiresIn = -1;\r
+ int interval = -1;\r
+\r
+ Log.d(TAG, "ResponseOAuth2->" + codeResponseJson.toString());\r
+\r
+ try {\r
+ // We get data that we must show to the user or we will use internally.\r
+ verificationUrl = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL);\r
+ userCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE);\r
+ expiresIn = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_EXPIRES_IN); \r
+\r
+ // And we get data that we must use to get a token.\r
+ deviceCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_DEVICE_CODE);\r
+ interval = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_INTERVAL);\r
+\r
+ } catch (JSONException e) {\r
+ Log.e(TAG, "Exception accesing data in Json object" + e.toString());\r
+ }\r
+\r
+ // Updating status widget to OK.\r
+ setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established);\r
+ \r
+ // Showing the dialog with instructions for the user.\r
+ showDialog(OAUTH2_LOGIN_PROGRESS);\r
+\r
+ // Loggin all the data.\r
+ Log.d(TAG, "verificationUrl->" + verificationUrl);\r
+ Log.d(TAG, "userCode->" + userCode);\r
+ Log.d(TAG, "deviceCode->" + deviceCode);\r
+ Log.d(TAG, "expiresIn->" + expiresIn);\r
+ Log.d(TAG, "interval->" + interval);\r
+\r
+ // Starting the pooling service.\r
+ try {\r
+ Intent tokenService = new Intent(this, OAuth2GetTokenService.class);\r
+ tokenService.putExtra(OAuth2GetTokenService.TOKEN_BASE_URI, oAuth2BaseUrl);\r
+ tokenService.putExtra(OAuth2GetTokenService.TOKEN_DEVICE_CODE, deviceCode);\r
+ tokenService.putExtra(OAuth2GetTokenService.TOKEN_INTERVAL, interval);\r
+\r
+ startService(tokenService);\r
+ }\r
+ catch (Exception e) {\r
+ Log.e(TAG, "tokenService creation problem :", e);\r
+ }\r
+ } \r
+\r
+ // We get data from the oAuth2 token service with this broadcast receiver.\r
+ private class TokenReceiver extends BroadcastReceiver {\r
+ /**\r
+ * The token is received.\r
+ * @author\r
+ * {@link BroadcastReceiver} to enable oAuth2 token receiving.\r
+ */\r
+ @Override\r
+ public void onReceive(Context context, Intent intent) {\r
+ @SuppressWarnings("unchecked")\r
+ HashMap<String, String> tokenResponse = (HashMap<String, String>)intent.getExtras().get(OAuth2GetTokenService.TOKEN_RECEIVED_DATA);\r
+ Log.d(TAG, "TokenReceiver->" + tokenResponse.get(OAuth2GetTokenService.TOKEN_ACCESS_TOKEN));\r
+ dismissDialog(OAUTH2_LOGIN_PROGRESS);\r
+\r
+ }\r
+ }\r
\r
@Override\r
public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {\r
public void onFailedSavingCertificate() {\r
showDialog(DIALOG_CERT_NOT_SAVED);\r
}\r
- \r
+\r
}\r