ecf71e045e9a23d56dbe90597f82cc63f7bea388
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / AuthenticatorActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.ui.activity;
20
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.util.HashMap;
24 import java.util.Map;
25
26 import org.json.JSONException;
27 import org.json.JSONObject;
28
29 import com.owncloud.android.AccountUtils;
30 import com.owncloud.android.authenticator.AccountAuthenticator;
31 import com.owncloud.android.authenticator.AuthenticationRunnable;
32 import com.owncloud.android.authenticator.OnAuthenticationResultListener;
33 import com.owncloud.android.authenticator.OnConnectCheckListener;
34 import com.owncloud.android.authenticator.oauth2.OAuth2Context;
35 import com.owncloud.android.authenticator.oauth2.OAuth2GetCodeRunnable;
36 import com.owncloud.android.authenticator.oauth2.OnOAuth2GetCodeResultListener;
37 import com.owncloud.android.authenticator.oauth2.connection.ConnectorOAuth2;
38 import com.owncloud.android.authenticator.oauth2.services.OAuth2GetTokenService;
39 import com.owncloud.android.ui.dialog.SslValidatorDialog;
40 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
41 import com.owncloud.android.utils.OwnCloudVersion;
42 import com.owncloud.android.network.OwnCloudClientUtils;
43 import com.owncloud.android.operations.ConnectionCheckOperation;
44 import com.owncloud.android.operations.ExistenceCheckOperation;
45 import com.owncloud.android.operations.GetOAuth2AccessToken;
46 import com.owncloud.android.operations.OnRemoteOperationListener;
47 import com.owncloud.android.operations.RemoteOperation;
48 import com.owncloud.android.operations.RemoteOperationResult;
49
50 import android.accounts.Account;
51 import android.accounts.AccountAuthenticatorActivity;
52 import android.accounts.AccountManager;
53 import android.app.AlertDialog;
54 import android.app.Dialog;
55 import android.app.ProgressDialog;
56 import android.content.BroadcastReceiver;
57 import android.content.ContentResolver;
58 import android.content.Context;
59 import android.content.DialogInterface;
60 import android.content.Intent;
61 import android.content.IntentFilter;
62 import android.content.SharedPreferences;
63 import android.net.Uri;
64 import android.os.Bundle;
65 import android.os.Handler;
66 import android.preference.PreferenceManager;
67 import android.text.InputType;
68 import android.util.Log;
69 import android.view.View;
70 import android.view.View.OnClickListener;
71 import android.view.View.OnFocusChangeListener;
72 import android.view.Window;
73 import android.widget.CheckBox;
74 import android.widget.EditText;
75 import android.widget.Button;
76 import android.widget.ImageView;
77 import android.widget.TextView;
78 import com.owncloud.android.R;
79
80 import eu.alefzero.webdav.WebdavClient;
81
82 /**
83 * This Activity is used to add an ownCloud account to the App
84 *
85 * @author Bartek Przybylski
86 *
87 */
88 public class AuthenticatorActivity extends AccountAuthenticatorActivity
89 implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener,
90 OnFocusChangeListener, OnClickListener, OnOAuth2GetCodeResultListener {
91
92 private static final int DIALOG_LOGIN_PROGRESS = 0;
93 private static final int DIALOG_SSL_VALIDATOR = 1;
94 private static final int DIALOG_CERT_NOT_SAVED = 2;
95
96 private static final String TAG = "AuthActivity";
97
98 private Thread mAuthThread;
99 private AuthenticationRunnable mAuthRunnable;
100 private ConnectionCheckOperation mConnChkRunnable;
101 private ExistenceCheckOperation mAuthChkOperation;
102 private final Handler mHandler = new Handler();
103 private String mBaseUrl;
104 private OwnCloudVersion mDiscoveredVersion;
105
106 private static final String STATUS_TEXT = "STATUS_TEXT";
107 private static final String STATUS_ICON = "STATUS_ICON";
108 private static final String STATUS_CORRECT = "STATUS_CORRECT";
109 private static final String IS_SSL_CONN = "IS_SSL_CONN";
110 private static final String OC_VERSION = "OC_VERSION";
111 private int mStatusText, mStatusIcon;
112 private boolean mStatusCorrect, mIsSslConn;
113 private RemoteOperationResult mLastSslUntrustedServerResult;
114
115 public static final String PARAM_USERNAME = "param_Username";
116 public static final String PARAM_HOSTNAME = "param_Hostname";
117
118 // oAuth2 variables.
119 private static final int OAUTH2_LOGIN_PROGRESS = 3;
120 private static final String OAUTH2_STATUS_TEXT = "OAUTH2_STATUS_TEXT";
121 private static final String OAUTH2_STATUS_ICON = "OAUTH2_STATUS_ICON";
122 private static final String OAUTH2_CODE_RESULT = "CODE_RESULT";
123 private static final String OAUTH2_IS_CHECKED = "OAUTH2_IS_CHECKED";
124 private Thread mOAuth2GetCodeThread;
125 private OAuth2GetCodeRunnable mOAuth2GetCodeRunnable;
126 private TokenReceiver tokenReceiver;
127 private JSONObject codeResponseJson;
128 private int mOAuth2StatusText, mOAuth2StatusIcon;
129
130 public ConnectorOAuth2 connectorOAuth2;
131
132 // Variables used to save the on the state the contents of all fields.
133 private static final String HOST_URL_TEXT = "HOST_URL_TEXT";
134 private static final String ACCOUNT_USERNAME = "ACCOUNT_USERNAME";
135 private static final String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD";
136
137 //private boolean mNewRedirectUriCaptured;
138 private Uri mNewCapturedUriFromOAuth2Redirection;
139
140 // END of oAuth2 variables.
141
142 @Override
143 protected void onCreate(Bundle savedInstanceState) {
144 super.onCreate(savedInstanceState);
145 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
146 setContentView(R.layout.account_setup);
147 ImageView iv = (ImageView) findViewById(R.id.refreshButton);
148 ImageView iv2 = (ImageView) findViewById(R.id.viewPassword);
149 TextView tv = (TextView) findViewById(R.id.host_URL);
150 TextView tv2 = (TextView) findViewById(R.id.account_password);
151 EditText oauth2Url = (EditText)findViewById(R.id.oAuth_URL);
152 oauth2Url.setText("OWNCLOUD AUTHORIZATION PROVIDER IN TEST");
153
154 if (savedInstanceState != null) {
155 mStatusIcon = savedInstanceState.getInt(STATUS_ICON);
156 mStatusText = savedInstanceState.getInt(STATUS_TEXT);
157 mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT);
158 mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN);
159 setResultIconAndText(mStatusIcon, mStatusText);
160 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
161 if (!mStatusCorrect)
162 iv.setVisibility(View.VISIBLE);
163 else
164 iv.setVisibility(View.INVISIBLE);
165
166 String ocVersion = savedInstanceState.getString(OC_VERSION, null);
167 if (ocVersion != null)
168 mDiscoveredVersion = new OwnCloudVersion(ocVersion);
169
170 // Getting the state of oAuth2 components.
171 mOAuth2StatusIcon = savedInstanceState.getInt(OAUTH2_STATUS_ICON);
172 mOAuth2StatusText = savedInstanceState.getInt(OAUTH2_STATUS_TEXT);
173 // We set this to true if the rotation happens when the user is validating oAuth2 user_code.
174 changeViewByOAuth2Check(savedInstanceState.getBoolean(OAUTH2_IS_CHECKED));
175 // We store a JSon object with all the data returned from oAuth2 server when we get user_code.
176 // Is better than store variable by variable. We use String object to serialize from/to it.
177 try {
178 if (savedInstanceState.containsKey(OAUTH2_CODE_RESULT)) {
179 codeResponseJson = new JSONObject(savedInstanceState.getString(OAUTH2_CODE_RESULT));
180 }
181 } catch (JSONException e) {
182 Log.e(TAG, "onCreate->JSONException: " + e.toString());
183 }
184 // END of getting the state of oAuth2 components.
185
186 // Getting contents of each field.
187 EditText hostUrl = (EditText)findViewById(R.id.host_URL);
188 hostUrl.setText(savedInstanceState.getString(HOST_URL_TEXT), TextView.BufferType.EDITABLE);
189 EditText accountUsername = (EditText)findViewById(R.id.account_username);
190 accountUsername.setText(savedInstanceState.getString(ACCOUNT_USERNAME), TextView.BufferType.EDITABLE);
191 EditText accountPassword = (EditText)findViewById(R.id.account_password);
192 accountPassword.setText(savedInstanceState.getString(ACCOUNT_PASSWORD), TextView.BufferType.EDITABLE);
193 // END of getting contents of each field
194
195 } else {
196 mStatusText = mStatusIcon = 0;
197 mStatusCorrect = false;
198 mIsSslConn = false;
199 }
200 iv.setOnClickListener(this);
201 iv2.setOnClickListener(this);
202 tv.setOnFocusChangeListener(this);
203 tv2.setOnFocusChangeListener(this);
204
205 Button b = (Button) findViewById(R.id.account_register);
206 if (b != null) {
207 b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name)));
208 }
209
210 mNewCapturedUriFromOAuth2Redirection = null;
211 }
212
213
214 @Override
215 protected void onNewIntent (Intent intent) {
216 Uri data = intent.getData();
217 //mNewRedirectUriCaptured = (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI));
218 if (data != null && data.toString().startsWith(OAuth2Context.MY_REDIRECT_URI)) {
219 mNewCapturedUriFromOAuth2Redirection = data;
220 }
221 Log.d(TAG, "onNewIntent()");
222
223 }
224
225
226 @Override
227 protected void onSaveInstanceState(Bundle outState) {
228 outState.putInt(STATUS_ICON, mStatusIcon);
229 outState.putInt(STATUS_TEXT, mStatusText);
230 outState.putBoolean(STATUS_CORRECT, mStatusCorrect);
231 if (mDiscoveredVersion != null)
232 outState.putString(OC_VERSION, mDiscoveredVersion.toString());
233
234 // Saving the state of oAuth2 components.
235 outState.putInt(OAUTH2_STATUS_ICON, mOAuth2StatusIcon);
236 outState.putInt(OAUTH2_STATUS_TEXT, mOAuth2StatusText);
237 CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
238 outState.putBoolean(OAUTH2_IS_CHECKED, oAuth2Check.isChecked());
239 if (codeResponseJson != null){
240 outState.putString(OAUTH2_CODE_RESULT, codeResponseJson.toString());
241 }
242 // END of saving the state of oAuth2 components.
243
244 // Saving contents of each field.
245 outState.putString(HOST_URL_TEXT,((TextView) findViewById(R.id.host_URL)).getText().toString().trim());
246 outState.putString(ACCOUNT_USERNAME,((TextView) findViewById(R.id.account_username)).getText().toString().trim());
247 outState.putString(ACCOUNT_PASSWORD,((TextView) findViewById(R.id.account_password)).getText().toString().trim());
248
249 super.onSaveInstanceState(outState);
250 }
251
252 @Override
253 protected Dialog onCreateDialog(int id) {
254 Dialog dialog = null;
255 switch (id) {
256 case DIALOG_LOGIN_PROGRESS: {
257 ProgressDialog working_dialog = new ProgressDialog(this);
258 working_dialog.setMessage(getResources().getString(
259 R.string.auth_trying_to_login));
260 working_dialog.setIndeterminate(true);
261 working_dialog.setCancelable(true);
262 working_dialog
263 .setOnCancelListener(new DialogInterface.OnCancelListener() {
264 @Override
265 public void onCancel(DialogInterface dialog) {
266 Log.i(TAG, "Login canceled");
267 if (mAuthThread != null) {
268 mAuthThread.interrupt();
269 finish();
270 }
271 }
272 });
273 dialog = working_dialog;
274 break;
275 }
276 // oAuth2 dialog. We show here to the user the URL and user_code that the user must validate in a web browser.
277 case OAUTH2_LOGIN_PROGRESS: {
278 ProgressDialog working_dialog = new ProgressDialog(this);
279 try {
280 if (codeResponseJson != null && codeResponseJson.has(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL)) {
281 working_dialog.setMessage(String.format(getString(R.string.oauth_code_validation_message),
282 codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL),
283 codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE)));
284 } else {
285 working_dialog.setMessage(String.format("Getting authorization"));
286 }
287 } catch (JSONException e) {
288 Log.e(TAG, "onCreateDialog->JSONException: " + e.toString());
289 }
290 working_dialog.setIndeterminate(true);
291 working_dialog.setCancelable(true);
292 working_dialog
293 .setOnCancelListener(new DialogInterface.OnCancelListener() {
294 @Override
295 public void onCancel(DialogInterface dialog) {
296 Log.i(TAG, "Login canceled");
297 if (mOAuth2GetCodeThread != null) {
298 mOAuth2GetCodeThread.interrupt();
299 finish();
300 }
301 if (tokenReceiver != null) {
302 unregisterReceiver(tokenReceiver);
303 tokenReceiver = null;
304 finish();
305 }
306 }
307 });
308 dialog = working_dialog;
309 break;
310 }
311 case DIALOG_SSL_VALIDATOR: {
312 dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this);
313 break;
314 }
315 case DIALOG_CERT_NOT_SAVED: {
316 AlertDialog.Builder builder = new AlertDialog.Builder(this);
317 builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
318 builder.setCancelable(false);
319 builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
320 @Override
321 public void onClick(DialogInterface dialog, int which) {
322 dialog.dismiss();
323 };
324 });
325 dialog = builder.create();
326 break;
327 }
328 default:
329 Log.e(TAG, "Incorrect dialog called with id = " + id);
330 }
331 return dialog;
332 }
333
334 @Override
335 protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
336 switch (id) {
337 case DIALOG_LOGIN_PROGRESS:
338 case DIALOG_CERT_NOT_SAVED:
339 case OAUTH2_LOGIN_PROGRESS:
340 break;
341 case DIALOG_SSL_VALIDATOR: {
342 ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult);
343 break;
344 }
345 default:
346 Log.e(TAG, "Incorrect dialog called with id = " + id);
347 }
348 }
349
350 @Override
351 protected void onResume() {
352 Log.d(TAG, "onResume() start");
353 // (old oauth code) Registering token receiver. We must listening to the service that is pooling to the oAuth server for a token.
354 if (tokenReceiver == null) {
355 IntentFilter tokenFilter = new IntentFilter(OAuth2GetTokenService.TOKEN_RECEIVED_MESSAGE);
356 tokenReceiver = new TokenReceiver();
357 this.registerReceiver(tokenReceiver,tokenFilter);
358 }
359 // (new oauth code)
360 /*if (mNewRedirectUriCaptured) {
361 mNewRedirectUriCaptured = false;*/
362 if (mNewCapturedUriFromOAuth2Redirection != null) {
363 getOAuth2AccessTokenFromCapturedRedirection();
364
365 }
366 super.onResume();
367 }
368
369 @Override
370 protected void onPause() {
371 Log.d(TAG, "onPause() start");
372 super.onPause();
373 }
374
375
376 public void onAuthenticationResult(boolean success, String message) {
377 if (success) {
378 TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password);
379
380 URL url;
381 try {
382 url = new URL(message);
383 } catch (MalformedURLException e) {
384 // should never happen
385 Log.e(getClass().getName(), "Malformed URL: " + message);
386 return;
387 }
388
389 String username = username_text.getText().toString().trim();
390 String accountName = username + "@" + url.getHost();
391 if (url.getPort() >= 0) {
392 accountName += ":" + url.getPort();
393 }
394 Account account = new Account(accountName,
395 AccountAuthenticator.ACCOUNT_TYPE);
396 AccountManager accManager = AccountManager.get(this);
397 accManager.addAccountExplicitly(account, password_text.getText()
398 .toString(), null);
399
400 // Add this account as default in the preferences, if there is none
401 // already
402 Account defaultAccount = AccountUtils
403 .getCurrentOwnCloudAccount(this);
404 if (defaultAccount == null) {
405 SharedPreferences.Editor editor = PreferenceManager
406 .getDefaultSharedPreferences(this).edit();
407 editor.putString("select_oc_account", accountName);
408 editor.commit();
409 }
410
411 final Intent intent = new Intent();
412 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
413 AccountAuthenticator.ACCOUNT_TYPE);
414 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
415 intent.putExtra(AccountManager.KEY_AUTHTOKEN,
416 AccountAuthenticator.ACCOUNT_TYPE);
417 intent.putExtra(AccountManager.KEY_USERDATA, username);
418
419 accManager.setUserData(account, AccountAuthenticator.KEY_OC_URL,
420 url.toString());
421 accManager.setUserData(account,
422 AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString());
423
424 accManager.setUserData(account,
425 AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
426
427 setAccountAuthenticatorResult(intent.getExtras());
428 setResult(RESULT_OK, intent);
429 Bundle bundle = new Bundle();
430 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
431 //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI,
432 // bundle);
433 ContentResolver.requestSync(account, "org.owncloud", bundle);
434
435 /*
436 * if
437 * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion
438 * .owncloud_v2) >= 0) { Intent i = new Intent(this,
439 * ExtensionsAvailableActivity.class); startActivity(i); }
440 */
441
442 finish();
443 } else {
444 try {
445 dismissDialog(DIALOG_LOGIN_PROGRESS);
446 } catch (IllegalArgumentException e) {
447 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
448 }
449 TextView tv = (TextView) findViewById(R.id.account_username);
450 tv.setError(message + " "); // the extra spaces are a workaround for an ugly bug:
451 // 1. insert wrong credentials and connect
452 // 2. put the focus on the user name field with using hardware controls (don't touch the screen); the error is shown UNDER the field
453 // 3. touch the user name field; the software keyboard appears; the error popup is moved OVER the field and SHRINKED in width, losing the last word
454 // Seen, at least, in Android 2.x devices
455 }
456 }
457 public void onCancelClick(View view) {
458 setResult(RESULT_CANCELED);
459 finish();
460 }
461
462 public void onOkClick(View view) {
463 String prefix = "";
464 String url = ((TextView) findViewById(R.id.host_URL)).getText()
465 .toString().trim();
466 if (mIsSslConn) {
467 prefix = "https://";
468 } else {
469 prefix = "http://";
470 }
471 if (url.toLowerCase().startsWith("http://")
472 || url.toLowerCase().startsWith("https://")) {
473 prefix = "";
474 }
475 CheckBox oAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check);
476 if (oAuth2Check != null && oAuth2Check.isChecked()) {
477 startOauthorization();
478
479 } else {
480 continueConnection(prefix);
481 }
482 }
483
484 private void startOauthorization() {
485 // We start a thread to get an authorization code from the oAuth2 server.
486 setOAuth2ResultIconAndText(R.drawable.progress_small, R.string.oauth_login_connection);
487 mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_F_AUTHORIZATION_ENDPOINT_URL, this);
488 //mOAuth2GetCodeRunnable = new OAuth2GetCodeRunnable(OAuth2Context.OAUTH2_G_DEVICE_GETCODE_URL, this);
489 mOAuth2GetCodeRunnable.setListener(this, mHandler);
490 mOAuth2GetCodeThread = new Thread(mOAuth2GetCodeRunnable);
491 mOAuth2GetCodeThread.start();
492 }
493
494 public void onRegisterClick(View view) {
495 Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register)));
496 setResult(RESULT_CANCELED);
497 startActivity(register);
498 }
499
500 private void continueConnection(String prefix) {
501 String url = ((TextView) findViewById(R.id.host_URL)).getText()
502 .toString().trim();
503 String username = ((TextView) findViewById(R.id.account_username))
504 .getText().toString();
505 String password = ((TextView) findViewById(R.id.account_password))
506 .getText().toString();
507 if (url.endsWith("/"))
508 url = url.substring(0, url.length() - 1);
509
510 URL uri = null;
511 mDiscoveredVersion = mConnChkRunnable.getDiscoveredVersion();
512 String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion, false);
513
514 if (webdav_path == null) {
515 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
516 return;
517 }
518
519 try {
520 mBaseUrl = prefix + url;
521 String url_str = prefix + url + webdav_path;
522 uri = new URL(url_str);
523 } catch (MalformedURLException e) {
524 // should never happen
525 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
526 return;
527 }
528
529 showDialog(DIALOG_LOGIN_PROGRESS);
530 mAuthRunnable = new AuthenticationRunnable(uri, username, password, this);
531 mAuthRunnable.setOnAuthenticationResultListener(this, mHandler);
532 mAuthThread = new Thread(mAuthRunnable);
533 mAuthThread.start();
534 }
535
536 @Override
537 public void onConnectionCheckResult(ResultType type) {
538 mStatusText = mStatusIcon = 0;
539 mStatusCorrect = false;
540 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
541 .toString().trim().toLowerCase();
542
543 switch (type) {
544 case OK_SSL:
545 mIsSslConn = true;
546 mStatusIcon = android.R.drawable.ic_secure;
547 mStatusText = R.string.auth_secure_connection;
548 mStatusCorrect = true;
549 break;
550 case OK_NO_SSL:
551 mIsSslConn = false;
552 mStatusCorrect = true;
553 if (t_url.startsWith("http://") ) {
554 mStatusText = R.string.auth_connection_established;
555 mStatusIcon = R.drawable.ic_ok;
556 } else {
557 mStatusText = R.string.auth_nossl_plain_ok_title;
558 mStatusIcon = android.R.drawable.ic_partial_secure;
559 }
560 break;
561 case BAD_OC_VERSION:
562 mStatusIcon = R.drawable.common_error;
563 mStatusText = R.string.auth_bad_oc_version_title;
564 break;
565 case WRONG_CONNECTION:
566 mStatusIcon = R.drawable.common_error;
567 mStatusText = R.string.auth_wrong_connection_title;
568 break;
569 case TIMEOUT:
570 mStatusIcon = R.drawable.common_error;
571 mStatusText = R.string.auth_timeout_title;
572 break;
573 case INCORRECT_ADDRESS:
574 mStatusIcon = R.drawable.common_error;
575 mStatusText = R.string.auth_incorrect_address_title;
576 break;
577 case SSL_UNVERIFIED_SERVER:
578 mStatusIcon = R.drawable.common_error;
579 mStatusText = R.string.auth_ssl_unverified_server_title;
580 break;
581 case SSL_INIT_ERROR:
582 mStatusIcon = R.drawable.common_error;
583 mStatusText = R.string.auth_ssl_general_error_title;
584 break;
585 case HOST_NOT_AVAILABLE:
586 mStatusIcon = R.drawable.common_error;
587 mStatusText = R.string.auth_unknown_host_title;
588 break;
589 case NO_NETWORK_CONNECTION:
590 mStatusIcon = R.drawable.no_network;
591 mStatusText = R.string.auth_no_net_conn_title;
592 break;
593 case INSTANCE_NOT_CONFIGURED:
594 mStatusIcon = R.drawable.common_error;
595 mStatusText = R.string.auth_not_configured_title;
596 break;
597 case UNKNOWN_ERROR:
598 mStatusIcon = R.drawable.common_error;
599 mStatusText = R.string.auth_unknown_error_title;
600 break;
601 case FILE_NOT_FOUND:
602 mStatusIcon = R.drawable.common_error;
603 mStatusText = R.string.auth_incorrect_path_title;
604 break;
605 default:
606 Log.e(TAG, "Incorrect connection checker result type: " + type);
607 }
608 setResultIconAndText(mStatusIcon, mStatusText);
609 if (!mStatusCorrect)
610 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
611 else
612 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
613 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
614 }
615
616 public void onFocusChange(View view, boolean hasFocus) {
617 if (view.getId() == R.id.host_URL) {
618 if (!hasFocus) {
619 TextView tv = ((TextView) findViewById(R.id.host_URL));
620 String uri = tv.getText().toString().trim();
621 if (uri.length() != 0) {
622 setResultIconAndText(R.drawable.progress_small,
623 R.string.auth_testing_connection);
624 //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
625 mConnChkRunnable = new ConnectionCheckOperation(uri, this);
626 //mConnChkRunnable.setListener(this, mHandler);
627 //mAuthThread = new Thread(mConnChkRunnable);
628 //mAuthThread.start();
629 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this);
630 mDiscoveredVersion = null;
631 mAuthThread = mConnChkRunnable.execute(client, this, mHandler);
632 } else {
633 findViewById(R.id.refreshButton).setVisibility(
634 View.INVISIBLE);
635 setResultIconAndText(0, 0);
636 }
637 } else {
638 // avoids that the 'connect' button can be clicked if the test was previously passed
639 findViewById(R.id.buttonOK).setEnabled(false);
640 }
641 } else if (view.getId() == R.id.account_password) {
642 ImageView iv = (ImageView) findViewById(R.id.viewPassword);
643 if (hasFocus) {
644 iv.setVisibility(View.VISIBLE);
645 } else {
646 TextView v = (TextView) findViewById(R.id.account_password);
647 int input_type = InputType.TYPE_CLASS_TEXT
648 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
649 v.setInputType(input_type);
650 iv.setVisibility(View.INVISIBLE);
651 }
652 }
653 }
654
655 private void setResultIconAndText(int drawable_id, int text_id) {
656 ImageView iv = (ImageView) findViewById(R.id.action_indicator);
657 TextView tv = (TextView) findViewById(R.id.status_text);
658
659 if (drawable_id == 0 && text_id == 0) {
660 iv.setVisibility(View.INVISIBLE);
661 tv.setVisibility(View.INVISIBLE);
662 } else {
663 iv.setImageResource(drawable_id);
664 tv.setText(text_id);
665 iv.setVisibility(View.VISIBLE);
666 tv.setVisibility(View.VISIBLE);
667 }
668 }
669
670 @Override
671 public void onClick(View v) {
672 if (v.getId() == R.id.refreshButton) {
673 onFocusChange(findViewById(R.id.host_URL), false);
674 } else if (v.getId() == R.id.viewPassword) {
675 TextView view = (TextView) findViewById(R.id.account_password);
676 int input_type = view.getInputType();
677 if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
678 input_type = InputType.TYPE_CLASS_TEXT
679 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
680 } else {
681 input_type = InputType.TYPE_CLASS_TEXT
682 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
683 }
684 view.setInputType(input_type);
685 }
686 }
687
688 @Override protected void onDestroy() {
689 // We must stop the service thats it's pooling to oAuth2 server for a token.
690 Intent tokenService = new Intent(this, OAuth2GetTokenService.class);
691 stopService(tokenService);
692
693 // We stop listening the result of the pooling service.
694 if (tokenReceiver != null) {
695 unregisterReceiver(tokenReceiver);
696 tokenReceiver = null;
697 finish();
698 }
699
700 super.onDestroy();
701 }
702
703 // Controlling the oAuth2 checkbox on the activity: hide and show widgets.
704 public void onOff_check_Click(View view) {
705 CheckBox oAuth2Check = (CheckBox)view;
706 changeViewByOAuth2Check(oAuth2Check.isChecked());
707
708 }
709
710 public void changeViewByOAuth2Check(Boolean checked) {
711
712 EditText oAuth2Url = (EditText) findViewById(R.id.oAuth_URL);
713 EditText accountUsername = (EditText) findViewById(R.id.account_username);
714 EditText accountPassword = (EditText) findViewById(R.id.account_password);
715 ImageView viewPassword = (ImageView) findViewById(R.id.viewPassword);
716 ImageView auth2ActionIndicator = (ImageView) findViewById(R.id.auth2_action_indicator);
717 TextView oauth2StatusText = (TextView) findViewById(R.id.oauth2_status_text);
718
719 if (checked) {
720 oAuth2Url.setVisibility(View.VISIBLE);
721 accountUsername.setVisibility(View.GONE);
722 accountPassword.setVisibility(View.GONE);
723 viewPassword.setVisibility(View.GONE);
724 auth2ActionIndicator.setVisibility(View.INVISIBLE);
725 oauth2StatusText.setVisibility(View.INVISIBLE);
726 } else {
727 oAuth2Url.setVisibility(View.GONE);
728 accountUsername.setVisibility(View.VISIBLE);
729 accountPassword.setVisibility(View.VISIBLE);
730 viewPassword.setVisibility(View.INVISIBLE);
731 auth2ActionIndicator.setVisibility(View.GONE);
732 oauth2StatusText.setVisibility(View.GONE);
733 }
734
735 }
736
737 // Controlling the oAuth2 result of server connection.
738 private void setOAuth2ResultIconAndText(int drawable_id, int text_id) {
739 ImageView iv = (ImageView) findViewById(R.id.auth2_action_indicator);
740 TextView tv = (TextView) findViewById(R.id.oauth2_status_text);
741
742 if (drawable_id == 0 && text_id == 0) {
743 iv.setVisibility(View.INVISIBLE);
744 tv.setVisibility(View.INVISIBLE);
745 } else {
746 iv.setImageResource(drawable_id);
747 tv.setText(text_id);
748 iv.setVisibility(View.VISIBLE);
749 tv.setVisibility(View.VISIBLE);
750 }
751 }
752
753 // Results from the first call to oAuth2 server : getting the user_code and verification_url.
754 @Override
755 public void onOAuth2GetCodeResult(ResultOAuthType type, JSONObject responseJson) {
756 if ((type == ResultOAuthType.OK_SSL)||(type == ResultOAuthType.OK_NO_SSL)) {
757 codeResponseJson = responseJson;
758 if (codeResponseJson != null) {
759 getOAuth2AccessTokenFromJsonResponse();
760 } // else - nothing to do here - wait for callback !!!
761
762 } else if (type == ResultOAuthType.HOST_NOT_AVAILABLE) {
763 setOAuth2ResultIconAndText(R.drawable.common_error, R.string.oauth_connection_url_unavailable);
764 }
765 }
766
767 // If the results of getting the user_code and verification_url are OK, we get the received data and we start
768 // the polling service to oAuth2 server to get a valid token.
769 private void getOAuth2AccessTokenFromJsonResponse() {
770 String deviceCode = null;
771 String verificationUrl = null;
772 String userCode = null;
773 int expiresIn = -1;
774 int interval = -1;
775
776 Log.d(TAG, "ResponseOAuth2->" + codeResponseJson.toString());
777
778 try {
779 // We get data that we must show to the user or we will use internally.
780 verificationUrl = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_VERIFICATION_URL);
781 userCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_USER_CODE);
782 expiresIn = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_EXPIRES_IN);
783
784 // And we get data that we must use to get a token.
785 deviceCode = codeResponseJson.getString(OAuth2GetCodeRunnable.CODE_DEVICE_CODE);
786 interval = codeResponseJson.getInt(OAuth2GetCodeRunnable.CODE_INTERVAL);
787
788 } catch (JSONException e) {
789 Log.e(TAG, "Exception accesing data in Json object" + e.toString());
790 }
791
792 // Updating status widget to OK.
793 setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established);
794
795 // Showing the dialog with instructions for the user.
796 showDialog(OAUTH2_LOGIN_PROGRESS);
797
798 // Loggin all the data.
799 Log.d(TAG, "verificationUrl->" + verificationUrl);
800 Log.d(TAG, "userCode->" + userCode);
801 Log.d(TAG, "deviceCode->" + deviceCode);
802 Log.d(TAG, "expiresIn->" + expiresIn);
803 Log.d(TAG, "interval->" + interval);
804
805 // Starting the pooling service.
806 try {
807 Intent tokenService = new Intent(this, OAuth2GetTokenService.class);
808 tokenService.putExtra(OAuth2GetTokenService.TOKEN_URI, OAuth2Context.OAUTH2_G_DEVICE_GETTOKEN_URL);
809 tokenService.putExtra(OAuth2GetTokenService.TOKEN_DEVICE_CODE, deviceCode);
810 tokenService.putExtra(OAuth2GetTokenService.TOKEN_INTERVAL, interval);
811
812 startService(tokenService);
813 }
814 catch (Exception e) {
815 Log.e(TAG, "tokenService creation problem :", e);
816 }
817
818 }
819
820 private void getOAuth2AccessTokenFromCapturedRedirection() {
821 Map<String, String> responseValues = new HashMap<String, String>();
822 //String queryParameters = getIntent().getData().getQuery();
823 String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery();
824 mNewCapturedUriFromOAuth2Redirection = null;
825
826 Log.v(TAG, "Queryparameters (Code) = " + queryParameters);
827
828 String[] pairs = queryParameters.split("&");
829 Log.v(TAG, "Pairs (Code) = " + pairs.toString());
830
831 int i = 0;
832 String key = "";
833 String value = "";
834
835 StringBuilder sb = new StringBuilder();
836
837 while (pairs.length > i) {
838 int j = 0;
839 String[] part = pairs[i].split("=");
840
841 while (part.length > j) {
842 String p = part[j];
843 if (j == 0) {
844 key = p;
845 sb.append(key + " = ");
846 } else if (j == 1) {
847 value = p;
848 responseValues.put(key, value);
849 sb.append(value + "\n");
850 }
851
852 Log.v(TAG, "[" + i + "," + j + "] = " + p);
853 j++;
854 }
855 i++;
856 }
857
858
859 // Updating status widget to OK.
860 setOAuth2ResultIconAndText(R.drawable.ic_ok, R.string.auth_connection_established);
861
862 // Showing the dialog with instructions for the user.
863 showDialog(OAUTH2_LOGIN_PROGRESS);
864
865 //
866 RemoteOperation operation = new GetOAuth2AccessToken(responseValues);
867 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(OAuth2Context.OAUTH2_F_TOKEN_ENDPOINT_URL), getApplicationContext());
868 operation.execute(client, this, mHandler);
869 }
870
871
872
873 // We get data from the oAuth2 token service with this broadcast receiver.
874 private class TokenReceiver extends BroadcastReceiver {
875 /**
876 * The token is received.
877 * @author
878 * {@link BroadcastReceiver} to enable oAuth2 token receiving.
879 */
880 @Override
881 public void onReceive(Context context, Intent intent) {
882 @SuppressWarnings("unchecked")
883 HashMap<String, String> tokenResponse = (HashMap<String, String>)intent.getExtras().get(OAuth2GetTokenService.TOKEN_RECEIVED_DATA);
884 Log.d(TAG, "TokenReceiver->" + tokenResponse.get(OAuth2GetTokenService.TOKEN_ACCESS_TOKEN));
885 dismissDialog(OAUTH2_LOGIN_PROGRESS);
886
887 }
888 }
889
890 @Override
891 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
892 if (operation instanceof ConnectionCheckOperation) {
893
894 mStatusText = mStatusIcon = 0;
895 mStatusCorrect = false;
896 String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
897 .toString().trim().toLowerCase();
898
899 switch (result.getCode()) {
900 case OK_SSL:
901 mIsSslConn = true;
902 mStatusIcon = android.R.drawable.ic_secure;
903 mStatusText = R.string.auth_secure_connection;
904 mStatusCorrect = true;
905 break;
906
907 case OK_NO_SSL:
908 case OK:
909 mIsSslConn = false;
910 mStatusCorrect = true;
911 if (t_url.startsWith("http://") ) {
912 mStatusText = R.string.auth_connection_established;
913 mStatusIcon = R.drawable.ic_ok;
914 } else {
915 mStatusText = R.string.auth_nossl_plain_ok_title;
916 mStatusIcon = android.R.drawable.ic_partial_secure;
917 }
918 break;
919
920
921 case BAD_OC_VERSION:
922 mStatusIcon = R.drawable.common_error;
923 mStatusText = R.string.auth_bad_oc_version_title;
924 break;
925 case WRONG_CONNECTION:
926 mStatusIcon = R.drawable.common_error;
927 mStatusText = R.string.auth_wrong_connection_title;
928 break;
929 case TIMEOUT:
930 mStatusIcon = R.drawable.common_error;
931 mStatusText = R.string.auth_timeout_title;
932 break;
933 case INCORRECT_ADDRESS:
934 mStatusIcon = R.drawable.common_error;
935 mStatusText = R.string.auth_incorrect_address_title;
936 break;
937
938 case SSL_RECOVERABLE_PEER_UNVERIFIED:
939 mStatusIcon = R.drawable.common_error;
940 mStatusText = R.string.auth_ssl_unverified_server_title;
941 mLastSslUntrustedServerResult = result;
942 showDialog(DIALOG_SSL_VALIDATOR);
943 break;
944
945 case SSL_ERROR:
946 mStatusIcon = R.drawable.common_error;
947 mStatusText = R.string.auth_ssl_general_error_title;
948 break;
949
950 case HOST_NOT_AVAILABLE:
951 mStatusIcon = R.drawable.common_error;
952 mStatusText = R.string.auth_unknown_host_title;
953 break;
954 case NO_NETWORK_CONNECTION:
955 mStatusIcon = R.drawable.no_network;
956 mStatusText = R.string.auth_no_net_conn_title;
957 break;
958 case INSTANCE_NOT_CONFIGURED:
959 mStatusIcon = R.drawable.common_error;
960 mStatusText = R.string.auth_not_configured_title;
961 break;
962 case FILE_NOT_FOUND:
963 mStatusIcon = R.drawable.common_error;
964 mStatusText = R.string.auth_incorrect_path_title;
965 break;
966 case UNHANDLED_HTTP_CODE:
967 case UNKNOWN_ERROR:
968 mStatusIcon = R.drawable.common_error;
969 mStatusText = R.string.auth_unknown_error_title;
970 break;
971 default:
972 Log.e(TAG, "Incorrect connection checker result type: " + result.getHttpCode());
973 }
974 setResultIconAndText(mStatusIcon, mStatusText);
975 if (!mStatusCorrect)
976 findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
977 else
978 findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
979 findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
980
981 } else if (operation instanceof GetOAuth2AccessToken) {
982
983 try {
984 dismissDialog(OAUTH2_LOGIN_PROGRESS);
985 } catch (IllegalArgumentException e) {
986 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
987 }
988
989 if (result.isSuccess()) {
990
991 /// time to test the retrieved access token on the ownCloud server
992 String url = ((TextView) findViewById(R.id.host_URL)).getText()
993 .toString().trim();
994 if (url.endsWith("/"))
995 url = url.substring(0, url.length() - 1);
996
997 Uri uri = null;
998 /*String webdav_path = AccountUtils.getWebdavPath(mDiscoveredVersion);
999
1000 if (webdav_path == null) {
1001 onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
1002 return;
1003 }*/
1004
1005 String prefix = "";
1006 if (mIsSslConn) {
1007 prefix = "https://";
1008 } else {
1009 prefix = "http://";
1010 }
1011 if (url.toLowerCase().startsWith("http://")
1012 || url.toLowerCase().startsWith("https://")) {
1013 prefix = "";
1014 }
1015
1016 try {
1017 mBaseUrl = prefix + url;
1018 //String url_str = prefix + url + webdav_path;
1019 String url_str = prefix + url + "/remote.php/odav";
1020 uri = Uri.parse(url_str);
1021
1022 } catch (Exception e) {
1023 // should never happen
1024 onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title));
1025 return;
1026 }
1027
1028 showDialog(DIALOG_LOGIN_PROGRESS);
1029 String accessToken = ((GetOAuth2AccessToken)operation).getResultTokenMap().get(OAuth2Context.KEY_ACCESS_TOKEN);
1030 Log.d(TAG, "Got ACCESS TOKEN: " + accessToken);
1031 mAuthChkOperation = new ExistenceCheckOperation("", this, accessToken);
1032 WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(uri, getApplicationContext());
1033 mAuthChkOperation.execute(client, this, mHandler);
1034
1035
1036 } else {
1037 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1038 tv.setError("A valid authorization could not be obtained");
1039
1040 }
1041
1042 } else if (operation instanceof ExistenceCheckOperation) {
1043
1044 try {
1045 dismissDialog(DIALOG_LOGIN_PROGRESS);
1046 } catch (IllegalArgumentException e) {
1047 // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens
1048 }
1049
1050 if (result.isSuccess()) {
1051 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1052 Log.d(TAG, "Checked access - time to save the account");
1053
1054 Uri uri = Uri.parse(mBaseUrl);
1055 String username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
1056 String accountName = username + "@" + uri.getHost();
1057 if (uri.getPort() >= 0) {
1058 accountName += ":" + uri.getPort();
1059 }
1060 // TODO - check that accountName does not exist
1061 Account account = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE);
1062 AccountManager accManager = AccountManager.get(this);
1063 accManager.addAccountExplicitly(account, "", null); // with our implementation, the password is never input in the app
1064
1065 // Add this account as default in the preferences, if there is none
1066 Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this);
1067 if (defaultAccount == null) {
1068 SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
1069 editor.putString("select_oc_account", accountName);
1070 editor.commit();
1071 }
1072
1073 /// account data to save by the AccountManager
1074 final Intent intent = new Intent();
1075 intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE);
1076 intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
1077 intent.putExtra(AccountManager.KEY_USERDATA, username);
1078
1079 accManager.setAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, ((ExistenceCheckOperation) operation).getAccessToken());
1080
1081 accManager.setUserData(account, AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable.getDiscoveredVersion().toString());
1082 accManager.setUserData(account, AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
1083 accManager.setUserData(account, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE");
1084
1085 setAccountAuthenticatorResult(intent.getExtras());
1086 setResult(RESULT_OK, intent);
1087
1088 /// enforce the first account synchronization
1089 Bundle bundle = new Bundle();
1090 bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
1091 ContentResolver.requestSync(account, "org.owncloud", bundle);
1092
1093 finish();
1094
1095 } else {
1096 TextView tv = (TextView) findViewById(R.id.oAuth_URL);
1097 tv.setError(result.getLogMessage());
1098 Log.d(TAG, "Access failed: " + result.getLogMessage());
1099 }
1100 }
1101 }
1102
1103
1104 public void onSavedCertificate() {
1105 mAuthThread = mConnChkRunnable.retry(this, mHandler);
1106 }
1107
1108 @Override
1109 public void onFailedSavingCertificate() {
1110 showDialog(DIALOG_CERT_NOT_SAVED);
1111 }
1112
1113 }