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